# # Copyright 2021 Quantum Benchmark 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])