Tutorial 2: Circuit Cutting with Manual Wire Cutting¶
Circuit cutting is a technique to decompose a quantum circuit into smaller circuits, whose results can be knitted together to reconstruct the original circuit output.
The circuit knitting toolbox implements a wire cutting method presented in CutQC (Tang et al.). This method allows a circuit wire to be cut such that the generated subcircuits are amended by measurements in the Pauli bases and by state preparation of four Pauli eigenstates (see Fig. 4 of CutQC).
This wire cutting technique is comprised of the following basic steps:
Decompose: Cut a circuit into multiple subcircuits. Here, we’ll use a manual method to specify the cut(s). See tutorial 1 to automatically cut a circuit.
Evaluate: Execute those subcircuits on quantum backend(s).
Reconstruct: Knit the subcircuit results together to reconstruct the original circuit output (in this case, the full probability distribution).
Create a quantum circuit with Qiskit¶
In this tutorial, we’ll use the example circuit shown in CutQC.
[4]:
import numpy as np
from qiskit import QuantumCircuit
num_qubits = 5
circuit = QuantumCircuit(num_qubits)
for i in range(num_qubits):
circuit.h(i)
circuit.cx(0, 1)
for i in range(2, num_qubits):
circuit.t(i)
circuit.cx(0, 2)
circuit.rx(np.pi / 2, 4)
circuit.rx(np.pi / 2, 0)
circuit.rx(np.pi / 2, 1)
circuit.cx(2, 4)
circuit.t(0)
circuit.t(1)
circuit.cx(2, 3)
circuit.ry(np.pi / 2, 4)
for i in range(num_qubits):
circuit.h(i)
circuit.draw("mpl", fold=-1, scale=0.75)
[4]:

Set up the Qiskit Runtime Service¶
The Qiskit Runtime Service provides access to IBM Runtime Primitives and quantum backends. Alternatively, a local statevector simulator can be used with the Qiskit primitives.
[5]:
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Options,
)
# Use local versions of the primitives by default.
service = None
# Uncomment the following line to instead use Qiskit Runtime.
# service = QiskitRuntimeService()
The wire cutter tool uses a Sampler
primitive to evaluate the probabilities of each subcircuit. Here, we configure the options for the Runtime Sampler and specify the backend(s) to be used to evaluate the subcircuits.
If no service was set up, the backend_names
argument will be ignored, and Qiskit primitives will be used with statevector simulator.
[6]:
# Set the Sampler and runtime options
options = Options(execution={"shots": 4000})
# Run 2 parallel qasm simulator threads
backend_names = ["ibmq_qasm_simulator"] * 2
Decompose the circuit with wire cutting¶
In this example, we will use a manual method to specify the wire cuts. See tutorial 1 for how to automatically cut a circuit. The figure below shows the steps for producing the subcircuit_vertices
argument for the cut_circuit_wires
function.
method='manual'
: Manually specify the wire cutssubcircuit_vertices
: A list of lists containing the two-qubit gate indices appearing on either side of the cut(s)
[7]:
%%capture
from circuit_knitting_toolbox.circuit_cutting.wire_cutting import cut_circuit_wires
cuts = cut_circuit_wires(
circuit=circuit, method="manual", subcircuit_vertices=[[0, 1], [2, 3]]
)
The two subcircuits produced
[8]:
# visualize the first subcircuit
cuts["subcircuits"][0].draw("mpl", fold=-1, scale=0.6)
[8]:

[9]:
# visualize the second subcircuit
cuts["subcircuits"][1].draw("mpl", fold=-1, scale=0.6)
[9]:

