1001 lines
33 KiB
Plaintext
1001 lines
33 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "eb16d89d-f6fc-417f-9ce5-19251d039df7",
|
||
"metadata": {},
|
||
"source": [
|
||
"{/* cspell:ignore edgecolor */}\n",
|
||
"\n",
|
||
"# Circuit cutting for periodic boundary conditions\n",
|
||
"*Usage estimate: 2 minutes on IBM Brisbane (NOTE: This is an estimate only. Your runtime may vary.)*"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f053a0b1",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Background\n",
|
||
"\n",
|
||
"In this notebook, we consider the simulation of a periodic chain of qubits where there is two qubit operation between every two adjacent qubits, including the first and the last ones. Periodic chains are often found in physics and chemistry problems such as Ising models, molecular simulation, etc.\n",
|
||
"\n",
|
||
"Current IBM Quantum devices are planar. It is possible to embed some periodic chains on the topology directly where the first and last qubits are neighbors. However, for big enough problems, the first and last qubits can be far apart, thus requiring many SWAP gates for the 2-qubit operation between these two qubits. Such a periodic boundary problem has been studied in <a href=\"https://arxiv.org/abs/2402.17833\">this paper</a>.\n",
|
||
"\n",
|
||
"In this notebook we show the usage of circuit cutting to deal with such a utility scale periodic chain problem where the first and last qubits are not neighbors. Cutting this long range connectivity avoids the extra SWAP gates at the cost of executing multiple instances of the circuit, and some classical postprocessing. In summary, cutting can be incorporated to logically calculate the long distance 2-qubit operations. In other words, this approach leads to an effective increase in the connectivity of the coupling map, thus leading to a fewer number of SWAP gates.\n",
|
||
"\n",
|
||
"Note that there are two types of cuts - cutting the wire of a circuit (called `wire cutting`), or replacing a 2-qubit gate with multiple single qubit operations (called `gate cutting`). In this notebook, we shall focus on gate cutting. For more details on gate cutting, refer to the <a href=\"https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html\">explanatory materials</a> in `qiskit-addon-cutting`, and the corresponding references. For more details on wire cutting, refer to <a href=\"https://learning.quantum.ibm.com/tutorial/wire-cutting-to-improve-performance\">this notebook</a>, or the tutorials in <a href='https://qiskit.github.io/qiskit-addon-cutting/tutorials/index.html'>qiskit-addon-cutting</a>."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "120312e5-0eed-4168-9098-633a3d0e6e57",
|
||
"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.3 or later (`pip install qiskit-ibm-runtime`)\n",
|
||
"- Circuit cutting Qiskit addon 0.9.0 or later (`pip install qiskit-addon-cutting`)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f0af3c7d",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Setup"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "f01e3062",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import numpy as np\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import matplotlib as mpl\n",
|
||
"\n",
|
||
"from qiskit.transpiler import PassManager\n",
|
||
"from qiskit.transpiler.passes import (\n",
|
||
" BasisTranslator,\n",
|
||
" Optimize1qGatesDecomposition,\n",
|
||
")\n",
|
||
"from qiskit.circuit.equivalence_library import (\n",
|
||
" SessionEquivalenceLibrary as sel,\n",
|
||
")\n",
|
||
"from qiskit.converters import circuit_to_dag, dag_to_circuit\n",
|
||
"from qiskit.result import sampled_expectation_value\n",
|
||
"from qiskit.quantum_info import SparsePauliOp\n",
|
||
"from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
|
||
"from qiskit.circuit.library import TwoLocal\n",
|
||
"\n",
|
||
"from qiskit_addon_cutting import (\n",
|
||
" cut_gates,\n",
|
||
" generate_cutting_experiments,\n",
|
||
" reconstruct_expectation_values,\n",
|
||
")\n",
|
||
"\n",
|
||
"\n",
|
||
"from qiskit_ibm_runtime import QiskitRuntimeService\n",
|
||
"from qiskit_ibm_runtime import SamplerV2, SamplerOptions, Batch"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "6e7c685c-3e10-4cf1-a435-2b0fc761ebc4",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 1: Map classical inputs to a quantum problem\n",
|
||
"\n",
|
||
"Here, we'll generate a TwoLocal circuit and define some observables.\n",
|
||
"\n",
|
||
"<ul>\n",
|
||
" <li>Input: Parameters to create a circuit</li>\n",
|
||
" <li>Output: Abstract circuit and observables</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "8590c112-cf8a-4bc3-ab8b-411d1a1b010a",
|
||
"metadata": {},
|
||
"source": [
|
||
"We consider a hardware-efficient `entangler map` for the TwoLocal circuit with periodic connectivity between the last and the first qubits of the `entangler map`. This long range interaction can lead to extra SWAP gates during transpilation, thus increasing the depth of the circuit."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "0776185d-4ea8-4a8c-ab14-121636444d8f",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Select backend and initial layout"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "81c616a7-248a-412b-bac5-080eb9199760",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"service = QiskitRuntimeService()\n",
|
||
"backend = service.least_busy(\n",
|
||
" operational=True, simulator=False, min_num_qubits=127\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "15b9c4e5-7b25-4b95-9d79-bf73b9adad97",
|
||
"metadata": {},
|
||
"source": [
|
||
"For this notebook we shall consider a 109 qubit periodic 1D chain, which is the longest 1D chain in the topology of a 127 qubit IBM Quantum device. It is not possible to arrange a 109 qubit periodic chain on a 127 qubit device such that the first and last qubits are neighbors without incorporating extra SWAP gates."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"id": "6f8c4588-0532-41f9-9d6d-50e754466593",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"109"
|
||
]
|
||
},
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"init_layout = [\n",
|
||
" 13,\n",
|
||
" 12,\n",
|
||
" 11,\n",
|
||
" 10,\n",
|
||
" 9,\n",
|
||
" 8,\n",
|
||
" 7,\n",
|
||
" 6,\n",
|
||
" 5,\n",
|
||
" 4,\n",
|
||
" 3,\n",
|
||
" 2,\n",
|
||
" 1,\n",
|
||
" 0,\n",
|
||
" 14,\n",
|
||
" 18,\n",
|
||
" 19,\n",
|
||
" 20,\n",
|
||
" 21,\n",
|
||
" 22,\n",
|
||
" 23,\n",
|
||
" 24,\n",
|
||
" 25,\n",
|
||
" 26,\n",
|
||
" 27,\n",
|
||
" 28,\n",
|
||
" 29,\n",
|
||
" 30,\n",
|
||
" 31,\n",
|
||
" 32,\n",
|
||
" 36,\n",
|
||
" 51,\n",
|
||
" 50,\n",
|
||
" 49,\n",
|
||
" 48,\n",
|
||
" 47,\n",
|
||
" 46,\n",
|
||
" 45,\n",
|
||
" 44,\n",
|
||
" 43,\n",
|
||
" 42,\n",
|
||
" 41,\n",
|
||
" 40,\n",
|
||
" 39,\n",
|
||
" 38,\n",
|
||
" 37,\n",
|
||
" 52,\n",
|
||
" 56,\n",
|
||
" 57,\n",
|
||
" 58,\n",
|
||
" 59,\n",
|
||
" 60,\n",
|
||
" 61,\n",
|
||
" 62,\n",
|
||
" 63,\n",
|
||
" 64,\n",
|
||
" 65,\n",
|
||
" 66,\n",
|
||
" 67,\n",
|
||
" 68,\n",
|
||
" 69,\n",
|
||
" 70,\n",
|
||
" 74,\n",
|
||
" 89,\n",
|
||
" 88,\n",
|
||
" 87,\n",
|
||
" 86,\n",
|
||
" 85,\n",
|
||
" 84,\n",
|
||
" 83,\n",
|
||
" 82,\n",
|
||
" 81,\n",
|
||
" 80,\n",
|
||
" 79,\n",
|
||
" 78,\n",
|
||
" 77,\n",
|
||
" 76,\n",
|
||
" 75,\n",
|
||
" 90,\n",
|
||
" 94,\n",
|
||
" 95,\n",
|
||
" 96,\n",
|
||
" 97,\n",
|
||
" 98,\n",
|
||
" 99,\n",
|
||
" 100,\n",
|
||
" 101,\n",
|
||
" 102,\n",
|
||
" 103,\n",
|
||
" 104,\n",
|
||
" 105,\n",
|
||
" 106,\n",
|
||
" 107,\n",
|
||
" 108,\n",
|
||
" 112,\n",
|
||
" 126,\n",
|
||
" 125,\n",
|
||
" 124,\n",
|
||
" 123,\n",
|
||
" 122,\n",
|
||
" 121,\n",
|
||
" 120,\n",
|
||
" 119,\n",
|
||
" 118,\n",
|
||
" 117,\n",
|
||
" 116,\n",
|
||
" 115,\n",
|
||
" 114,\n",
|
||
" 113,\n",
|
||
"]\n",
|
||
"\n",
|
||
"# the number of qubits in the circuit is governed by the length of the initial layout\n",
|
||
"num_qubits = len(init_layout)\n",
|
||
"num_qubits"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "da94a31f-3bce-40e5-a580-b938ff255425",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Build the entangler map for the TwoLocal circuit"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "15bc3ac1-dbb6-4bb5-8d3e-63f3638f7a96",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]\n",
|
||
"coupling_map.append(\n",
|
||
" (len(init_layout) - 1, 0)\n",
|
||
") # adding in the periodic connectivity"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "b7b4970f-ac13-4644-a92f-1796de876767",
|
||
"metadata": {},
|
||
"source": [
|
||
"TwoLocal circuit allows the repetition of the `rotation_blocks` and the `entangler map` multiple times. For this case, the number of repetitions determine the number of periodic gates that need to be cut. Since the sampling overhead increases exponentially with the number of cuts (refer to <a href=\"https://learning.quantum.ibm.com/tutorial/wire-cutting-to-improve-performance\">this notebook</a> for more details), we shall fix the number of repetitions to 2 in this notebook."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"id": "0cd786ff-9798-4e20-937f-a258eea88077",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"num_reps = 2\n",
|
||
"entangler_map = []\n",
|
||
"\n",
|
||
"for even_edge in coupling_map[0 : len(coupling_map) : 2]:\n",
|
||
" entangler_map.append(even_edge)\n",
|
||
"\n",
|
||
"for odd_edge in coupling_map[1 : len(coupling_map) : 2]:\n",
|
||
" entangler_map.append(odd_edge)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "79428537-66cf-40ce-87cf-0f75f591cb4b",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<Image src=\"/docs/images/tutorials/periodic-boundary-conditions-with-circuit-cutting/extracted-outputs/79428537-66cf-40ce-87cf-0f75f591cb4b-0.avif\" alt=\"Output of the previous code cell\" />"
|
||
]
|
||
},
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"ansatz = TwoLocal(\n",
|
||
" num_qubits=num_qubits,\n",
|
||
" rotation_blocks=\"rx\",\n",
|
||
" entanglement_blocks=\"cx\",\n",
|
||
" entanglement=entangler_map,\n",
|
||
" reps=num_reps,\n",
|
||
").decompose()\n",
|
||
"ansatz.draw(\"mpl\", fold=-1)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "18b2f9b3-07f0-47d0-88db-7182e03c5f53",
|
||
"metadata": {},
|
||
"source": [
|
||
"In order to verify the quality of the outcome using circuit cutting, we need to know the ideal outcome. The current circuit of choice is beyond brute force classical simulation. Therefore, we fix the parameters to the circuit carefully to make it clifford.\n",
|
||
"\n",
|
||
"We shall assign the parameter value $0$ for the first two layers of `Rx` gates, and the value $\\pi$ for the last layer. This ensures that the ideal outcome of this circuit is $|1\\rangle^{\\otimes n}$, $n$ being the number of qubits. Therefore, the expectation values of $\\langle Z_i \\rangle$ and $\\langle Z_i Z_{i+1} \\rangle$, where $i$ is the index of the qubit, are $-1$ and $+1$ respectively."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "a0d70827-ae61-49a0-b14c-c10b27963262",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"params_last_layer = [np.pi] * ansatz.num_qubits\n",
|
||
"params = [0] * (ansatz.num_parameters - ansatz.num_qubits)\n",
|
||
"params.extend(params_last_layer)\n",
|
||
"\n",
|
||
"ansatz.assign_parameters(params, inplace=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "8f4289dd-30fd-4295-9d33-488f2fc03a3a",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Select observables"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "6a359888-c685-4b28-b2d4-c29b89448e3b",
|
||
"metadata": {},
|
||
"source": [
|
||
"To quantify the benefits of gate cutting we measure the expectation values of the observables $\\frac{1}{n}\\sum_{i=1}^n \\langle Z_i \\rangle$ and $\\frac{1}{n-1}\\sum_{i=1}^{n-1} \\langle Z_i Z_{i+1} \\rangle$. As discussed before, the ideal expectation values are $-1$ and $+1$ respectively."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "87a6a367-2d4e-410c-8e5f-3ef069f968d8",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"observables = []\n",
|
||
"\n",
|
||
"for i in range(num_qubits):\n",
|
||
" obs = \"I\" * (i) + \"Z\" + \"I\" * (num_qubits - i - 1)\n",
|
||
" observables.append(obs)\n",
|
||
"\n",
|
||
"for i in range(num_qubits):\n",
|
||
" if i == num_qubits - 1:\n",
|
||
" obs = \"Z\" + \"I\" * (num_qubits - 2) + \"Z\"\n",
|
||
" else:\n",
|
||
" obs = \"I\" * i + \"ZZ\" + \"I\" * (num_qubits - i - 2)\n",
|
||
" observables.append(obs)\n",
|
||
"\n",
|
||
"observables = SparsePauliOp(observables)\n",
|
||
"paulis = observables.paulis\n",
|
||
"coeffs = observables.coeffs"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "906440c1-4e5b-4f18-9bc7-d450dd7d2b24",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 2: Optimize problem for quantum hardware execution\n",
|
||
"\n",
|
||
"<ul>\n",
|
||
" <li>Input: Abstract circuit and observables</li>\n",
|
||
" <li>Output: Target circuit and observables produced by cutting long range gates</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2a7387e5-ead4-4dd8-a01f-974138cda3d9",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Transpile the circuit\n",
|
||
"\n",
|
||
"Note that the circuit can be transpiled at this stage, or after cutting. If we transpile after cutting, that will require us to transpile each of the subexperiments generated due to the sampling overhead. Therefore, it is more prudent to transpile at this stage to reduce the overhead of transpilation.\n",
|
||
"\n",
|
||
"However, if transpilation is done at this stage with native hardware connectivity, the transpiler will append multiple SWAP gates to place the periodic 2-qubit operation – obfuscating the benefits of circuit cutting. To avoid this problem we can leverage that we know the exact gates that needs to be cut. Specifically, we can create a virtual coupling map by adding virtual connections between far away qubits to accommodate these periodic 2-qubit gates. This will ensure that the circuit can be transpiled at this stage without incorporating the extra SWAP gates."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"id": "c12bb07e-54a0-4597-8b4d-4d0fbf6a4c99",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"coupling_map = backend.configuration().coupling_map\n",
|
||
"\n",
|
||
"# create a virtual coupling map with long range connectivity\n",
|
||
"virtual_coupling_map = coupling_map.copy()\n",
|
||
"virtual_coupling_map.append([init_layout[-1], init_layout[0]])\n",
|
||
"virtual_coupling_map.append([init_layout[0], init_layout[-1]])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "ad38aa32-4613-46c5-bf62-da332a1b9dfb",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<Image src=\"/docs/images/tutorials/periodic-boundary-conditions-with-circuit-cutting/extracted-outputs/ad38aa32-4613-46c5-bf62-da332a1b9dfb-0.avif\" alt=\"Output of the previous code cell\" />"
|
||
]
|
||
},
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"pm_virtual = generate_preset_pass_manager(\n",
|
||
" optimization_level=1,\n",
|
||
" coupling_map=virtual_coupling_map,\n",
|
||
" initial_layout=init_layout,\n",
|
||
" basis_gates=backend.configuration().basis_gates,\n",
|
||
")\n",
|
||
"\n",
|
||
"virtual_mapped_circuit = pm_virtual.run(ansatz)\n",
|
||
"virtual_mapped_circuit.draw(\"mpl\", fold=-1, idle_wires=False)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "ecb4ae6e-f9a5-4453-a8a3-1a5aaaf954da",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Cut the long range periodic connectivities"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c155eda9-ba08-4069-9962-ece21926f1db",
|
||
"metadata": {},
|
||
"source": [
|
||
"Now we cut the gates in the transpiled circuit. Note that the 2-qubit gates that need to be cut are the ones connecting the last and the first qubits of the layout."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "718ea31e-c8d8-4cf9-975b-fc0e77fb27c0",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Find the indices of the distant gates\n",
|
||
"cut_indices = [\n",
|
||
" i\n",
|
||
" for i, instruction in enumerate(virtual_mapped_circuit.data)\n",
|
||
" if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}\n",
|
||
" == {init_layout[-1], init_layout[0]}\n",
|
||
"]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "c6091cb0-227d-454a-a2d8-7ba685b66121",
|
||
"metadata": {},
|
||
"source": [
|
||
"We shall apply the layout of the transpiled circuit to the observable."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"id": "af57d942-5997-4920-91c6-295fbfef478d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "6a9a20e4-b722-456d-885b-d68ead3d341e",
|
||
"metadata": {},
|
||
"source": [
|
||
"Finally the subexperiments are generated by sampling over different measurement and preparation bases."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "981f111c-69e6-44c2-a297-c7df302cbc0e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)\n",
|
||
"subexperiments, coefficients = generate_cutting_experiments(\n",
|
||
" circuits=qpd_circuit,\n",
|
||
" observables=trans_observables.paulis,\n",
|
||
" num_samples=np.inf,\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "60582298-52ae-4f10-b8c2-bc754cb6d24c",
|
||
"metadata": {},
|
||
"source": [
|
||
"Note that cutting the long range interactions leads to the execution of multiple samples of the circuit that differ in the measurement and preparation bases. More information about this can be found in <a href='https://arxiv.org/abs/1909.07534'>Constructing a virtual two-qubit gate by sampling single-qubit operations</a> and <a href='https://arxiv.org/abs/2312.11638'>Cutting circuits with multiple two-qubit unitaries</a>.\n",
|
||
"\n",
|
||
"The number of periodic gates to be cut is equal to the number of repetitions of the `TwoLocal` layer, defined as `num_reps` above. The sampling overhead of gate cutting is 6. Therefore, the total number of subexperiments will be $6^{num\\_reps}$."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"id": "be4a43b4-c035-4814-a486-45eb9fe23d86",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Number of subexperiments is 36 = 6**2\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"print(f\"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "41a11eed-a8c0-4cce-b59d-ca49c54b52b1",
|
||
"metadata": {},
|
||
"source": [
|
||
"#### Transpile the subexperiments"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "5772d662-c030-4de6-9be4-e90cd599b173",
|
||
"metadata": {},
|
||
"source": [
|
||
"At this point, the subexperiments contains circuits with some 1-qubit gates that are not in the basis gate set. This is because the cut qubits are measured in different basis, and the rotation gates used for this does not necessarily belong to the basis gate set. For example, measurement in X basis implies applying a Hadamard gate before the usual measurement in Z basis. But Hadamard is not a part of the basis gate set.\n",
|
||
"\n",
|
||
"Instead of applying the entire transpilation process on each of the circuits in the subexperiments, we can use specific transpilation passes. Refer to <a href=\"/docs/api/qiskit/transpiler_passes\">this documentation</a> for a detailed description of all the available transpilation passes.\n",
|
||
"\n",
|
||
"We shall apply ```BasisTranslator``` and then ```Optimize1qGatesDecomposition``` passes to ensure that all the gates in these circuits belong to the basis gate set. Using these two passes is faster than the entire transpilation process, since other steps such as routing, initial layout selection etc. are not performed again."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "9c6d074f-5e48-4adb-9f4f-c5b03c9f7e36",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"pass_ = PassManager(\n",
|
||
" [Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]\n",
|
||
")\n",
|
||
"\n",
|
||
"subexperiments = pass_.run(\n",
|
||
" [\n",
|
||
" dag_to_circuit(\n",
|
||
" BasisTranslator(sel, target_basis=backend.basis_gates).run(\n",
|
||
" circuit_to_dag(circ)\n",
|
||
" )\n",
|
||
" )\n",
|
||
" for circ in subexperiments\n",
|
||
" ]\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "4c5c0c60-3caa-4bd6-80aa-f7dc412680bb",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 3: Execute using Qiskit primitives\n",
|
||
"\n",
|
||
"<ul>\n",
|
||
" <li>Input: Target circuits</li>\n",
|
||
" <li>Output: Quasi-probability distributions</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1fb3b96c-e55c-40e4-a5ca-4c327a192d73",
|
||
"metadata": {},
|
||
"source": [
|
||
"We use a `SamplerV2` primitive for execution of the cut circuits. We disable `dynamical decoupling` and `twirling` so that any improvement we obtain in the result will solely be due to effective application of gate cutting for this type of circuit."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "69a7cc63-173b-467c-87c5-f0924b943f34",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"options = SamplerOptions()\n",
|
||
"options.default_shots = 10000\n",
|
||
"options.dynamical_decoupling.enable = False\n",
|
||
"options.twirling.enable_gates = False\n",
|
||
"options.twirling.enable_measure = False"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e9311a78-cdc7-4ce2-9716-a0cb5fc03589",
|
||
"metadata": {},
|
||
"source": [
|
||
"Now we shall submit the jobs using batch mode."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "6c1ba1d4-b4ed-4781-99f5-41e3f93672d7",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Job ID cwxf7wq60bqg008pvt8g\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"with Batch(backend=backend) as batch:\n",
|
||
" sampler = SamplerV2(options=options)\n",
|
||
" cut_job = sampler.run(subexperiments)\n",
|
||
"\n",
|
||
"print(f\"Job ID {cut_job.job_id()}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"id": "54ca98b9-7f31-45d2-910a-97ad29b37a0d",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"result = cut_job.result()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "dd7cbb19-22ab-4e5a-bd18-3c7b06f67478",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Step 4: Post-process and return result in desired classical format\n",
|
||
"<ul>\n",
|
||
" <li>Input: Quasi-probability distributions</li>\n",
|
||
" <li>Output: Reconstructed expectation values</li>\n",
|
||
"</ul>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "3ee4af4c-4585-4a8d-87a6-921c2cdb1bd2",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"reconstructed_expvals = reconstruct_expectation_values(\n",
|
||
" result,\n",
|
||
" coefficients,\n",
|
||
" paulis,\n",
|
||
")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f2d78c56-001c-4fd5-97eb-6d45b0f84bda",
|
||
"metadata": {},
|
||
"source": [
|
||
"We now calculate the average of weight-1 and weight-2 Z-type observables."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 20,
|
||
"id": "00714269-8c72-47eb-8651-3e2f5f65d505",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Average of weight-1 expectation values is -0.741733944954063\n",
|
||
"Average of weight-2 expectation values is 0.6968862385320495\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])\n",
|
||
"cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])\n",
|
||
"\n",
|
||
"print(f\"Average of weight-1 expectation values is {cut_weight_1}\")\n",
|
||
"print(f\"Average of weight-2 expectation values is {cut_weight_2}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1d15b180-107a-4078-afad-d306cf3098f5",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Cross Verify: Obtain uncut expectation value"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "b6387268-cbd6-4766-a43d-154e461dfbc4",
|
||
"metadata": {},
|
||
"source": [
|
||
"It is useful to cross-verify the advantage of the circuit cutting technique against uncut. Here we shall compute the expectation values without cutting the circuit. Note that such an uncut circuit will suffer from a large number of SWAP gates required to implement the 2-qubit operation between the first and the last qubits. We shall use the `sampled_expectation_value` function to obtain the expectation values of the uncut circuit after obtaining the probability distribution via `SamplerV2`. This allows a homogenous usage of primitive over all the instances. However, note that we could have used `EstimatorV2` as well to directly compute the expectation values."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 23,
|
||
"id": "0fcd5c53-703d-4e67-93d5-3ee4a7870753",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"if ansatz.num_clbits == 0:\n",
|
||
" ansatz.measure_all()\n",
|
||
"\n",
|
||
"pm_uncut = generate_preset_pass_manager(\n",
|
||
" optimization_level=1, backend=backend, initial_layout=init_layout\n",
|
||
")\n",
|
||
"\n",
|
||
"transpiled_circuit = pm_uncut.run(ansatz)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 24,
|
||
"id": "00e5b011-bec5-4917-a0d3-91943cad5927",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"sampler = SamplerV2(mode=backend, options=options)\n",
|
||
"uncut_job = sampler.run([transpiled_circuit])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 25,
|
||
"id": "ec68d688-7de4-4d95-8e52-23a1eea7d94e",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"uncut_job_id = uncut_job.job_id()\n",
|
||
"print(f\"The job id for the uncut clifford circuit is {uncut_job_id}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 26,
|
||
"id": "1e04489f-3dc9-4253-aa9a-719952f260e3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"uncut_result = uncut_job.result()[0]\n",
|
||
"uncut_counts = uncut_result.data.meas.get_counts()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "fb150558-6571-4475-aa81-08860c2782d6",
|
||
"metadata": {},
|
||
"source": [
|
||
"Now we shall calculate the average expectation values of all the weight-1 and weight-2 Z-type observables without cutting."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "20991ea2-49a8-4258-9dd1-c064655674f1",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Average of weight-1 expectation values is -0.32494128440366965\n",
|
||
"Average of weight-2 expectation values is 0.32340917431192656\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"uncut_expvals = [\n",
|
||
" sampled_expectation_value(uncut_counts, obs) for obs in paulis\n",
|
||
"]\n",
|
||
"\n",
|
||
"uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])\n",
|
||
"uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])\n",
|
||
"\n",
|
||
"print(f\"Average of weight-1 expectation values is {uncut_weight_1}\")\n",
|
||
"print(f\"Average of weight-2 expectation values is {uncut_weight_2}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "ed843ab6-9614-45a8-864a-932d897c0d22",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Visualize"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "94eccfea-feb6-4a1c-a8fd-b7491ddb3b7d",
|
||
"metadata": {},
|
||
"source": [
|
||
"Let us now visualize the improvement obtained for weight-1 and weight-2 observables when using gate cutting for periodic chain circuit"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "2ba8913f-ba35-409c-bc4c-5f28e3698f20",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"<Image src=\"/docs/images/tutorials/periodic-boundary-conditions-with-circuit-cutting/extracted-outputs/2ba8913f-ba35-409c-bc4c-5f28e3698f20-0.avif\" alt=\"Output of the previous code cell\" />"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
}
|
||
],
|
||
"source": [
|
||
"mpl.rcParams.update(mpl.rcParamsDefault)\n",
|
||
"\n",
|
||
"fig = plt.subplots(figsize=(12, 8), dpi=200)\n",
|
||
"width = 0.25\n",
|
||
"labels = [\"Weight-1\", \"Weight-2\"]\n",
|
||
"x = np.arange(len(labels))\n",
|
||
"\n",
|
||
"ideal = [-1, 1]\n",
|
||
"cut = [cut_weight_1, cut_weight_2]\n",
|
||
"uncut = [uncut_weight_1, uncut_weight_2]\n",
|
||
"\n",
|
||
"br1 = np.arange(len(ideal))\n",
|
||
"br2 = [x + width for x in br1]\n",
|
||
"br3 = [x + width for x in br2]\n",
|
||
"\n",
|
||
"plt.bar(\n",
|
||
" br1, ideal, width=width, edgecolor=\"k\", label=\"Ideal\", color=\"#4589ff\"\n",
|
||
")\n",
|
||
"plt.bar(br2, cut, width=width, edgecolor=\"k\", label=\"Cut\", color=\"#a56eff\")\n",
|
||
"plt.bar(\n",
|
||
" br3, uncut, width=width, edgecolor=\"k\", label=\"Uncut\", color=\"#009d9a\"\n",
|
||
")\n",
|
||
"\n",
|
||
"plt.axhline(y=0, color=\"k\", linestyle=\"-\")\n",
|
||
"\n",
|
||
"plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)\n",
|
||
"plt.yticks(fontsize=14)\n",
|
||
"\n",
|
||
"plt.legend(fontsize=14)\n",
|
||
"plt.show()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "55de226b-1c87-48c8-b5e3-ba28e0640110",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Summary\n",
|
||
"\n",
|
||
"In summary, we calculated the average expectation values of weight-1 and weight-2 Z-types observables for a periodic 1D chain of 109 qubits. In order to do so, we\n",
|
||
"\n",
|
||
"- created a virtual coupling map by adding a long range connectivity between the first and the last qubits of the 1D chain, and transpiled the circuit.\n",
|
||
" - transpilation at this stage allowed us to avoid the overhead of transpiling each subexperiment separately after cutting,\n",
|
||
" - using virtual coupling map allowed us to avoid extra SWAP gates for the 2-qubit operation between the first and the last qubits.\n",
|
||
"- removed the long range connectivity from the transpiled circuit via gate cutting.\n",
|
||
"- converted the cut circuits into basis gate set by applying appropriate transpilation passes.\n",
|
||
"- executed the cut circuits on IBM Quantum device using a `SamplerV2` primitive.\n",
|
||
"- obtained the expectation value by reconstructing the outcomes of the cut circuits."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a556c909-8cac-4500-ab52-9146b47194bf",
|
||
"metadata": {},
|
||
"source": [
|
||
"### Inference\n",
|
||
"\n",
|
||
"We notice from the results that the average of the weight-1 $\\langle Z \\rangle$ and weight-2 $\\langle ZZ \\rangle$ type observables are significantly improved by cutting the periodic gates. Note that this study does not include any error suppression or mitigation techniques. The improvement observed is solely due to the proper usage of gate cutting for this problem. The results could have been further improved by using the mitigation and suppression techniques.\n",
|
||
"\n",
|
||
"This study shows an example of effectively using gate cutting to improve the performance of computation."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "6b013b53",
|
||
"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_3fQQYAIjTxvIChg)"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"description": "Use circuit cutting to deal with a utility-scale periodic chain problem where the first and last qubits are not neighbors.",
|
||
"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": "Circuit cutting for periodic boundary conditions\n"
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|