{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "f767af9e-cdd6-41f4-9279-9e92fa2dde06",
      "metadata": {},
      "source": [
        "---\n",
        "title: Exploring uncertainty with Qiskit\n",
        "description: Learn what physical measurements affect each other and explore fundamental uncertainty in quantum mechanics using a real quantum computer\n",
        "---\n",
        "\n",
        "{/* cspell:ignore prodxz delx delz dely prodxy uparrow downarrow */}\n",
        "\n",
        "# Exploring uncertainty\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "132cdf03-164e-4334-aab6-206aacacc25b",
      "metadata": {},
      "source": [
        "For this Qiskit in Classrooms module, students must have a working Python environment with the following packages installed:\n",
        "\n",
        "* `qiskit` v2.1.0 or newer\n",
        "* `qiskit-ibm-runtime` v0.40.1 or newer\n",
        "* `qiskit-aer` v0.17.0 or newer\n",
        "* `qiskit.visualization`\n",
        "* `numpy`\n",
        "* `pylatexenc`\n",
        "\n",
        "To set up and install the packages above, see the [Install Qiskit](/docs/guides/install-qiskit) guide.\n",
        "In order to run jobs on real quantum computers, students will need to set up an account with IBM Quantum® by following the steps in the [Set up your IBM Cloud account](/docs/guides/cloud-setup) guide.\n",
        "\n",
        "This module was tested and used 8 minutes of QPU time. This is an estimate only. Your actual usage may vary. Two time-consuming calculations are marked as such in header comments and can be carried out on simulators if students are short on QPU time. With those removed, the module requires only \\~30 seconds of QPU time.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "dbda80f2-62c5-4f25-95cf-c5495bc171b1",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Uncomment and modify this line as needed to install dependencies\n",
        "#!pip install 'qiskit>=2.1.0' 'qiskit-ibm-runtime>=0.40.1' 'qiskit-aer>=0.17.0' 'numpy' 'pylatexenc'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4283087e-cc02-467d-b02c-c1c131ad15f3",
      "metadata": {},
      "source": [
        "Watch the module walkthrough by Dr. Katie McCormick below, or click [here](https://www.youtube.com/watch?v=3h3pwrECbb8\\&list=PLOFEBzvs-Vvrs2fuvsuT039ariYPsua3d\\&index=5) to watch it on YouTube.\n",
        "\n",
        "***\n",
        "\n",
        "<IBMVideo id=\"134413662\" title=\"Katie McCormick introduces quantum uncertainty and tests it on a real quantum computer. Learn which quantities can be simultaneously known, and which ones cannot.\" />\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "be385aa2-9080-4c39-aba0-bc35087ced78",
      "metadata": {},
      "source": [
        "## Introduction\n",
        "\n",
        "You have probably heard of the uncertainty principle, even outside of your physics courses. A common colloquial restatement of uncertainty is \"By looking at something, you influence it.\" That is certainly true. But a more physical way of describing uncertainty is that there are certain physical observables that have an incompatibility that prevents them both from being simultaneously known to arbitrary accuracy. Many students first encounter the pair of incompatible variables $x$ and $p_x$, meaning the position along one axis called the $x$-axis, and the linear momentum along that direction, respectively. For those variables the constraint on uncertainty is written\n",
        "$\\Delta x \\Delta p_x \\geq \\frac{\\hbar}{2}.$\n",
        "Here, $\\Delta x$ is called the \"uncertainty in $x$\", which has the same definition as standard deviation in statistics, and can be defined as\n",
        "$\\Delta x = \\sqrt{\\langle x^2 \\rangle - \\langle x \\rangle^2}.$\n",
        "$\\Delta p_x$ is defined in the same way.\n",
        "Here, we will not derive this uncertainty relation; we will point out that it is consistent with our understanding of classical waves. That is, a wave with truly one perfect frequency $f$ and wavelength $\\lambda$ would go on and on forever as a perfect sinusoid. Quantum mechanically, this would correspond to knowing the momentum perfectly according to de Broglie's hypothesis: $\\lambda = h/p$. But in order to know $where$ a wave-like particle is located, the wave describing it must become more sharply peaked in space, like a very narrow Gaussian, for example. We know that we can express any continuous function, including such sharply-peaked wave functions, as a Fourier series of sinusoidal functions with different wavelengths. But as the wave function becomes more sharply peaked (and the position is better known), we will require more terms in the Fourier series, meaning a mixture of more wavelengths (and thus, quantum mechanically, more values of momentum).\n",
        "\n",
        "Stated more simply: a state with a well-defined momentum (a perfect sinusoid in space) has very uncertain position. A state with a well-defined position (like a Dirac delta distribution) has a very uncertain momentum.\n",
        "\n",
        "There are other variables that exhibit such incompatibility. For example, a particle's spin may have a well-defined projection along one axis, but then we know nothing about the project on an orthogonal axis. For example the state $|0\\rangle \\sim |\\uparrow\\rangle$ (for a qubit or spin-1/2 particle) has a definite projection along the $z$ axis (of 1 in the context of a qubit, and of $\\hbar/2$ in the context of a spin-1/2 particle). But this state can be written as a superposition of two states each of which has a well-defined projection onto the $x$ axis:\n",
        "$|0\\rangle = \\frac{1}{\\sqrt{2}}(|+\\rangle_x+|-\\rangle_x)$\n",
        "or equivalently\n",
        "$\\begin{pmatrix} 1 \\\\ 0\\end{pmatrix} = \\frac{1}{\\sqrt{2}}\\left[\\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 \\\\ 1\\end{pmatrix}+\\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 \\\\ -1\\end{pmatrix}\\right].$\n",
        "$|+\\rangle_x$ has a well-defined projection onto $x$, as does $|-\\rangle_x$. So if we specify the projection of a state along the $x$ axis, we do not know the projection along the $z$ axis. And if we specify the projection on the $z$ axis, we don't know the projection along $x$. There are minor differences when discussing this in the context of spin and in qubits. But generally speaking, eigenstates of the Pauli matrices have an interesting relationship that we can explore.\n",
        "Throughout this lesson, we will be experimentally checking our intuition for the uncertainty in these incompatible variables, and verifying that uncertainty relations hold on IBM® quantum computers.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eb8c7a0c-c962-4efd-ab82-905ba7415284",
      "metadata": {},
      "source": [
        "## Simple check of intuition\n",
        "\n",
        "In this first experiment and throughout the module, we will use a framework for quantum computing known as \"Qiskit patterns\", which breaks workflows into the following steps:\n",
        "\n",
        "* Step 1: Map classical inputs to a quantum problem\n",
        "* Step 2: Optimize problem for quantum execution\n",
        "* Step 3: Execute using Qiskit Runtime Primitives\n",
        "* Step 4: Post-processing and classical analysis\n",
        "\n",
        "We will generally follow these steps, though we may not always explicitly label them.\n",
        "\n",
        "Let's start by loading some necessary packages including Runtime primitives. We will also select the least-busy quantum computer available to us.\n",
        "\n",
        "There is code below for saving your credentials upon first use. Be sure to delete this information from the notebook after saving it to your environment, so that your credentials are not accidentally shared when you share the notebook. See [Set up your IBM Cloud account](/docs/guides/initialize-account) and [Initialize the service in an untrusted environment](/docs/guides/cloud-setup-untrusted) for more guidance.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c26c8f6f-1ed7-4079-ba21-4747d318996f",
      "metadata": {},
      "outputs": [],
      "source": [
        "from numpy import pi\n",
        "\n",
        "# Load the Qiskit Runtime service\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "\n",
        "# Syntax for first saving your token.  Delete these lines after saving your credentials.\n",
        "# QiskitRuntimeService.save_account(channel='ibm_quantum_platform',\n",
        "# instance = '<YOUR_IBM_INSTANCE_CRN>', token='<YOUR-API_KEY>', overwrite=True, set_as_default=True)\n",
        "# service = QiskitRuntimeService(channel='ibm_quantum_platform')\n",
        "\n",
        "# Load saved credentials\n",
        "service = QiskitRuntimeService()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "7d34071a-6fb1-4a0a-9e66-c9ee40c3767b",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ibm_sherbrooke\n"
          ]
        }
      ],
      "source": [
        "# Load the Runtime primitive and session\n",
        "from qiskit_ibm_runtime import (\n",
        "    Batch,\n",
        "    SamplerV2 as Sampler,\n",
        "    EstimatorV2 as Estimator,\n",
        ")\n",
        "\n",
        "# Use the least busy backend\n",
        "backend = service.least_busy(min_num_qubits=127)\n",
        "print(backend.name)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fcbf932e-45b7-4a50-90e1-59dcb7577dfc",
      "metadata": {},
      "source": [
        "If a student exhausts their available quantum computing time during the lesson, the lines below can be un-commented and used to set up a simulator that partly mimics the noise behavior of the quantum computer selected above.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7265c432-19eb-41ba-940d-a80541fe0218",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Import an estimator, this time from qiskit (we will import from Runtime for real hardware)\n",
        "from qiskit_aer.primitives import SamplerV2, EstimatorV2\n",
        "from qiskit_aer.noise import NoiseModel\n",
        "\n",
        "# Generate the noise model from the backend properties\n",
        "noise_model = NoiseModel.from_backend(backend)\n",
        "\n",
        "noisy_sampler = SamplerV2(options={\"backend_options\": {\"noise_model\": noise_model}})\n",
        "noisy_estimator = EstimatorV2(options={\"backend_options\": {\"noise_model\": noise_model}})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5f072c75-efb7-4596-8bc6-ccae553684df",
      "metadata": {},
      "source": [
        "You might recall that an eigenstate of one operator, Z, is not an eigenstate of another operator X. We will observe that now, experimentally by making measurements along the $x$ and $z$ axes. For the measurement along $z$, we simply use `qc.measure()`, because IBM quantum computers are structured to measure along $z$. But to measure along $x$, we must rotate the system to effectively move the $x$ axis up to the orientation along which we measure. This is accomplished with a Hadamard gate. There is a similar step required for measurements along $y$. The necessary steps are collected here for convenience:\n",
        "\n",
        "* To measure along $z$: `qc.measure()`\n",
        "* To measure along $x$: `qc.h()` then `qc.measure()`\n",
        "* To measure along $y$: `qc.sdg()`, `qc.h()`, `qc.s`then `qc.measure()`\n",
        "\n",
        "### Step 1: Map classical inputs to a quantum problem\n",
        "\n",
        "In this case, the mapping step is simply expressing the measurements and rotations described above in a quantum circuit:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8baef7f9-78bf-48c8-8359-efd65f943d96",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/8baef7f9-78bf-48c8-8359-efd65f943d96-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 1: Map\n",
        "\n",
        "# Import some general packages\n",
        "from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister\n",
        "\n",
        "# Define registers\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(2, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Add a first measurement\n",
        "qc.measure(qr, cr[0])\n",
        "qc.barrier()\n",
        "\n",
        "# Change basis so that measurements made on quantum computer which normally tell us about z,\n",
        "# now tell us about x.\n",
        "qc.h(qr)\n",
        "\n",
        "# Add a second measurement\n",
        "qc.measure(qr, cr[1])\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d931ec8-6f8f-405f-a9ce-41724435f3a1",
      "metadata": {},
      "source": [
        "### Step 2: Optimize problem for quantum execution\n",
        "\n",
        "This step takes the operations we want to perform and expresses them in terms of the functionality of a specific quantum computer. It also maps our problem onto the layout of the quantum computer.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "d613ac18-61ff-4e91-9f7d-73887bd2306a",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Transpile\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "target = backend.target\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "\n",
        "qc_isa = pm.run(qc)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2f31c2b1-08be-43c7-8af3-5d9ee8a4dab0",
      "metadata": {},
      "source": [
        "### Step 3: Execute using Qiskit Runtime primitives\n",
        "\n",
        "We can use sampler to collect statistics on the measurements. We will construct the Sampler primitive to run on a real quantum computer using `mode = backend`. There are other modes for other workflows, and we will use one below. Sampler will be used by calling its run() method with a list of \"pubs\" (Primitive Unified Blocs). Each pub contains up to three values that, together, define a computation unit of work for the estimator to complete: circuits, observables, parameters. You can also provide a list of circuits, a list of observables, and a list of parameters. For more information, read the [Overview of PUBs.](/docs/guides/primitive-input-output#pubs)\n",
        "\n",
        "We want to run on a real quantum computer, so that we are carrying out a real quantum physics experiment. If you exhaust your allotted time on real quantum computers, you can comment out the code below for the quantum computer, and uncomment the code for running on a simulator.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8996bcf0-1848-4eda-b5a1-47190fede630",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Run the job on a real quantum computer\n",
        "\n",
        "\n",
        "sampler = Sampler(mode=backend)\n",
        "pubs = [qc_isa]\n",
        "job = sampler.run(pubs)\n",
        "res = job.result()\n",
        "\n",
        "counts = res[0].data.c.get_counts()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "\n",
        "# job = noisy_sampler.run([qc_isa])\n",
        "# res=job.result()\n",
        "# counts=res[0].data.c.get_counts()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "01dedf77-2ea7-45f4-9a84-8cd1caa26c14",
      "metadata": {},
      "source": [
        "### Step 4: Post-processing\n",
        "\n",
        "This is an especially simple case of post-processing, in which we simply visualize the counts.\n",
        "\n",
        "Note that Qiskit orders qubits, measurements, and other things by listing the lowest-numbered item last / on the right, a convention referred to as \"little-endian\". This means that the column below labeled \"10\" refers to counts where the first measurement yielded a \"0\", and the second measurements yielded a \"1\".\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "7cfeafbf-6a40-4829-9e1d-6c8dbdab9f23",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/7cfeafbf-6a40-4829-9e1d-6c8dbdab9f23-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 4: Post-process\n",
        "\n",
        "from qiskit.visualization import plot_histogram\n",
        "\n",
        "plot_histogram(counts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2d21379b-b27f-4448-adb5-91f55e79b0e2",
      "metadata": {},
      "source": [
        "If this convention does not appeal to you, you can use `marginal_counts` to visualize the results of each measurement separately:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "8c94042b-0e52-41f3-8231-ede9fab3ee51",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/8c94042b-0e52-41f3-8231-ede9fab3ee51-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit.result import marginal_counts\n",
        "\n",
        "plot_histogram(\n",
        "    marginal_counts(counts, indices=[0]), title=\"Counts after first measurement\"\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "d0ad384b-c2f1-4bc6-8730-ec8a4b593a50",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/d0ad384b-c2f1-4bc6-8730-ec8a4b593a50-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 9,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "plot_histogram(\n",
        "    marginal_counts(counts, indices=[1]), title=\"Counts after second measurement\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "258d66a0-ddfc-4c69-8943-37efb7c09b40",
      "metadata": {},
      "source": [
        "By default, states in Qiskit are initialized to the $|0\\rangle$ state. So it is no surprise that almost all of the first measurements yeilded $|0\\rangle$. Note however, that there was almost an even split in the second measurement (the one giving information about projections of the state onto $x$). It seems like this state that gives us a very predictable outcome of measurements along $z$ gives us a very unpredictable set of outcomes for measurements along $x$. Let's explore this.\n",
        "\n",
        "What happens if the make the measurements in the opposite order? We could start by using the Hadamard gate to obtain statistics on the probability of $|0\\rangle$ being measured in $|\\pm\\rangle_x$. Then for the second measurement, we will change back to the $z$ basis using a second Hadamard gate.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "4029873f-1260-4fc3-bfd2-e5a3f579a09c",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/4029873f-1260-4fc3-bfd2-e5a3f579a09c-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 10,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 1:\n",
        "\n",
        "# Define registers\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(2, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Change basis to measure along x.\n",
        "qc.h(qr)\n",
        "qc.measure(qr, cr[0])\n",
        "qc.barrier()\n",
        "\n",
        "# Change our basis back to z and make a second measurement\n",
        "qc.h(qr)\n",
        "qc.measure(qr, cr[1])\n",
        "\n",
        "qc.draw(\"mpl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "c56d1649-de18-4417-8033-2be2a04a2582",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 2: Transpile the circuit for running on a quantum computer\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "qc_isa = pm.run(qc)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "782f01c9-f9c2-4e5e-8638-7c9ba3347bfb",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 3: Run the job on a real quantum computer\n",
        "\n",
        "sampler = Sampler(mode=backend)\n",
        "pubs = [qc_isa]\n",
        "job = sampler.run(pubs)\n",
        "res = job.result()\n",
        "counts = res[0].data.c.get_counts()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "# job = noisy_sampler.run([qc_isa])\n",
        "# res=job.result()\n",
        "# counts=res[0].data.c.get_counts()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "ce3fb0fd-a859-4c91-b2e5-c09343a397c2",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/ce3fb0fd-a859-4c91-b2e5-c09343a397c2-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Step 4: Post-process\n",
        "from qiskit.visualization import plot_histogram\n",
        "\n",
        "plot_histogram(counts)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d7862ea1-a9fd-4b23-9198-cfbfb05cf3dc",
      "metadata": {},
      "source": [
        "Here, we seem to have even less predictability! Previously, we at least knew what the outcome would be of the first measurement, now we have a fairly even distribution across all possible states. It's not too hard to see why this happened. We started in $|0\\rangle$, which is a 50-50 mixture of $|+\\rangle_x$ and $|-\\rangle_x$, according to\n",
        "$|0\\rangle=\\frac{1}{\\sqrt{2}}(|+\\rangle_x+|-\\rangle_x)$\n",
        "So clearly there should be equal probability of obtaining the + or - state (mapped to 0 and 1 in the chart) for the first measurement. The measurement along $x$ collapses the state into either an eigenstate $|+\\rangle_x$ or the eigenstate $|-\\rangle_x$. Each one of those states is a 50-50 mixture of $|0\\rangle$ and $|1\\rangle$, according to\n",
        "$|+\\rangle_x = \\frac{1}{\\sqrt{2}}(|0\\rangle+|1\\rangle)$\n",
        "$|-\\rangle_x = \\frac{1}{\\sqrt{2}}(|0\\rangle-|1\\rangle)$\n",
        "So once the system is in an eigenstate of $x$, clearly, measurements along $z$ will yield both $|0\\rangle$ and $|1\\rangle$, and will do so with roughly equal probability.\n",
        "So our first example showed us that some states will have very predictable outcomes of some measurements, but unpredictable outcomes for other measurements. The current example shows us that we can do worse than that. There are states that can give us unpredictable outcomes for both measurements, even if all we do is swap the order of the measurements. Let's investigate how certain or uncertain a quantity is for a given state.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "292b878f-1792-4f55-8991-49e37c7db58b",
      "metadata": {},
      "source": [
        "## Calculating uncertainty\n",
        "\n",
        "We can quantify this using uncertainty, or variance. The \"uncertainty\" is often defined to be the square root of the \"variance\" of a distribution. That is, the uncertainty for some observable $S$ is denoted $\\Delta S$ and is given by\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "(\\Delta S)^2 & \\equiv \\langle (S - \\langle S \\rangle)^2 \\rangle\\\\\n",
        "\n",
        "(\\Delta S)^2 & = \\langle S^2 - 2 S \\langle S \\rangle +\\langle S \\rangle^2 \\rangle\\\\\n",
        "\n",
        "(\\Delta S)^2 & = \\langle S^2 \\rangle - \\langle S \\rangle^2\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "For the case of Pauli matrices, for which $S^2 = I$, this becomes\n",
        "\n",
        "$$\n",
        "(\\Delta S)^2 = 1 - \\langle S \\rangle^2\n",
        "$$\n",
        "\n",
        "Let's apply this to a concrete example. Let's start with the state $|\\psi\\rangle = |+\\rangle_y = \\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 \\\\ i \\end{pmatrix},$ and let's determine the uncertainty of the observable $X$ in that state.\n",
        "\n",
        "#### Check your understanding\n",
        "\n",
        "Calculate the uncertainty of $X$ in the state $|+\\rangle_y = |+i\\rangle$, by hand.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    $$\n",
        "    \\Delta X =\\sqrt{\\langle+i| X^2 |+i\\rangle - \\langle+i| X |+i\\rangle^2}\n",
        "    $$\n",
        "\n",
        "    In the given state, this yields:\n",
        "\n",
        "    $$\n",
        "    \\begin{aligned}\n",
        "    \\Delta X & =\\sqrt{\\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 & -i\\end{pmatrix} \\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix} \\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix} \\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 \\\\ i\\end{pmatrix} - \\left(\\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 & -i\\end{pmatrix} \\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix} \\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 \\\\ i\\end{pmatrix}\\right)^2}\\\\\n",
        "\n",
        "    \\Delta X & =\\sqrt{\\frac{1}{2}\\begin{pmatrix}1 & -i\\end{pmatrix} \\begin{pmatrix}1 & 0 \\\\ 0 & 1\\end{pmatrix} \\begin{pmatrix}1 \\\\ i\\end{pmatrix} - \\left(\\frac{1}{2}\\begin{pmatrix}1 & -i\\end{pmatrix} \\begin{pmatrix}i \\\\ 1\\end{pmatrix}\\right)^2}\\\\\n",
        "\n",
        "    \\Delta X & =\\sqrt{\\frac{1}{2}\\begin{pmatrix}1 & -i\\end{pmatrix} \\begin{pmatrix}1 \\\\ i\\end{pmatrix} - \\left(\\frac{1}{2}(0))\\right)^2}\\\\\n",
        "\n",
        "    \\Delta X & =\\sqrt{\\frac{1}{2}(2)} = 1\n",
        "    \\end{aligned}\n",
        "    $$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "We can create an arbitrary initial state using `qc.initialize()`. Note that the syntax for the imaginary unit here is $1j$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bf16b90b-d77a-477b-b432-963f8b81feed",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "-0.02408454165642664\n"
          ]
        }
      ],
      "source": [
        "# Step 1: Map the problem into a quantum circuit\n",
        "\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "import numpy as np\n",
        "\n",
        "obs = SparsePauliOp(\"X\")\n",
        "\n",
        "# Define registers\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(1, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Initialize the state\n",
        "qc.initialize([1, 1j] / np.sqrt(2))\n",
        "\n",
        "# Step 2: Transpile the circuit\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "qc_isa = pm.run(qc)\n",
        "obs_isa = obs.apply_layout(layout=qc_isa.layout)\n",
        "\n",
        "# Step 3: Run the circuit on a real quantum computer\n",
        "\n",
        "\n",
        "estimator = Estimator(mode=backend)\n",
        "pubs = [(qc_isa, obs_isa)]\n",
        "job = estimator.run([[qc_isa, obs_isa]])\n",
        "res = job.result()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "# job = noisy_estimator.run([[qc_isa,obs_isa]])\n",
        "# res=job.result()\n",
        "\n",
        "# Step 4: Return the result in classical form, and analyze.\n",
        "\n",
        "print(res[0].data.evs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e162abde-f346-4f2c-8358-d95c818a1021",
      "metadata": {},
      "source": [
        "According to our equation above,\n",
        "$(\\Delta X)^2 = 1 - \\langle X \\rangle^2 = 1-(0.0015...)^2 \\rightarrow \\Delta X = 0.999...$\n",
        "Let's stick with that same state, but find the expectation value of $Z$, now:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c1918196-a26e-4419-8a25-488a3d1d4cc1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "0.04958271968581247\n"
          ]
        }
      ],
      "source": [
        "# Step 1: Map the problem into a quantum circuit\n",
        "\n",
        "obs = SparsePauliOp(\"Z\")\n",
        "\n",
        "# Define registers\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(1, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Initialize the state to |+>_y\n",
        "qc.initialize([1, 1j] / np.sqrt(2))\n",
        "\n",
        "\n",
        "# Step 2: Transpile the circuit\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "qc_isa = pm.run(qc)\n",
        "obs_isa = obs.apply_layout(layout=qc_isa.layout)\n",
        "\n",
        "# Step 3: Run the circuit on a real quantum computer\n",
        "\n",
        "estimator = Estimator(mode=backend)\n",
        "pubs = [(qc_isa, obs_isa)]\n",
        "job = estimator.run(pubs)\n",
        "res = job.result()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "# job = noisy_estimator.run([[qc_isa,obs_isa]])\n",
        "# res=job.result()\n",
        "\n",
        "# Step 4: Return the result in classical form, and analyze.\n",
        "\n",
        "print(res[0].data.evs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8ba18eed-5982-45ca-b0ed-5708b7d4aa17",
      "metadata": {},
      "source": [
        "We could do the same math as before, but we would see that the variance is again very close to 1.0. We could conclude that $\\Delta X \\Delta Z \\approx 1.0$. Indeed this approximately correct for the state we chose. But can we do better? Or worse?\n",
        "\n",
        "Recall that there is an uncertainty relation between position along one direction, $x,$ and momentum along the same direction, $p_x.$ For those variables, but most familiar form is probably\n",
        "$\\Delta x \\Delta p_x \\geq \\hbar/2$\n",
        "If this is all we remember, we might be tempted to think that $\\Delta X$ and $\\Delta Z$ could also have such a fundamental limit on uncertainty. Perhaps it is impossible for the product $\\Delta X \\Delta Z$ to reach zero? Let's try another state and see if this holds. This time, we'll use $|\\psi\\rangle = \\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 \\\\ 1 \\end{pmatrix}.$ Let's see what happens. Note that in the code below, estimator can accept two sets of circuits and observables in the same job submission.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ef107fd6-03c2-42f2-a51d-e52957cfb82e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The expectation value of the first observable is:  1.0011036174126302\n",
            "The expectation value of the second observable is:  0.0029429797670141016\n"
          ]
        }
      ],
      "source": [
        "# Step 1: Map the problem into a quantum circuit\n",
        "\n",
        "obs1 = SparsePauliOp(\"X\")\n",
        "obs2 = SparsePauliOp(\"Z\")\n",
        "\n",
        "# Define registers\n",
        "\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(1, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Initialize the state\n",
        "qc.initialize([1, 1] / np.sqrt(2))\n",
        "\n",
        "# Step 2: Transpile the circuit\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "qc_isa = pm.run(qc)\n",
        "obs1_isa = obs1.apply_layout(layout=qc_isa.layout)\n",
        "obs2_isa = obs2.apply_layout(layout=qc_isa.layout)\n",
        "\n",
        "# Step 3: Run the circuit on a real quantum computer\n",
        "\n",
        "with Batch(backend=backend) as batch:\n",
        "    estimator = Estimator(mode=batch)\n",
        "    pubs = [(qc_isa, obs1_isa), (qc_isa, obs2_isa)]\n",
        "    job = estimator.run(pubs)\n",
        "    res = job.result()\n",
        "batch.close()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "\n",
        "# job = noisy_estimator.run([[qc,obs1],[qc,obs2]])\n",
        "# res=job.result()\n",
        "\n",
        "# Step 4: Return the result in classical form, and analyze.\n",
        "\n",
        "print(\"The expectation value of the first observable is: \", res[0].data.evs)\n",
        "print(\"The expectation value of the second observable is: \", res[1].data.evs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "489472e8-eefc-4740-aa7e-eba8e71d0d72",
      "metadata": {},
      "source": [
        "The expectation value of $X$ should be close to 1.0, but should not exceed 1.0. Don't worry if it exceeds 1.0 by a very small amount. This can be attributed to factors like noise and/or readout error. Although this is a very important topic, we can ignore it for now.\n",
        "\n",
        "We obtained an expectation value of $X$ that is very close to 1.0 (corresponding to a very low variance for $X$). This makes the product of the two variances quite low:\n",
        "\n",
        "$$\n",
        "\\Delta X \\Delta Z = \\sqrt{1-(0.9853)^2} \\times \\sqrt{1-(-0.00195)^2} = 0.171.\n",
        "$$\n",
        "\n",
        "While this is not exactly zero, this value is getting small in comparison with the eigenvalues of the Pauli operators ($\\pm 1$). Well, you may recall that the uncertainty relation between linear position and momentum could be written differently, explicitly using the commutation relation between the operators $x$ and $p_x$:\n",
        "\n",
        "$$\n",
        "\\Delta x \\Delta p_x \\geq \\frac{1}{2}|\\langle [x,p_x] \\rangle|\n",
        "$$\n",
        "\n",
        "where\n",
        "\n",
        "$$\n",
        "[x,p_x] = xp_x-p_xx\n",
        "$$\n",
        "\n",
        "is the commutator of $x$ and $p_x$.\n",
        "\n",
        "This is the form that that can be most easily extended to the Pauli operators. Generally, for two operators $A$ and $B$,\n",
        "\n",
        "$$\n",
        "\\Delta A \\Delta B \\geq \\frac{1}{2}|\\langle [A,B] \\rangle|.\n",
        "$$\n",
        "\n",
        "And in the case of the Pauli matrices $X$ and $Z$, we need $[X,Z]$ in order to calculate\n",
        "\n",
        "$$\n",
        "\\Delta X \\Delta Z \\geq \\frac{1}{2}|\\langle [X,Z] \\rangle|.\n",
        "$$\n",
        "\n",
        "We show this here, and leave similar calculations to the reader as an exercise:\n",
        "\n",
        "$$\n",
        "[X,Z] = XZ-ZX = \\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix}\\begin{pmatrix}1 & 0 \\\\ 0 & -1\\end{pmatrix}-\\begin{pmatrix}1 & 0 \\\\ 0 & -1\\end{pmatrix}\\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix}\n",
        "$$\n",
        "\n",
        "$$\n",
        "[X,Z] = \\begin{pmatrix}0 & -1 \\\\ 1 & 0\\end{pmatrix}-\\begin{pmatrix}0 & 1 \\\\ -1 & 0\\end{pmatrix} = 2\\begin{pmatrix}0 & -1 \\\\ 1 & 0\\end{pmatrix}\n",
        "$$\n",
        "\n",
        "This is a perfectly acceptable answer, but with one more step, we see\n",
        "\n",
        "$$\n",
        "[X,Z] = -2i\\begin{pmatrix}0 & -i \\\\ i & 0\\end{pmatrix}=-2iY\n",
        "$$\n",
        "\n",
        "Our uncertainty relation thus becomes\n",
        "\n",
        "$$\n",
        "\\Delta X \\Delta Z \\geq |\\langle Y \\rangle|.\n",
        "$$\n",
        "\n",
        "#### Check your understanding\n",
        "\n",
        "Determine $[X,Y]$ and $[Y,Z]$. Use this to write down the uncertainty relations between $X$ & $Y$, and $Y$ & $Z$.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    $$\n",
        "    [X,Y] = XY-YX = \\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix}\\begin{pmatrix}0 & -i \\\\ i & 0\\end{pmatrix}-\\begin{pmatrix}0 & -i \\\\ i & 0\\end{pmatrix}\\begin{pmatrix}0 & 1 \\\\ 1 & 0\\end{pmatrix} = 2 \\begin{pmatrix}i & 0 \\\\ 0 & -i\\end{pmatrix}=2iZ\n",
        "    $$\n",
        "\n",
        "    $$\n",
        "    [Y,Z] = YZ-ZY = \\begin{pmatrix}0 & -i \\\\ i & 0\\end{pmatrix}\\begin{pmatrix}1 & 0 \\\\ 0 & -1\\end{pmatrix}-\\begin{pmatrix}1 & 0 \\\\ 0 & -1\\end{pmatrix}\\begin{pmatrix}0 & -i \\\\ i & 0\\end{pmatrix} = 2 \\begin{pmatrix}0 & i \\\\ i & 0\\end{pmatrix}=2iX\n",
        "    $$\n",
        "\n",
        "    Combining with the general uncertainty relation, we have\n",
        "\n",
        "    $$\n",
        "    \\Delta X \\Delta Y \\geq |\\langle Z \\rangle|,\n",
        "    $$\n",
        "\n",
        "    $$\n",
        "    \\Delta Y \\Delta Z \\geq |\\langle X \\rangle|.\n",
        "    $$\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "### Verify consistency\n",
        "\n",
        "Before moving on, let us check that this was consistent with our previous finding. We used the state $|\\psi\\rangle = \\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 \\\\ 1 \\end{pmatrix}.$ And we found that $\\Delta X \\Delta Z  = 0.171.$ Now we know this product should be greater than or equal to\n",
        "\n",
        "$$\n",
        "|\\langle Y \\rangle|=\\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 & 1 \\end{pmatrix}\\begin{pmatrix}0 & -i \\\\ i & 0\\end{pmatrix}\\frac{1}{\\sqrt{2}}\\begin{pmatrix} 1 \\\\ 1 \\end{pmatrix}\n",
        "$$\n",
        "\n",
        "$$\n",
        "|\\langle Y \\rangle| = \\frac{1}{2}\\begin{pmatrix} 1 & 1 \\end{pmatrix}\\begin{pmatrix} -i \\\\ i \\end{pmatrix} = \\frac{1}{2}(-i+i) = 0.\n",
        "$$\n",
        "\n",
        "So indeed, $\\Delta X \\Delta Z  = 0.171 \\geq |\\langle Y \\rangle|=0$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0ea50490-36e9-4ab6-b9e1-6840f6dedfec",
      "metadata": {},
      "source": [
        "Use the questions below to build some intuition for these findings:\n",
        "\n",
        "#### Check your understanding\n",
        "\n",
        "Answer the following items together as a set:\n",
        "\n",
        "(a) What states would you expect to have zero uncertainty in $X$?\n",
        "\n",
        "(b) What states would you expect to have zero uncertainty in $Z$?\n",
        "\n",
        "(c) In what states would you obtain a zero expectation value $\\langle Y \\rangle$?\n",
        "\n",
        "(d) Are the answers to the above questions consistent with the case $\\Delta X \\Delta Z \\geq |\\langle Y \\rangle|$?\n",
        "\n",
        "(e) Write code to explicitly check this using estimator.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    (a) We might expect eigenstates of the $X$ operator to yield zero uncertainty in $X$. Indeed, using $|\\psi\\rangle = |+\\rangle_x,$ we have\n",
        "    $\\Delta X = \\sqrt{1-\\langle X \\rangle^2} = \\sqrt{1-1^2} = 0.$\n",
        "\n",
        "    (b) We might expect eigenstates of the $Z$ operator to yield zero uncertainty in $Z$. Indeed, using $|\\psi\\rangle = |1\\rangle,$ we have\n",
        "    $\\Delta Z = \\sqrt{1-\\langle Z \\rangle^2} = \\sqrt{1-(-1)^2} = 0.$\n",
        "\n",
        "    (c) We expect to find $\\langle Y \\rangle=0$ for any states that, when measured, yield a positive projection on the $y$ axis just as often as a negative projection. These include the eigenstates of $X$ and $Z$.\n",
        "\n",
        "    (d) Yes. One would expect a very small value for the product of uncertainties $\\Delta X \\Delta Z $ for eigenstates of $X$ or $Z$: $\\Delta X \\Delta Z \\approx 0.$ This can hold because we would also expect $\\langle Y \\rangle=0$ for those same states. So the uncertainty relation could be satisfied.\n",
        "\n",
        "    (e) Code such as the following would verify this:\n",
        "\n",
        "    ```python\n",
        "    obs1 = SparsePauliOp.from_list(\n",
        "        [(\"X\", 1.000)]\n",
        "    )\n",
        "    obs2 = SparsePauliOp.from_list(\n",
        "        [(\"Y\", 1.000)]\n",
        "    )\n",
        "    obs3 = SparsePauliOp.from_list(\n",
        "        [(\"Z\", 1.000)]\n",
        "    )\n",
        "    qc = QuantumCircuit(1,1)\n",
        "    qc.ry(pi/2,0)\n",
        "\n",
        "    job = estimator.run([(qc, [[obs1], [obs2], [obs3]])], precision=0.001)\n",
        "    res=job.result()\n",
        "    ```\n",
        "\n",
        "    Where the results returns all expectation values. To retrieve all expectation values and calculate uncertainties, we could use:\n",
        "\n",
        "    ```python\n",
        "    xs=res[0].data.evs[0]\n",
        "    ys=abs(res[0].data.evs[1])\n",
        "    zs=res[0].data.evs[2]\n",
        "\n",
        "    import math\n",
        "    prodxz=((1-xs[i]*xs[i])**0.5)*(1-zs[i]*zs[i])**0.5\n",
        "\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Answer the following items together as a set:\n",
        "\n",
        "(a) Can you think of a state in which you would have a large expectation value $\\langle Y \\rangle$?\n",
        "\n",
        "(b) Would you expect that same state to have large or small uncertainty in $X$?\n",
        "\n",
        "(c) Would you expect that same state to have large or small uncertainty in $Z$?\n",
        "\n",
        "(d) Are the answers to the above questions consistent with the case $\\Delta X \\Delta Z \\geq |\\langle Y \\rangle|$?\n",
        "\n",
        "(e) Write code to explicitly check this using estimator.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    (a) We expect to find $\\langle Y \\rangle\\approx 1$ for the eigenstate of $Y$: $|+\\rangle_y$.\n",
        "\n",
        "    (b) We might expect $X$ to have large uncertainty in the state $|+\\rangle_y,$ since measuring $X$ in that state would yield a positive and negative result with equal frequency/probability.\n",
        "\n",
        "    (c) We might expect $Z$ to have large uncertainty in the state $|+\\rangle_y,$ since measuring $Z$ in that state would yield a positive and negative result with equal frequency/probability.\n",
        "\n",
        "    (d) Yes. One would expect a large value for the product of uncertainties $\\Delta X \\Delta Z $ for eigenstates of $Y,$ and for $|+\\rangle_y,$ specifically. We would also expect $\\langle Y \\rangle\\approx 1$ for that same state. So both $\\langle Y \\rangle$ and $\\Delta X \\Delta Z $ are both fairly large in this state, and it is plausible that the uncertainty relation could again be satisfied.\n",
        "\n",
        "    (e) Code such as the following would verify this:\n",
        "\n",
        "    ```python\n",
        "    obs1 = SparsePauliOp.from_list(\n",
        "        [(\"X\", 1.000)]\n",
        "    )\n",
        "    obs2 = SparsePauliOp.from_list(\n",
        "        [(\"Y\", 1.000)]\n",
        "    )\n",
        "    obs3 = SparsePauliOp.from_list(\n",
        "        [(\"Z\", 1.000)]\n",
        "    )\n",
        "    qc = QuantumCircuit(1,1)\n",
        "    qc.rx(-pi/2,0)\n",
        "\n",
        "    job = estimator.run([(qc, [[obs1], [obs2], [obs3]])], precision=0.001)\n",
        "    res=job.result()\n",
        "    ```\n",
        "\n",
        "    Where the results returns all expectation values. To retrieve all expectation values and calculate uncertainties, we could use:\n",
        "\n",
        "    ```python\n",
        "    xs=res[0].data.evs[0]\n",
        "    ys=abs(res[0].data.evs[1])\n",
        "    zs=res[0].data.evs[2]\n",
        "\n",
        "    import math\n",
        "    prodxz=((1-xs[i]*xs[i])**0.5)*(1-zs[i]*zs[i])**0.5\n",
        "\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "26f11700-d6be-4ce1-9f97-b2d47ea933ec",
      "metadata": {},
      "source": [
        "## Testing uncertainty relations\n",
        "\n",
        "The test above only demonstrated the validity of the uncertainty relation for a single choice of state vector $|\\psi\\rangle = |+\\rangle_x$. To convince ourselves that this is generally consistent with experiment, we should carry out similar calculations using estimator for many choices of the state vector. Let's start by rotating our state vector away from the $z$ axis, using an `RY` gate to produce different initial states using a parameter $\\theta$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c434a14d-c0ce-40bd-8f5e-7d35722e97bb",
      "metadata": {},
      "outputs": [],
      "source": [
        "# The calculation below uses approximately 3-4 minutes of QPU time.\n",
        "# Step 1: Map the problem into a quantum circuit\n",
        "\n",
        "from qiskit.circuit import Parameter\n",
        "import numpy as np\n",
        "\n",
        "# Specify observables\n",
        "obs1 = SparsePauliOp(\"X\")\n",
        "obs2 = SparsePauliOp(\"Y\")\n",
        "obs3 = SparsePauliOp(\"Z\")\n",
        "\n",
        "# Define registers\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(1, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Rotate away from |0>\n",
        "theta = Parameter(\"θ\")\n",
        "qc.ry(theta, 0)\n",
        "\n",
        "params = np.linspace(0, 2, num=21)\n",
        "\n",
        "# Step 2: Transpile the circuit\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "qc_isa = pm.run(qc)\n",
        "obs1_isa = obs1.apply_layout(layout=qc_isa.layout)\n",
        "obs2_isa = obs2.apply_layout(layout=qc_isa.layout)\n",
        "obs3_isa = obs3.apply_layout(layout=qc_isa.layout)\n",
        "\n",
        "# Step 3: Run the circuit on a real quantum computer\n",
        "\n",
        "with Batch(backend=backend) as batch:\n",
        "    estimator = Estimator(mode=batch)\n",
        "    pubs = [(qc_isa, [[obs1_isa], [obs2_isa], [obs3_isa]], [params])]\n",
        "    job = estimator.run(pubs, precision=0.01)\n",
        "    res = job.result()\n",
        "\n",
        "batch.close()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "\n",
        "# job = noisy_estimator.run([(qc, [[obs1], [obs2], [obs3]], [params])])\n",
        "# res=job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "41d974bb-da9e-459e-8145-294f71cab4c7",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 4: Post-processing and classical analysis.\n",
        "xs = res[0].data.evs[0]\n",
        "ys = abs(res[0].data.evs[1])\n",
        "zs = res[0].data.evs[2]\n",
        "\n",
        "# Calculate uncertainties\n",
        "\n",
        "delx = []\n",
        "delz = []\n",
        "prodxz = []\n",
        "for i in range(len(xs)):\n",
        "    delx.append(abs((1 - xs[i] * xs[i])) ** 0.5)\n",
        "    delz.append(abs((1 - zs[i] * zs[i])) ** 0.5)\n",
        "    prodxz.append(delx[i] * delz[i])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "1599d4df-38f8-4171-95ae-6c3c61409201",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/1599d4df-38f8-4171-95ae-6c3c61409201-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Here we can plot the results from this simulation.\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(params, delx, label=r\"$\\Delta$ X\")\n",
        "plt.plot(params, ys, label=r\"$\\langle$ Y $\\rangle$\")\n",
        "plt.plot(params, delz, label=r\"$\\Delta$ Z\")\n",
        "plt.plot(params, prodxz, label=r\"$\\Delta$X $\\Delta$Z\")\n",
        "plt.xlabel(r\"$\\theta$\")\n",
        "plt.ylabel(\"Expectation/Uncertainty Values\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1694ab4f-b5e2-46be-b453-2599f73c582d",
      "metadata": {},
      "source": [
        "Note that the red curve $(\\Delta X \\Delta Z)$ is always greater than the orange curve $\\langle Y \\rangle.$ Sometimes the uncertainty product dips and is somewhat close to the limit, and other times it rises and is farther from the limit, but it always obeys the uncertainty relation.\n",
        "\n",
        "Of course, this might not be the best test of the uncertainty relation, since our limit $\\langle Y \\rangle$ is always very close to zero. Let's use a quantum state that has a larger projection on eigenstates of $Y$. Specifically, we will still rotate rotate $|0\\rangle$ down from the $z$ axis by varying angles, but now we will also rotate that resulting state around $z$ by some angle, perhaps $\\pi/4$, and see what happens.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c9700dd1-d116-4f94-8c81-70383ce38671",
      "metadata": {},
      "outputs": [],
      "source": [
        "# The calculation below uses approximately 3-4 minutes of QPU time.\n",
        "from qiskit.circuit import Parameter\n",
        "import numpy as np\n",
        "\n",
        "# Step 1: Map the problem to a quantum circuit\n",
        "\n",
        "# Specify observables\n",
        "obs1 = SparsePauliOp(\"X\")\n",
        "obs2 = SparsePauliOp(\"Y\")\n",
        "obs3 = SparsePauliOp(\"Z\")\n",
        "\n",
        "# Define registers\n",
        "qr = QuantumRegister(1, \"q\")\n",
        "cr = ClassicalRegister(1, \"c\")\n",
        "qc = QuantumCircuit(qr, cr)\n",
        "\n",
        "# Rotate away from |0> along one plane, and then along a transverse direction.\n",
        "theta = Parameter(\"θ\")\n",
        "qc.ry(theta, 0)\n",
        "qc.rz(pi / 4, 0)\n",
        "\n",
        "params = np.linspace(0, 2, num=21)\n",
        "\n",
        "# Step 2: Transpile the circuit\n",
        "\n",
        "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n",
        "qc_isa = pm.run(qc)\n",
        "\n",
        "obs1_isa = obs1.apply_layout(layout=qc_isa.layout)\n",
        "obs2_isa = obs2.apply_layout(layout=qc_isa.layout)\n",
        "obs3_isa = obs3.apply_layout(layout=qc_isa.layout)\n",
        "\n",
        "# Step 3: Run the circuit on a real quantum computer\n",
        "\n",
        "with Batch(backend=backend) as batch:\n",
        "    estimator = Estimator(mode=batch)\n",
        "    pubs = [(qc_isa, [[obs1_isa], [obs2_isa], [obs3_isa]], [params])]\n",
        "    job = estimator.run(pubs, precision=0.01)\n",
        "    res = job.result()\n",
        "\n",
        "batch.close()\n",
        "\n",
        "# Run the job on the Aer simulator with noise model from real backend\n",
        "\n",
        "# job = noisy_estimator.run([(qc, [[obs1], [obs2], [obs3]], [params])])\n",
        "# res=job.result()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "7e85e759-33a7-4539-80f7-05c769c46144",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Step 4: Post-processing and classical analysis.\n",
        "xs = res[0].data.evs[0]\n",
        "ys = abs(res[0].data.evs[1])\n",
        "zs = res[0].data.evs[2]\n",
        "\n",
        "# Calculate uncertainties\n",
        "\n",
        "delx = []\n",
        "delz = []\n",
        "prodxz = []\n",
        "for i in range(len(xs)):\n",
        "    delx.append(abs((1 - xs[i] * xs[i])) ** 0.5)\n",
        "    delz.append(abs((1 - zs[i] * zs[i])) ** 0.5)\n",
        "    prodxz.append(delx[i] * delz[i])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "6f129a2d-e029-45b7-bb77-bfa83babd8c9",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/extracted-outputs/6f129a2d-e029-45b7-bb77-bfa83babd8c9-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Here we can plot the results from this simulation.\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.plot(params, delx, label=r\"$\\Delta$ X\")\n",
        "plt.plot(params, ys, label=r\"$\\langle$ Y $\\rangle$\")\n",
        "plt.plot(params, delz, label=r\"$\\Delta$ Z\")\n",
        "plt.plot(params, prodxz, label=r\"$\\Delta$X $\\Delta$Z\")\n",
        "plt.xlabel(r\"$\\theta$\")\n",
        "plt.ylabel(\"Expectation/Uncertainty Values\")\n",
        "plt.legend()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7b90bcd5-f561-4f7f-a5a3-7882b3ddb464",
      "metadata": {},
      "source": [
        "Now we see that the limit on the uncertainty $(\\Delta X \\Delta Z)$ is being put to the test! The red curve comes much closer to the orange curve than before. In fact, in the absence of noise, the uncertainty relation would be exactly saturated ($(\\Delta X \\Delta Z) = \\langle Y \\rangle$) one point. In the presence of noise and readout error, it should not be surprising if a run occasionally yields $(\\Delta X \\Delta Z)$ very slightly larger than $\\langle Y \\rangle.$ This is not a true violation of uncertainty; it is just an artifact of non-zero error.\n",
        "\n",
        "#### Check your understanding\n",
        "\n",
        "Explain how you would push this to the absolute limit, making $\\langle Y \\rangle$ as large as possible.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    The code currently has lines that rotate the default initial state $|0\\rangle$, down from the $z$ axis by some parametrized angle $\\theta$ and then also around the $z$ axis by an angle $\\pi/4,$ which rotates the state vector part of the way over to the $y$ axis.\n",
        "\n",
        "    `qc.ry(theta,0)`\n",
        "\n",
        "    `qc.rz(pi/4,0)`\n",
        "\n",
        "    We could change the rotation about $z$ from $\\pi/4$ to $\\pi/2$, rotating all the way to an eigenstate of $Y$:\n",
        "\n",
        "    `qc.ry(theta,0)`\n",
        "\n",
        "    `qc.rz(pi/2,0)`\n",
        "\n",
        "    No other changes would be required.\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n",
        "Change the code or copy it over and implement that check of the uncertainty relation with the expectation value of Y maximized. Does the uncertainty relation hold?\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    We would use exactly the code from the example above, with\n",
        "\n",
        "    `qc.rz(pi/2,0)`\n",
        "\n",
        "    swapped in to replace\n",
        "\n",
        "    `qc.rz(pi/4,0).`\n",
        "\n",
        "    The resulting figure should look like that below, and yes, the uncertainty principle should still be valid.\n",
        "\n",
        "    ![A plot comparing uncertainty to the maximum expectation value of the Y operator.](https://quantum.cloud.ibm.com/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/checkin.avif)\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6dd8c749-00d6-4a08-8d3c-2f5b5f3a0902",
      "metadata": {},
      "source": [
        "Modify the code above to make a similar picture, demonstrating that from measurements on the quantum computer the product $\\Delta X \\Delta Y$ behaves as it should. Choose any set of states you like.\n",
        "\n",
        "<Accordion>\n",
        "  <AccordionItem title=\"Answer\">\n",
        "    We would use exactly the code from the example above, and in face we could use the same results from above, just using the expectation values to calculate different uncertainties. For example, we could use\n",
        "\n",
        "    ```python\n",
        "    xs=res[0].data.evs[0]\n",
        "    ys=res[0].data.evs[1]\n",
        "    zs=abs(res[0].data.evs[2])\n",
        "    import math\n",
        "    delx = []\n",
        "    dely = []\n",
        "    prodxy=[]\n",
        "    for i in range(len(xs)):\n",
        "        delx.append((1-xs[i]*xs[i])**0.5)\n",
        "        dely.append((1-ys[i]*ys[i])**0.5)\n",
        "        prodxy.append(((1-xs[i]*xs[i])**0.5)*(1-ys[i]*ys[i])**0.5)\n",
        "    ```\n",
        "\n",
        "    and we could plot\n",
        "\n",
        "    ```python\n",
        "    import matplotlib.pyplot as plt\n",
        "    plt.plot(params, delx, label=r'$\\Delta$ X')\n",
        "    plt.plot(params, dely, label=r'$\\langle$ Y $\\rangle$')\n",
        "    plt.plot(params, zs, label=r'$\\Delta$ Z')\n",
        "    plt.plot(params, prodxy, label=r'$\\Delta$X $\\Delta$Z')\n",
        "    plt.xlabel(r'$\\theta$')\n",
        "    plt.ylabel('Expectation/Uncertainty Values')\n",
        "    plt.legend()\n",
        "    plt.show()\n",
        "    ```\n",
        "  </AccordionItem>\n",
        "</Accordion>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "571a0120-a9d3-44c5-9dce-1b021090f9d1",
      "metadata": {},
      "source": [
        "Challenge: Write code to scan through many values of $\\phi$, just as we scanned through many values of $\\theta$, and make a 3-D plot showing that the uncertainty relation is never violated. Pick any observables you like.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b89eaae3-6ea9-486c-b93c-55431839d5f1",
      "metadata": {},
      "source": [
        "## Questions\n",
        "\n",
        "Instructors can request versions of these notebooks with answer keys and guidance on placement in common curricula by filling out this [quick survey](https://ibm.biz/classrooms_instructor_key_request) on how the notebooks are being used.\n",
        "\n",
        "### Critical concepts:\n",
        "\n",
        "* There are uncertainty relations between many sets of physical observables, including position & linear momentum, and components of spin.\n",
        "* The Pauli matrices do not commute. This is a mathematical reflection of the fact that not all components of spin can be simultaneously known/determined.\n",
        "* Quantum computing makes heavy use of the Pauli operators/matrices, so it is useful to know the uncertainty relation for Pauli operators, as well as the closely-related spin operators.\n",
        "* A general formula for the uncertainty of two operators $A$ and $B$ is $\\Delta A \\Delta B \\geq \\frac{1}{2}|\\langle [A,B] \\rangle|.$\n",
        "* An eigenstate $|a\\rangle$ of some operator $A$ yields zero uncertainty in the physical observable associated with that operator. Even experimentally, $\\Delta A \\approx 0.$\n",
        "* An eigenstate $|a\\rangle$ of some operator $A$ will yield a larger uncertainty for an operator $B$ that does not commute with $A$.\n",
        "* Experimental results using a real quantum computer confirm the intuition we gain from matrix representations of physical operators.\n",
        "\n",
        "### T/F Questions:\n",
        "\n",
        "1. T/F One can simultaneously measure $X$ and $Y$, but not $Z$.\n",
        "2. T/F One can simultaneously measure $X$ and $Z$, but not $Y$.\n",
        "3. T/F Linear position and linear momentum operators do not commute.\n",
        "4. T/F IBM quantum computers measure along $Z$ by default, so a rotation must be performed to measure along any other direction.\n",
        "5. T/F The circuit below effectively measures $Z$ and then $X$.\n",
        "\n",
        "![A circuit diagram showing a measurement, a Hadamard gate, and then another measurement.](https://quantum.cloud.ibm.com/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/tf-q5.avif)\n",
        "\n",
        "### MC Questions:\n",
        "\n",
        "1. The diagram below demonstrates which of the following uncertainty relations?\n",
        "\n",
        "   * a. $\\Delta X \\Delta Y \\geq |\\langle Z \\rangle|$\n",
        "   * b. $\\Delta Y \\Delta Z \\geq |\\langle X \\rangle|$\n",
        "   * c. $\\Delta Z \\Delta X \\geq |\\langle Y \\rangle|$\n",
        "   * d. None of the above\n",
        "\n",
        "![A plot comparing uncertainty to the maximum expectation value of the Y operator.](https://quantum.cloud.ibm.com/learning/images/modules/quantum-mechanics/exploring-uncertainty-with-qiskit/checkin.avif)\n",
        "\n",
        "2. Which of the following is the standard sequence to perform a measurement along $x$?\n",
        "   * a. Only `qc.measure()`\n",
        "   * b. `qc.h()` then `qc.measure()`\n",
        "   * c. `qc.h()`, `qc.h()` then `qc.measure()`\n",
        "   * d. `qc.h()`, `qc.s`, `qc.h()`then `qc.measure()`\n",
        "   * e. `qc.sdg()`, `qc.h()`, `qc.s` then `qc.measure()`\n",
        "   * f. `qc.sdg()`, `qc.h()`, `qc.s`, `qc.h()` then `qc.measure()`\n",
        "\n",
        "3. Which of the following states yields the largest expectation value $\\langle X \\rangle$?\n",
        "   * a. $|+\\rangle_x$\n",
        "   * b. $|-\\rangle_x$\n",
        "   * c. $|+\\rangle_y$ also called $|+i\\rangle$\n",
        "   * d. $|-\\rangle_y$ also called $|-i\\rangle$\n",
        "   * e. $|0\\rangle$ also called $|\\uparrow\\rangle$\n",
        "   * f. $|1\\rangle$ also called $|\\downarrow\\rangle$\n",
        "\n",
        "4. Which of the following states yields the largest uncertainty $\\Delta X$?\n",
        "   * a. $|+\\rangle_x$\n",
        "   * b. $|+\\rangle_y$ also called $|+i\\rangle$\n",
        "   * c. $|0\\rangle$ also called $|\\uparrow\\rangle$\n",
        "   * d. a and b are tied\n",
        "   * e. b and c are tied\n",
        "   * f. a, b, and c are tied\n",
        "\n",
        "### Discussion Questions:\n",
        "\n",
        "1. Does this concept of uncertainty conflict in any way with the notion of spin as a vector arrow in Cartesian space? How about on the Bloch sphere?\n",
        "\n",
        "2. Suppose you orient a measurement device along a direction half-way between the $x$ and $y$ axes. What happens? Can you make a measurement along this direction? How does this relate to uncertainty in $X$ and $Y$?\n",
        "\n",
        "3. What additional experiments would you like to do to convince yourself of the results obtained here?\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "in_page_toc_max_heading_level": 2,
    "in_page_toc_min_heading_level": 2,
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}