Noise Scaling Methods#
In this tutorial we will compare two noise scaling methods available for use in ZeroNoise Extrapolation (ZNE): identity insertion and unitary folding. ZNE works by running multiple versions of the desired circuit, each intended to scale the noise up from the baselevel achieved by the hardware. Experimentally these experiments are often performed by pulse stretching, but as a quantum programmer, we typically do not have access to such lowlevel control. For this reason, we use “digital” methods that allow us to scale the noise using gatebased methods. To this end, we will study circuit folding and identity insertion as methods to increase the amount of noise present in our computation.
These techniques are summarized by the following equations, and can be performed in Mitiq with the associated functions.
Folding 
Identity Insertion 


Equation 
\(G \to GG^\dagger G\) 
\(G \to I G\) 
Mitiq Function 
Comparison#
To get started, we can demo what these two functions do to a small GHZ circuit.
Each function (fold_global
and insert_id_layers
) will take a circuit, and a specified scale factor as inputs.
The argument scale_factor
controls how much to increase the depth of the input circuit so that the achieved scale factor is exactly equal, or very close, to the specified scale factor.
from mitiq.benchmarks import generate_ghz_circuit
from mitiq.zne.scaling import insert_id_layers, fold_global
demo = generate_ghz_circuit(3)
scale_factor = 3
print("ORIGINAL")
print(demo)
print("\nFOLDING")
print(fold_global(demo, scale_factor))
print("\nSCALING")
print(insert_id_layers(demo, scale_factor))
ORIGINAL
0: ───H───@───────
│
1: ───────X───@───
│
2: ───────────X───
FOLDING
0: ───H───@───────────@───H───H───@───────
│ │ │
1: ───────X───@───@───X───────────X───@───
│ │ │
2: ───────────X───X───────────────────X───
SCALING
0: ───H───I───I───@───I───I───────I───I───
│
1: ───────I───I───X───I───I───@───I───I───
│
2: ───────I───I───────I───I───X───I───I───
Theoretically, these circuits should give the same result when measured, but due to noise, this is almost never the case. Both methods work by extending the duration of the circuit, but do so in different ways that might be beneficial for different scenarios. When using folding, noise is amplified by applying additional gates, and in particular inverse gates. Scaling amplifies noise by letting the qubits idle for longer between computation.
Warning
Unitary folding scales noise by applying an additional layer \(G^\dagger G\) to the circuit. For nonhermitian gates \(G\) and \(G^\dagger\) may not have the same noise model, and hence noise is potentially scaled in an nonlinear way.
Similarly, the noise that predominantly scaled in identity insertion is that of idle qubit noise/decoherence.
Let’s now look at how much the depth of these circuit increase with different scale factors.
print(
"{: >12} {: ^14} {: ^14} {: ^15}".format(
"scale factor", "original depth", "folded depth", "id insertion depth"
)
)
for scale_factor in range(1, 10):
folded_depth = len(fold_global(demo, scale_factor))
id_insert_depth = len(insert_id_layers(demo, scale_factor))
print(
"{: >12} {: ^14} {: ^14} {: ^15}".format(
scale_factor, len(demo), folded_depth, id_insert_depth
)
)
scale factor original depth folded depth id insertion depth
1 3 3 3
2 3 7 6
3 3 9 9
4 3 13 12
5 3 15 15
6 3 19 18
7 3 21 21
8 3 25 24
9 3 27 27
As expected, we have \(\mathtt{depth} * \mathtt{scale\_factor} = \mathtt{scaled\_depth}\) when the scale factor is an integer. The scale factor can also take on noninteger values, where this equation will hold approximately.
Using noise scaling methods#
Here, we demo how you can use these noisescaling technique in ZNE. First, we define an executor which is needed to tell Mitiq how to run the circuit. We choose depolarizing noise via a simple density matrix simulation to act between every circuit layer.
from mitiq.zne import execute_with_zne
import cirq
def execute(circuit, noise_level=0.05):
noisy_circuit = circuit.with_noise(cirq.depolarize(p=noise_level))
return (
cirq.DensityMatrixSimulator()
.simulate(noisy_circuit)
.final_density_matrix[0, 0]
.real
)
We can then pass the desired noisescaling method into execute_with_zne
using the scale_noise
keyword argument.
execute_with_zne(generate_ghz_circuit(6), execute, scale_noise=insert_id_layers)
0.32638816162943857
To give a slightly more systematic understanding of the differences between these two methods, we will perform a small experiment on the following variational circuit.
def variational_circuit(gamma):
q0, q1 = cirq.LineQubit.range(2)
return cirq.Circuit(
[
cirq.rx(gamma)(q0),
cirq.CNOT(q0, q1),
cirq.rx(gamma)(q1),
cirq.CNOT(q0, q1),
cirq.rx(gamma)(q0),
]
)
We can run this circuit many times, varying \(\gamma\) each time, and compute ideal and noisy expectation values to compare to the mitigated versions. We do this comparison by using an improvement factor (IF) which is defined as
from random import uniform
import numpy as np
results = {"fold": [], "id": []}
for _ in range(100):
gamma = uniform(0, 2 * np.pi)
circuit = variational_circuit(gamma)
ideal_expval = execute(circuit, noise_level=0.0)
noisy_expval = execute(circuit)
folded_expval = execute_with_zne(circuit, execute, scale_noise=fold_global)
id_expval = execute_with_zne(circuit, execute, scale_noise=insert_id_layers)
noisy_error = abs(ideal_expval  noisy_expval)
folded_IF = noisy_error / abs(ideal_expval  folded_expval)
scaled_IF = noisy_error / abs(ideal_expval  id_expval)
results["fold"].append(folded_IF)
results["id"].append(scaled_IF)
print("Avg improvement factor (`fold_global`): ", round(np.average(results["fold"]), 4))
print("Avg improvement factor (`insert_id_layers`): ", round(np.average(results["id"]), 4))
Avg improvement factor (`fold_global`): 1.8547
Avg improvement factor (`insert_id_layers`): 7.0778
As we can see, both techniques offer an improvement, but for this problem identity insertion outperforms folding dramatically. This is due to the fact that ZNE assumes one can scale the noise throughout a circuit by doubling, tripling, etc, the noise’s strength. On the simple noise model used in our example, identity insertion does exactly this, and hence has great performance. Noise models with increased complexity and gatedependent errors would likely see more equal, or perhaps better performance by foldingbased techniques.
Conclusion#
In this tutorial, we’ve shown how to use both folding and identity insertion as noise scaling methods for ZeroNoise Extrapolation. If you’re interested in finding out more about these techniques, check out our Noise Scaling Functions section of our users guide!