---
title: Wire cutting for expectation values estimation
description: Use wire cutting to partition circuits into many smaller subcircuits.
source: https://eu-de.quantum.cloud.ibm.com/docs/en/tutorials/wire-cutting
---

# Wire cutting for expectation values estimation

*Usage estimate: 22 seconds on a Heron processor (NOTE: This is an estimate only. Your runtime might vary.)*

## Learning outcomes

After going through this tutorial, users should understand:

- How to use [`qiskit-addon-cutting`](https://github.com/Qiskit/qiskit-addon-cutting) to partition a large circuit into smaller subcircuits, thereby reducing the effect of noise

## Prerequisites

We suggest that users are familiar with the following topic before going through this tutorial:

- Using the [Sampler](/docs/api/qiskit-ibm-runtime/sampler-v2) primitive, which is used in this workflow

## Background

Circuit-knitting is an umbrella term that encapsulates various methods of partitioning a circuit in multiple smaller subcircuits involving fewer gates or qubits. Each of the subcircuits can be executed independently, and the final result is obtained by some classical post-processing over the outcome of each subcircuit. This technique is accessible in the [Circuit cutting Qiskit addon](https://qiskit.github.io/qiskit-addon-cutting/index.html); see the [documentation](https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html) along with other [introductory material](https://qiskit.github.io/qiskit-addon-cutting/tutorials/index.html) for a detailed explanation of the technique.

This tutorial focuses on a method called **wire cutting**, where the circuit is partitioned along the wire [\[1\], \[2\]](#references). Note that partitioning is simple in classical circuits since the outcome at the point of partition can be determined deterministically, and is either 0 or 1. However, the state of the qubit at the point of the cut is, in general, a mixed state. Therefore, each subcircuit needs to be measured multiple times in different bases (usually a tomographically complete basis, such as the Pauli basis [\[3\], \[4\]](#references)) and correspondingly prepared in its eigenstate. The figure below (courtesy: [\[7\]](#references)) shows an example of wire cutting for a four-qubit GHZ state into three subcircuits. Here $M_j$ denotes a set of bases (usually Pauli X, Y and Z), and $P_i$ denotes a set of eigenstates (usually $|0\rangle$, $|1\rangle$, $|+\rangle$ and $|+i\rangle$).

![wc-1.png](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/0ce8857b-7f5f-400e-8536-6a496c724d50.avif)
![wc-2.png](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/cbce4455-4794-4c81-8630-3e3993e1b29f.avif)

Since each subcircuit has fewer qubits and gates, they are expected to be less amenable to noise. This tutorial shows an example where this method can be used to effectively suppress the noise in the system.

## Requirements

Before starting this tutorial, be sure you have the following installed:

- Qiskit SDK v2.0 or later, with [visualization](/docs/api/qiskit/visualization) support
- Qiskit Runtime v0.22 or later ( `pip install qiskit-ibm-runtime` )
- Circuit cutting Qiskit addon v0.10.0 or later (`pip install qiskit-addon-cutting`)
- Qiskit addon utils 0.3 or later (`pip install qiskit-addon-utils`)
- Qiskit Aer (`pip install qiskit-aer` )

## Setup

```python
import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit.result import sampled_expectation_value

from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
    cut_wires,
    expand_observables,
    partition_problem,
    generate_cutting_experiments,
    reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch
```

## Small-scale simulator example

This tutorial implements a [Qiskit pattern](/docs/guides/intro-to-patterns) to simulate a one-dimensional (1D) Many-Body Localization (MBL) circuit. The MBL circuit is a hardware-efficient circuit and is parameterized by two parameters $\theta$ and $\vec{\phi}$. When $\theta$ is set to $0$ and the initial state is prepared in $|0\rangle$ for all the qubits, the ideal expectation value of $\langle Z_i \rangle$ is $+1$ for every qubit site $i$ irrespective of the values of $\vec{\phi}$. More details on this circuit are available in this [article](https://www.nature.com/articles/s41467-025-57623-x).

Note that in a noiseless simulator, the expectation value obtained with and without circuit cutting will be same.

### Step 1: Map classical inputs to a quantum problem

#### Construct the 1D MBL circuit

First, we present a function for constructing the 1D MBL circuit.

```python
class MBLChainCircuit(QuantumCircuit):
    def __init__(
        self, num_qubits: int, depth: int, use_cut: bool = False
    ) -> None:
        super().__init__(
            num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
        )
        evolution = MBLChainEvolution(num_qubits, depth, use_cut)
        self.compose(evolution, inplace=True)


class MBLChainEvolution(QuantumCircuit):
    def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
        super().__init__(
            num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
        )

        theta = Parameter("θ")
        phis = ParameterVector("φ", num_qubits)

        for layer in range(depth):
            layer_parity = layer % 2
            # print("layer parity", layer_parity)
            for qubit in range(layer_parity, num_qubits - 1, 2):
                # print(qubit)
                self.cz(qubit, qubit + 1)
                self.u(theta, 0, np.pi, qubit)
                self.u(theta, 0, np.pi, qubit + 1)
                if (
                    use_cut
                    and layer_parity == 0
                    and (
                        qubit == num_qubits // 2 - 1
                        or qubit == num_qubits // 2
                    )
                ):
                    self.append(CutWire(), [num_qubits // 2])
                if use_cut and layer < depth - 1 and layer_parity == 1:
                    if qubit == num_qubits // 2:
                        self.append(CutWire(), [qubit])
            for qubit in range(num_qubits):
                self.p(phis[qubit], qubit)
```

```python
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/a3debf65-06df-4277-933e-14b6f6170756-0.avif)

We calculate the average expectation value $O = \frac{1}{n} \sum_i Z_i$ over all qubits for $\theta = 0$. Since the ideal expectation value of $\langle Z_i \rangle = 1$ $\forall$ $i$, the ideal expectation value of $O$ is also $1$. The parameters $\phi$ are selected randomly.

```python
np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
```

The circuit needs to be annotated by inserting **CutWire** in the desired locations to partition it. For this tutorial, we opt for equal partition. The MBL circuit is designed so that setting `use_cut=True` in the function inserts the annotation properly after $\frac{n}{2}$ qubits, $n$ being the number of qubits in the original circuit. We also assigned the randomly generated parameters to the circuit.

```python
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/5208e0a8-0.avif)

### Step 2: Optimize problem for quantum hardware execution

#### Cut the circuit into smaller subcircuits

Now we partition the circuit into two smaller subcircuits using [`qiskit-addon-cutting`](https://qiskit.github.io/qiskit-addon-cutting/). `qiskit-addon-cutting` appends a virtual `Move` gate to split the wire cut location by appropriately adjusting the number of qubits. Now we create the circuit with this virtual gate. Since there is one wire cut, the number of associated qubits will be increased by 1.

```python
mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/1834cb22-0.avif)

#### Construct and expand the observable

The observable, as defined before, will be the average of $Z$ on each qubit. However, upon inserting the virtual `Move` gate, the effective number of qubits in the circuit increases. The observable must also be expanded accordingly to account for this change in the number of qubits. Note the observable always acts trivially (as in $I$) on the extra qubit added for the virtual `Move` gate.

```python
observable = PauliList(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
```

Output:

```
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
           'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
           'IIIIIIIIZI', 'IIIIIIIIIZ'])
```

```python
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
```

Output:

```
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
           'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
           'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
```

Now the circuit can be partitioned along the `Move` gate and we obtain the subcircuits, as well as the subobservable, which is the portion of the original observable associated with each subcircuit.

```python
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
```

Here we visualize the two subcircuits:

```python
subcircuits[0].draw("mpl", fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/1b0e779f-0.avif)

```python
subcircuits[1].draw("mpl", fold=-1)
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/3c802f28-0.avif)

Expanding the observable using the `Move` operation requires a `PauliList` data structure. To reconstruct the expectation value of the original circuit, we require the observable in the `SparsePauliOp` format.

```python
M_z = SparsePauliOp(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
    coeffs=[1 / num_qubits] * num_qubits,
)
```

As discussed before, for each cut the upstream circuit must be measured in a Pauli basis, and the downstream circuit must be prepared in the eigenstate of the basis. The function `generate_cutting_experiments` creates all of these necessary circuits and the coefficients associated with each circuit required for reconstruction. Find more detail in [this paper](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.125.150504).

```python
subexperiments, coefficients = generate_cutting_experiments(
    circuits=subcircuits,
    observables=subobservables,
    num_samples=np.inf,
)
```

#### Transpile the circuits onto the backend

For the first example involving only simulation, we transpile the circuit into the basis gate set of the backend:

```python
service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=133
)

print(backend)
```

Output:

```
<IBMBackend('ibm_fez')>
```

### Step 3: Execute using Qiskit primitives

Now, execute each subexperiment:

```python
pm_basis = generate_preset_pass_manager(
    optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
    label: pm_basis.run(partition_subexpts)
    for label, partition_subexpts in subexperiments.items()
}
```

```python
sampler = SamplerV2(mode=AerSimulator())
jobs = {
    label: sampler.run(subsystem_subexpts, shots=2**12)
    for label, subsystem_subexpts in basis_subexperiments.items()
}
```

### Step 4: Post-process and return result in desired classical format

Now we retrieve the result of each subexperiment run and reconstruct the expectation value of the uncut circuit:

```python
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
```

```python
reconstructed_expval_terms = reconstruct_expectation_values(
    results,
    coefficients,
    subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
```

Output:

```
np.float64(0.9953821063041687)
```

```python
methods = [
    "Uncut",
    "Wire cut",
]
values = [
    1,
    reconstructed_expval,
]  # since the ideal expectation value in noiseless simulation is +1

ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)
```

Output:

```
Text(0, 0.5, '$M_Z$')
```

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/fb5f955a-1.avif)

## Large-scale hardware example

Now we demonstrate wire cutting for a 60-qubit MBL circuit. The uncut, as well as the cut circuits, will be executed on IBM Quantum® hardware:

```python
num_qubits = 60
depth = 2

# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)

# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)

# Define observable and expand to account for the wire cut
observable = PauliList(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)

# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
    ["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
    coeffs=[1 / num_qubits] * num_qubits,
)

# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

# Obtain subexperiments and coefficients
subexperiments, coefficients = generate_cutting_experiments(
    circuits=subcircuits,
    observables=subobservables,
    num_samples=np.inf,
)

# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
    label: pm.run(partition_subexpts)
    for label, partition_subexpts in subexperiments.items()
}

# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
    sampler = SamplerV2(mode=batch)
    sampler.options.environment.job_tags = ["TUT_WC"]
    jobs = {
        label: sampler.run(subsystem_subexpts, shots=2**12)
        for label, subsystem_subexpts in isa_subexperiments.items()
    }
results = {label: job.result() for label, job in jobs.items()}

# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
    results,
    coefficients,
    subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real

# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]

if mbl.num_clbits == 0:
    mbl.measure_all()
isa_mbl = pm.run(mbl)

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])

uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)

# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]

plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()
```

Output:

![Output of the previous code cell](https://eu-de.quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/extracted-outputs/37834c72-0.avif)

```python
uncut_expval
```

Output:

```
0.9202473958333336
```

## Next steps

> **Recommendations**
>
> If you found this work interesting, you might be interested in the following material:
>
> - [Periodic boundary conditions with circuit cutting](/docs/tutorials/periodic-boundary-conditions-with-circuit-cutting)
> - [Circuit cutting for depth reduction](/docs/tutorials/depth-reduction-with-circuit-cutting)

## References

\[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.

\[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).

\[3]  Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.

\[4]  Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.

\[5]  Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.

\[6]  Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.

\[7]  Majumdar, R. (2024). Efficient Reduction of Resources and Noise in Discrete Quantum Computing Circuits (Doctoral dissertation, Indian Statistical Institute - Kolkata). [https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar\&cbl=2026366\&diss=y](https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar\&cbl=2026366\&diss=y)
