Readout Calibration (RCAL)

This example illustrates how to generate readout calibration (RCAL) circuits and use them to correct readout errors in other circuits.

The make_rcal() method produces a set of circuits that prepare both the ground state (through application of identity gates) and excited states (through the application of bitflip or \(X\) gates in the case of qubits, or their multi-dimensional equivalent in the case of qudits). The relative frequency with which the ground and excited states are measured are used to estimate state-dependent readout errors.

In this example we use a simulator to execute the RCAL circuits, but the same procedure can be followed for circuits that are run on hardware.

Readout Calibration in Qubits

The code below generates RCAL circuits for two qubits (labelled 0 and 1), populates their results using a simulator with readout error, and displays the resulting state probabilities in the form of confusion matrices.

import trueq as tq

# generate RCAL circuits to measure the readout errors on qubits [0, 1]
circuits = tq.make_rcal([0, 1])

# display the circuits
circuits[0]
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Circuit
Key:
  • compiled_pauli: XX
  • protocol: RCAL
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(0): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
(1): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()


circuits[1]
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Circuit
Key:
  • compiled_pauli: II
  • protocol: RCAL
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(0): Gate.id
Name:
  • Gate.id
Aliases:
  • Gate.id
  • Gate.i
  • Gate.cliff0
Likeness:
  • Identity
Generators:
  • 'I': 0
Matrix:
  • 1.00 1.00
(1): Gate.id
Name:
  • Gate.id
Aliases:
  • Gate.id
  • Gate.i
  • Gate.cliff0
Likeness:
  • Identity
Generators:
  • 'I': 0
Matrix:
  • 1.00 1.00
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()


Next, we initialize a simulator with a 20% readout error on both qubits:

sim = tq.Simulator().add_readout_error(0.2)

# run the circuits on the simulator to populate their results
sim.run(circuits, n_shots=1000)

We can analyze and fit the results by calling the fit() method that produces the following output:

circuits.fit()
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
RCAL
Readout Calibration
Confusion
(0,) P(0 | 0) = 0.791
P(1 | 1) = 0.811
Confusion Matrix:
0.791 0.189 0.209 0.811
Key:
  • labels: (0, 1)
  • protocol: RCAL
(1,) P(0 | 0) = 0.802
P(1 | 1) = 0.783
Confusion Matrix:
0.802 0.217 0.198 0.783
Key:
  • labels: (0, 1)
  • protocol: RCAL


Here, \(P(A|B)\) are conditional probabilities that denote the probability that we are measuring state \(A\) when we expect to have state \(B\) prepared. When you hover over each row in the table above it displays the confusion matrices of the outcomes, that is, the probabilities \(P(0|0), P(0|1), P(1|0)\) and \(P(1|1)\) arranged in matrix form. Given that we simulated these circuits with a 20% readout error, we observe a more or less 80/20 split in probabilities on the diagonal and the off-diagonal elements.

Readout Calibration in Qudits

The make_rcal() function can be used for qudits in exactly the same way as for qubits. The qudit dimension determines how many circuits are needed to estimate the error on all states, because there are more computational basis states for higher-dimensional systems and therefore more conditional probabilities to measure.

In this example we consider qutrits, that is qudits of dimension three, by setting the set_dim() parameter to 3. Higher-dimensional qudits can be analyzed in the same fashion.

For a qutrit, make_rcal() generates three circuits that prepare the ground state on each qudit by applying identity gates, the first excited state by applying \(X\) gates, and the second excited state by applying \(X^2\) gates to each qudit.

As before, we consider a system of two qutrits (labelled 0 and 1) and start by generating those circuits:

tq.settings.set_dim(3)

circuits = tq.make_rcal([0, 1])

# display the circuits
circuits[0]
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Circuit
Key:
  • compiled_pauli: W20W20
  • protocol: RCAL
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(0): Gate.x3pow2
Name:
  • Gate.x3pow2
Aliases:
  • Gate.x3pow2
Matrix:
  • 1.00 1.00 1.00
(1): Gate.x3pow2
Name:
  • Gate.x3pow2
Aliases:
  • Gate.x3pow2
Matrix:
  • 1.00 1.00 1.00
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()


circuits[1]
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Circuit
Key:
  • compiled_pauli: W10W10
  • protocol: RCAL
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(0): Gate.x3
Name:
  • Gate.x3
Aliases:
  • Gate.x3
  • Gate.x3pow1
Matrix:
  • 1.00 1.00 1.00
(1): Gate.x3
Name:
  • Gate.x3
Aliases:
  • Gate.x3
  • Gate.x3pow1
Matrix:
  • 1.00 1.00 1.00
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()


circuits[2]
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
Circuit
Key:
  • compiled_pauli: W00W00
  • protocol: RCAL
 
Marker 0
Compilation tools may only recompile cycles with equal markers.
(0): Gate.id3
Name:
  • Gate.id3
Aliases:
  • Gate.id3
Matrix:
  • 1.00 1.00 1.00
(1): Gate.id3
Name:
  • Gate.id3
Aliases:
  • Gate.id3
Matrix:
  • 1.00 1.00 1.00
