596 lines
25 KiB
Plaintext
596 lines
25 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "480f0896-8638-436c-8694-9fa2d74964e0",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Combine error mitigation options with the Estimator primitive\n",
|
|
"*Usage estimate: 8 minutes on IBM Sherbrooke (NOTE: This is an estimate only. Your runtime may vary.)*"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c8e8f022-2450-401f-8b7b-d3673be9f871",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Background\n",
|
|
"\n",
|
|
"In this tutorial, you'll explore the error suppression and error mitigation options available with the Estimator primitive from Qiskit Runtime. You will construct a circuit and observable and submit jobs using the Estimator primitive using different combinations of error mitigation settings. Then, you will plot the results to observe the effects of the various settings. Most of the tutorial uses a 10-qubit circuit to make visualizations easier, and at the end, you can scale up the workflow to 50 qubits.\n",
|
|
"\n",
|
|
"These are the error suppression and mitigation options you will use:\n",
|
|
"\n",
|
|
"- Dynamical decoupling\n",
|
|
"- Measurement error mitigation\n",
|
|
"- Gate twirling\n",
|
|
"- Zero-noise extrapolation (ZNE)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "964b8213-f852-4ab6-95bf-c57f4127af86",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Requirements\n",
|
|
"\n",
|
|
"Before starting this tutorial, ensure that you have the following installed:\n",
|
|
"\n",
|
|
"- Qiskit SDK 1.0 or later with visualization support (`pip install 'qiskit[visualization]'`)\n",
|
|
"- Qiskit Runtime 0.22 or later (`pip install qiskit-ibm-runtime`)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e0169882-5dfe-4db7-b951-14254d02114d",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "44cb7018-2fbd-46f8-81e3-ceb919d07362",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"from qiskit.circuit.library import EfficientSU2, UnitaryOverlap\n",
|
|
"from qiskit.quantum_info import SparsePauliOp\n",
|
|
"from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
|
|
"\n",
|
|
"from qiskit_ibm_runtime import QiskitRuntimeService\n",
|
|
"from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "bf93d326-e0f3-49c9-998b-c65495b0fbe8",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 1: Map classical inputs to a quantum problem\n",
|
|
"\n",
|
|
"This tutorial assumes that the classical problem has already been mapped to quantum. Begin by constructing a circuit and observable to measure. While the techniques used in this tutorial apply to many different kinds of circuits, for simplicity this tutorial uses the [`EfficientSU2`](/docs/api/qiskit/qiskit.circuit.library.EfficientSU2#efficientsu2) circuit included in Qiskit's circuit library.\n",
|
|
"\n",
|
|
"`EfficientSU2` is a parameterized quantum circuit designed to be efficiently executable on quantum hardware with limited qubit connectivity, while still being expressive enough to solve problems in application domains like optimization and chemistry. It's built by alternating layers of parameterized single-qubit gates with a layer containing a fixed pattern of two-qubit gates, for a chosen number of repetitions. The pattern of two-qubit gates can be specified by the user. Here you can use the built-in `pairwise` pattern because it minimizes the circuit depth by packing the two-qubit gates as densely as possible. This pattern can be executed using only linear qubit connectivity."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "616b7382-9e06-43c8-aa32-1162c27b9a04",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/combine-error-mitigation-techniques/extracted-outputs/616b7382-9e06-43c8-aa32-1162c27b9a04-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"n_qubits = 10\n",
|
|
"reps = 1\n",
|
|
"\n",
|
|
"circuit = EfficientSU2(n_qubits, entanglement=\"pairwise\", reps=reps)\n",
|
|
"\n",
|
|
"circuit.decompose().draw(\"mpl\", scale=0.7)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "02399b47-0eea-4859-83c4-77403a4b757f",
|
|
"metadata": {},
|
|
"source": [
|
|
"For our observable, let's take the Pauli $Z$ operator acting on the last qubit, $Z I \\cdots I$."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "d961ea45-99ef-43bf-89f7-94584b1eb74c",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Z on the last qubit (index -1) with coefficient 1.0\n",
|
|
"observable = SparsePauliOp.from_sparse_list(\n",
|
|
" [(\"Z\", [-1], 1.0)], num_qubits=n_qubits\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "52e59040-7023-4af3-8d18-468c4d244ea9",
|
|
"metadata": {},
|
|
"source": [
|
|
"At this point, you could proceed to run your circuit and measure the observable. However, you also want to compare the output of the quantum device with the correct answer - that is, the theoretical value of the observable, if the circuit had been executed without error. For small quantum circuits you can calculate this value by simulating the circuit on a classical computer, but this is not possible for larger, utility-scale circuits. You can work around this issue with the \"mirror circuit\" technique (also known as \"compute-uncompute\"), which is useful for benchmarking the performance of quantum devices.\n",
|
|
"\n",
|
|
"#### Mirror circuit\n",
|
|
"\n",
|
|
"In the mirror circuit technique, you concatenate the circuit with its inverse circuit, which is formed by inverting each gate of the circuit in reverse order. The resulting circuit implements the identity operator, which can trivially be simulated. Because the structure of the original circuit is preserved in the mirror circuit, executing the mirror circuit still gives an idea of how the quantum device would perform on the original circuit.\n",
|
|
"\n",
|
|
"The following code cell assigns random parameters to your circuit, and then constructs the mirror circuit using the [`UnitaryOverlap`](/docs/api/qiskit/qiskit.circuit.library.UnitaryOverlap#unitaryoverlap) class. Before mirroring the circuit, append a [barrier](/docs/api/qiskit/circuit#qiskit.circuit.Barrier) instruction to it to prevent the transpiler from merging the two parts of the circuit on either side of the barrier. Without the barrier, the transpiler would merge the original circuit with its inverse, resulting in a transpiled circuit without any gates."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "ed100452-a7f6-4e6c-ac7a-f4173cab0e98",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/combine-error-mitigation-techniques/extracted-outputs/ed100452-a7f6-4e6c-ac7a-f4173cab0e98-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Generate random parameters\n",
|
|
"rng = np.random.default_rng(1234)\n",
|
|
"params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)\n",
|
|
"\n",
|
|
"# Assign the parameters to the circuit\n",
|
|
"assigned_circuit = circuit.assign_parameters(params)\n",
|
|
"\n",
|
|
"# Add a barrier to prevent circuit optimization of mirrored operators\n",
|
|
"assigned_circuit.barrier()\n",
|
|
"\n",
|
|
"# Construct mirror circuit\n",
|
|
"mirror_circuit = UnitaryOverlap(assigned_circuit, assigned_circuit)\n",
|
|
"\n",
|
|
"mirror_circuit.decompose().draw(\"mpl\", scale=0.7)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e9aa983c-fb1a-4ef6-9977-b5d6cb377336",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 2: Optimize problem for quantum hardware execution\n",
|
|
"\n",
|
|
"You must optimize your circuit before running it on hardware. This process involves a few steps:\n",
|
|
"\n",
|
|
"- Pick a qubit layout that maps the virtual qubits of your circuit to physical qubits on the hardware.\n",
|
|
"- Insert swap gates as needed to route interactions between qubits that are not connected.\n",
|
|
"- Translate the gates in your circuit to [Instruction Set Architecture (ISA)](/docs/guides/transpile#instruction-set-architecture) instructions that can directly be executed on the hardware.\n",
|
|
"- Perform circuit optimizations to minimize the circuit depth and gate count.\n",
|
|
"\n",
|
|
"The transpiler built into Qiskit can perform all of these steps for you. Because this tutorial uses a hardware-efficient circuit, the transpiler should be able to pick a qubit layout that does not require any swap gates to be inserted for routing interactions.\n",
|
|
"\n",
|
|
"You need to choose the hardware device to use before you optimize your circuit. The following code cell requests the least busy device with at least 127 qubits."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "68b5b3d9-af02-41d7-8990-4dad6cb1d098",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"service = QiskitRuntimeService()\n",
|
|
"backend = service.least_busy(\n",
|
|
" operational=True, simulator=False, min_num_qubits=127\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0b607d83-e7a2-4c03-83c9-3b52d23f8772",
|
|
"metadata": {},
|
|
"source": [
|
|
"You can transpile your circuit for your chosen backend by creating a pass manager and then running the pass manager on the circuit. An easy way to create a pass manager is to use the [`generate_preset_pass_manager`](/docs/api/qiskit/qiskit.transpiler.generate_preset_pass_manager) function. See [Transpile with pass managers](/docs/guides/transpile-with-pass-managers) for a more detailed explanation of transpiling with pass managers."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "767f36e0-39cd-4a6e-8090-7a417593ad6c",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/combine-error-mitigation-techniques/extracted-outputs/767f36e0-39cd-4a6e-8090-7a417593ad6c-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"pass_manager = generate_preset_pass_manager(\n",
|
|
" optimization_level=3, backend=backend, seed_transpiler=1234\n",
|
|
")\n",
|
|
"isa_circuit = pass_manager.run(mirror_circuit)\n",
|
|
"\n",
|
|
"isa_circuit.draw(\"mpl\", idle_wires=False, scale=0.7, fold=-1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "761595f3-99bd-4a75-b493-e1a89afce6f6",
|
|
"metadata": {},
|
|
"source": [
|
|
"The transpiled circuit now contains only ISA instructions. The single-qubit gates have been decomposed in terms of $\\sqrt{X}$ gates and $R_z$ rotations, and the CX gates have been decomposed into [ECR gates](/docs/api/qiskit/qiskit.circuit.library.ECRGate#ecrgate) and single-qubit rotations.\n",
|
|
"\n",
|
|
"The transpilation process has mapped the virtual qubits of the circuit to physical qubits on the hardware. The information about the qubit layout is stored in the `layout` attribute of the transpiled circuit. The observable was also defined in terms of the virtual qubits, so you need to apply this layout to the observable, which you can do with the [`apply_layout`](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp#apply_layout) method of `SparsePauliOp`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "80651f1b-4b7c-4fd4-bd7f-ede857698abd",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Original observable:\n",
|
|
"SparsePauliOp(['ZIIIIIIIII'],\n",
|
|
" coeffs=[1.+0.j])\n",
|
|
"\n",
|
|
"Observable with layout applied:\n",
|
|
"SparsePauliOp(['IIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],\n",
|
|
" coeffs=[1.+0.j])\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"isa_observable = observable.apply_layout(isa_circuit.layout)\n",
|
|
"\n",
|
|
"print(\"Original observable:\")\n",
|
|
"print(observable)\n",
|
|
"print()\n",
|
|
"print(\"Observable with layout applied:\")\n",
|
|
"print(isa_observable)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8d88a259-084a-4b46-802f-4121b11b7efd",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 3: Execute using Qiskit primitives\n",
|
|
"\n",
|
|
"You are now ready to run your circuit using the Estimator primitive.\n",
|
|
"\n",
|
|
"Here you will submit five separate jobs, starting with no error suppression or mitigation, and successively enabling various error suppression and mitigation options available in Qiskit Runtime. For information about the options, refer to the following pages:\n",
|
|
"\n",
|
|
"- [Overview of all options](/docs/api/qiskit-ibm-runtime/options)\n",
|
|
"- [Dynamical decoupling](/docs/api/qiskit-ibm-runtime/options-dynamical-decoupling-options)\n",
|
|
"- [Resilience, including measurement error mitigation and zero-noise extrapolation (ZNE)](/docs/api/qiskit-ibm-runtime/options-resilience-options-v2)\n",
|
|
"- [Twirling](/docs/api/qiskit-ibm-runtime/options-twirling-options)\n",
|
|
"\n",
|
|
"Because these jobs can run independently of each other, you can use [batch mode](/docs/guides/run-jobs-batch) to allow Qiskit Runtime to optimize the timing of their execution."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1902e8b6-f3b5-412c-b1ce-3b6c00aee285",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"pub = (isa_circuit, isa_observable)\n",
|
|
"\n",
|
|
"jobs = []\n",
|
|
"\n",
|
|
"with Batch(backend=backend) as batch:\n",
|
|
" estimator = Estimator(mode=batch)\n",
|
|
" # Set number of shots\n",
|
|
" estimator.options.default_shots = 100_000\n",
|
|
" # Disable runtime compilation and error mitigation\n",
|
|
" estimator.options.resilience_level = 0\n",
|
|
"\n",
|
|
" # Run job with no error mitigation\n",
|
|
" job0 = estimator.run([pub])\n",
|
|
" jobs.append(job0)\n",
|
|
"\n",
|
|
" # Add dynamical decoupling (DD)\n",
|
|
" estimator.options.dynamical_decoupling.enable = True\n",
|
|
" estimator.options.dynamical_decoupling.sequence_type = \"XpXm\"\n",
|
|
" job1 = estimator.run([pub])\n",
|
|
" jobs.append(job1)\n",
|
|
"\n",
|
|
" # Add readout error mitigation (DD + TREX)\n",
|
|
" estimator.options.resilience.measure_mitigation = True\n",
|
|
" job2 = estimator.run([pub])\n",
|
|
" jobs.append(job2)\n",
|
|
"\n",
|
|
" # Add gate twirling (DD + TREX + Gate Twirling)\n",
|
|
" estimator.options.twirling.enable_gates = True\n",
|
|
" estimator.options.twirling.num_randomizations = \"auto\"\n",
|
|
" job3 = estimator.run([pub])\n",
|
|
" jobs.append(job3)\n",
|
|
"\n",
|
|
" # Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)\n",
|
|
" estimator.options.resilience.zne_mitigation = True\n",
|
|
" estimator.options.resilience.zne.noise_factors = (1, 3, 5)\n",
|
|
" estimator.options.resilience.zne.extrapolator = (\"exponential\", \"linear\")\n",
|
|
" job4 = estimator.run([pub])\n",
|
|
" jobs.append(job4)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "072d6c32-4b39-4b53-b793-9c0f6f0e4a56",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 4: Post-process and return result in desired classical format\n",
|
|
"\n",
|
|
"Finally, you can analyze the data. Here you will retrieve the job results, extract the measured expectation values from them, and plot the values, including error bars of one standard deviation."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "84977ce8-700f-44b2-b202-b13752d2dd94",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/combine-error-mitigation-techniques/extracted-outputs/84977ce8-700f-44b2-b202-b13752d2dd94-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Retrieve the job results\n",
|
|
"results = [job.result() for job in jobs]\n",
|
|
"\n",
|
|
"# Unpack the PUB results (there's only one PUB result in each job result)\n",
|
|
"pub_results = [result[0] for result in results]\n",
|
|
"\n",
|
|
"# Unpack the expectation values and standard errors\n",
|
|
"expectation_vals = np.array(\n",
|
|
" [float(pub_result.data.evs) for pub_result in pub_results]\n",
|
|
")\n",
|
|
"standard_errors = np.array(\n",
|
|
" [float(pub_result.data.stds) for pub_result in pub_results]\n",
|
|
")\n",
|
|
"\n",
|
|
"# Plot the expectation values\n",
|
|
"fig, ax = plt.subplots()\n",
|
|
"labels = [\"No mitigation\", \"+ DD\", \"+ TREX\", \"+ Twirling\", \"+ ZNE\"]\n",
|
|
"ax.bar(\n",
|
|
" range(len(labels)),\n",
|
|
" expectation_vals,\n",
|
|
" yerr=standard_errors,\n",
|
|
" label=\"experiment\",\n",
|
|
")\n",
|
|
"ax.axhline(y=1.0, color=\"gray\", linestyle=\"--\", label=\"ideal\")\n",
|
|
"ax.set_xticks(range(len(labels)))\n",
|
|
"ax.set_xticklabels(labels)\n",
|
|
"ax.set_ylabel(\"Expectation value\")\n",
|
|
"ax.legend(loc=\"upper left\")\n",
|
|
"\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "40db8903-b108-4d73-b984-afcc74bb5d91",
|
|
"metadata": {},
|
|
"source": [
|
|
"At this small scale, it is difficult to see the effect of most of the error mitigation techniques, but zero-noise extrapolation does give a noticeable improvement. However, note that this improvement does not come for free, because the ZNE result also has a larger error bar."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "192f35a4-a5f6-49f5-a92e-f5ebc30c6979",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Scale the experiment up\n",
|
|
"\n",
|
|
"When developing an experiment, it's useful to start with a small circuit to make visualizations and simulations easier. Now that you've developed and tested our workflow on a 10-qubit circuit, you can scale it up to 50 qubits. The following code cell repeats all of the steps in this tutorial, but now applies them to a 50-qubit circuit."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "3b08a959-07d1-4a43-a01b-36b27a9574c3",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/combine-error-mitigation-techniques/extracted-outputs/3b08a959-07d1-4a43-a01b-36b27a9574c3-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"n_qubits = 50\n",
|
|
"reps = 1\n",
|
|
"\n",
|
|
"# Construct circuit and observable\n",
|
|
"circuit = EfficientSU2(n_qubits, entanglement=\"pairwise\", reps=reps)\n",
|
|
"observable = SparsePauliOp.from_sparse_list(\n",
|
|
" [(\"Z\", [-1], 1.0)], num_qubits=n_qubits\n",
|
|
")\n",
|
|
"\n",
|
|
"# Assign parameters to circuit\n",
|
|
"params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)\n",
|
|
"assigned_circuit = circuit.assign_parameters(params)\n",
|
|
"assigned_circuit.barrier()\n",
|
|
"\n",
|
|
"# Construct mirror circuit\n",
|
|
"mirror_circuit = UnitaryOverlap(assigned_circuit, assigned_circuit)\n",
|
|
"\n",
|
|
"# Transpile circuit and observable\n",
|
|
"isa_circuit = pass_manager.run(mirror_circuit)\n",
|
|
"isa_observable = observable.apply_layout(isa_circuit.layout)\n",
|
|
"\n",
|
|
"# Run jobs\n",
|
|
"pub = (isa_circuit, isa_observable)\n",
|
|
"\n",
|
|
"jobs = []\n",
|
|
"\n",
|
|
"with Batch(backend=backend) as batch:\n",
|
|
" estimator = Estimator(mode=batch)\n",
|
|
" # Set number of shots\n",
|
|
" estimator.options.default_shots = 100_000\n",
|
|
" # Disable runtime compilation and error mitigation\n",
|
|
" estimator.options.resilience_level = 0\n",
|
|
"\n",
|
|
" # Run job with no error mitigation\n",
|
|
" job0 = estimator.run([pub])\n",
|
|
" jobs.append(job0)\n",
|
|
"\n",
|
|
" # Add dynamical decoupling (DD)\n",
|
|
" estimator.options.dynamical_decoupling.enable = True\n",
|
|
" estimator.options.dynamical_decoupling.sequence_type = \"XpXm\"\n",
|
|
" job1 = estimator.run([pub])\n",
|
|
" jobs.append(job1)\n",
|
|
"\n",
|
|
" # Add readout error mitigation (DD + TREX)\n",
|
|
" estimator.options.resilience.measure_mitigation = True\n",
|
|
" job2 = estimator.run([pub])\n",
|
|
" jobs.append(job2)\n",
|
|
"\n",
|
|
" # Add gate twirling (DD + TREX + Gate Twirling)\n",
|
|
" estimator.options.twirling.enable_gates = True\n",
|
|
" estimator.options.twirling.num_randomizations = \"auto\"\n",
|
|
" job3 = estimator.run([pub])\n",
|
|
" jobs.append(job3)\n",
|
|
"\n",
|
|
" # Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)\n",
|
|
" estimator.options.resilience.zne_mitigation = True\n",
|
|
" estimator.options.resilience.zne.noise_factors = (1, 3, 5)\n",
|
|
" estimator.options.resilience.zne.extrapolator = (\"exponential\", \"linear\")\n",
|
|
" job4 = estimator.run([pub])\n",
|
|
" jobs.append(job4)\n",
|
|
"\n",
|
|
"# Retrieve the job results\n",
|
|
"results = [job.result() for job in jobs]\n",
|
|
"\n",
|
|
"# Unpack the PUB results (there's only one PUB result in each job result)\n",
|
|
"pub_results = [result[0] for result in results]\n",
|
|
"\n",
|
|
"# Unpack the expectation values and standard errors\n",
|
|
"expectation_vals = np.array(\n",
|
|
" [float(pub_result.data.evs) for pub_result in pub_results]\n",
|
|
")\n",
|
|
"standard_errors = np.array(\n",
|
|
" [float(pub_result.data.stds) for pub_result in pub_results]\n",
|
|
")\n",
|
|
"\n",
|
|
"# Plot the expectation values\n",
|
|
"fig, ax = plt.subplots()\n",
|
|
"labels = [\"No mitigation\", \"+ DD\", \"+ TREX\", \"+ Twirling\", \"+ ZNE\"]\n",
|
|
"ax.bar(\n",
|
|
" range(len(labels)),\n",
|
|
" expectation_vals,\n",
|
|
" yerr=standard_errors,\n",
|
|
" label=\"experiment\",\n",
|
|
")\n",
|
|
"ax.axhline(y=1.0, color=\"gray\", linestyle=\"--\", label=\"ideal\")\n",
|
|
"ax.set_xticks(range(len(labels)))\n",
|
|
"ax.set_xticklabels(labels)\n",
|
|
"ax.set_ylabel(\"Expectation value\")\n",
|
|
"ax.legend(loc=\"upper left\")\n",
|
|
"\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "9bf5cb8a-0cb4-4b35-86ec-551d8db85155",
|
|
"metadata": {},
|
|
"source": [
|
|
"When you compare the 50-qubit results with the 10-qubit results from earlier, you might note the following (your results might differ across runs):\n",
|
|
"\n",
|
|
"- The results without error mitigation are worse. Running the larger circuit involves executing more gates, so there are more opportunities for errors to accumulate.\n",
|
|
"- The addition of dynamical decoupling might have worsened performance. This is not surprising, because the circuit is very dense. Dynamical decoupling is primarily useful when there are large gaps in the circuit during which qubits sit idle without gates being applied to them. When these gaps are not present, dynamical decoupling is not effective, and can actually worsen performance due to errors in the dynamical decoupling pulses themselves. The 10-qubit circuit may have been too small for us to observe this effect.\n",
|
|
"- With zero-noise extrapolation, the result is as good, or nearly as good, as the 10-qubit result, though the error bar is much larger. This demonstrates the power of the ZNE technique!\n",
|
|
"\n",
|
|
"## Conclusion\n",
|
|
"\n",
|
|
"In this tutorial, you investigated different error mitigation options available for the Qiskit Runtime Estimator primitive. You developed a workflow using a 10-qubit circuit, and then scaled it up to 50 qubits. You might have observed that enabling more error suppression and mitigation options doesn't always improve performance (specifically, enabling dynamical decoupling in this case). Most of the options accept additional configuration, which you can test out in your own work!"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d55d58a9-ee64-438e-bb17-15969ac43799",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Tutorial survey\n",
|
|
"\n",
|
|
"Please take one minute to provide feedback on this tutorial. Your insights will help us improve our content offerings and user experience.\n",
|
|
"\n",
|
|
"[Link to survey](https://your.feedback.ibm.com/jfe/form/SV_cUCGeqAAI7suE3I)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8097f7e1-0e8c-4d49-b0a2-9aeb8d706660",
|
|
"metadata": {},
|
|
"source": [
|
|
"© IBM Corp. 2024"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"description": "Combine error mitigation options for utility-scale experiments using 100Q+ IBM Quantum systems and the Qiskit Runtime Estimator primitive.",
|
|
"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"
|
|
},
|
|
"platform": "cloud",
|
|
"title": "Combine error mitigation options with the Estimator primitive"
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|