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 mitiq.QuantumResult from \(\rho\).

  3. An observable \(O\) which specifies what we wish to compute via \(\mathrm{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.rz(1.75).on(a),
    cirq.rz(2.31).on(b),
    cirq.CNOT.on(a, b),  # Clifford
    cirq.rz(-1.17).on(b),
    cirq.rz(3.23).on(a),
    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 layer and returns the final density matrix.

from mitiq.interface.mitiq_cirq import compute_density_matrix

compute_density_matrix(circuit).round(3)
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   ]],
      dtype=complex64)

3. Observable#

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

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

Warning

To apply CDR the Observable must be Hermitian (i.e. it must 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,))

simulate(circuit).round(3)
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   ]],
      dtype=complex64)

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 ideal result#

ideal_expval = obs.expectation(circuit, simulate).real
print(f"ideal expectation value: {ideal_expval:.2f}")
ideal expectation value: 1.02

The noisy result#

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

unmitigated_expval = obs.expectation(circuit, compute_density_matrix).real
print(f"unmitigated expectation value: {unmitigated_expval:.2f}")
unmitigated expectation value: 0.80

The mitigated result#

mitigated_expval = cdr.execute_with_cdr(
    circuit,
    compute_density_matrix,
    observable=obs,
    simulator=simulate,
    seed=0,
).real
print(f"mitigated expectation value: {mitigated_expval:.2f}")
mitigated expectation value: 1.03
unmitigated_error = abs(unmitigated_expval - ideal_expval)
mitigated_error = abs(mitigated_expval - ideal_expval)

print(f"Unmitigated Error:           {unmitigated_error:.2f}")
print(f"Mitigated Error:             {mitigated_error:.2f}")

improvement_factor = unmitigated_error / mitigated_error
print(f"Improvement factor with CDR: {improvement_factor:.2f}")
Unmitigated Error:           0.21
Mitigated Error:             0.01
Improvement factor with CDR: 20.93