diff --git a/docs/tutorials/_toc.json b/docs/tutorials/_toc.json index 01e117d2065..cee15c4ca80 100644 --- a/docs/tutorials/_toc.json +++ b/docs/tutorials/_toc.json @@ -221,6 +221,10 @@ "title": "Combine error mitigation options with the Estimator primitive", "url": "/docs/tutorials/combine-error-mitigation-techniques" }, + { + "title": "Probabilistic error cancellation with shaded lightcones", + "url": "/docs/tutorials/pec-with-shaded-lightcones" + }, { "title": "Real-time benchmarking for qubit selection", "url": "/docs/tutorials/real-time-benchmarking-for-qubit-selection" diff --git a/docs/tutorials/index.mdx b/docs/tutorials/index.mdx index e085cd74905..ef33bb27e75 100644 --- a/docs/tutorials/index.mdx +++ b/docs/tutorials/index.mdx @@ -157,6 +157,8 @@ Error mitigation addresses the challenge of noise without full fault tolerance b * [Combine error mitigation options with the Estimator primitive](/docs/tutorials/combine-error-mitigation-techniques) +* [Probabilistic error cancellation with shaded lightcones](/docs/tutorials/pec-with-shaded-lightcones) + * [Real-time benchmarking for qubit selection](/docs/tutorials/real-time-benchmarking-for-qubit-selection) diff --git a/docs/tutorials/pec-with-shaded-lightcones.ipynb b/docs/tutorials/pec-with-shaded-lightcones.ipynb new file mode 100644 index 00000000000..83ffa3217a0 --- /dev/null +++ b/docs/tutorials/pec-with-shaded-lightcones.ipynb @@ -0,0 +1,1790 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "slc-title-0000-4000-8000-000000000000", + "metadata": {}, + "source": [ + "---\n", + "title: Probabilistic error cancellation with shaded lightcones\n", + "description: Use probabilistic error cancellation (PEC) with shaded lightcones to cut PEC sampling overhead and mitigate errors in expectation value estimation on hardware\n", + "---\n", + "\n", + "{/* cspell:ignore samplomatic samplex Trotterized Trotter Lindblad lightcone lightcones Eddins reverser broadcastable postselection postselect antinoise eigval atol starmap unmitigated NoiseLearner PauliLindblad QubitSparsePauli Kingston kicked qubit canonical isa plm qspl Rzz pauli TREX levelname xticks expvals markerfacecolor ncols */}\n", + "\n", + "# Probabilistic error cancellation with shaded lightcones\n", + "\n", + "*Usage estimate: 10 minutes on an IBM Heron processor (NOTE: This is an estimate only. Your runtime might vary.)*" + ] + }, + { + "cell_type": "markdown", + "id": "slc-lout-0000-4000-8000-000000000001", + "metadata": {}, + "source": [ + "## Learning outcomes\n", + "After going through this tutorial, users should understand:\n", + "- What probabilistic error cancellation (PEC) is, and why its sampling overhead $\\gamma^2$ grows exponentially with the total noise acting on the circuit\n", + "- How shaded lightcones (SLC) bound each noise term's contribution to the target observable, letting you spend the mitigation budget where it matters and trade a bounded residual bias for lower sampling overhead\n", + "- How to learn layer noise with `NoiseLearnerV3` and inject anti-noise through `samplomatic` and the `Executor`\n", + "- How to combine PEC and PEC+SLC with TREX and post-selection to estimate an expectation value on hardware\n", + "\n", + "## Prerequisites\n", + "We suggest that users are familiar with the following topics before going through this tutorial:\n", + "- The [Qiskit patterns](/docs/guides/intro-to-patterns) workflow\n", + "- Using the [Estimator](/docs/api/qiskit-ibm-runtime/estimator-v2) primitive to calculate expectation values of an observable\n", + "- Error mitigation techniques such as Pauli twirling and TREX, covered in [Combine error mitigation options with the Estimator primitive](/docs/tutorials/combine-error-mitigation-techniques)" + ] + }, + { + "cell_type": "markdown", + "id": "slc-bkgd-0000-4000-8000-000000000002", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "This tutorial demonstrates how to mitigate errors by using the shaded lightcone (SLC) addon. This addon is an evolution of the [probabilistic error cancellation (PEC) technique](/docs/guides/error-mitigation-and-suppression-techniques#probabilistic-error-cancellation-pec), wherein a user learns the noise of unique layers in a circuit and then cancels out the noise by applying single-qubit gates and post-processing techniques. Compared to other methods, PEC offers more robust bounds on the bias of the mitigated result, but tends to suffer from a higher overhead in terms of QPU time. During PEC, to compensate for attenuation of the expectation value by noise, the average result is rescaled by a factor of $\\gamma = \\exp(\\sum_{l,\\sigma} 2\\lambda_{l,\\sigma})$, where $\\lambda_{l,\\sigma}$ is the learned noise rate of error Pauli $\\sigma$ at layer $l$ in the circuit. This rescaling increases the variance by a factor of $\\gamma^2$, and thus also multiplies the number of circuit executions needed on the QPU by $\\gamma^2$, which we call the sampling cost or sampling overhead. Because $\\gamma$ grows exponentially, PEC is often limited to shallow or few-qubit circuits. Learn more about PEC in [Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors](https://arxiv.org/abs/2201.09866).\n", + "\n", + "If we can identify errors that do not need to be mitigated, we can decrease this sampling cost exponentially. A first step in this direction is implementing locally aware error mitigation, which uses a quickly computable conventional \"lightcone\" to reduce the PEC overhead by bounding an observable's sensitivity to errors throughout the circuit, extending the feasibility of PEC to larger scales for some problems. Errors outside of this lightcone cannot affect the measured outcome and can therefore be excluded from the error cancellation procedure. This exclusion decreases the sampling overhead, in some cases substantially, without introducing additional bias. In particular, for measuring a local observable $O$ of a fixed-depth circuit, the required sampling overhead eventually plateaus when scaling the number of qubits in the circuit (see Fig. 2b in [Locality and error mitigation of quantum circuits](https://arxiv.org/abs/2303.06496)).\n", + "\n", + "Shaded lightcones (SLC) go further, using classical simulations to more tightly bound the sensitivity to errors throughout the circuit. This trades some QPU time for CPU time and reduces the sampling overhead needed to renormalize the bias. Instead of a hard cutoff, each potential error in the circuit is assigned a graded \"shade\" that upper-bounds the susceptibility of the observable to that error. This refined characterization allows for more efficient, targeted applications of PEC with reduced variance, while giving the user the ability to controllably tune the bias in the observable estimation. See [Lightcone shading for classically accelerated quantum error mitigation](https://arxiv.org/abs/2409.04401) for more details.\n", + "\n", + "Our workflow for the SLC addon leverages the [`samplomatic`](https://github.com/Qiskit/samplomatic) library together with the `QuantumProgram` and `Executor` classes added to Qiskit Runtime in `qiskit-ibm-runtime` 0.47.0, allowing users to have more modular control of execution settings for error suppression and mitigation while retaining ease of use.\n", + "\n", + "### SLC error-mitigation workflow at a glance\n", + "For modeling the QPU's noise, we use a sparse Pauli-Lindblad noise model with one- and two-qubit Pauli error rates, locally generated on each qubit and edge of the device. With this choice, the SLC error-mitigation workflow presented in this tutorial is as follows:\n", + "\n", + "a. CPU — Bound per-error impact of one- and two-qubit Pauli errors\n", + "\n", + " 1. Forward propagation (bound effect on observable). Propagate each error to the end of the circuit and compute its commutator with the observable.\n", + " - Truncate operator terms during evolution to keep computation tractable.\n", + " - Further tighten these bounds by a loose back-propagation of the observable based on quantum speed limits.\n", + " 2. Backward propagation (bound effect on initial state). Propagate each error to the start of the circuit and compute its commutator with the initial state.\n", + "\n", + "b. QPU — Learn noise rates. Use `NoiseLearnerV3` to estimate rates of the Pauli-Lindblad noise model.\n", + "\n", + "c. CPU — Prioritize mitigation\n", + "\n", + " 1. Update merged bounds with learned noise rates. Combine forward and backward bounds that were previously computed and update them with learned noise rates.\n", + " 2. Rank noise components to mitigate by using the computed bounds and learned rates. Prioritize each possible noise error based on its estimated impact on bias and the associated expense to correct.\n", + "\n", + "d. QPU — Insert antinoise and run. Execute the circuit of interest with antinoise (inverse noise) specified by using `Box` annotations.\n", + "\n", + "e. CPU — Estimate observable. Compute the expectation value, applying measurement-based post-selection to reduce non-Markovian noise impact.\n", + "\n", + "### Noise learning overview\n", + "Noise learning is a common step in several error-mitigation methods, carried out by the [noise learner](/docs/guides/noise-learning); it also appears in the [probabilistic error amplification tutorial](/docs/tutorials/probabilistic-error-amplification). In `NoiseLearnerV3`, a user can specifically identify the to-be-learned noise layers as [`CircuitInstruction`](/docs/api/qiskit/qiskit.circuit.CircuitInstruction) objects, which allows users to compute the desired SLC noise bounds for each layer in the manner described above. The learned Pauli-Lindblad model provides coefficients to be used in the PEC+SLC prioritization. The way in which the gates are collected into layers can be determined by using the `generate_boxing_pass_manager` and `find_unique_box_instructions` convenience functions, and then fed into the SLC utility function `generate_noise_model_paulis`, as described in Step 2 below.\n", + "\n", + "| **Part 1** | **Part 2** | **Part 3** |\n", + "|-----------|-----------|-----------|\n", + "| Pauli-twirl two-qubit gate layers | Repeat identity pairs of layers and learn noise | Derive a fidelity (error for each noise channel) |\n", + "| ![Pauli twirling](/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif) | ![Learn layer](/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif) | ![Curve fit](/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif) |\n", + "\n", + "### Post-processing overview\n", + "After executing on quantum hardware by using the `samplomatic` and `Executor` framework, we convert our bitstring measurements into the desired observable value. In the case of our mirrored Ising circuit, we will ideally get a measured observable of 1, as all qubits should ideally return to their starting point of $|0\\rangle$. When computing the observable value with the `executor_expectation_values` function, we apply a few post-processing techniques that reduce noise impact. These include removing shots affected by non-Markovian noise, readout-error mitigation, and accounting for details of our PEC implementation. Details are discussed in Step 4 below." + ] + }, + { + "cell_type": "markdown", + "id": "slc-reqs-0000-4000-8000-000000000003", + "metadata": {}, + "source": [ + "## Requirements\n", + "Before starting this tutorial, be sure you have the following installed:\n", + "\n", + "- Qiskit SDK v2.2 or later, with [visualization](/docs/api/qiskit/visualization) support (`pip install 'qiskit[visualization]'`)\n", + "- Qiskit Runtime v0.47 or later (`pip install qiskit-ibm-runtime`)\n", + "- Shaded lightcone Qiskit addon 0.1 or later (`pip install qiskit-addon-slc`)\n", + "- Qiskit addon utils 0.3 or later (`pip install 'qiskit-addon-utils'`)\n", + "- Samplomatic v0.13 or later (`pip install samplomatic`)" + ] + }, + { + "cell_type": "markdown", + "id": "slc-setup-000-4000-8000-000000000004", + "metadata": {}, + "source": [ + "## Setup\n", + "First, import the packages and functions needed to run this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "946458b7-ab59-448c-8268-f62d8f53bc6d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "env: OMP_NUM_THREADS=1\n" + ] + } + ], + "source": [ + "from multiprocessing import set_start_method\n", + "\n", + "# Setting this value prevents itertools.starmap deadlock on UNIX systems\n", + "set_start_method(\"spawn\")\n", + "\n", + "# Needed to prevent PySCF from parallelizing internally (SLC only)\n", + "%set_env OMP_NUM_THREADS=1" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "slc-imports-00-4000-8000-000000000005", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "\n", + "from qiskit import QuantumCircuit\n", + "from qiskit.quantum_info import SparsePauliOp\n", + "from qiskit.transpiler import generate_preset_pass_manager, PassManager\n", + "\n", + "from qiskit_ibm_runtime import (\n", + " QiskitRuntimeService,\n", + " QuantumProgram,\n", + " Executor,\n", + " NoiseLearnerV3,\n", + ")\n", + "\n", + "import samplomatic\n", + "from samplomatic.utils import find_unique_box_instructions, get_annotation\n", + "from samplomatic.annotations import InjectNoise\n", + "from samplomatic.transpiler import generate_boxing_pass_manager\n", + "\n", + "from qiskit_addon_utils.exp_vals.measurement_bases import (\n", + " get_measurement_bases,\n", + ")\n", + "from qiskit_addon_utils.exp_vals.expectation_values import (\n", + " executor_expectation_values,\n", + ")\n", + "from qiskit_addon_utils.noise_management import (\n", + " gamma_from_noisy_boxes,\n", + " trex_factors,\n", + ")\n", + "from qiskit_addon_utils.noise_management.post_selection import PostSelector\n", + "from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (\n", + " AddPostSelectionMeasures,\n", + " AddSpectatorMeasures,\n", + ")\n", + "\n", + "from qiskit_addon_slc.bounds import (\n", + " compute_backward_bounds,\n", + " compute_forward_bounds,\n", + " compute_local_scales,\n", + " merge_bounds,\n", + " tighten_with_speed_limit,\n", + ")\n", + "from qiskit_addon_slc.utils import (\n", + " generate_noise_model_paulis,\n", + " map_modifier_ref_to_ref,\n", + ")\n", + "from qiskit_addon_slc.visualization import draw_shaded_lightcone" + ] + }, + { + "cell_type": "markdown", + "id": "slc-sim-0000-4000-8000-000000000006", + "metadata": {}, + "source": [ + "## Small-scale simulator example\n", + "\n", + "Like other learning-based error mitigation methods, PEC with shaded lightcones mitigates the *physical* noise of a specific quantum processor, so it depends on hardware services with no meaningful analog on an ideal simulator:\n", + "\n", + "- `NoiseLearnerV3` experimentally characterizes the sparse Pauli-Lindblad noise channel on each unique two-qubit layer. On a noiseless simulator there is no noise to cancel.\n", + "- The `Executor` primitive samples the twirled, anti-noise-injected circuits generated by `samplomatic` on a backend.\n", + "\n", + "The shaded-lightcone bound computation is classical, but it is only meaningful relative to the learned hardware noise rates, which set the mitigation budget and sampling overhead. For these reasons we skip the small-scale simulator example and demonstrate the full PEC+SLC workflow directly on hardware, with each step of the Qiskit pattern broken out below." + ] + }, + { + "cell_type": "markdown", + "id": "slc-hw1-0000-4000-8000-000000000007", + "metadata": {}, + "source": [ + "## Large-scale hardware example\n", + "\n", + "We run the complete PEC+SLC workflow on a 20-qubit mirrored Ising circuit executed on IBM Quantum® hardware, following the four steps of a Qiskit pattern.\n", + "\n", + "### Step 1: Map the problem\n", + "For ease of demonstration, we select a 1D mirror Ising chain. The 1D Ising chain gives a nicely dense circuit structure, which is convenient for showcasing PEC implementations. A mirror circuit makes it straightforward to know the expected outcome (namely, we should measure an observable of 1).\n", + "\n", + "Because we run a mirror circuit, for every gate in the second half of the circuit there is an inverse gate in the first half. As the measured observable $\\langle X_6 Z_{13}\\rangle$ has non-Z-basis measurements, and the executor accounts for the desired basis at the end of the circuit, we provide a `prepare_basis` function that inserts the appropriate gates at the start of the mirror circuit. This detail is specific to our mirror-circuit demonstration. The `get_measurement_bases` function lets us identify which gates are needed and where to append them, while keeping track of qubit-index subtleties arising from `box` annotation conventions, as discussed in the section on preparing canonical basis measurements." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5bfefe99-5958-486e-b8ca-ef2573c7f1e5", + "metadata": {}, + "outputs": [], + "source": [ + "num_qubits = 20\n", + "target_obs_sparse = [(\"XZ\", [6, 13], 1.0)]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e5d4d6d5-9016-4c9a-916b-5075ff459fbf", + "metadata": {}, + "outputs": [], + "source": [ + "observable = SparsePauliOp.from_sparse_list(\n", + " target_obs_sparse, num_qubits=num_qubits\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "bb40bad8-cb18-4542-9e18-edf5fdfb5452", + "metadata": {}, + "outputs": [], + "source": [ + "bases_virt, reverser_virt = get_measurement_bases(observable)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5b92fd4e-c559-4c56-8bf6-a6f2e397550c", + "metadata": {}, + "outputs": [], + "source": [ + "num_trotter_steps = 10\n", + "rx_angle = np.pi / 4" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cc2b001f-c006-45f1-9942-dcbdabefbec4", + "metadata": {}, + "outputs": [], + "source": [ + "def construct_ising_circuit(\n", + " num_qubits: int,\n", + " num_trotter_steps: int,\n", + " rx_angle: float,\n", + " barrier: bool = True,\n", + ") -> QuantumCircuit:\n", + " circuit = QuantumCircuit(num_qubits)\n", + "\n", + " for step in range(num_trotter_steps):\n", + " circuit.rx(rx_angle, range(num_qubits))\n", + " if barrier:\n", + " circuit.barrier()\n", + " for first_qubit in (1, 2):\n", + " for idx in range(first_qubit, num_qubits, 2):\n", + " # equivalent to Rzz(-pi/2):\n", + " circuit.sdg([idx - 1, idx])\n", + " circuit.cz(idx - 1, idx)\n", + " if barrier:\n", + " circuit.barrier()\n", + "\n", + " return circuit\n", + "\n", + "\n", + "def prepare_basis(\n", + " circuit: QuantumCircuit, basis: list[int]\n", + ") -> QuantumCircuit:\n", + " # basis is a list of integer values from 0 to 3. These map to the basis measurement as:\n", + " # 0 = I; 1 = Z; 2 = X; 3 = Y\n", + " assert len(basis) == circuit.num_qubits\n", + "\n", + " out_circ = circuit.copy_empty_like()\n", + " for qb, bas in enumerate(basis):\n", + " if bas in {0, 1}:\n", + " continue\n", + " if bas == 2:\n", + " out_circ.h(qb)\n", + " elif bas == 3:\n", + " out_circ.rx(-np.pi / 2, qb)\n", + "\n", + " out_circ.barrier()\n", + " out_circ.compose(circuit, inplace=True)\n", + " return out_circ\n", + "\n", + "\n", + "def mirror_circuit(\n", + " circuit: QuantumCircuit, *, inverse_first: bool = False\n", + ") -> QuantumCircuit:\n", + " mirror_circ = circuit.copy_empty_like()\n", + " mirror_circ.compose(\n", + " circuit.inverse() if inverse_first else circuit, inplace=True\n", + " )\n", + " mirror_circ.barrier()\n", + " mirror_circ.compose(\n", + " circuit if inverse_first else circuit.inverse(), inplace=True\n", + " )\n", + " mirror_circ.measure_active()\n", + " return mirror_circ" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "67553310-f1f0-4736-85ad-bfebd3c00414", + "metadata": {}, + "outputs": [], + "source": [ + "# Instantiate circuit\n", + "circuit = construct_ising_circuit(\n", + " num_qubits, num_trotter_steps, rx_angle, barrier=False\n", + ")\n", + "mirrored_circuit = mirror_circuit(circuit, inverse_first=True)\n", + "mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0dab284e-dbed-4d2f-92aa-c6c9c0e909a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mirrored_circuit.draw(\n", + " \"mpl\", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "slc-step2-000-4000-8000-000000000013", + "metadata": {}, + "source": [ + "### Step 2: Optimize\n", + "We will optimize details associated with the circuit to be run, the observable to be measured, and the noise-learning parameters. As a starting point, we instantiate a backend with fractional gates turned on. These fractional gates allow for greater sensitivity in some of our post-selection filtering." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "slc-creds-000-4000-8000-000000000014", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using backend: ibm_boston\n" + ] + } + ], + "source": [ + "# Initialize the Qiskit Runtime service using your saved credentials\n", + "service = QiskitRuntimeService()\n", + "\n", + "# Fractional gates allow greater sensitivity in some of our post-selection filtering\n", + "backend = service.backend(\"ibm_boston\", use_fractional_gates=True)\n", + "print(f\"Using backend: {backend.name}\")" + ] + }, + { + "cell_type": "markdown", + "id": "81dd2580-d4a8-45bb-bd05-707c64aa4ac7", + "metadata": {}, + "source": [ + "First, we will transpile our circuit to ISA instructions, [as required for execution on our QPUs](https://www.ibm.com/quantum/blog/isa-circuits). For the data collected in this experiment, we hand select our qubits based on evaluation of the highest quality chain." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e9dd4699-a935-45af-90b2-0e48286a587d", + "metadata": {}, + "outputs": [], + "source": [ + "layout = [\n", + " 44,\n", + " 45,\n", + " 46,\n", + " 47,\n", + " 57,\n", + " 67,\n", + " 68,\n", + " 69,\n", + " 78,\n", + " 89,\n", + " 88,\n", + " 87,\n", + " 97,\n", + " 107,\n", + " 106,\n", + " 105,\n", + " 104,\n", + " 103,\n", + " 96,\n", + " 83,\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a7505de6-70ef-4996-9f6b-0025f267e920", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "isa_pm = generate_preset_pass_manager(\n", + " backend=backend, initial_layout=layout, optimization_level=0\n", + ")\n", + "\n", + "isa_circuit = isa_pm.run(mirrored_circuit)\n", + "assert isa_circuit.layout.final_index_layout() == layout\n", + "\n", + "isa_observable = observable.apply_layout(\n", + " layout, num_qubits=isa_circuit.num_qubits\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "87f19e08-0cae-4959-9215-354ab099ab02", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wire_order = layout + [\n", + " q for q in range(isa_circuit.num_qubits) if q not in layout\n", + "]\n", + "isa_circuit.draw(\n", + " \"mpl\",\n", + " fold=-1,\n", + " scale=0.3,\n", + " idle_wires=False,\n", + " wire_order=wire_order,\n", + " measure_arrows=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7e7135e7-1e86-4d16-b806-30d4a9225e88", + "metadata": {}, + "source": [ + "### Box the circuit\n", + "For ease of implementation, we will utilize the `generate_boxing_pass_manager` transpilation pass, which places the circuit instructions into annotated boxes. These boxes clearly indicate where, in the case of PEC, antinoise should be injected into the circuit. For details on settings, refer to the [Samplomatic documentation.](https://qiskit.github.io/samplomatic/)\n", + "\n", + "Note that the SLC workflow requires the use of `inject_noise_strategy=\"individual_modification\"` later in the process because this allows us to uniquely identify each `BoxOp` in the circuit.\n", + "\n", + "The `find_unique_box_instructions` function iterates through the provided boxed circuit and identifies those that have unique 2Q layers or measurements, for the purpose of noise learning and noise injection." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "440f818c-9417-4855-b31b-db47b8595618", + "metadata": {}, + "outputs": [], + "source": [ + "# Box circuit with Twirl and InjectNoise annotations\n", + "boxes_pm = generate_boxing_pass_manager(\n", + " twirling_strategy=\"active\",\n", + " inject_noise_strategy=\"individual_modification\",\n", + " inject_noise_targets=\"gates\",\n", + " measure_annotations=\"all\",\n", + ")\n", + "\n", + "\n", + "boxed_circuit = boxes_pm.run(isa_circuit)\n", + "\n", + "# Find the unique instructions (layers) from boxed circuit\n", + "unique_2q_instructions = find_unique_box_instructions(\n", + " boxed_circuit, normalize_annotations=None, undress_boxes=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d662b076-005e-46c7-826d-4c29a4937404", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "boxed_circuit.draw(\n", + " \"mpl\",\n", + " fold=-1,\n", + " scale=0.3,\n", + " idle_wires=False,\n", + " wire_order=wire_order,\n", + " measure_arrows=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "aa8794ef-7906-4ee3-a9b0-e0399b3e9129", + "metadata": {}, + "source": [ + "### Prepare canonical bases measurements\n", + "Due to how the qubits are labeled when identifying unique 2Q layers, one must take special care to keep track of qubit ordering. Below, we introduce the notion of `canonical_qubits` as a means to appropriately update the qubit ordering when providing it to the executor, as a result of how qubit order is captured when boxing circuits and finding unique instructions. See the [Qubit ordering convention](https://qiskit.github.io/samplomatic/guides/samplex_io.html#qubit-ordering-convention) documentation for details." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "e242702b-0274-4ebf-837d-4319a6772339", + "metadata": {}, + "outputs": [], + "source": [ + "# Determine the canonical qubits order\n", + "meas_box = boxed_circuit.data[-1]\n", + "canonical_qubits = [\n", + " idx\n", + " for idx, qubit in enumerate(boxed_circuit.qubits)\n", + " if qubit in meas_box.qubits\n", + "]\n", + "\n", + "# map canonical qubit to physical (isa) qubit\n", + "c_2_p = {c: p for c, p in enumerate(canonical_qubits)}\n", + "# map physical (isa) qubit to virtual qubit (index in original circuit)\n", + "p_2_v = {p: v for v, p in enumerate(layout)}\n", + "# compute map between virtual and canonical qubit indices.\n", + "c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}\n", + "\n", + "assert len(c_2_v) == num_qubits\n", + "\n", + "bases_canon = [\n", + " np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)\n", + " for base_i in bases_virt\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "slc-wfnote-00-4000-8000-000000000025", + "metadata": {}, + "source": [ + "### Workflow for lightcone shading, noise learning, and anti-noise injection\n", + "\n", + "> **Note**: In this tutorial, we run the SLC bound computations *before* the noise learning completes, so the to-be-mitigated circuit is run as close in time as possible to the learned noise model. In principle this workflow can be parallelized further: a noise-learning job can run while, in parallel, the noise bounds are estimated. For an arbitrary quantum circuit the noise-bound computation can scale with a weakly exponential dependence, so parallelizing the bound computation (for example, across many CPU cores) yields tighter bounds for a given compute-time budget, and the QPU executions and bound computations can themselves be parallelized for the most efficient workflow." + ] + }, + { + "cell_type": "markdown", + "id": "4a2ec5ed-c339-4273-b407-6bb46b9ede19", + "metadata": {}, + "source": [ + "#### Predict to-be-learned noise-model Paulis\n", + "\n", + "The `generate_noise_model_paulis` function goes through each boxed layer of the provided circuit and generates all relevant weight-one and weight-two Pauli terms, taking into account the circuit's qubit connectivity, and selecting terms that are relevant to the active nodes and edges. These terms are then used to compute forward and backward noise bounds." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e6783d37-c91f-4c6e-af4e-fb8f37d79e6b", + "metadata": {}, + "outputs": [], + "source": [ + "noise_model_paulis = generate_noise_model_paulis(\n", + " unique_2q_instructions, backend.coupling_map, boxed_circuit\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f9fd1389-8c61-4585-85a6-ad17ab7a2f1c", + "metadata": {}, + "outputs": [], + "source": [ + "noise_model_rates = {ref: None for ref in noise_model_paulis}" + ] + }, + { + "cell_type": "markdown", + "id": "942dd468-b2c5-49db-ada5-bcc45024df83", + "metadata": {}, + "source": [ + "##### a. Compute and tighten forward bounds\n", + "\n", + "The `compute_forward_bounds` function evaluates the commutation relations between the gates in each layer and the above-generated Pauli terms in terms of how forward-propagation errors affect the desired observable $A$. For gates that commute with the Pauli terms, nothing is done. For Clifford gates, they are pushed toward the beginning of the circuit. For non-Clifford gates, we approximate their influence on the target observables to later be prioritized for noise cancellation (after all bounds have been merged). This bound is achieved by first applying the L2 norm (namely, the square root of the sum of squares of the relevant Pauli-term coefficients). When there are too many qubit terms involved, we revert to a looser bound that uses the triangle inequality." + ] + }, + { + "cell_type": "markdown", + "id": "slc-bparam-00-4000-8000-000000000030", + "metadata": {}, + "source": [ + "#### Set the bound-computation parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "79fe4a21-b80d-496b-ba20-f16d9b11834d", + "metadata": {}, + "outputs": [], + "source": [ + "slc_atol = 1e-8\n", + "slc_eigval_max_qubits = 18\n", + "slc_evolution_max_terms = 1000\n", + "slc_num_processes = 8\n", + "slc_timeout = 60" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "705fcb9f-c753-45a6-97dd-9ff8ecb07018", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Bounds computation timed out.\n" + ] + } + ], + "source": [ + "forward_bounds = compute_forward_bounds(\n", + " boxed_circuit,\n", + " noise_model_paulis,\n", + " isa_observable,\n", + " evolution_max_terms=slc_evolution_max_terms,\n", + " eigval_max_qubits=slc_eigval_max_qubits,\n", + " atol=slc_atol,\n", + " num_processes=slc_num_processes,\n", + " timeout=slc_timeout,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "aaf7c188-c85b-42cf-a096-0a5dc72a68e3", + "metadata": {}, + "source": [ + "#### Visualize the SLC for manual inspection\n", + "\n", + "You can interpret the behavior of the shaded bounds by examining how the measurements and Pauli terms interact with the local errors. These patterns are characteristic of this kicked Ising Hamiltonian time-evolution problem and also appear in the paper [Lightcone Shading for Classically Accelerated Quantum Error Mitigation](https://arxiv.org/abs/2409.04401), with several telltale features:\n", + "\n", + "- We can clearly distinguish the two cones arising from the two non-identity Paulis in the observable.\n", + "- We can see that the X measurement on qubit 6 commutes with the X error in the rightmost layer.\n", + "- We can see that the Z Pauli on qubit 13 commutes with the Z error in the rightmost layer.\n", + "- When we reach the timeout specified above, the remaining layers to the left are filled entirely with trivial bounds of two." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "285edaef-8e79-41eb-a563-4675ea75d5b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for p in \"XYZ\":\n", + " display(\n", + " draw_shaded_lightcone(\n", + " boxed_circuit,\n", + " forward_bounds,\n", + " noise_model_paulis,\n", + " pauli_filter=p,\n", + " scale=0.15,\n", + " fold=-1,\n", + " idle_wires=False,\n", + " wire_order=wire_order,\n", + " measure_arrows=False,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "1aee6b29-8077-4019-9e37-3e80292aadf6", + "metadata": {}, + "source": [ + "#### b. Compute and tighten forward bounds\n", + "We next tighten the bounds by using the `tighten_with_speed_limit` function, which tracks how the observable spreads backward through the circuit and uses that spread to place upper bounds on the effect of each noise operator, taking the smaller of the forward bound just computed, and the backward-propagation bound." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e060d547-79da-4f54-bf5e-677f70306040", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "forward_bounds_tighter = tighten_with_speed_limit(\n", + " forward_bounds, boxed_circuit, noise_model_paulis, isa_observable\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "85f3c9c2-d60c-4044-a496-dae88c3dd749", + "metadata": {}, + "source": [ + "#### Visualize the SLC for manual inspection\n", + "\n", + "We can further tighten the bounds by accounting for lightcone limitation. In principle, this gives us a smoother transition from the computed bounds to the trivial bounds set forth after the timeout was reached. Here, the effect is not as visible because the lightcones have already reached the edge of the circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "6ba4b397-e903-4b85-9482-31a96c689dc0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for p in \"XYZ\":\n", + " display(\n", + " draw_shaded_lightcone(\n", + " boxed_circuit,\n", + " forward_bounds_tighter,\n", + " noise_model_paulis,\n", + " pauli_filter=p,\n", + " scale=0.15,\n", + " fold=-1,\n", + " idle_wires=False,\n", + " wire_order=wire_order,\n", + " measure_arrows=False,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "ce0f9cf4-565a-4f18-baa5-bec5c7b354a2", + "metadata": {}, + "source": [ + "#### c. Compute backward bounds\n", + "\n", + "This part of noise prediction evaluates how an error at a particular layer can affect the input state $\\rho$. The `compute_backward_bounds` function first inverts the circuit, removes measurement gates, and then proceeds with a similar analysis as was done for the forward-bound computations." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "ff895191-feda-4958-a58a-3d1fb973cb68", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "backward_bounds = compute_backward_bounds(\n", + " boxed_circuit,\n", + " noise_model_paulis,\n", + " evolution_max_terms=slc_evolution_max_terms,\n", + " num_processes=slc_num_processes,\n", + " timeout=slc_timeout,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c707d352-37fe-4c53-82a3-a1207b7a7bfc", + "metadata": {}, + "source": [ + "#### Visualize the SLC for manual inspection\n", + "\n", + "From computing backward bounds, we can see how the initial state structure governs the early behavior of error propagation:\n", + "\n", + "- We can clearly see how the Z errors initially commute with the |0⟩ initial state.\n", + "- Only on qubit 6, where we initialize the +1 eigenstate of the X basis, does a Z error fail to commute, while an X error does commute." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "40d0eba9-5e0b-4b1d-8fb2-c239e262062f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for p in \"XYZ\":\n", + " display(\n", + " draw_shaded_lightcone(\n", + " boxed_circuit,\n", + " backward_bounds,\n", + " noise_model_paulis,\n", + " pauli_filter=p,\n", + " scale=0.15,\n", + " fold=-1,\n", + " idle_wires=False,\n", + " wire_order=wire_order,\n", + " measure_arrows=False,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "c8960e51-733e-4458-b77c-287ba3abdb8d", + "metadata": {}, + "source": [ + "#### Preview merged bounds without learned noise rates\n", + "\n", + "The `merged_bounds` function determines the point in the circuit where switching from backward bounds to forward bounds minimizes the total estimated bias on the desired observable. This bias is computed as the sum of the backward-bound contributions for all noise locations before that point, plus the forward-bound contributions for all noise locations after it. Currently, this is done uniformly for all qubits.\n", + "\n", + "> **Important Note**: The point to switch from forward to backward bounds depends on the learned noise rates." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b5cd8752-3bb5-44ee-97a8-6edaf5946e9d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.\n", + "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n" + ] + } + ], + "source": [ + "merged_bounds = merge_bounds(\n", + " boxed_circuit,\n", + " forward_bounds_tighter,\n", + " backward_bounds,\n", + " noise_model_rates,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ace27801-b949-44e0-801d-e74ccb2f877a", + "metadata": {}, + "source": [ + "### Visualize the SLC for manual inspection\n", + "\n", + "After merging the backward and tightened forward bounds, the behavior of the combined SLCs becomes clear:\n", + "\n", + "- The function above tells us that a partition is chosen at which the switch from backward to tightened forward bounds takes place.\n", + "- We can see below that the SLCs now contain partial backward and partial tightened forward bounds." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "eb2948bc-fa27-4108-b03c-0291d3b2f7c8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for p in \"XYZ\":\n", + " display(\n", + " draw_shaded_lightcone(\n", + " boxed_circuit,\n", + " merged_bounds,\n", + " noise_model_paulis,\n", + " pauli_filter=p,\n", + " scale=0.15,\n", + " fold=-1,\n", + " idle_wires=False,\n", + " wire_order=wire_order,\n", + " measure_arrows=False,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "slc-step3-000-4000-8000-000000000050", + "metadata": {}, + "source": [ + "### Step 3: Execute\n", + "In this section we begin the part of the workflow that uses a real quantum device. For this learning-based error mitigation method, there are two steps:\n", + "\n", + "1. Learn the noise by using `NoiseLearnerV3`.\n", + "2. Execute an error-mitigation circuit with the `samplomatic` and `Executor` framework.\n", + "\n", + "With the bounded errors from our quantum circuit, we learn the associated noise rates to prioritize our error budget, determine the sampling overhead, and execute on a QPU." + ] + }, + { + "cell_type": "markdown", + "id": "slc-nlmd-0000-4000-8000-000000000051", + "metadata": {}, + "source": [ + "### a. Learn the noise rates\n", + "\n", + "The noise learner characterizes the noise processes affecting the gates in one or more circuits of interest, based on the sparse [Pauli-Lindblad noise model](https://arxiv.org/abs/2201.09866). The `run()` method launches a noise-learning job for the provided unique two-qubit layers, using the options specified in the noise-learner configuration. These options control the Pauli-twirling strategy, the number of randomizations and shots, the learning depths, and post-selection.\n", + "\n", + "We also choose the learning depths deliberately. A practical finding for learning-based mitigation with `samplomatic` is that it is highly beneficial for the deepest learning depth to match the depth of the circuit you want to mitigate. Because NLv3 `layer_pair_depths` are measured in layer *pairs* (a layer plus its inverse), we set the deepest value to half the circuit's two-qubit-layer depth." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "slc-psflag-00-4000-8000-000000000052", + "metadata": {}, + "outputs": [], + "source": [ + "post_selection_enabled = True" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "slc-nlopt-00-4000-8000-000000000053", + "metadata": {}, + "outputs": [], + "source": [ + "# Match the deepest noise-learning depth to the depth of the circuit being\n", + "# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer\n", + "# plus its inverse), so the deepest value is half the circuit's two-qubit-layer\n", + "# depth. Learning to this depth markedly improves the quality of the mitigation.\n", + "num_2q_layers = sum(\n", + " get_annotation(inst.operation, InjectNoise) is not None\n", + " for inst in boxed_circuit.data\n", + ")\n", + "max_layer_pair_depth = num_2q_layers // 2\n", + "layer_pair_depths = sorted({1, 2, 4, 8, 12, 16, max_layer_pair_depth})\n", + "\n", + "noise_learner_options = {\n", + " \"num_randomizations\": 64,\n", + " \"shots_per_randomization\": 128,\n", + " \"layer_pair_depths\": layer_pair_depths,\n", + " \"post_selection\": {\n", + " \"enable\": post_selection_enabled,\n", + " \"strategy\": \"edge\",\n", + " \"x_pulse_type\": \"rx\",\n", + " },\n", + " \"environment\": {\"job_tags\": [\"TUT_SLC\"]},\n", + "}\n", + "\n", + "noise_learner = NoiseLearnerV3(backend, noise_learner_options)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "slc-nlrun-00-4000-8000-000000000054", + "metadata": {}, + "outputs": [], + "source": [ + "noise_learner_job = noise_learner.run(unique_2q_instructions)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "db624045-7abe-41f1-a60c-1ba141aaa6f6", + "metadata": {}, + "outputs": [], + "source": [ + "noise_learner_result = noise_learner_job.result()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "b08c37a6-5acb-41e9-a76c-abc0195d87f7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimum fraction of shots kept for noise learning experiments: 0.85\n" + ] + } + ], + "source": [ + "if post_selection_enabled:\n", + " print(\n", + " \"Minimum fraction of shots kept for noise learning experiments: \",\n", + " end=\"\",\n", + " )\n", + " print(\n", + " f\"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "99144d7f-b891-4115-8cb3-ecdbedd10a73", + "metadata": {}, + "outputs": [], + "source": [ + "# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap\n", + "refs_2_plm = noise_learner_result.to_dict(\n", + " unique_2q_instructions, require_refs=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e6a790a3-bc11-46e2-8cb4-2e43257926c8", + "metadata": {}, + "source": [ + "### b.i. Update merged bounds with actual learned noise rates\n", + "\n", + "Now that the specific noise model has been learned, we can apply the learned noise rates to the predicted noise bounds and obtain a final determination of which bounds have the most impact on minimizing the bias." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "bba8fca6-5339-4d4d-be01-45ca8bf94a5a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.\n" + ] + } + ], + "source": [ + "merged_bounds = merge_bounds(\n", + " boxed_circuit,\n", + " forward_bounds_tighter,\n", + " backward_bounds,\n", + " refs_2_plm,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "649b9640-e89f-4021-9f7d-f1e136c4eb6d", + "metadata": {}, + "source": [ + "#### b.ii. Compute the `local_scales` for the hardware execution\n", + "\n", + "`compute_local_scales` looks at each possible noise error in the circuit and estimates how much that error could bias the final measurement, as well as how expensive it would be to correct it. It then ranks the errors by how worthwhile they are to mitigate and selects the subset that reduces bias as much as possible, while staying within the allowed sampling-cost budget (or achieving a desired accuracy). The result is a set of scaling factors indicating which errors will be actively mitigated and which will be left unmitigated (`local_scales`), along with the predicted total sampling cost overhead (`sampling_costs`) and remaining bias (`residual_bias_bound`).\n", + "\n", + "The ability to control the desired remaining bias is a critical feature of the SLC implementation of PEC. Whereas in the [original implementation](https://arxiv.org/abs/2201.09866), the sampling overhead always targeted zero bias, we can tune the required sampling overhead with a trade-off in the expected remaining bias. This helps let the user stay within a fixed sampling budget, which can be particularly useful when initially prototyping a workflow." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "8914e00e-ba21-41dc-9e64-b942bb23fde1", + "metadata": {}, + "outputs": [], + "source": [ + "id_map = map_modifier_ref_to_ref(boxed_circuit)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "4c971d31-f2b4-4d17-8a83-4f6d14f81f6f", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Full PEC gamma=294.94827441509057, sampling cost (gamma^2) = 86994.48458043957\n" + ] + } + ], + "source": [ + "summed_rates = 0.0\n", + "for box_id, noise_id in id_map.items():\n", + " learned_plm = refs_2_plm[noise_id]\n", + " summed_rates += np.sum(learned_plm.rates)\n", + " # print(f\"{box_id}:\\tgamma = {np.exp(2 * summed_rates):1.6e}\\tsampling cost = {np.exp(4 * summed_rates):1.6e}\")\n", + "total_gamma = np.exp(2 * summed_rates)\n", + "print(\n", + " f\"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "fb6f2aa0-8a35-420c-aae3-6fc5e9aa8747", + "metadata": {}, + "outputs": [], + "source": [ + "biases = []\n", + "costs = []\n", + "for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():\n", + " _, cost_, bias_ = compute_local_scales(\n", + " boxed_circuit,\n", + " merged_bounds,\n", + " refs_2_plm,\n", + " sampling_cost_budget=np.inf,\n", + " bias_tolerance=bias,\n", + " )\n", + " biases.append(bias_)\n", + " costs.append(cost_)" + ] + }, + { + "cell_type": "markdown", + "id": "slc-trade-00-4000-8000-000000000065", + "metadata": {}, + "source": [ + "#### Trade off sampling overhead against residual bias" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "slc-ovplot-0-4000-8000-000000000066", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "xticks = np.arange(0, 11)\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.scatter(\n", + " [0], [total_gamma**2], marker=\"D\", c=\"tab:orange\", label=\"full PEC\"\n", + ")\n", + "ax.plot(\n", + " 100 * np.array(biases),\n", + " np.array(costs),\n", + " \"o-\",\n", + " c=\"tab:blue\",\n", + " label=\"PEC+SLC\",\n", + ")\n", + "ax.set_yscale(\"log\")\n", + "ax.set_xticks(xticks, [f\"{x:.1f}\" for x in xticks])\n", + "\n", + "ax.set_xlabel(\"Remaining bias [%]\")\n", + "ax.set_ylabel(r\"Sampling overhead, $\\gamma^2$\")\n", + "ax.grid()\n", + "ax.legend()\n", + "fig.suptitle(\"PEC sampling overhead reduction due to SLC\")" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "1df482f9-c12c-4ea7-a71e-7ffc0837b5a6", + "metadata": {}, + "outputs": [], + "source": [ + "chosen_bias_thres = 0.1" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "slc-lscale-0-4000-8000-000000000068", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PEC+SLC sampling cost (gamma^2) = 4200.504852399781 w/ remaining bias = 9.4%\n" + ] + } + ], + "source": [ + "local_scales, sampling_cost, residual_bias_bound = compute_local_scales(\n", + " boxed_circuit,\n", + " merged_bounds,\n", + " refs_2_plm,\n", + " sampling_cost_budget=np.inf,\n", + " bias_tolerance=chosen_bias_thres,\n", + ")\n", + "print(\n", + " f\"PEC+SLC sampling cost (gamma^2) = {sampling_cost} \"\n", + " f\"w/ remaining bias = {100 * residual_bias_bound:.1f}%\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8b03c0ed-d026-4b29-bc09-68c4d0c94889", + "metadata": {}, + "source": [ + "### c. Execute the circuit of interest with antinoise\n", + "#### c.i. Prepare template circuit by using `samplex`\n", + "The `samplex` is an output of the `build` method of Samplomatic, which encodes all the information that is required to generate randomized parameters for `template_circuit`. These are then used to set up the `QuantumProgram` objects, which are in turn run on a QPU with the `Executor` primitive. Each `QuantumProgram` can contain several items, which you can think of as a pair of `template` and `samplex`.\n", + "\n", + "See the [Hello samplomatic](https://github.com/qiskit-community/qdc-challenges-2025/blob/main/day3_tutorials/Track_A/hello_samplomatic/Samplomatic%20-%20Hello%20World.ipynb) tutorial for details." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "a68109cc-3b38-40e0-b145-edaa06b9934d", + "metadata": {}, + "outputs": [], + "source": [ + "# Build template circuit and samplex for later use with the \"Executor\"\n", + "template_circuit, samplex = samplomatic.build(boxed_circuit)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "0de4922b-b958-48d7-b63a-819edfb354bd", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up postselection if it's been enabled\n", + "if post_selection_enabled:\n", + " # Set up post selection PM (to add PS instructions)\n", + " post_selection_pm = PassManager(\n", + " [\n", + " AddSpectatorMeasures(backend.coupling_map),\n", + " AddPostSelectionMeasures(x_pulse_type=\"rx\"),\n", + " ]\n", + " )\n", + " final_template_circuit = post_selection_pm.run(template_circuit)\n", + "else:\n", + " final_template_circuit = template_circuit" + ] + }, + { + "cell_type": "markdown", + "id": "4f00c61a-4c4d-40d9-9a41-11ea8e9a9337", + "metadata": {}, + "source": [ + "#### c.ii. Set up the `QuantumProgram`" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "a63d8881-2bbb-4c99-afd3-43f03ee04e5c", + "metadata": {}, + "outputs": [], + "source": [ + "num_randomizations = 4096\n", + "shots_per_randomization = 64\n", + "chunk_size = 256" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "038a04c9-20ba-432c-bfec-cf69e67ac82f", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up QuantumProgram\n", + "program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)\n", + "\n", + "# no EM\n", + "\n", + "# Collect up a dict of the other arguments that need to be bound to samplex_inputs\n", + "samplex_inputs = {\n", + " f\"noise_scales.{ref}\": float(0) for ref in local_scales.keys()\n", + "}\n", + "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n", + "\n", + "# Convert samplex_inputs into a dict to pass to QuantumProgram\n", + "samplex_arguments = (\n", + " samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n", + ")\n", + "\n", + "program.append_samplex_item(\n", + " circuit=final_template_circuit,\n", + " samplex=samplex,\n", + " samplex_arguments=samplex_arguments,\n", + " shape=(num_randomizations,),\n", + " chunk_size=chunk_size,\n", + ")\n", + "\n", + "# plain PEC\n", + "\n", + "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n", + "samplex_inputs = {\n", + " f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n", + "}\n", + "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n", + "\n", + "# Convert samplex_inputs into a dict to pass to QuantumProgram\n", + "samplex_arguments = (\n", + " samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n", + ")\n", + "\n", + "program.append_samplex_item(\n", + " circuit=final_template_circuit,\n", + " samplex=samplex,\n", + " samplex_arguments=samplex_arguments,\n", + " shape=(num_randomizations,),\n", + " chunk_size=chunk_size,\n", + ")\n", + "\n", + "# PEC+SLC\n", + "\n", + "# Collect a dict of the other arguments that need to be bound to samplex_inputs\n", + "samplex_inputs = {\n", + " f\"noise_scales.{ref}\": float(-1) for ref in local_scales.keys()\n", + "}\n", + "samplex_inputs |= {\"basis_changes\": {\"basis0\": bases_canon[0]}}\n", + "samplex_inputs |= {\"local_scales\": local_scales}\n", + "\n", + "# Convert samplex_inputs into a dict to pass to QuantumProgram\n", + "samplex_arguments = (\n", + " samplex.inputs().bind(**samplex_inputs).make_broadcastable()\n", + ")\n", + "\n", + "program.append_samplex_item(\n", + " circuit=final_template_circuit,\n", + " samplex=samplex,\n", + " samplex_arguments=samplex_arguments,\n", + " shape=(num_randomizations,),\n", + " chunk_size=chunk_size,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3100fff6-9fe9-49ad-9662-549cea099e9b", + "metadata": {}, + "source": [ + "#### c.iii. Execute program with the `Executor` primitive" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "2a1c0fe1-8761-462b-a1d9-b951d5224164", + "metadata": {}, + "outputs": [], + "source": [ + "executor = Executor(backend)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "slc-exec-000-4000-8000-000000000078", + "metadata": {}, + "outputs": [], + "source": [ + "job_exec = executor.run(program)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "70dd5d10-9cc5-4341-9642-18ba3a0ee58b", + "metadata": {}, + "outputs": [], + "source": [ + "results_exec = job_exec.result()" + ] + }, + { + "cell_type": "markdown", + "id": "slc-step4-000-4000-8000-000000000080", + "metadata": {}, + "source": [ + "### Step 4: Post-process\n", + "As we calculate the final expectation value of interest by using `executor_expectation_values`, we implement a few post-processing techniques to help ensure we obtain the highest-quality results possible. First, we apply our [twirled readout error extinction (TREX)](/docs/guides/error-mitigation-and-suppression-techniques#twirled-readout-error-extinction-trex), which accounts for any errors occurring during the readout process. Then, we fix errors due to non-Markovian noise on our Heron backends by using a post-selection method. This method measures active and spectator qubits, then applies a slow rotation to each qubit, and then measures again. In instances where the two measurements do not confirm a flipped qubit as expected, these shots are discarded by applying a `mask` from the `PostSelector`. Within the mask computation, a specific strategy can be set to filter based on single-qubit nodes or neighboring spectator edges, which can influence both the number of shots filtered out and the quality of the results." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "147bcd34-f79e-4abb-821d-5ea9d8388c9f", + "metadata": {}, + "outputs": [], + "source": [ + "measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()\n", + "trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "981353e7-95a9-48bb-92c8-87b931e2b295", + "metadata": {}, + "outputs": [], + "source": [ + "post_selection_strategy = \"node\"" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "826afe02-3308-4d20-a9e1-ac0b2ba25eca", + "metadata": {}, + "outputs": [], + "source": [ + "def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):\n", + " meas = datum[\"meas\"]\n", + " flips = datum[\"measurement_flips.meas\"]\n", + " signs = datum.get(\"pauli_signs\", None)\n", + "\n", + " meas_basis_axis = None\n", + " avg_axis = 0\n", + "\n", + " mask = None\n", + " if ps and post_selection_enabled:\n", + " # Post-select the results\n", + " post_selector = PostSelector.from_circuit(\n", + " circuit=final_template_circuit, coupling_map=backend.coupling_map\n", + " )\n", + "\n", + " # Compute the ps mask for filtering results\n", + " mask = post_selector.compute_mask(\n", + " datum, strategy=post_selection_strategy\n", + " )\n", + "\n", + " # Compute fraction of shots kept from post selection\n", + " total_num_shots = num_randomizations * shots_per_randomization\n", + " ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)\n", + " print(\n", + " f\"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):\"\n", + " )\n", + "\n", + " results = []\n", + " for i in range(steps, num_randomizations + 1, steps):\n", + " # Compute mitigated expvals w/out post-selection\n", + " res = executor_expectation_values(\n", + " meas[:i],\n", + " reverser_virt,\n", + " meas_basis_axis,\n", + " avg_axis=avg_axis,\n", + " measurement_flips=flips[:i],\n", + " pauli_signs=signs[:i] if signs is not None else None,\n", + " postselect_mask=mask[:i] if mask is not None else None,\n", + " rescale_factors=trex_scale_factors if trex else None,\n", + " gamma_factor=gamma,\n", + " )\n", + " results.append(res[0])\n", + " return results" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "57b68d19-cda9-4688-b07e-0c7f9da5f252", + "metadata": {}, + "outputs": [], + "source": [ + "gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)\n", + "gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "b5244ee6-98b2-43da-8238-3d6fb0f4cdd0", + "metadata": {}, + "outputs": [], + "source": [ + "steps = 16" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "95bedd10-5ce9-4145-9b20-1459aa8e7db9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "With node-based post selection (26.7% of shots kept):\n", + "With node-based post selection (26.8% of shots kept):\n" + ] + } + ], + "source": [ + "results = {}\n", + "\n", + "for label, result_idx, gamma, use_ps, use_trex in [\n", + " (\"PEC\", 1, gamma_pec, True, True),\n", + " (\"PEC+SLC\", 2, gamma_slc, True, True),\n", + " (\"Unmitigated\", 0, None, False, False),\n", + "]:\n", + " res = post_process_conv(\n", + " results_exec[result_idx],\n", + " steps=steps,\n", + " gamma=gamma,\n", + " ps=use_ps,\n", + " trex=use_trex,\n", + " )\n", + " results[label] = res" + ] + }, + { + "cell_type": "markdown", + "id": "1d778145-9ead-4b9f-82a3-cec8aa8c8307", + "metadata": {}, + "source": [ + "From examination of the experimental results, we can directly compare the behavior of the different approaches: PEC, PEC combined with SLC, and the unmitigated baseline. Some specific details to highlight:\n", + "\n", + "- The unmitigated result sits far outside the 10% bias band (near 0.3) and is unaffected by the number of randomizations.\n", + "- On this device, full PEC carries an enormous sampling overhead ($\\gamma^2 \\approx 8.7\\times10^4$). At that cost it cannot converge within the randomizations used: the plain-PEC estimates stay wildly scattered with large error bars across the full range.\n", + "- SLC reduces the overhead roughly 20-fold (to $\\gamma^2 \\approx 4.2\\times10^3$, for a residual-bias bound of about 9%). PEC+SLC consequently behaves far better than plain PEC: its error bars are much smaller and contract steadily as randomizations accumulate, and its estimate settles into the physical region and converges close to the exact value of 1, approaching the 10% bias band from just below. The contrast with plain PEC — which remains scattered across nearly the entire plot — clearly demonstrates the benefit of lightcone shading.\n", + "- The error bars also shrink faster for PEC+SLC than for plain PEC." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "8df6612b-d611-4201-b1f4-6d8de4a192ad", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-50.0, 4100.0)" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(12, 6))\n", + "\n", + "ax.axhline(1.0, color=\"black\", label=\"Exact\")\n", + "ax.fill_between(\n", + " [-50, 4100], -10, 0, color=\"grey\", alpha=0.25, label=\"Unphysical\"\n", + ")\n", + "ax.fill_between([-50, 4100], 1, 10, color=\"grey\", alpha=0.25)\n", + "ax.fill_between(\n", + " [-50, 4100], 0.9, 1.1, color=\"red\", alpha=0.25, label=\"10% bias\"\n", + ")\n", + "\n", + "for label, res in results.items():\n", + " ax.errorbar(\n", + " list(range(steps, num_randomizations + 1, steps)),\n", + " [r[0] for r in res],\n", + " yerr=[r[1] for r in res],\n", + " alpha=0.75,\n", + " marker=\"o\",\n", + " linestyle=\"\",\n", + " markerfacecolor=\"none\",\n", + " label=label,\n", + " )\n", + "\n", + "ax.set_ylabel(r\"$\\langle X_{6}Z_{13}\\rangle$\")\n", + "ax.set_xlabel(\"# randomizations\")\n", + "ax.grid()\n", + "\n", + "ax.legend(ncols=2)\n", + "ax.set_ylim([-0.1, 2.0])\n", + "ax.set_xlim([-50, 4100])" + ] + }, + { + "cell_type": "markdown", + "id": "slc-next-000-4000-8000-000000000099", + "metadata": {}, + "source": [ + "## Next steps\n", + "If you found this work interesting, you might be interested in the following material:\n", + "\n", + "- [Combine error mitigation options with the Estimator primitive](/docs/tutorials/combine-error-mitigation-techniques)\n", + "- [Utility-scale error mitigation with probabilistic error amplification](/docs/tutorials/probabilistic-error-amplification)\n", + "- The [`qiskit-addon-slc`](https://github.com/Qiskit/qiskit-addon-slc) and [`samplomatic`](https://github.com/Qiskit/samplomatic) documentation\n", + "- [Lightcone shading for classically accelerated quantum error mitigation](https://arxiv.org/abs/2409.04401)\n", + "" + ] + } + ], + "metadata": { + "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" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif new file mode 100644 index 00000000000..d080513f2ad Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/curvefit.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/0dab284e-dbed-4d2f-92aa-c6c9c0e909a6-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/0dab284e-dbed-4d2f-92aa-c6c9c0e909a6-0.avif new file mode 100644 index 00000000000..9addfd1191a Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/0dab284e-dbed-4d2f-92aa-c6c9c0e909a6-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-0.avif new file mode 100644 index 00000000000..9a4917c1f8c Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-1.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-1.avif new file mode 100644 index 00000000000..387647c4b30 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-1.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-2.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-2.avif new file mode 100644 index 00000000000..e002f00e5a4 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/285edaef-8e79-41eb-a563-4675ea75d5b1-2.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-0.avif new file mode 100644 index 00000000000..713734e1397 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-1.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-1.avif new file mode 100644 index 00000000000..66dd2def4d2 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-1.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-2.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-2.avif new file mode 100644 index 00000000000..ed52e560df0 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/40d0eba9-5e0b-4b1d-8fb2-c239e262062f-2.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-0.avif new file mode 100644 index 00000000000..9a4917c1f8c Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-1.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-1.avif new file mode 100644 index 00000000000..387647c4b30 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-1.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-2.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-2.avif new file mode 100644 index 00000000000..e002f00e5a4 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/6ba4b397-e903-4b85-9482-31a96c689dc0-2.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/87f19e08-0cae-4959-9215-354ab099ab02-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/87f19e08-0cae-4959-9215-354ab099ab02-0.avif new file mode 100644 index 00000000000..87cfb52348d Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/87f19e08-0cae-4959-9215-354ab099ab02-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/8df6612b-d611-4201-b1f4-6d8de4a192ad-1.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/8df6612b-d611-4201-b1f4-6d8de4a192ad-1.avif new file mode 100644 index 00000000000..d2c34f8cbb4 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/8df6612b-d611-4201-b1f4-6d8de4a192ad-1.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/d662b076-005e-46c7-826d-4c29a4937404-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/d662b076-005e-46c7-826d-4c29a4937404-0.avif new file mode 100644 index 00000000000..bca9b96510b Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/d662b076-005e-46c7-826d-4c29a4937404-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-0.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-0.avif new file mode 100644 index 00000000000..6ce3061ea78 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-0.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-1.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-1.avif new file mode 100644 index 00000000000..a52fc809c0e Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-1.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-2.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-2.avif new file mode 100644 index 00000000000..a8d41cb0b04 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/eb2948bc-fa27-4108-b03c-0291d3b2f7c8-2.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/slc-ovplot-0-4000-8000-000000000066-1.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/slc-ovplot-0-4000-8000-000000000066-1.avif new file mode 100644 index 00000000000..382a4859d34 Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/extracted-outputs/slc-ovplot-0-4000-8000-000000000066-1.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif new file mode 100644 index 00000000000..cfe1278ad2c Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/learnlayer.avif differ diff --git a/public/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif b/public/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif new file mode 100644 index 00000000000..4e258a82f1a Binary files /dev/null and b/public/docs/images/tutorials/pec-with-shaded-lightcones/paulitwirl.avif differ diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml index b5dfc9e49d1..ad7a68f3927 100644 --- a/qiskit_bot.yaml +++ b/qiskit_bot.yaml @@ -686,6 +686,8 @@ notifications: "docs/tutorials/combine-error-mitigation-techniques": - "`@nathanearnestnoble`" - "`@ibrahim-shehzad`" + "docs/tutorials/pec-with-shaded-lightcones": + - "@henryzou50" "docs/tutorials/nishimori-phase-transition": - "`@nathanearnestnoble`" - "@kevinsung" diff --git a/scripts/config/notebook-testing.toml b/scripts/config/notebook-testing.toml index 555510e41e7..d44abf36bda 100644 --- a/scripts/config/notebook-testing.toml +++ b/scripts/config/notebook-testing.toml @@ -204,6 +204,7 @@ notebooks = [ "docs/tutorials/wire-cutting.ipynb", "docs/tutorials/krylov-quantum-diagonalization.ipynb", "docs/tutorials/probabilistic-error-amplification.ipynb", + "docs/tutorials/pec-with-shaded-lightcones.ipynb", "docs/tutorials/sample-based-quantum-diagonalization.ipynb", "docs/tutorials/pauli-correlation-encoding-for-qaoa.ipynb", "docs/tutorials/nishimori-phase-transition.ipynb",