1
Marker 1
Compilation tools may only recompile cycles with equal markers.
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()


Next, we simulate the outcome of these circuits with a 20% readout error on both qutrits and fit the results:

sim = tq.Simulator().add_readout_error(0.2)
sim.run(circuits, n_shots=1000)

estimate = circuits.fit()
estimate
True-Q formatting will not be loaded without trusting this notebook or rerunning the affected cells. Notebooks can be marked as trusted by clicking "File -> Trust Notebook".
RCAL
Readout Calibration
Confusion
(0,) P(0 | 0) = 0.812
P(1 | 1) = 0.813
P(2 | 2) = 0.813
Confusion Matrix:
0.812 0.093 0.085 0.095 0.813 0.102 0.093 0.094 0.813
Key:
  • labels: (0, 1)
  • protocol: RCAL
(1,) P(0 | 0) = 0.781
P(1 | 1) = 0.793
P(2 | 2) = 0.788
Confusion Matrix:
0.781 0.105 0.109 0.107 0.793 0.103 0.112 0.102 0.788
Key:
  • labels: (0, 1)
  • protocol: RCAL


And just as in the qubit case, when hovering over this table you can again inspect the confusion matrices that now have a dimension of \(3\times3\) and contain all the conditional probabilities \(P(0|0), P(0|1), P(0|2), P(1|0) \dots P(2|2)\).

Applying the Correction

The estimate returned by the RCAL protocol upon calling the fit() method is an RCalEstimate instance that can be used to apply a correction to a different set of results that were obtained under the same readout error. For this, we can call the apply_correction() method that inverts the confusion matrices and multiplies them onto the corresponding indices of the results that we wish to correct.

For example, consider the following two-qutrit circuit with a Fourier or \(F\) gate (the generalized version of the Hadamard gate for higher qudit dimensions) on the first qutrit followed by a \(CX\) gate on both:

circuit = tq.Circuit([{0: tq.Gate.f3}, {(0, 1): tq.Gate.cx3}])
circuit.measure_all()

circuit.draw()
0 1 Key: Labels: (0,) Name: Gate.f3 Aliases: Gate.f3 0.58 0.58 0.58 0.58 -0.29 0.50j -0.29 -0.50j 0.58 -0.29 -0.50j -0.29 0.50j F3 Labels: (0, 1) Name: Gate.cx3 Aliases: Gate.cx3 Gate.cnot3 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 1.00 CX CX 1 Labels: (0,) Name: Meas M Labels: (1,) Name: Meas M


In the absence of readout errors, this circuit should produce an equal superposition between the the states 00, 11 and 20. However, when we simulate this circuit with the noisy simulator from above, the actual results differ. But using the RCalEstimate we obtained from the calibration, we can correct those results using the apply_correction() method:

results = sim.sample(circuit, n_shots=1000)

tq.visualization.plot_results(
    results,
    estimate[0].apply_correction(results),
    labels=["Bare Circuit", "With Readout Calibration"],
)
rcal

Note that the linear transformation used to correct the readout results can yield negative-valued outputs in the histogram, which can be interpreted as zero outputs.

Automatic Correction

The correction we applied manually in the example above can also be automated. If you are using a CircuitCollection that also contains RCAL circuits, then that information will be used automatically to apply readout correction to your circuits when you call fit() or plot().

The code below illustrates this for SRB (Streamlined Randomized Benchmarking) circuits executed on a noisy simulator with readout error. It also shows how you can view the results with and without readout correction being applied. For simplicity, we switch our focus back to qubits but the example below works for qudits as well.

tq.settings.set_dim(2)

# generate RCAL circuits to measure the readout errors on three qubits [0, 1, 2]
circuits = tq.make_rcal([0, 1, 2])

# generate SRB circuits to simultaneously characterize a single qubit [0] and
# a pair of qubits [1, 2] with 30 circuits for each random cycle in [4, 32, 64]
circuits.append(tq.make_srb([[0], [1, 2]], [4, 32, 64], 30))

# initialize a noisy simulator with a 10% readout error
sim = tq.Simulator().add_stochastic_pauli(px=0.01).add_readout_error(0.1)

# RCAL generally needs more shots than the other protocols because it is estimating
# an absolute value rather than a decay over randomizations, thus in this simulation
# we use different numbers of shots for RCAL and SRB to populate their results
sim.run(circuits.subset(protocol="RCAL"), n_shots=1000)

# to simulate the SRB circuits, we create a subset that contains all circuits except
# those from RCAL, which we can achieve by using square brackets in our syntax:
sim.run(circuits.subset(protocol=["RCAL"]), n_shots=50)

# plot the exponential decay with readout correction,
# where each expectation value (dot) has been compensated
circuits.plot.raw()
SRB on [0], SRB on [1, 2]

To avoid performing readout correction during analysis, we can remove all RCAL circuits from the collection before calling plot(). Notice that the y-intercept is now lower than in the plot above:

# plot the exponential decay without readout correction
circuits.subset(protocol="SRB").plot.raw()
SRB on [0], SRB on [1, 2]

Total running time of the script: ( 0 minutes 1.178 seconds)

Gallery generated by Sphinx-Gallery