#
# Copyright 2021 Quantum Benchmark Inc.
#
"""
Readout Calibration (RCAL)
==========================
"""
#%%
# This example illustrates how to generate readout calibration (:tqdoc:`RCAL`\) circuits
# and use them to correct readout errors in other circuits. While this example uses a
# :doc:`simulator<../../guides/run/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()
#%%
# 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
# :py:meth:`~trueq.CircuitCollection.fit` or :py:meth:`~trueq.CircuitCollection.plot`\.
# The code below illustrates this for :tqdoc:`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()
#%%
# To avoid performing readout correction during analysis, we can remove all RCAL
# circuits from the collection before calling :py:meth:`~trueq.CircuitCollection.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()
#%%
# Optional Arguments
# ------------------
#
# One optional argument to :py:func:`~trueq.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 :math:`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)
#%%
# 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 :py:func:`~trueq.make_rcal` is ``stagger=False`` that
# specifies whether :math:`X` gates should appear in separate uniquely marked 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 :math:`T_1`.
# generate RCAL circuits with staggered X gates
circuits = tq.make_rcal([0, 1, 2], stagger=True)
# display the first circuit
circuits[0]
#%%
# display the second circuit
circuits[1]
#%%
# The circuit above contains four cycles with the :math:`X` gates appearing in separate
# marked cycles, which would normally occupy a single cycle if ``stagger`` was set to
# ``False``.