Evaluate the subcircuits¶
Set up the Qiskit Runtime Service
The Qiskit Runtime Service provides access to Qiskit Runtime Primitives and quantum backends. See the Qiskit Runtime documentation for more information. Alternatively, if a Qiskit Runtime Service is not passed, then a local statevector simulator will be used with the Qiskit Primitives.
[10]:
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Options,
)
# Use local versions of the primitives by default.
service = None
# Uncomment the following line to instead use Qiskit Runtime Service.
# service = QiskitRuntimeService()
Configure the Qiskit Runtime Primitive
The wire cutter tool uses a Sampler
primitive to evaluate the probabilities of each subcircuit. Here, we configure the options for the Qiskit Runtime Sampler and specify the backend(s) to be used to evaluate the subcircuits. Backends could be simulator(s) and/or quantum device(s). In this tutorial, two local cores will be used to support each of the parallel backend threads we’ll specify below.
If no service was set up, the backend_names
argument will be ignored, and Qiskit Primitives will be used with statevector simulator.
[11]:
# Set the Sampler and runtime options
options = Options(execution={"shots": 4000})
# Run 2 parallel qasm simulator threads
backend_names = ["ibmq_qasm_simulator"] * 2
Evaluate the subcircuits on the backend(s)
[12]:
from circuit_knitting_toolbox.circuit_cutting.wire_cutting import evaluate_subcircuits
subcircuit_instance_probabilities = evaluate_subcircuits(cuts)
# Uncomment the following lines to instead use Qiskit Runtime Service as configured above.
# subcircuit_instance_probabilities = evaluate_subcircuits(cuts,
# service_args=service.active_account(),
# backend_names=backend_names,
# options=options,
# )
See Tutorial 1 for more info about the subcircuit results.
Reconstruct the full circuit output¶
Next, the results of the subcircuit experiments are classically postprocessed to reconstruct the original circuit’s full probability distribution.
[13]:
%%capture
from circuit_knitting_toolbox.circuit_cutting.wire_cutting import (
reconstruct_full_distribution,
)
reconstructed_probabilities = reconstruct_full_distribution(
circuit, subcircuit_instance_probabilities, cuts
)
Verify the results¶
If the original circuit is small enough, we can use a statevector simulator to check the results of cutting against the original circuit’s exact probability distribution (ground truth).
[14]:
from circuit_knitting_toolbox.circuit_cutting.wire_cutting import verify
metrics, exact_probabilities = verify(circuit, reconstructed_probabilities)
The verify step includes several metrics
For example, the chi square loss is computed. Since we’re using the Qiskit Sampler with statevector simulator, we expect the reconstructed distributed to exactly match the ground truth. More info about each metric can be found in the utils metrics file.
[15]:
metrics
[15]:
{'nearest': {'chi2': 0,
'Mean Squared Error': 1.554441697306333e-32,
'Mean Absolute Percentage Error': 4.748059826691265e-14,
'Cross Entropy': 2.599681088367844,
'HOP': 0.9004283905932716},
'naive': {'chi2': 0,
'Mean Squared Error': 6.5760386594463524e-34,
'Mean Absolute Percentage Error': 5.260720927719812e-14,
'Cross Entropy': 2.5996810883678423,
'HOP': 0.9004283905932736}}
Visualize both distributions
If we calculated the ground truth above, we can visualize a comparison to the reconstructed probabilities
[16]:
from qiskit.visualization import plot_histogram
from qiskit.result import ProbDistribution
# Create a dict for the reconstructed distribution
reconstructed_distribution = {
i: prob for i, prob in enumerate(reconstructed_probabilities)
}
# Represent states as bitstrings (instead of ints)
reconstructed_dict_bitstring = ProbDistribution(
data=reconstructed_distribution
).binary_probabilities(num_bits=num_qubits)
# Create the ground truth distribution dict
exact_distribution = {i: prob for i, prob in enumerate(exact_probabilities)}
# Represent states as bitstrings (instead of ints)
exact_dict_bitstring = ProbDistribution(data=exact_distribution).binary_probabilities(
num_bits=num_qubits
)
# plot a histogram of the distributions
plot_histogram(
[exact_dict_bitstring, reconstructed_dict_bitstring],
number_to_keep=8,
figsize=(16, 6),
sort="asc",
legend=["Exact", "Reconstructed"],
)
[16]:

[17]:
import qiskit.tools.jupyter
%qiskit_version_table
Version Information
Qiskit Software | Version |
---|---|
qiskit-terra | 0.22.0 |
qiskit-aer | 0.11.0 |
qiskit-ibmq-provider | 0.19.2 |
qiskit-nature | 0.4.5 |
System information | |
Python version | 3.10.6 |
Python compiler | Clang 13.1.6 (clang-1316.0.21.2.5) |
Python build | main, Aug 11 2022 13:49:25 |
OS | Darwin |
CPUs | 8 |
Memory (Gb) | 32.0 |
Wed Oct 26 11:28:28 2022 CDT |
This code is a Qiskit project. © Copyright IBM 2022.
This code is licensed under the Apache License, Version 2.0. You may obtain a copy of this license in the LICENSE.txt file in the root directory of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this copyright notice, and modified files need to carry a notice indicating that they have been altered from the originals.