Comparing LRE and ZNE#
Both LRE and ZNE work in two main stages: generate noise-scaled circuits via scaling, and apply inference to resulting measurements post-execution.
This workflow can be executed by a single call to execute_with_lre or execute_with_zne.
For resource estimation, Mitiq provides multivariate_layer_scaling to inspect the circuits that are to be executed.
For ZNE we have access to the scaled circuits using the function scaled_circuits.
Problem Setup#
For this demonstration, we’ll first define a quantum circuit, and a method of executing circuits for demonstration purposes.
Here we will use the rotated randomized benchmarking circuits on a single qubit and generate 50 random such circuits.
from mitiq.benchmarks import generate_rotated_rb_circuits
circuits = generate_rotated_rb_circuits(
n_qubits=1, num_cliffords=3, theta=0.7, trials=50, seed=4
)
print(circuits[0])
0: ───X^-0.5───Y^0.5───Y^-0.5───X^0.5───Rz(0.223π)───Y^-0.5───Y───X^-0.5───(Z^0.5-X^0.5)───
We define an executor which simulates the input circuit subjected to depolarizing noise, and returns the probability of measuring the ground state.
By altering the value for noise_level, ideal and noisy expectation values can be obtained.
from cirq import DensityMatrixSimulator, depolarize
def execute(circuit, noise_level=0.025):
noisy_circuit = circuit.with_noise(depolarize(p=noise_level))
rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix
return rho[0, 0].real
Let’s compute the expectation values with and without noise.
# Collect ideal and noisy values (probability of measuring 0 across all circuits).
noisy_values = []
ideal_values = []
for circuit in circuits:
noisy_values.append(execute(circuit))
ideal_values.append(execute(circuit, noise_level=0.0))
import numpy as np
# The theoretical value for the probability of measuring 0 when taking
# an average over all the rotated rb circuits.
p = lambda theta: 1 - (2 / 3) * np.sin(theta / 2) ** 2
print(f"Average error for noisy values: {abs(np.mean(noisy_values) - p(0.7))}")
print(f"Average error for ideal values: {abs(np.mean(ideal_values) - p(0.7))}")
Average error for noisy values: 0.1065902825590832
Average error for ideal values: 0.0015678521391613298
For the ideal values we still see a small error, because we are only taking the average over 50 rotated randomized benchmarking circuits, so there will be noise due to randomness.
The ideal value, defined in the funcion p, is attained when computing this average over all the rotated randomized benchmarking circuits.
If you increase the number of circuits, you will find that the average error for ideal values tends to zero.
Apply LRE and ZNE directly#
With the circuit and executor defined, we just need to choose the polynomial extrapolation degree as well as the fold multiplier.
For ZNE we use the default values for the scale factors.
from mitiq.lre import execute_with_lre
from mitiq.zne import execute_with_zne
degree = 2
fold_multiplier = 3
# Collect mitigated values (probability of measuring 0 across all circuits) using LRE and ZNE.
mitigated_values_lre = []
mitigated_values_zne = []
for circuit in circuits:
mitigated_lre = execute_with_lre(
circuit, execute, degree=degree, fold_multiplier=fold_multiplier
)
mitigated_values_lre.append(mitigated_lre)
mitigated_zne = execute_with_zne(circuit, execute)
mitigated_values_zne.append(mitigated_zne)
error_lre = abs(np.mean(mitigated_values_lre) - p(0.7))
error_zne = abs(np.mean(mitigated_values_zne) - p(0.7))
print(f"Average error of mitigated values using LRE: {error_lre}")
print(f"Average error of mitigated values using ZNE: {error_zne}")
Average error of mitigated values using LRE: 0.006404454300471651
Average error of mitigated values using ZNE: 0.018986975788759075
Resource estimation#
We now compare the resources required (number of circuits to run and circuit depth) to run these protocols to have a fair comparison. First we do this for LRE.
from mitiq.lre.multivariate_scaling import multivariate_layer_scaling
avg_num_scaled_circuits_lre = 0.0
avg_depth_scaled_circuits_lre = 0.0
for circuit in circuits:
noise_scaled_circuits_lre = multivariate_layer_scaling(
circuit, degree, fold_multiplier
)
num_scaled_circuits_lre = len(noise_scaled_circuits_lre)
avg_num_scaled_circuits_lre += num_scaled_circuits_lre / len(circuits)
avg_depth_scaled_circuits_lre += (
(1 / len(circuits))
* sum(len(circuit) for circuit in noise_scaled_circuits_lre)
/ num_scaled_circuits_lre
)
print(
f"Average number of noise-scaled circuits for LRE = {avg_num_scaled_circuits_lre}"
)
print(f"Average circuit depth = {avg_depth_scaled_circuits_lre}")
Average number of noise-scaled circuits for LRE = 50.01999999999999
Average circuit depth = 19.20442424242425
Next for ZNE.
from mitiq.zne.scaling import fold_gates_at_random
from mitiq.zne import construct_circuits
scale_factors = [1.0, 2.0, 3.0]
avg_num_scaled_circuits_zne = 0.0
avg_depth_scaled_circuits_zne = 0.0
for circuit in circuits:
noise_scaled_circuits_zne = construct_circuits(
circuit=circuit,
scale_factors=[1.0, 2.0, 3.0],
scale_method=fold_gates_at_random,
)
num_scaled_circuits_zne = len(noise_scaled_circuits_zne)
avg_num_scaled_circuits_zne += num_scaled_circuits_zne / len(circuits)
avg_depth_scaled_circuits_zne += (
(1 / len(circuits))
* sum(len(circuit) for circuit in noise_scaled_circuits_zne)
/ num_scaled_circuits_zne
)
print(
f"Average number of noise-scaled circuits for ZNE = {avg_num_scaled_circuits_zne}"
)
print(f"Average circuit depth = {avg_depth_scaled_circuits_zne}")
Average number of noise-scaled circuits for ZNE = 3.000000000000002
Average circuit depth = 16.81333333333334
print(f"Error improvement LRE over ZNE: {error_zne/error_lre}")
print(
f"Ratio number of circuits required for LRE vs ZNE: {avg_num_scaled_circuits_lre/avg_num_scaled_circuits_zne}"
)
Error improvement LRE over ZNE: 2.9646516155733664
Ratio number of circuits required for LRE vs ZNE: 16.673333333333318
Conclusion#
With an additional cost of many circuits—in this case, around 17 times more—LRE achieves a notable improvement, reducing the error rate by approximately threefold.
Although our current tests were limited in the number of circuits, which means these results carry some uncertainty, the potential of LRE is clear.
There’s exciting promise and further research will help us better understand the balance between performance gains and resource requirements.