How do I use CDR?#

Here we show how to use CDR by means of a simple example.

import numpy as np
import warnings
warnings.simplefilter("ignore", np.ComplexWarning)

import cirq
from mitiq import cdr, Observable, PauliString

Problem setup#

To use CDR, we call cdr.execute_with_cdr() with four “ingredients”:

  1. A quantum circuit to prepare a state \(\rho\).

  2. A quantum computer or noisy simulator to return a QuantumResult from \(\rho\).

  3. An observable \(O\) which specifies what we wish to compute via \(\text{Tr} [ \rho O ]\).

  4. A near-Clifford (classical) circuit simulator.

1. Define a quantum circuit#

The quantum circuit can be specified as any quantum circuit supported by Mitiq but must be compiled into a gateset in which the only non-Clifford gates are single-qubit rotations around the \(Z\) axis: \(R_Z(\theta)\). For example:

\[\{ \sqrt{X}, R_Z(\theta), \text{CNOT}\},\]
\[\{{R_X(\pi/2)}, R_Z(\theta), \text{CZ}\},\]
\[\{H, S, R_Z(\theta), \text{CNOT}\},\]
\[ \dots\]

In the next cell we define (as an example) a quantum circuit which contains some Clifford gates and some non-Clifford \(R_Z(\theta)\) rotations.

a, b = cirq.LineQubit.range(2)
circuit = cirq.Circuit(
    cirq.H.on(a), # Clifford
    cirq.H.on(b), # Clifford
    cirq.CNOT.on(a, b),  # Clifford
    cirq.rx(np.pi / 2).on(a),  # Clifford
    cirq.rx(np.pi / 2).on(b),  # Clifford

# CDR works better if the circuit is not too short. So we increase its depth.
circuit = 5 * circuit

2. Define an executor#

We define an executor function which inputs a circuit and returns a QuantumResult. Here for sake of example we use a simulator that adds single-qubit depolarizing noise after each moment and returns the final density matrix.

from mitiq.interface.mitiq_cirq import compute_density_matrix

array([[ 0.834+0.j   , -0.027+0.075j, -0.004+0.061j, -0.022-0.052j],
       [-0.027-0.075j,  0.057-0.j   ,  0.007-0.006j, -0.003+0.002j],
       [-0.004-0.061j,  0.007+0.006j,  0.053+0.j   , -0.005+0.004j],
       [-0.022+0.052j, -0.003-0.002j, -0.005-0.004j,  0.056+0.j   ]],

3. Observable#

As an example, assume that we wish to compute the expectation value \(\text{Tr} [ \rho O ]\) of the following observable \(O\):

# Observable to measure.
obs = Observable(PauliString("ZZ"), PauliString("X", coeff=-1.75))
Z(q(0))*Z(q(1)) + (-1.75+0j)*X(q(0))

Note: To apply CDR the Observable should be Hermitian, i.e., it should always produce real expectation values.

You can read more about the Observable class in the documentation.

4. (Near-Clifford) Simulator#

The CDR method creates a set of “training circuits” which are related to the input circuit and are efficiently simulable. These circuits are simulated on a classical (noiseless) simulator to collect data for regression. The simulator should also return a QuantumResult.

To use CDR at scale, an efficient near-Clifford circuit simulator must be specified. In this example, the circuit is small enough to use any classical simulator, and we use the same density matrix simulator as above but without noise.

def simulate(circuit: cirq.Circuit) -> np.ndarray:
    return compute_density_matrix(circuit, noise_level=(0.0,))

array([[ 0.982-0.j   , -0.016+0.081j, -0.004+0.049j, -0.049-0.075j],
       [-0.016-0.081j,  0.007-0.j   ,  0.004-0.j   , -0.005+0.005j],
       [-0.004-0.049j,  0.004+0.j   ,  0.002+0.j   , -0.004+0.003j],
       [-0.049+0.075j, -0.005-0.005j, -0.004-0.003j,  0.008+0.j   ]],

Run CDR#

Now we can run CDR. We first compute the noiseless result then the noisy result to compare to the mitigated result from CDR.

The noiseless result#

ideal_measurement = obs.expectation(circuit, simulate).real
print("ideal_measurement = ",ideal_measurement)
ideal_measurement =  1.0153722763061523

The noisy result#

We now generate the noisy result. Note that compute_density_matrix() function by default runs a simulation with noise.

unmitigated_measurement = obs.expectation(circuit, compute_density_matrix).real
print("unmitigated_measurement = ", unmitigated_measurement)
unmitigated_measurement =  0.8030939102172852

The mitigated result#

mitigated_measurement = cdr.execute_with_cdr(
print("mitigated_measurement = ", mitigated_measurement)
mitigated_measurement =  1.0284055695316374
error_unmitigated = abs(unmitigated_measurement-ideal_measurement)
error_mitigated = abs(mitigated_measurement-ideal_measurement)

print("Error (unmitigated):", error_unmitigated)
print("Error (mitigated with CDR):", error_mitigated)

print("Relative error (unmitigated):", (error_unmitigated/ideal_measurement))
print("Relative error (mitigated with CDR):", error_mitigated/ideal_measurement)

print(f"Error reduction with CDR: {(error_unmitigated-error_mitigated)/error_unmitigated :.1%}.")
Error (unmitigated): 0.2122783660888672
Error (mitigated with CDR): 0.013033293225485076
Relative error (unmitigated): 0.20906456778701882
Relative error (mitigated with CDR): 0.012835975070049395
Error reduction with CDR: 93.9%.