diff --git a/docs/tutorials/chsh-inequality.ipynb b/docs/tutorials/chsh-inequality.ipynb index fce48a2c44c..9b65a070fc7 100644 --- a/docs/tutorials/chsh-inequality.ipynb +++ b/docs/tutorials/chsh-inequality.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "30734ce9", + "id": "492d7cec", "metadata": {}, "source": [ "---\n", @@ -10,109 +10,60 @@ "description: Run an experiment on a quantum computer to demonstrate the violation of the CHSH inequality with the Estimator primitive.\n", "---\n", "\n", + "{/* cspell:ignore zorder Tsirelson */}\n", "\n", - "{/* cspell:ignore zorder */}\n", + "# CHSH inequality\n", "\n", - "# CHSH inequality" + "*Usage estimate: Two minutes on a Heron r3 processor (NOTE: This is an estimate only. Your runtime might vary.)*" ] }, { "cell_type": "markdown", - "id": "6b41d6b3", + "id": "a57d9139", "metadata": {}, "source": [ - "*Usage estimate: Two minutes on a Heron r2 processor (NOTE: This is an estimate only. Your runtime might vary.)*" + "## Learning outcomes\n", + "After completing this tutorial, you can expect to understand the following information:\n", + "- How to construct a parameterized Bell-state CHSH circuit and measure the four expectation values that make up the CHSH witnesses.\n", + "- How to compute expectation values of multiple observables on a parameter sweep in a single call to the [`EstimatorV2`](/docs/api/qiskit-ibm-runtime/estimator-v2) primitive.\n", + "- How to validate a quantum workflow on a noisy local simulator with `AerSimulator.from_backend` before submitting to hardware.\n", + "- How to scale a CHSH experiment into a device-wide entanglement benchmark by running many independent Bell pairs in parallel on IBM Quantum® hardware.\n", + "\n", + "## Prerequisites\n", + "It is recommended that you familiarize yourself with these topics:\n", + "- [Entanglement in action](/learning/courses/basics-of-quantum-information/entanglement-in-action/chsh-game), a course lesson on Bell states and the CHSH game.\n", + "- [`SparsePauliOp`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp) and the Qiskit primitives [introduction](/docs/guides/primitives)." ] }, { "cell_type": "markdown", - "id": "93ae1484", + "id": "f956eda1", "metadata": {}, "source": [ "## Background\n", "\n", "In this tutorial, you will run an experiment on a quantum computer to demonstrate the violation of the CHSH inequality with the Estimator primitive.\n", "\n", - "The CHSH inequality, named after the authors Clauser, Horne, Shimony, and Holt, is used to experimentally prove Bell's theorem (1969). This theorem asserts that local hidden variable theories cannot account for some consequences of entanglement in quantum mechanics. The violation of the CHSH inequality is used to show that quantum mechanics is incompatible with local hidden-variable theories. This is an important experiment for understanding the foundation of quantum mechanics.\n", - "\n", - "The 2022 Nobel Prize for Physics was awarded to Alain Aspect, John Clauser and Anton Zeilinger in part for their pioneering work in quantum information science, and in particular, for their experiments with entangled photons demonstrating violation of Bell’s inequalities." - ] - }, - { - "cell_type": "markdown", - "id": "6f5d9398-5aaa-4596-a554-61c923f0c2d2", - "metadata": {}, - "source": [ - "## Requirements\n", - "\n", - "Before starting this tutorial, ensure that you have the following installed:\n", - "\n", - "* Qiskit SDK v1.0 or later, with [visualization](/docs/api/qiskit/visualization) support\n", - "* Qiskit Runtime (`pip install qiskit-ibm-runtime`) v0.22 or later" - ] - }, - { - "cell_type": "markdown", - "id": "b5df762e", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "3938d4a6", - "metadata": {}, - "outputs": [], - "source": [ - "# General\n", - "import numpy as np\n", + "The CHSH inequality, named after Clauser, Horne, Shimony, and Holt, is used to experimentally test Bell's theorem (1969). The theorem asserts that local hidden-variable theories cannot account for some consequences of entanglement in quantum mechanics. Demonstrating a violation of the CHSH inequality shows that quantum mechanics is incompatible with local hidden-variable theories, an experiment that is foundational to our understanding of quantum mechanics.\n", "\n", - "# Qiskit imports\n", - "from qiskit import QuantumCircuit\n", - "from qiskit.circuit import Parameter\n", - "from qiskit.quantum_info import SparsePauliOp\n", - "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", + "The 2022 Nobel Prize for Physics was awarded to Alain Aspect, John Clauser, and Anton Zeilinger in part for their pioneering work in quantum information science, and in particular, for their experiments with entangled photons demonstrating violation of Bell's inequalities.\n", "\n", - "# Qiskit Runtime imports\n", - "from qiskit_ibm_runtime import QiskitRuntimeService\n", - "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n", - "\n", - "# Plotting routines\n", - "import matplotlib.pyplot as plt\n", - "import matplotlib.ticker as tck" - ] - }, - { - "cell_type": "markdown", - "id": "66d5e2e2", - "metadata": {}, - "source": [ - "## Step 1: Map classical inputs to a quantum problem" - ] - }, - { - "cell_type": "markdown", - "id": "36669708", - "metadata": {}, - "source": [ - "For this experiment, we will create an entangled pair on which we measure each qubit on two different bases. We will label the bases for the first qubit $A$ and $a$ and the bases for the second qubit $B$ and $b$. This allows us to compute the CHSH quantity $S_1$:\n", + "For this experiment, we will create an entangled pair on which we measure each qubit in two different bases. We will label the bases for the first qubit $A$ and $a$ and the bases for the second qubit $B$ and $b$. This allows us to compute the CHSH quantity $S_1$:\n", "\n", "$$\n", "S_1 = A(B-b) + a(B+b).\n", "$$\n", "\n", - "Each observable is either $+1$ or $-1$. Clearly, one of the terms $B\\pm b$ must be $0$, and the other must be $\\pm 2$. Therefore, $S_1 = \\pm 2$. The average value of $S_1$ must satisfy the inequality:\n", + "Each observable is either $+1$ or $-1$. Clearly, one of the terms $B\\pm b$ must be $0$, and the other must be $\\pm 2$. Therefore, $S_1 = \\pm 2$. The average value of $S_1$ must satisfy the inequality:\n", "\n", "$$\n", "|\\langle S_1 \\rangle|\\leq 2.\n", "$$\n", "\n", - "Expanding $S_1$ in terms of $A$, $a$, $B$, and $b$ results in:\n", + "Expanding $S_1$ in terms of $A$, $a$, $B$, and $b$ gives:\n", "\n", "$$\n", - "|\\langle S_1 \\rangle| = |\\langle AB \\rangle - \\langle Ab \\rangle + \\langle aB \\rangle + \\langle ab \\rangle| \\leq 2\n", + "|\\langle S_1 \\rangle| = |\\langle AB \\rangle - \\langle Ab \\rangle + \\langle aB \\rangle + \\langle ab \\rangle| \\leq 2.\n", "$$\n", "\n", "You can define another CHSH quantity $S_2$:\n", @@ -121,45 +72,90 @@ "S_2 = A(B+b) - a(B-b),\n", "$$\n", "\n", - "This leads to another inequality:\n", + "which leads to another inequality:\n", "\n", "$$\n", - "|\\langle S_2 \\rangle| = |\\langle AB \\rangle + \\langle Ab \\rangle - \\langle aB \\rangle + \\langle ab \\rangle| \\leq 2\n", + "|\\langle S_2 \\rangle| = |\\langle AB \\rangle + \\langle Ab \\rangle - \\langle aB \\rangle + \\langle ab \\rangle| \\leq 2.\n", "$$\n", "\n", - "If quantum mechanics can be described by local hidden variable theories, the previous inequalities must hold true. However, as is demonstrated in this tutorial, these inequalities can be violated in a quantum computer. Therefore, quantum mechanics is not compatible with local hidden variable theories." + "If quantum mechanics could be described by local hidden-variable theories, these inequalities would always hold. As demonstrated in this tutorial, they can be violated on a quantum computer, so quantum mechanics is not compatible with local hidden-variable theories.\n", + "\n", + "We create the entangled pair by preparing the Bell state $|\\Phi^+\\rangle = \\frac{|00\\rangle + |11\\rangle}{\\sqrt{2}}$. Using the Estimator primitive, we obtain the expectation values $\\langle AB \\rangle, \\langle Ab \\rangle, \\langle aB \\rangle$, and $\\langle ab \\rangle$ directly, without reconstructing them from raw counts. We measure the second qubit in the $Z$ and $X$ bases. The first qubit is measured in orthogonal bases as well, but with a rotation angle $\\theta$ that we sweep between $0$ and $2\\pi$. The Estimator primitive evaluates this parameter sweep in a single [primitive unified bloc (PUB)](/docs/guides/primitive-input-output)." ] }, { "cell_type": "markdown", - "id": "2033cfcd", + "id": "c7346a67", "metadata": {}, "source": [ - "If you want to learn more theory, explore [Entanglement in Action](/learning/courses/basics-of-quantum-information/entanglement-in-action/chsh-game) with John Watrous." + "## Requirements\n", + "Before starting this tutorial, be sure you have the following installed:\n", + "\n", + "- Qiskit SDK v2.0 or later, with [visualization](/docs/api/qiskit/visualization) support\n", + "- Qiskit Runtime v0.40 or later (`pip install qiskit-ibm-runtime`)\n", + "- Qiskit Aer v0.17 or later (`pip install qiskit-aer`)" ] }, { "cell_type": "markdown", - "id": "d4cec8d8", + "id": "d5ffeb42", "metadata": {}, "source": [ - "You will create an entangled pair between two qubits in a quantum computer by creating the Bell state $|\\Phi^+\\rangle = \\frac{|00\\rangle + |11\\rangle}{\\sqrt{2}}$. Using the Estimator primitive, you can directly obtain the expectation values needed ($\\langle AB \\rangle, \\langle Ab \\rangle, \\langle aB \\rangle$, and $\\langle ab \\rangle$) to calculate the expectation values of the two CHSH quantities $\\langle S_1\\rangle$ and $\\langle S_2\\rangle$. Before the introduction of the Estimator primitive, you would have to construct the expectation values from the measurement outcomes.\n", + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "323b27db", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:49.177326Z", + "iopub.status.busy": "2026-05-19T22:38:49.177203Z", + "iopub.status.idle": "2026-05-19T22:38:49.804694Z", + "shell.execute_reply": "2026-05-19T22:38:49.804154Z" + } + }, + "outputs": [], + "source": [ + "# General\n", + "import numpy as np\n", "\n", - "You will measure the second qubit in the $Z$ and $X$ bases. The first qubit will be measured also in orthogonal bases, but with an angle with respect to the second qubit, which we are going to sweep between $0$ and $2\\pi$. As you will see, the Estimator primitive makes running parameterized circuits very easy. Rather than creating a series of CHSH circuits, you only need to create *one* CHSH circuit with a parameter specifying the measurement angle and a series of phase values for the parameter.\n", + "# Qiskit imports\n", + "from qiskit import QuantumCircuit\n", + "from qiskit.circuit import Parameter\n", + "from qiskit.quantum_info import SparsePauliOp\n", + "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", "\n", - "Finally, you will analyze the results and plot them against the measurement angle. You will see that for certain range of measurement angles, the expectation values of CHSH quantities $|\\langle S_1\\rangle| > 2$ or $|\\langle S_2\\rangle| > 2$, which demonstrates the violation of the CHSH inequality." + "# Qiskit Runtime imports\n", + "from qiskit_ibm_runtime import QiskitRuntimeService\n", + "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n", + "\n", + "# Qiskit Aer for local noisy simulation\n", + "from qiskit_aer import AerSimulator\n", + "\n", + "# Plotting routines\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as tck" ] }, { "cell_type": "code", - "execution_count": 2, - "id": "16f323fb", - "metadata": {}, + "execution_count": null, + "id": "fd284a0f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:49.806301Z", + "iopub.status.busy": "2026-05-19T22:38:49.806113Z", + "iopub.status.idle": "2026-05-19T22:38:53.208796Z", + "shell.execute_reply": "2026-05-19T22:38:53.208025Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "'ibm_kingston'" + "'ibm_pittsburgh'" ] }, "execution_count": 2, @@ -168,34 +164,51 @@ } ], "source": [ - "# To run on hardware, select the backend with the fewest number of jobs in the queue\n", + "# Select an IBM Quantum backend.\n", "service = QiskitRuntimeService()\n", "backend = service.least_busy(\n", - " operational=True, simulator=False, min_num_qubits=127\n", + " min_num_qubits=127, operational=True, simulator=False\n", ")\n", "backend.name" ] }, { "cell_type": "markdown", - "id": "1ab329f2", + "id": "ca035271", + "metadata": {}, + "source": [ + "## Small-scale simulator example\n", + "\n", + "Before submitting a hardware job, we validate the entire workflow on a local noisy simulator. We use `AerSimulator.from_backend(backend)` to build a simulator that inherits the noise model and coupling map of the backend you selected, so the simulator response is qualitatively similar to what we expect from hardware." + ] + }, + { + "cell_type": "markdown", + "id": "0539a796", "metadata": {}, "source": [ - "### Create a parameterized CHSH circuit\n", + "### Step 1: Map classical inputs to a quantum problem\n", "\n", - "First, we write the circuit with the parameter $\\theta$, which we call `theta`. The [`Estimator` primitive](https://docs.quantum-computing.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.EstimatorV2) can enormously simplify circuit building and output analysis by directly providing expectation values of observables. Many problems of interest, especially for near-term applications on noisy systems, can be formulated in terms of expectation values. `Estimator` (V2) primitive can automatically change measurement basis based on the supplied observable." + "We write the CHSH circuit with a single parameter $\\theta$, which sweeps the measurement basis of the first qubit. The [`Estimator`](/docs/api/qiskit-ibm-runtime/estimator-v2) primitive simplifies the analysis: it returns expectation values of observables directly, and it can evaluate a parameterized circuit at many parameter values in a single call." ] }, { "cell_type": "code", "execution_count": 3, - "id": "6c77e40a", - "metadata": {}, + "id": "c3f57d25", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:53.210931Z", + "iopub.status.busy": "2026-05-19T22:38:53.210786Z", + "iopub.status.idle": "2026-05-19T22:38:53.349189Z", + "shell.execute_reply": "2026-05-19T22:38:53.348782Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, "execution_count": 3, @@ -204,7 +217,7 @@ } ], "source": [ - "theta = Parameter(\"$\\\\theta$\")\n", + "theta = Parameter(r\"$\\theta$\")\n", "\n", "chsh_circuit = QuantumCircuit(2)\n", "chsh_circuit.h(0)\n", @@ -215,50 +228,68 @@ }, { "cell_type": "markdown", - "id": "de760250", + "id": "af999dde", "metadata": {}, "source": [ - "### Create a list of phase values to be assigned later\n", - "\n", - "After creating the parameterized CHSH circuit, you will create a list of phase values to be assigned to the circuit in the next step. You can use the following code to create a list of 21 phase values range from $0$ to $2 \\pi$ with equal spacing, that is, $0$, $0.1 \\pi$, $0.2 \\pi$, ..., $1.9 \\pi$, $2 \\pi$." + "Next, we create a list of 21 phase values from $0$ to $2\\pi$ at which to evaluate the parameterized circuit ($0$, $0.1\\pi$, $0.2\\pi$, ..., $1.9\\pi$, $2\\pi$)." ] }, { "cell_type": "code", "execution_count": 4, - "id": "bcb3b32b", - "metadata": {}, + "id": "9d587e2f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:53.350400Z", + "iopub.status.busy": "2026-05-19T22:38:53.350337Z", + "iopub.status.idle": "2026-05-19T22:38:53.352045Z", + "shell.execute_reply": "2026-05-19T22:38:53.351729Z" + } + }, "outputs": [], "source": [ "number_of_phases = 21\n", "phases = np.linspace(0, 2 * np.pi, number_of_phases)\n", - "# Phases need to be expressed as list of lists in order to work\n", + "# Phases need to be expressed as a list of lists for the Estimator PUB\n", "individual_phases = [[ph] for ph in phases]" ] }, { "cell_type": "markdown", - "id": "6e559aed", + "id": "af917afb", "metadata": {}, "source": [ - "### Observables\n", + "Finally we define the observables. The first qubit is measured along axes rotated by $\\theta$; the second qubit is measured in $Z$ and $X$. With those choices, the four CHSH correlators map to the Pauli operators $ZZ$, $ZX$, $XZ$, and $XX$:\n", "\n", - "Now we need observables from which to compute the expectation values. In our case we are looking at orthogonal bases for each qubit, letting the parameterized $Y-$ rotation for the first qubit sweep the measurement basis nearly continuously with respect to the second qubit basis. We will therefore choose the observables $ZZ$, $ZX$, $XZ$, and $XX$." + "$$\n", + "\\langle S_1 \\rangle = \\langle ZZ \\rangle - \\langle ZX \\rangle + \\langle XZ \\rangle + \\langle XX \\rangle,\n", + "$$\n", + "\n", + "$$\n", + "\\langle S_2 \\rangle = \\langle ZZ \\rangle + \\langle ZX \\rangle - \\langle XZ \\rangle + \\langle XX \\rangle.\n", + "$$" ] }, { "cell_type": "code", "execution_count": 5, - "id": "940fc1dc-89c5-4137-bea7-5e892dd7f36e", - "metadata": {}, + "id": "21ae6238", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:53.353007Z", + "iopub.status.busy": "2026-05-19T22:38:53.352948Z", + "iopub.status.idle": "2026-05-19T22:38:53.355217Z", + "shell.execute_reply": "2026-05-19T22:38:53.354857Z" + } + }, "outputs": [], "source": [ - "# = - + + -> - + + \n", + "# = - + + \n", "observable1 = SparsePauliOp.from_list(\n", " [(\"ZZ\", 1), (\"ZX\", -1), (\"XZ\", 1), (\"XX\", 1)]\n", ")\n", "\n", - "# = + - + -> + - + \n", + "# = + - + \n", "observable2 = SparsePauliOp.from_list(\n", " [(\"ZZ\", 1), (\"ZX\", 1), (\"XZ\", -1), (\"XX\", 1)]\n", ")" @@ -266,38 +297,31 @@ }, { "cell_type": "markdown", - "id": "efabedc4", + "id": "2d571aa2", "metadata": {}, "source": [ - "## Step 2: Optimize problem for quantum hardware execution" - ] - }, - { - "cell_type": "markdown", - "id": "4b2b4637-a2d4-41d6-a9c5-98fe3b8a2cd2", - "metadata": {}, - "source": [ - "To reduce the total job execution time, V2 primitives only accept circuits and observables that conforms to the instructions and connectivity supported by the target system (referred to as instruction set architecture (ISA) circuits and observables)." - ] - }, - { - "cell_type": "markdown", - "id": "5dcd3fea-3754-4c75-b11e-9216570336b7", - "metadata": {}, - "source": [ - "### ISA Circuit" + "### Step 2: Optimize problem for quantum hardware execution\n", + "\n", + "V2 primitives only accept circuits and observables that conform to the instructions and connectivity supported by the target system (instruction set architecture, or ISA, circuits and observables). We build the `AerSimulator` from the backend and transpile against the simulator's target so the same pass manager is exercised end-to-end." ] }, { "cell_type": "code", "execution_count": 6, - "id": "9a5561eb", - "metadata": {}, + "id": "3e139a89", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:53.356197Z", + "iopub.status.busy": "2026-05-19T22:38:53.356140Z", + "iopub.status.idle": "2026-05-19T22:38:56.713188Z", + "shell.execute_reply": "2026-05-19T22:38:56.712683Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, "execution_count": 6, @@ -306,34 +330,34 @@ } ], "source": [ - "target = backend.target\n", - "pm = generate_preset_pass_manager(target=target, optimization_level=3)\n", + "# Build a noisy simulator from the ibm_pittsburgh backend\n", + "aer_sim = AerSimulator.from_backend(backend)\n", "\n", + "pm = generate_preset_pass_manager(target=aer_sim.target, optimization_level=3)\n", "chsh_isa_circuit = pm.run(chsh_circuit)\n", "chsh_isa_circuit.draw(output=\"mpl\", idle_wires=False, style=\"iqp\")" ] }, { "cell_type": "markdown", - "id": "718759d4-f1d8-45c3-9142-c8404059a892", + "id": "2817b069", "metadata": {}, "source": [ - "### ISA Observables" - ] - }, - { - "cell_type": "markdown", - "id": "76b721cc-7f45-4195-ae61-53d29e2bf8d4", - "metadata": {}, - "source": [ - "Similarly, we need to transform the observables to make it backend compatible before running jobs with [`Runtime Estimator V2`](/docs/api/qiskit-ibm-runtime/estimator-v2#run). We can perform the transformation using the `apply_layout` the method of `SparsePauliOp` object." + "We also transform the observables to match the transpiled circuit's qubit layout using `SparsePauliOp.apply_layout`." ] }, { "cell_type": "code", "execution_count": 7, - "id": "a787d6f9-5df4-4e0b-b2f0-07948b882a94", - "metadata": {}, + "id": "d1faa7ab", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:56.714405Z", + "iopub.status.busy": "2026-05-19T22:38:56.714288Z", + "iopub.status.idle": "2026-05-19T22:38:56.716319Z", + "shell.execute_reply": "2026-05-19T22:38:56.715992Z" + } + }, "outputs": [], "source": [ "isa_observable1 = observable1.apply_layout(layout=chsh_isa_circuit.layout)\n", @@ -342,74 +366,164 @@ }, { "cell_type": "markdown", - "id": "b7194399", + "id": "e734572e", "metadata": {}, "source": [ - "## Step 3: Execute using Qiskit primitives\n", + "### Step 3: Execute using Qiskit primitives\n", "\n", - "In order to execute the entire experiment in one call to the [`Estimator`](https://docs.quantum-computing.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.EstimatorV2)." - ] - }, - { - "cell_type": "markdown", - "id": "2d8ad9e1", - "metadata": {}, - "source": [ - "We can create a [Qiskit Runtime `Estimator`](/docs/api/qiskit-ibm-runtime/estimator-v2) primitive to compute our expectation values. The `EstimatorV2.run()` method takes an iterable of `primitive unified blocs (PUBs)`. Each PUB is an iterable in the format `(circuit, observables, parameter_values: Optional, precision: Optional)`." + "Run the parameter sweep with `EstimatorV2` in `aer_sim` mode. The Estimator `run()` method takes an iterable of PUBs. Each PUB has the format `(circuit, observables, parameter_values, precision)`. We pass both observables together so they share the same parameter sweep." ] }, { "cell_type": "code", "execution_count": 8, - "id": "e73db51f", - "metadata": {}, + "id": "7f954aeb", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:56.717549Z", + "iopub.status.busy": "2026-05-19T22:38:56.717473Z", + "iopub.status.idle": "2026-05-19T22:38:58.217539Z", + "shell.execute_reply": "2026-05-19T22:38:58.217000Z" + } + }, "outputs": [], "source": [ - "# To run on a local simulator:\n", - "# Use the StatevectorEstimator from qiskit.primitives instead.\n", - "\n", - "estimator = Estimator(mode=backend)\n", + "# Use the AerSimulator-backed Estimator to validate the workflow locally\n", + "estimator_sim = Estimator(mode=aer_sim)\n", "\n", "pub = (\n", " chsh_isa_circuit, # ISA circuit\n", - " [[isa_observable1], [isa_observable2]], # ISA Observables\n", + " [[isa_observable1], [isa_observable2]], # ISA observables\n", " individual_phases, # Parameter values\n", ")\n", "\n", - "job_result = estimator.run(pubs=[pub]).result()" + "sim_result = estimator_sim.run(pubs=[pub]).result()" ] }, { "cell_type": "markdown", - "id": "ace7dc90", + "id": "9c5230fc", "metadata": {}, "source": [ - "## Step 4: Post-process and return result in desired classical format\n", + "### Step 4: Post-process and return result in desired classical format\n", "\n", - "The estimator returns expectation values for both of the observables, $\\langle ZZ \\rangle - \\langle ZX \\rangle + \\langle XZ \\rangle + \\langle XX \\rangle$ and $\\langle ZZ \\rangle + \\langle ZX \\rangle - \\langle XZ \\rangle + \\langle XX \\rangle$." + "The Estimator returns expectation values for both observables. We plot them against $\\theta$ together with the classical bound ($\\pm 2$) and the Tsirelson bound ($\\pm 2\\sqrt{2}$). The shaded grey regions mark the gap between the two. Points that lie inside these bands violate the CHSH inequality." ] }, { "cell_type": "code", "execution_count": 9, - "id": "947fc660-073b-4dee-b347-358fd3e52ea2", + "id": "c8fd5140", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:58.219407Z", + "iopub.status.busy": "2026-05-19T22:38:58.219306Z", + "iopub.status.idle": "2026-05-19T22:38:58.285202Z", + "shell.execute_reply": "2026-05-19T22:38:58.284609Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "chsh1_sim = sim_result[0].data.evs[0]\n", + "chsh2_sim = sim_result[0].data.evs[1]\n", + "\n", + "\n", + "def plot_chsh(phases, chsh1, chsh2, title):\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + " ax.plot(\n", + " phases / np.pi, chsh1, \"o-\", label=r\"$\\langle S_1 \\rangle$\", zorder=3\n", + " )\n", + " ax.plot(\n", + " phases / np.pi, chsh2, \"o-\", label=r\"$\\langle S_2 \\rangle$\", zorder=3\n", + " )\n", + "\n", + " # classical bound +-2\n", + " ax.axhline(y=2, color=\"0.9\", linestyle=\"--\")\n", + " ax.axhline(y=-2, color=\"0.9\", linestyle=\"--\")\n", + "\n", + " # quantum bound, +-2*sqrt(2)\n", + " ax.axhline(y=np.sqrt(2) * 2, color=\"0.9\", linestyle=\"-.\")\n", + " ax.axhline(y=-np.sqrt(2) * 2, color=\"0.9\", linestyle=\"-.\")\n", + " ax.fill_between(phases / np.pi, 2, 2 * np.sqrt(2), color=\"0.6\", alpha=0.7)\n", + " ax.fill_between(\n", + " phases / np.pi, -2, -2 * np.sqrt(2), color=\"0.6\", alpha=0.7\n", + " )\n", + "\n", + " ax.xaxis.set_major_formatter(tck.FormatStrFormatter(\"%g $\\\\pi$\"))\n", + " ax.xaxis.set_major_locator(tck.MultipleLocator(base=0.5))\n", + "\n", + " ax.set_xlabel(r\"$\\theta$\")\n", + " ax.set_ylabel(\"CHSH witness\")\n", + " ax.set_title(title)\n", + " ax.legend()\n", + " plt.show()\n", + "\n", + "\n", + "plot_chsh(\n", + " phases,\n", + " chsh1_sim,\n", + " chsh2_sim,\n", + " \"CHSH witnesses from AerSimulator (ibm_pittsburgh noise model)\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e9744b84", "metadata": {}, - "outputs": [], "source": [ - "chsh1_est = job_result[0].data.evs[0]\n", - "chsh2_est = job_result[0].data.evs[1]" + "The simulator's CHSH witnesses already exceed the classical bound of $\\pm 2$ at several values of $\\theta$, even with the backend's noise model. The peaks fall just short of the Tsirelson bound $\\pm 2\\sqrt{2}$ because of simulated device noise. With the workflow validated, we move on to actual hardware." + ] + }, + { + "cell_type": "markdown", + "id": "747e948d", + "metadata": {}, + "source": [ + "## Large-scale hardware example\n", + "\n", + "A CHSH test is intrinsically a *two-qubit* experiment, so it does not scale by making one circuit bigger. Instead, it scales by running **many tests in parallel**. Here we tile the backend with as many disjoint Bell pairs as its connectivity allows (a *matching* of the coupling map) and run an independent CHSH sub-circuit on every pair, all in a single job.\n", + "\n", + "This turns CHSH into a **device-wide benchmark of entanglement quality**: rather than a single hand-picked pair, we test entanglement across a large fraction of the chip at once, under realistic conditions where every pair contends with its neighbors' crosstalk and parallel-gate errors. Violating the inequality on every pair *simultaneously* certifies that genuine entanglement is available everywhere on the device." ] }, { "cell_type": "code", "execution_count": 10, - "id": "f6267448", - "metadata": {}, + "id": "3376bc73", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-19T22:38:58.286726Z", + "iopub.status.busy": "2026-05-19T22:38:58.286656Z", + "iopub.status.idle": "2026-05-19T22:38:59.198086Z", + "shell.execute_reply": "2026-05-19T22:38:59.197690Z" + } + }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tiling ibm_pittsburgh with 64 parallel Bell pairs (128 of 156 qubits)\n", + "Job ID: d86efd5g7okc73el0rp0\n", + "63/64 Bell pairs violated the CHSH inequality (mean peak witness 2.75, classical bound 2)\n" + ] + }, { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, "metadata": {}, @@ -417,51 +531,142 @@ } ], "source": [ + "# -------------------------Step 1: Map classical inputs to a quantum problem-------------------------\n", + "# A CHSH test is bipartite, so we scale up by running one independent CHSH\n", + "# experiment on every disjoint Bell pair the device can host. A greedy\n", + "# matching of the coupling map gives a set of edges that share no qubits.\n", + "num_qubits = backend.num_qubits\n", + "used = set()\n", + "pairs = []\n", + "for qa, qb in backend.coupling_map.get_edges():\n", + " if qa not in used and qb not in used:\n", + " pairs.append((qa, qb))\n", + " used.update((qa, qb))\n", + "num_pairs = len(pairs)\n", + "print(\n", + " f\"Tiling {backend.name} with {num_pairs} parallel Bell pairs \"\n", + " f\"({2 * num_pairs} of {num_qubits} qubits)\"\n", + ")\n", + "\n", + "# One parameterized CHSH sub-circuit per pair, all sharing the angle theta\n", + "theta = Parameter(r\"$\\theta$\")\n", + "chsh_circuit = QuantumCircuit(num_qubits)\n", + "for qa, qb in pairs:\n", + " chsh_circuit.h(qa)\n", + " chsh_circuit.cx(qa, qb)\n", + " chsh_circuit.ry(theta, qa)\n", + "\n", + "# Embed the two CHSH observables onto each pair's qubits (identity elsewhere)\n", + "obs1 = SparsePauliOp.from_list([(\"ZZ\", 1), (\"ZX\", -1), (\"XZ\", 1), (\"XX\", 1)])\n", + "obs2 = SparsePauliOp.from_list([(\"ZZ\", 1), (\"ZX\", 1), (\"XZ\", -1), (\"XX\", 1)])\n", + "observables = []\n", + "for qa, qb in pairs:\n", + " observables.append([obs1.apply_layout([qa, qb], num_qubits)])\n", + " observables.append([obs2.apply_layout([qa, qb], num_qubits)])\n", + "\n", + "number_of_phases = 21\n", + "phases = np.linspace(0, 2 * np.pi, number_of_phases)\n", + "individual_phases = [[ph] for ph in phases]\n", + "\n", + "# -------------------------Step 2: Optimize problem for quantum hardware execution-------------------------\n", + "pm = generate_preset_pass_manager(target=backend.target, optimization_level=3)\n", + "chsh_isa_circuit = pm.run(chsh_circuit)\n", + "isa_observables = [\n", + " [o[0].apply_layout(chsh_isa_circuit.layout)] for o in observables\n", + "]\n", + "\n", + "# -------------------------Step 3: Execute using Qiskit primitives-------------------------\n", + "estimator_hw = Estimator(mode=backend)\n", + "estimator_hw.options.environment.job_tags = [\"TUT_CI\"]\n", + "\n", + "pub = (chsh_isa_circuit, isa_observables, individual_phases)\n", + "job = estimator_hw.run(pubs=[pub])\n", + "print(f\"Job ID: {job.job_id()}\")\n", + "hw_result = job.result()\n", + "\n", + "# -------------------------Step 4: Post-process and return result in desired classical format-------------------------\n", + "# evs has shape (2 * num_pairs, number_of_phases); rows alternate S1, S2\n", + "evs = np.asarray(hw_result[0].data.evs)\n", + "chsh1_all = evs[0::2]\n", + "chsh2_all = evs[1::2]\n", + "\n", + "# A pair \"violates\" CHSH if its strongest witness exceeds the classical bound\n", + "peak = np.maximum(\n", + " np.abs(chsh1_all).max(axis=1), np.abs(chsh2_all).max(axis=1)\n", + ")\n", + "n_violate = int(np.sum(peak > 2))\n", + "print(\n", + " f\"{n_violate}/{num_pairs} Bell pairs violated the CHSH inequality \"\n", + " f\"(mean peak witness {peak.mean():.2f}, classical bound 2)\"\n", + ")\n", + "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "\n", - "# results from hardware\n", - "ax.plot(phases / np.pi, chsh1_est, \"o-\", label=\"CHSH1\", zorder=3)\n", - "ax.plot(phases / np.pi, chsh2_est, \"o-\", label=\"CHSH2\", zorder=3)\n", + "# Faint individual per-pair curves\n", + "for row in chsh1_all:\n", + " ax.plot(phases / np.pi, row, color=\"#1f77b4\", alpha=0.2, lw=1)\n", + "for row in chsh2_all:\n", + " ax.plot(phases / np.pi, row, color=\"#ff7f0e\", alpha=0.2, lw=1)\n", + "\n", + "# Bold mean curves across all pairs\n", + "ax.plot(\n", + " phases / np.pi,\n", + " chsh1_all.mean(axis=0),\n", + " color=\"#1f77b4\",\n", + " lw=2.5,\n", + " label=r\"$\\langle S_1 \\rangle$ (mean)\",\n", + ")\n", + "ax.plot(\n", + " phases / np.pi,\n", + " chsh2_all.mean(axis=0),\n", + " color=\"#ff7f0e\",\n", + " lw=2.5,\n", + " label=r\"$\\langle S_2 \\rangle$ (mean)\",\n", + ")\n", "\n", - "# classical bound +-2\n", + "# classical bound +-2 and Tsirelson bound +-2*sqrt(2)\n", "ax.axhline(y=2, color=\"0.9\", linestyle=\"--\")\n", "ax.axhline(y=-2, color=\"0.9\", linestyle=\"--\")\n", - "\n", - "# quantum bound, +-2√2\n", "ax.axhline(y=np.sqrt(2) * 2, color=\"0.9\", linestyle=\"-.\")\n", "ax.axhline(y=-np.sqrt(2) * 2, color=\"0.9\", linestyle=\"-.\")\n", "ax.fill_between(phases / np.pi, 2, 2 * np.sqrt(2), color=\"0.6\", alpha=0.7)\n", "ax.fill_between(phases / np.pi, -2, -2 * np.sqrt(2), color=\"0.6\", alpha=0.7)\n", "\n", - "# set x tick labels to the unit of pi\n", "ax.xaxis.set_major_formatter(tck.FormatStrFormatter(\"%g $\\\\pi$\"))\n", "ax.xaxis.set_major_locator(tck.MultipleLocator(base=0.5))\n", - "\n", - "# set labels, and legend\n", - "plt.xlabel(\"Theta\")\n", - "plt.ylabel(\"CHSH witness\")\n", - "plt.legend()\n", + "ax.set_xlabel(r\"$\\theta$\")\n", + "ax.set_ylabel(\"CHSH witness\")\n", + "ax.set_title(\n", + " f\"CHSH witnesses for {num_pairs} parallel Bell pairs on {backend.name}\"\n", + ")\n", + "ax.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", - "id": "63e63853", + "id": "3c32f01c", "metadata": {}, "source": [ - "In the figure, the lines and gray areas delimit the bounds; the outer-most (dash-dotted) lines delimit the quantum-bounds ($\\pm 2\\sqrt{2}$), whereas the inner (dashed) lines delimit the classical bounds ($\\pm 2$). You can see that there are regions where the CHSH witness quantities exceeds the classical bounds. Congratulations! You have successfully demonstrated the violation of CHSH inequality in a real quantum system!" + "The faint curves are the individual Bell pairs and the bold curves are their mean across the device. Every pair traces the same sinusoid predicted by quantum mechanics, and the spread between the faint curves reflects the variation in noise from pair to pair. Wherever a curve enters the grey bands, it has crossed the classical bound of $\\pm 2$, and the printed summary confirms that essentially every pair violates the CHSH inequality at the same time.\n", + "\n", + "The peaks fall short of the Tsirelson bound $\\pm 2\\sqrt{2}$ because of device noise, but the conclusion is unambiguous: the backend sustains genuine entanglement across the whole chip simultaneously, not just on a single hand-picked pair. This is the sense in which the CHSH experiment \"scales\": not as one larger circuit, but as a parallel benchmark that certifies entanglement everywhere at once." ] }, { "cell_type": "markdown", - "id": "24942533", + "id": "86e635a3", "metadata": {}, "source": [ - "## Tutorial survey\n", - "\n", - "Please take this short survey to provide feedback on this tutorial. Your insights will help us improve our content offerings and user experience.\n", + "## Next steps\n", + "\n", "\n", - "[Link to survey](https://your.feedback.ibm.com/jfe/form/SV_3xxAgm1SF1wGp9k)" + "If you found this work interesting, you might be interested in the following material:\n", + "- [Entanglement in action](/learning/courses/basics-of-quantum-information/entanglement-in-action/chsh-game): a course lesson by John Watrous on Bell states and the CHSH game.\n", + "- [Get started with the Estimator primitive](/docs/guides/primitives): a guide on PUBs and parameter sweeps.\n", + "- [Real-time benchmarking for qubit selection](/docs/tutorials/real-time-benchmarking-for-qubit-selection): another way to characterize qubit and entanglement quality across a device.\n", + "- [`SparsePauliOp` API reference](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp).\n", + "" ] } ], @@ -482,11 +687,6 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3" - }, - "vscode": { - "interpreter": { - "hash": "1910f834ba40d1043b9ae723a4d216fb6c2faf55822807adca52e5ece8e3f1c8" - } } }, "nbformat": 4, diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/3376bc73-1.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/3376bc73-1.avif new file mode 100644 index 00000000000..8b7344f6798 Binary files /dev/null and b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/3376bc73-1.avif differ diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/3e139a89-0.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/3e139a89-0.avif new file mode 100644 index 00000000000..44e99c06257 Binary files /dev/null and b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/3e139a89-0.avif differ diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/6c77e40a-0.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/6c77e40a-0.avif deleted file mode 100644 index 2c0ff5513af..00000000000 Binary files a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/6c77e40a-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/9a5561eb-0.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/9a5561eb-0.avif deleted file mode 100644 index 02ce749488b..00000000000 Binary files a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/9a5561eb-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/c3f57d25-0.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/c3f57d25-0.avif new file mode 100644 index 00000000000..01fb6787f08 Binary files /dev/null and b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/c3f57d25-0.avif differ diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/c8fd5140-0.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/c8fd5140-0.avif new file mode 100644 index 00000000000..ac9c1ea7b1f Binary files /dev/null and b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/c8fd5140-0.avif differ diff --git a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/f6267448-0.avif b/public/docs/images/tutorials/chsh-inequality/extracted-outputs/f6267448-0.avif deleted file mode 100644 index ed89b30c427..00000000000 Binary files a/public/docs/images/tutorials/chsh-inequality/extracted-outputs/f6267448-0.avif and /dev/null differ