474 lines
14 KiB
Plaintext
474 lines
14 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "7ed4867f",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Circuit cutting for depth reduction\n",
|
|
"*Usage estimate: 8 minutes on IBM Brisbane (NOTE: This is an estimate only. Your runtime may vary.)*"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "387c5e80",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Background\n",
|
|
"\n",
|
|
"In this tutorial, we will show how to build a `Qiskit Pattern` for cutting gates in a quantum circuit to reduce circuit depth. For more in-depth discussion on circuit cutting, visit the [circuit cutting Qiskit addon docs](https://qiskit.github.io/qiskit-addon-cutting/)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a62460d9",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Requirements\n",
|
|
"\n",
|
|
"Before starting this tutorial, be sure you have the following installed:\n",
|
|
"- Qiskit SDK v1.0 or later, with visualization support ( `pip install 'qiskit[visualization]'` )\n",
|
|
"- Qiskit Runtime 0.22 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": "a89fe306",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "c795c670",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"from qiskit.circuit.library import EfficientSU2\n",
|
|
"from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp\n",
|
|
"from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
|
|
"\n",
|
|
"from qiskit_addon_cutting import (\n",
|
|
" cut_gates,\n",
|
|
" generate_cutting_experiments,\n",
|
|
" reconstruct_expectation_values,\n",
|
|
")\n",
|
|
"\n",
|
|
"from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "07f7f75d",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 1: Map classical inputs to a quantum problem\n",
|
|
"\n",
|
|
"We will implement our Qiskit Pattern using the four steps outlined in the [docs](/docs/guides/intro-to-patterns). In this case, we will simulate expectation values on a circuit of a certain depth by cutting gates resulting in swap gates and executing subexperiments on shallower circuits. Gate cutting is relevant for Steps 2 (optimize circuit for quantum execution by decomposing distant gates) and 4 (post-processing to reconstruct expectation values on original circuit).\n",
|
|
"In the first step, we'll generate a circuit from the Qiskit circuit library and define some observables.\n",
|
|
"\n",
|
|
"* Input: Classical parameters to define a circuit\n",
|
|
"* Output: Abstract circuit and observables"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "54ed0f13",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/depth-reduction-with-circuit-cutting/extracted-outputs/54ed0f13-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"circuit = EfficientSU2(num_qubits=4, entanglement=\"circular\").decompose()\n",
|
|
"circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)\n",
|
|
"observables = PauliList([\"ZZII\", \"IZZI\", \"IIZZ\", \"XIXI\", \"ZIZZ\", \"IXIX\"])\n",
|
|
"circuit.draw(\"mpl\", scale=0.8, style=\"iqp\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "080a2a8b",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 2: Optimize problem for quantum hardware execution\n",
|
|
"\n",
|
|
"* Input: Abstract circuit and observables\n",
|
|
"* Output: Target circuit and observables produced by cutting distant gates to reduce transpiled circuit depth\n",
|
|
"\n",
|
|
"We choose an initial layout that requires two swaps to execute the gates between qubits 3 and 0 and another two swaps to return the qubits to their initial positions. We choose `optimization_level=3`, which is the highest level of optimization available with a preset pass manager."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "b394da7a",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"service = QiskitRuntimeService()\n",
|
|
"backend = service.least_busy(\n",
|
|
" operational=True, min_num_qubits=circuit.num_qubits, simulator=False\n",
|
|
")\n",
|
|
"\n",
|
|
"pm = generate_preset_pass_manager(\n",
|
|
" optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend\n",
|
|
")\n",
|
|
"transpiled_qc = pm.run(circuit)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "68c00476",
|
|
"metadata": {},
|
|
"source": [
|
|
""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "4fe4af43",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Transpiled circuit depth: 103\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/depth-reduction-with-circuit-cutting/extracted-outputs/4fe4af43-1.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"print(f\"Transpiled circuit depth: {transpiled_qc.depth()}\")\n",
|
|
"transpiled_qc.draw(\"mpl\", scale=0.4, idle_wires=False, style=\"iqp\", fold=-1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "babfbd0f",
|
|
"metadata": {},
|
|
"source": [
|
|
"*Find and cut the distant gates:* We will replace the distant gates (gates connecting non-local qubits, 0 and 3) with `TwoQubitQPDGate`s by specifying their indices. `cut_gates` will replace the gates in the specified indices with `TwoQubitQPDGate`s and also return a list of `QPDBasis` instances -- one for each gate decomposition. The `QPDBasis` object contains information about how to decompose the cut gates into single-qubit operations."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "23e3d25e",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/depth-reduction-with-circuit-cutting/extracted-outputs/23e3d25e-0.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Find the indices of the distant gates\n",
|
|
"cut_indices = [\n",
|
|
" i\n",
|
|
" for i, instruction in enumerate(circuit.data)\n",
|
|
" if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}\n",
|
|
"]\n",
|
|
"\n",
|
|
"# Decompose distant CNOTs into TwoQubitQPDGate instances\n",
|
|
"qpd_circuit, bases = cut_gates(circuit, cut_indices)\n",
|
|
"\n",
|
|
"qpd_circuit.draw(\"mpl\", scale=0.8)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "069eb942-e947-4eff-a250-9a99c5ec47f0",
|
|
"metadata": {},
|
|
"source": [
|
|
"*Generate the subexperiments to run on the backend*: `generate_cutting_experiments` accepts a circuit containing `TwoQubitQPDGate` instances and observables as a `PauliList`.\n",
|
|
"\n",
|
|
"To simulate the expectation value of the full-sized circuit, many subexperiments are generated from the decomposed gates' joint quasiprobability distribution and then executed on one or more backends. The number of samples taken from the distribution is controlled by `num_samples`, and one combined coefficient is given for each unique sample. For more information on how the coefficients are calculated, refer to the [explanatory material](https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "83b1efed-bafa-48c4-bbf0-cf7eb9027ac5",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Generate the subexperiments and sampling coefficients\n",
|
|
"subexperiments, coefficients = generate_cutting_experiments(\n",
|
|
" circuits=qpd_circuit, observables=observables, num_samples=np.inf\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6929264d",
|
|
"metadata": {},
|
|
"source": [
|
|
"*For comparison, we see that the QPD subexperiments will be shallower after cutting distant gates*: Here is an example of an arbitrarily chosen subexperiment generated from the QPD circuit. Its depth has been reduced by more than half. Many of these probabilistic subexperiments must be generated and evaluated in order to reconstruct an expectation value of the deeper circuit."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "70e2f1b6",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Original circuit depth after transpile: 103\n",
|
|
"QPD subexperiment depth after transpile: 46\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<Image src=\"/docs/images/tutorials/depth-reduction-with-circuit-cutting/extracted-outputs/70e2f1b6-1.avif\" alt=\"Output of the previous code cell\" />"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"# Transpile the decomposed circuit to the same layout\n",
|
|
"transpiled_qpd_circuit = pm.run(subexperiments[100])\n",
|
|
"\n",
|
|
"print(f\"Original circuit depth after transpile: {transpiled_qc.depth()}\")\n",
|
|
"print(\n",
|
|
" f\"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}\"\n",
|
|
")\n",
|
|
"transpiled_qpd_circuit.draw(\n",
|
|
" \"mpl\", scale=0.6, style=\"iqp\", idle_wires=False, fold=-1\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "87368cb9",
|
|
"metadata": {},
|
|
"source": [
|
|
"*On the other hand, cutting results in the need of extra sampling*. Here we cut three CNOT gates, resulting in a sampling overhead of $9^3$. For more on the sampling overhead incurred by circuit cutting, refer to the [Circuit Knitting Toolbox documentation](https://qiskit-extensions.github.io/circuit-knitting-toolbox/circuit_cutting/explanation/index.html)."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "2ab65bd4",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Sampling overhead: 729.0\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(f\"Sampling overhead: {np.prod([basis.overhead for basis in bases])}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "fd9a126c",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 3: Execute using Qiskit primitives\n",
|
|
"\n",
|
|
"Execute the target circuits (\"subexperiments\") with the Sampler Primitive.\n",
|
|
"\n",
|
|
"* Input: Target circuits\n",
|
|
"* Output: Quasi-probability distributions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "a437de20-2042-4e62-87a7-804058cff5db",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Transpile the subexperiments to the backend's instruction set architecture (ISA)\n",
|
|
"isa_subexperiments = pm.run(subexperiments)\n",
|
|
"\n",
|
|
"# Set up the Qiskit Runtime Sampler primitive. For a fake backend, this will use a local simulator.\n",
|
|
"sampler = SamplerV2(backend)\n",
|
|
"\n",
|
|
"# Submit the subexperiments\n",
|
|
"job = sampler.run(isa_subexperiments)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"id": "ca53d638",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Retrieve the results\n",
|
|
"results = job.result()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"id": "32d35001",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"czypg1r6rr3g008mgp6g\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"print(job.job_id())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f04d9134-651b-446e-93f4-aa0281786200",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Step 4: Post-process and return result in desired classical format\n",
|
|
"\n",
|
|
"Use the subexperiment results, subobservables, and sampling coefficients to reconstruct the expectation value of the original circuit.\n",
|
|
"\n",
|
|
"Input: Quasi-probability distributions\n",
|
|
"Output: Reconstructed expectation values"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"id": "ace12f7f",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Final reconstructed expectation value\n",
|
|
"1.0751342773437473\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"reconstructed_expvals = reconstruct_expectation_values(\n",
|
|
" results,\n",
|
|
" coefficients,\n",
|
|
" observables,\n",
|
|
")\n",
|
|
"# Reconstruct final expectation value\n",
|
|
"final_expval = np.dot(reconstructed_expvals, [1] * len(observables))\n",
|
|
"print(\"Final reconstructed expectation value\")\n",
|
|
"print(final_expval)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "e6237a6f",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Ideal expectation value\n",
|
|
"1.2283177520039992\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"ideal_expvals = [\n",
|
|
" Statevector(circuit).expectation_value(SparsePauliOp(observable))\n",
|
|
" for observable in observables\n",
|
|
"]\n",
|
|
"print(\"Ideal expectation value\")\n",
|
|
"print(np.dot(ideal_expvals, [1] * len(observables)).real)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "e366d14e",
|
|
"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_2ftYFf9t72yFNIO)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "2f4233ea",
|
|
"metadata": {},
|
|
"source": [
|
|
"© IBM Corp. 2024"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"description": "Build a Qiskit Pattern for cutting gates in a quantum circuit to reduce circuit depth.",
|
|
"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 depth reduction"
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|