499 lines
17 KiB
Plaintext
499 lines
17 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "bb523f27-586e-4c9c-b641-1f1db71396ef",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Error mitigation with the IBM Circuit function\n",
|
||
"*Usage estimate: 26 minutes on IBM Brisbane (NOTE: This is an estimate only. Your runtime may vary.)*"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "34260848-be4c-45f7-914f-9d9bcc2be504",
|
||
"metadata": {},
|
||
"source": [
|
||
"In this notebook, we will walk through an example of building and running a workflow using the IBM Circuit function. This function takes [Primitive Unified Blocs](/docs/guides/primitive-input-output) (PUBs) as inputs and returns error-mitigated expectation values as outputs. It provides an automated and customized pipeline to optimize circuits and execute on quantum hardware so that researchers can focus on algorithm and application discovery.\n",
|
||
"\n",
|
||
"Visit the documentation for an [introduction to Qiskit Functions](/docs/guides/functions) and learn how to get started with the [IBM Circuit function](/docs/guides/ibm-circuit-function)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "8e5dd434-2a6a-4e85-8391-2c29139c5e4a",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Background\n",
|
||
"\n",
|
||
"For this tutorial, we consider a general hardware-efficient Trotterized time evolution circuit for the 2D transverse-field Ising model and compute the global magnetization. Such a circuit is useful in different application domains such as condensed matter physics, chemistry, machine learning etc. For more information on the structure of this model, refer to [Nature 618, 500–505 (2023)](https://www.nature.com/articles/s41586-023-06096-3).\n",
|
||
"\n",
|
||
"The IBM Circuit function combines capabilities from the Qiskit transpiler service and Qiskit Runtime Estimator to provide a simplified interface for running circuits. The function performs transpilation, error suppression, error mitigation, and circuit execution within a single managed service so that we can focus on mapping the problem to circuits rather than building out each step of the pattern ourselves."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "56319c83-641a-4c62-872f-f0563c4e3fcc",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Requirements\n",
|
||
"\n",
|
||
"Before starting this tutorial, be sure you have the following installed:\n",
|
||
"\n",
|
||
"- Qiskit SDK 1.2 or later (`pip install qiskit`)\n",
|
||
"- Qiskit Runtime 0.28 or later (`pip install qiskit-ibm-runtime`)\n",
|
||
"- IBM Qiskit Functions Catalog client 0.0.0 or later (`pip install qiskit-ibm-catalog`)\n",
|
||
"- Qiskit Aer 0.15.0 or later (`pip install qiskit-aer`)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1160c9eb",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Setup"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f27af69d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import rustworkx\n",
|
||
"from collections import defaultdict\n",
|
||
"from numpy import pi, mean\n",
|
||
"\n",
|
||
"from qiskit_ibm_runtime import QiskitRuntimeService\n",
|
||
"\n",
|
||
"from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
|
||
"\n",
|
||
"from qiskit.circuit import QuantumCircuit, Parameter\n",
|
||
"from qiskit.quantum_info import SparsePauliOp"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "16099381-e335-43ea-8756-94024eba10d0",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 1: Map classical inputs to a quantum problem\n",
|
||
"\n",
|
||
"<ul>\n",
|
||
" <li>Input: Parameters to create the quantum circuit</li>\n",
|
||
" <li>Output: Abstract circuit and observables</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "aba33d31-2417-4cd9-a3a6-a79b285d5f0d",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Construct the circuit"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "cece07d0-0a68-4c34-899d-03948e34a5fb",
|
||
"metadata": {},
|
||
"source": [
|
||
"The circuit we will create is a hardware-efficient, Trotterized time evolution circuit for the 2D transverse-field Ising model. We start with selecting a backend. Properties of this backend (i.e., its coupling map) will be used to define the quantum problem and ensure it is hardware-efficient."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "4cb57a1f-4a9e-4c5f-bd30-bf2c914a5191",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"service = QiskitRuntimeService()\n",
|
||
"backend = service.least_busy(\n",
|
||
" operational=True, simulator=False, min_num_qubits=127\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f1e2e925-3c8a-4e34-a5bc-0ddb32b0b9a1",
|
||
"metadata": {},
|
||
"source": [
|
||
"Next, we get the coupling map from the backend."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2f7541c9-782d-46df-bc6c-6bd0dbb30759",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"coupling_graph = backend.coupling_map.graph.to_undirected(multigraph=False)\n",
|
||
"layer_couplings = defaultdict(list)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "70ae3888-4672-4d3f-9ef2-c5590d4601b2",
|
||
"metadata": {},
|
||
"source": [
|
||
"We want to be careful in how we design the layers of our circuit. We will do this by coloring the edges of the coupling map (i.e., grouping the disjoint edges) and use that coloring to more efficiently place gates in the circuit. This will lead to a shallower circuit with layers of gates that can be executed simultaneously on the hardware."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "9a8c57e4-e5c6-410f-aeea-4a84d02e4fb6",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"edge_coloring = rustworkx.graph_bipartite_edge_color(coupling_graph)\n",
|
||
"\n",
|
||
"for edge_idx, color in edge_coloring.items():\n",
|
||
" layer_couplings[color].append(\n",
|
||
" coupling_graph.get_edge_endpoints_by_index(edge_idx)\n",
|
||
" )\n",
|
||
"layer_couplings = [\n",
|
||
" sorted(layer_couplings[i]) for i in sorted(layer_couplings.keys())\n",
|
||
"]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "bfbe498d-f3bf-4af4-b52f-02423fa87c29",
|
||
"metadata": {},
|
||
"source": [
|
||
"Next, we write a simple helper function that implements the hardware-efficient, Trotterized time evolution circuit for the 2D transverse-field Ising model using the edge coloring obtained above."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f028123a-80ac-48fc-8847-f7a242be7462",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def construct_trotter_circuit(\n",
|
||
" num_qubits: int,\n",
|
||
" num_trotter_steps: int,\n",
|
||
" layer_couplings: list,\n",
|
||
" barrier: bool = True,\n",
|
||
") -> QuantumCircuit:\n",
|
||
" theta, phi = Parameter(\"theta\"), Parameter(\"phi\")\n",
|
||
" circuit = QuantumCircuit(num_qubits)\n",
|
||
"\n",
|
||
" for _ in range(num_trotter_steps):\n",
|
||
" circuit.rx(theta, range(num_qubits))\n",
|
||
" for layer in layer_couplings:\n",
|
||
" for edge in layer:\n",
|
||
" if edge[0] < num_qubits and edge[1] < num_qubits:\n",
|
||
" circuit.rzz(phi, edge[0], edge[1])\n",
|
||
" if barrier:\n",
|
||
" circuit.barrier()\n",
|
||
"\n",
|
||
" return circuit"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "77cfb8f3-7425-403a-962d-e9f1975830b2",
|
||
"metadata": {},
|
||
"source": [
|
||
"We will choose the number of qubits and trotter steps and then construct the circuit."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"id": "18eefa99-f1c4-41b5-90b8-7fd8723cac84",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<Image src=\"/docs/images/tutorials/error-mitigation-with-qiskit-functions/extracted-outputs/18eefa99-f1c4-41b5-90b8-7fd8723cac84-0.avif\" alt=\"Output of the previous code cell\" />"
|
||
]
|
||
},
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"num_qubits = 100\n",
|
||
"num_trotter_steps = 2\n",
|
||
"\n",
|
||
"circuit = construct_trotter_circuit(\n",
|
||
" num_qubits, num_trotter_steps, layer_couplings\n",
|
||
")\n",
|
||
"circuit.draw(\"mpl\", fold=-1)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e7201b96-0ebd-4707-b1fe-d85877537d2c",
|
||
"metadata": {},
|
||
"source": [
|
||
"In order to benchmark the quality of the execution, we need to compare it with the ideal outcome. The circuit of choice is beyond brute force classical simulation. So, we fix the parameters of all the `Rx` gates in the circuit to $0$, and that of all `Rzz` gates to $\\pi$. This makes the circuit Clifford, which makes it possible to perform the ideal simulation and obtain the ideal outcome for comparison. In this case, we know that outcome will be `1.0`."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "314bdcc9-a9e6-4448-b67a-8af3c53b8527",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"parameters = [0, pi]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c3ab06fa-fc25-46d9-9db9-b73ce180af3c",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Construct the observable"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1f240bb6-21e6-48b3-bdf8-09d8e74e1c67",
|
||
"metadata": {},
|
||
"source": [
|
||
"In this tutorial, we will compute the global magnetization along $\\hat{z}$ for our $N$-qubit problem: $M_z = \\sum_{i=1}^N \\langle Z_i \\rangle / N$. This requires first computing the single-site magnetization $\\langle Z_i \\rangle$ for each qubit $i$, which we define below."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "dbcb7a27-abdb-4b1f-a765-c6144df675e2",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"SparsePauliOp(['ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],\n",
|
||
" coeffs=[1.+0.j])\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"observables = []\n",
|
||
"for i in range(num_qubits):\n",
|
||
" obs = \"I\" * (i) + \"Z\" + \"I\" * (num_qubits - i - 1)\n",
|
||
" observables.append(SparsePauliOp(obs))\n",
|
||
"\n",
|
||
"print(observables[0])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "99a7e860-709d-4860-94d0-76e6c6426b96",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Steps 2 and 3: Optimize problem for quantum hardware execution and execute with the IBM Circuit function\n",
|
||
"\n",
|
||
"<ul>\n",
|
||
" <li>Input: Abstract circuit and observables</li>\n",
|
||
" <li>Output: Mitigated expectation values</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "34f5d0f9-f3ac-4362-ac8c-fd4d02fec265",
|
||
"metadata": {},
|
||
"source": [
|
||
"Now, we can pass the abstract circuit and observables to the IBM Circuit function. It will handle transpilation and execution on quantum hardware for us and return mitigated expectation values. First, we load the function from the [IBM Qiskit Functions Catalog](/docs/guides/functions)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "1bc95a78-71b3-4404-b867-8fb708b48434",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"catalog = QiskitFunctionsCatalog(token=\"<YOUR_IQP_API_TOKEN>\")\n",
|
||
"function = catalog.load(\"ibm/circuit-function\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "282e957a-d542-43f5-b4b1-0213f5327789",
|
||
"metadata": {},
|
||
"source": [
|
||
"The IBM Circuit function takes `pubs`, `backend_name`, as well as optional inputs for configuring transpilation, error mitigation, etc. We create the `pub` from the abstract circuit, observables, and circuit paramters. The name of the backend should be specified as a string."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"id": "3021bd3e-c7f9-4dfa-8358-3bdf695ae28e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"pubs = [(circuit, observables, parameters)]\n",
|
||
"backend_name = backend.name"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "082883fc-22fa-4539-a4e2-9f3ef97d7334",
|
||
"metadata": {},
|
||
"source": [
|
||
"We can also configure the `options` for transpilation, error suppression, and error mitigation. Default settings will be used if we don't wish to specify these. The IBM Circuit function comes with commonly used options for `optimization_level`, which controls how much circuit optimization to perform, and `mitigation_level`, which specifies how much error suppression and mitigation to apply. Note that the `mitigation_level` of the IBM Circuit function is distinct from the `resilience_level` used in the [Qiskit Runtime Estimator](/docs/guides/configure-error-mitigation). For a detailed description of these commonly used options as well as other advanced options, visit the [documentation for the IBM Circuit function](http://docs.quantum.ibm.com/guides/ibm-circuit-function).\n",
|
||
"\n",
|
||
"In this tutorial, we will set the `default_precision`, `optimization_level: 3` and `mitigation_level: 3`, which will turn on gate twirling and Zero Noise Extrapolation (ZNE) via Probabilistic Error Amplification (PEA) on top of the default level 1 settings."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"id": "3845e8fa-0fb5-4a43-a859-d8038e2a440c",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"options = {\n",
|
||
" \"default_precision\": 0.011,\n",
|
||
" \"optimization_level\": 3,\n",
|
||
" \"mitigation_level\": 3,\n",
|
||
"}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f2e3d1c1-c7f5-4084-bb21-e8f6479bea3a",
|
||
"metadata": {},
|
||
"source": [
|
||
"With the inputs specified, we submit the job to the IBM Circuit function for optimization and execution."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"id": "94bb07bc-c392-4ee6-9982-9fbcb2196e97",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"job = function.run(backend_name=backend_name, pubs=pubs, options=options)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "41917db3-039f-4940-b4bd-76be6e23af61",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 4: Post-process and return result in desired classical format\n",
|
||
"\n",
|
||
"<ul>\n",
|
||
" <li>Input: Results from the IBM Circuit function</li>\n",
|
||
" <li>Output: Global magnetization</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "958dd045-9eaa-40ce-8039-55cdbe882f10",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Compute the global magnetization"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e510ff6e-7022-48c6-b146-6e48611d7d12",
|
||
"metadata": {},
|
||
"source": [
|
||
"The result from running the function has the same format as the [Estimator](/docs/guides/primitive-input-output#estimator-output)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 22,
|
||
"id": "12aec700-7ee1-4729-bd67-c3fe7ae5ecd7",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"result = job.result()[0]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "18411ce9-5003-40b0-bda1-f2bba6509ec2",
|
||
"metadata": {},
|
||
"source": [
|
||
"We obtain the mitigated and non-mitigated expectation values from this result. These expectation values represent the single-site magnetization along the $\\hat{z}$ direction. We average these to arrive at the global magnetization and compare against the ideal value of `1.0` for this problem instance."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "4048098a-29b9-4bb7-b0a1-2a440061ee5d",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"mitigated: 0.9749883476088692\n",
|
||
"unmitigated: 0.7832977198447583\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"mitigated_expvals = result.data.evs\n",
|
||
"magnetization_mitigated = mean(mitigated_expvals)\n",
|
||
"\n",
|
||
"print(\"mitigated:\", magnetization_mitigated)\n",
|
||
"\n",
|
||
"unmitigated_expvals = [\n",
|
||
" result.data.evs_extrapolated[i][0][1] for i in range(num_qubits)\n",
|
||
"]\n",
|
||
"magnetization_unmitigated = mean(unmitigated_expvals)\n",
|
||
"\n",
|
||
"print(\"unmitigated:\", magnetization_unmitigated)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "d1717a9d",
|
||
"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_8jppdGBii6eMmsS)"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"description": "Walk through an example of building and running a workflow using the IBM Circuit function.",
|
||
"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": "Error mitigation with the IBM Circuit function\n"
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|