#
# Copyright 2021 Keysight Technologies Inc.
#
"""
Cycle Benchmarking (CB)
=======================
"""
#%%
# This example explores implementing Cycle Benchmarking (:tqdoc:`CB`\), including the
# selection of useful parameters. While the example uses a simulator, the circuits
# generated by this protocol can also be used to characterize the process infidelity of
# a dressed cycle (a target cycle preceded by a cycle of random elements of the twirling
# group) in hardware.
#%%
# Choosing Parameters
# -------------------
#
# The :py:func:`~trueq.make_cb` method generates a circuit collection to perform cycle
# benchmarking. The first argument required by :py:func:`~trueq.make_cb` is the cycle
# to be benchmarked. The second parameter ``n_random_cycles`` is also a required
# parameter, it tells :py:func:`~trueq.make_cb` how many times to apply the dressed
# cycle in each random circuitl.
#
# The number of circuits for each circuit length, ``n_circuits``\, is ``30`` by default
# and should be chosen to optimize the tradeoff between desired speed and accuracy of
# the estimation.
#
# The number of randomly chosen Pauli decay strings used to measure the process
# fidelity, ``n_decays``\, should be chosen to exceed
# :math:`min(20, 4 * n_{qubits} - 1)`. The default value for ``n_decays`` is ``20`` to
# satisfy this bound. Choosing a value lower than :math:`min(20, 4 * n_{qubits} - 1)`
# may result in a biased estimate of the fidelity of the dressed cycle.
#
# The ``twirl`` parameter specifies which twirling group the random gates which
# form the pre-compiled dressed cycles are pulled from. By default,
# :py:func:`~trueq.make_cb` uses the Pauli group, ``"P"``\.
#
# The choice of circuit lengths in ``n_random_cycles`` depends on the cycle being
# characterized as well as the selection of twirling group. If the cycle is a Clifford
# and the twirling group is ``"P"``, lengths should be chosen such that applying the
# cycle to be benchmarked ``n_random_cycles`` times will result in an identity
# operation. In the example below, we benchmark a cycle containing an :math:`X` gate and
# a controlled-:math:`Z` gate, both of which apply an identity operation when raised to
# even powers. We therefore choose cycle lengths ``4, 12, 64``. While the values of
# ``n_random_cycles`` can be any multiple of ``2`` for this example, users should be
# careful to choose a range of values such that the exponential decay is evident in the
# plot; this will ensure that the fit function gets enough information to accurately
# estimate the fidelity of the dressed cycle. To validate that this condition has been
# satisfied, users can plot the data from the CB circuits after running the circuits on
# a simulator or on hardware to see where the chosen data points fall.
#
# The final parameter is a bool, ``propagate_correction``, which tells
# :py:func:`~trueq.make_cb` whether to compile correction gates for the
# twirling group into neighbouring cycles (``propagate_correction = False``) or
# propagate them to the end of the circuit (``propagate_correction = True``). By
# default, ``propagate_correction = False``\. Warning: *propagating correction to the
# end of the circuit can result in arbitrary two-qubit gates at the end of the circuit!*
# The final circuit can be converted to a user-specified gateset using the
# :doc:`../../guides/compilation/configuration` tools.
#%%
# Benchmarking a Cycle
# --------------------
#%%
# Initialize a cycle to benchmark:
import trueq as tq
cycle = {(0,): tq.Gate.x, (1, 2): tq.Gate.cz}
#%%
# Generate a circuit collection to run CB on the above cycle with ``n_circuits = 30``
# random circuits for each circuit length in ``n_random_cycles = [4, 12, 64]`` and
# ``n_decays = 24`` randomly chosen Pauli decay strings. For the purpose of demonstrating
# how to recognize a poor choice of ``n_random_cycles``, we generate a second CB circuit
# collection with the same parameters, except ``n_random_cycles = [2, 4, 6]``. A good
# choice of ``n_random_cycles`` will result in a lower uncertainty in the fit parameters
# and therefore a more accurate estimate of the average gate fidelity.
circuits = tq.make_cb(cycle, [4, 12, 64], 30, 24)
bad_circuits = tq.make_cb(cycle, [2, 4, 6], 30, 6)
#%%
# Initialize a simulator with stochastic Pauli noise and run the cycle benchmarking
# circuits on the simulator to populate their results. Given this error model, we
# expect the individual Pauli infidelities corresponding to strings with more than two
# :math:`Z` or :math:`Y` to be :math:`0.01 \times 2 \times` \# of :math:`Z/X` terms,
# where the factor of two comes from simulating errors in both the random and
# interleaved cycles.
sim = tq.Simulator().add_stochastic_pauli(px=0.01)
sim.run(circuits)
sim.run(bad_circuits)
#%%
# Plot the results of the circuits with badly chosen ``n_random_cycles``:
bad_circuits.plot.raw()
#%%
# We can see in the plots above that our choices of ``n_random_cycles`` are sampling
# from the nearly linear portion of the exponential decay. While this is valid, it is
# not efficient (in terms of the amount of required data) at precisely learning the
# decay rates. Below, we show the same plots for the better choice ``n_random_cycles =
# [4, 12, 64]`` (ideally, the best place to sample is :math:`1/e\approx 0.37`\).
circuits.plot.raw()
#%%
# The output parameters of this protocol are displayed below using the
# :py:func:`~trueq.CircuitCollection.fit` function. When the circuits were generated,
# ``n_decays = 24`` random three-qubit Paulis were chosen as representatives, and the
# rate of decay of each was measured; these decay rates are reported. However, the
# parameter of interest is the composite parameter ``e_F``, which is an estimate of the
# process infidelity of the entire cycle.
circuits.fit()
#%%
# The default fit is to the process infidelity of the entire dressed cycle. We can also
# manually specify qubit labels to query the process infidelity of subsets of qubits,
# whose errors will be with respect to the context of the entire cycle.
circuits.fit(labels=[0, [1, 2], [0, 1, 2]]).plot.compare_twirl("e_F")
#%%
# Finally, we can compare the individual Pauli infidelities (see :tqdoc:`CB` for
# definitions), whose average value estimates the process infidelity. See also
# stochastic calibration :tqdoc:`SC` to select these curves manually.
circuits.plot.compare_pauli_infidelities()
#%%
# Targeting Specific Errors
# -------------------------
# We can also use cycle benchmarking to estimate the probabilities of specific errors.
# These errors can be supplied using an optional parameter ``targeted_errors`` at
# generation (see :py:func:`~trueq.make_cb` for more details) or by updating the keys
# of existing :tqdoc:`CB` circuits. We demonstrate the latter approach below. Note that
# if your circuits are already populated with results, the length of each error must
# correspond to the number of measurements in your circuits.
targets = ("XII", "ZII", "IIX", "IIZ")
circuits.update_keys(targeted_errors=targets)
circuits.fit()
#%%
# We can plot the probability of specific errors acting on the qubits during the cycle
# of interest:
circuits.plot.compare_twirl([f"e_{targ}" for targ in targets])