Readout Calibration (RCAL)

This example illustrates how to generate readout calibration (RCAL) circuits and use them to correct readout errors in other circuits. While this example uses a simulator to execute the circuits, the same procedure can be followed for hardware applications.

The code below generates RCAL circuits for qubits 0 and 1, populates their results using a simulator with readout error, and displays their confusion matrices.

import trueq as tq

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

# initialize a simulator with a 20% readout error on every qubit
sim = tq.Simulator().add_readout_error(0.2)

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

# display the confusion matrices
circuits.fit()
RCAL
Readout Calibration
Confusion
(0,) P(0 | 0) = 0.787
P(1 | 1) = 0.823
Confusion Matrix:
0.787 0.177 0.213 0.823
(1,) P(0 | 0) = 0.793
P(1 | 1) = 0.811
Confusion Matrix:
0.793 0.189 0.207 0.811


Automatic Correction

If your circuit collection 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 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.

# generate RCAL circuits to measure the readout errors on 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 large 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 amounts of shots for SRB and RCAL to populate their results
sim.run(circuits.subset(protocol=["RCAL"]), n_shots=50)
sim.run(circuits.subset(protocol="RCAL"), n_shots=1000)

# 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 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]

Optional Arguments

One optional argument to make_rcal() is independent=True that specifies whether it can be assumed that the readout error is independent over qubits. Under this assumption, only two circuits are required, whereas \(2^n\) circuits are required when this assumption does not hold. Note that if you measure the readout calibration matrix in both independent and non-independent modes, any observed non-independence could potentially be due to gate crosstalk errors.

# generate RCAL circuits assuming that readout error is qubit-dependent
circuits = tq.make_rcal([0, 1, 2], independent=False)

# print the length of the circuits
len(circuits)

Out:

8

As you can see, the above circuit collection contains eight circuits, whereas it would normally contain only two if independent was set to True.

Another optional argument to make_rcal() is stagger=False that specifies whether \(X\) gates should appear in separate immutable cycles. If the gates are much worse when applied in parallel, then staggering them will reduce systematic errors, provided that the gate duration multiplied by the number of qubits is much shorter than \(T_1\).

# generate RCAL circuits with staggered X gates
circuits = tq.make_rcal([0, 1, 2], stagger=True)

# display the first circuit
circuits[0]
Circuit
Key:
  • compiled_pauli: III
  • protocol: RCAL
     
imm
(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
(2): Gate.id
Name:
  • Gate.id
Aliases:
  • Gate.id
  • Gate.i
  • Gate.cliff0
Likeness:
  • Identity
Generators:
  • 'I': 0
Matrix:
  • 1.00 1.00
imm
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()
(2): Meas()
Name:
  • Meas()


# display the second circuit
circuits[1]
Circuit
Key:
  • compiled_pauli: XXX
  • protocol: RCAL
     
imm
(0): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
   
imm
(1): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
   
imm
(2): Gate.x
Name:
  • Gate.x
Aliases:
  • Gate.x
  • Gate.cliff1
Generators:
  • 'X': 180.0
Matrix:
  • 1.00 1.00
   
imm
(0): Meas()
Name:
  • Meas()
(1): Meas()
Name:
  • Meas()
(2): Meas()
Name:
  • Meas()


The circuit above contains four cycles with the \(X\) gates appearing in separate immutable cycles, which would normally occupy a single cycle if stagger was set to False.

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

Gallery generated by Sphinx-Gallery