K-body Noise Reconstruction (KNR)

K-body noise reconstruction (KNR) is an error diagnostic protocol that provides a detailed error profile of a cycle of interest. In this example, we will see how to generate and run KNR circuits, and how to interpret the results. First, let’s choose a cycle to benchmark:

import numpy as np
import trueq as tq
import trueq.math as tqm
import trueq.simulation as tqs

# define the cycle to benchmark using KNR
cycle_of_interest = tq.Cycle({(0, 1): tq.Gate.cx, 2: tq.Gate.z})

This cycle is to be implemented on a quantum processor. For the sake of this example, the device in question will consist of a Simulator. To avoid confusion, we will name it device. Our device will be a minimalistic 3-qubit processor. Now, let’s introduce a stochastic error map to our cycle_of_interest:

# define some simple 3-qubit errors, together with their probability
error_profile = {
    #  no error with prob 88%
    "III": 0.88,
    # IXI error with prob  1%
    "IXI": 0.01,
    # XII error with prob  3%
    "XII": 0.03,
    # IXX error with prob  6%
    "IXX": 0.06,
    # ZXZ error with prob  2%
    "ZXZ": 0.02,
}

# define Kraus operators based on the above error profile
kraus_list = [
    np.sqrt(prob) * tqm.Weyls(pauli, dim=2).herm_mat
    for pauli, prob in error_profile.items()
]

# instantiate a superoperator based on the Kraus operators
superop = tqm.Superop.from_kraus(kraus_list)

# instantiate a device simulator based on the above error profile
device = tqs.Simulator()
device.add_cycle_noise(
    # the 3-qubit error map is applied to qubits 0, 1, and 2
    {(0, 1, 2): superop},
    # the error map is only applied to the cycle of interest
    match=tqs.CycleMatch(cycle_of_interest),
    # the error map occurs before the cycle of interest
    cycle_offset=-1,
)
<trueq.simulation.simulator.Simulator object at 0x7fe9fde56790>

Now that the device has been instantiated, let’s try to learn the error profile of the cycle_of_interest. For this, we will generate KNR circuit collections. An important parameter when generating KNR circuits is subsystems. To understand this parameter, let’s first identify the gate suppports of our cycle of interest, which are \((0,1)\) and \((2)\). If subsystems=1, the KNR protocol will yield marginal error probabilities for each gate support in the cycle of interest.

Given our example, if we look at qubit \(2\), which is a gate support for the cycle of interest, we should expect to see an \(X\) error with probability \(6\%\), and a \(Z\) error with probability \(2\%\). If we look at the gate support \((0,1)\), we should expect to see an \(XI\) error with probability \(3\%\), a \(ZX\) error with probability \(2\%\), and a \(IX\) error with probability \(7\%\). To obtain the \(7\%\) value, we added the probabilities of \(IXI\) and \(IXX\), which both yield an \(IX\) error for the support \((0,1)\) (this is the marginal probability). These are indeed the reconstructed probabilities:

# generate KNR circuits to benchmark the cycle, targeting only single gate supports
knr_circuits_1 = tq.make_knr(
    cycle_of_interest, n_random_cycles=[4, 10], n_circuits=30, subsystems=1
)

# run the circuits on the device
device.run(knr_circuits_1)

# plot the reconstructed error profile with subsystems=1:
layout = tq.visualization.Graph.linear(3, show_labels=True)  # specify the chip layout
knr_circuits_1.plot.knr_heatmap(layout)  # plot the heatmap
knr

In the plot above, notice that the uncertainty for reconstructed error probabilities is seen in the variation of color in each cell. Some errors that are not included in the underlying error_profile might appear in dark purple due to the uncertainty of the estimates. These dark purple cells can be removed from the plot by simply changing the cutoff options (see the knr_heatmap API reference). Moreover, notice the label \(\{XX,XI\}\), which is associated with a probability of \(3\%\). This bracketed set of errors is short for “\(XX\) or \(XI\)" and indicates that KNR didn’t differentiate between the two and instead outputs the sum of their respective probability of occuring. \(XX\) and \(XI\) are then said to be “degenerate”. Here, we know from our construction that only \(XI\) could occur, but on an actual device where the noise model is unknown, we cannot learn the individual error probabilities in a degenerate set.



Aside from the \(\{XX, XI\}\) degeneracy, we might want to resolve our error profile further. Indeed, from the current experiment (i.e. with subsystems=1), we can’t know if some errors on different gate supports are correlated. For instance, we know that qubit \(2\) sees a \(Z\) error with probability of \(2\%\), and that the qubit pair sees a \(ZX\) error with probability of \(2\%\), but we didn’t learn if these errors occur at the same time or not (we know from our underlying error profile that they are perfectly correlated). To resolve the error correlations between any two gate supports, we can simply choose a KNR circuit collection with subsystems=2:

# generate KNR circuits to benchmark the cycle, targeting all pairs of gate supports
knr_circuits_2 = tq.make_knr(
    cycle_of_interest, n_random_cycles=[4, 10], n_circuits=30, subsystems=2
)

# run the circuits on the device
device.run(knr_circuits_2)

# plot the reconstructed error profile with subsystems=2:
knr_circuits_2.plot.knr_heatmap(layout)  # plot the heatmap
knr

Notice that this experiment yields marginal error probabilities for all pairs of gate supports. Since our cycle only contained a pair of gate supports (namely \((0,1)\) and \(2\)), the above plot shows a complete reconstruction of the error profile.

KNR with qudits

KNR seamlessly supports qudits. To see this, let’s recreate a similar example as above, but with qutrit instructions on a 2-qutrit processor:

# setting the register dimension to 3 (i.e. qutrits)
tq.settings.set_dim(3)

# define the cycle to benchmark using KNR
cycle_of_interest = {(0, 1): tq.Gate.cx3}

# define some simple error profile by pairing Weyl errors with corresponding
# probabilities
error_profile = {
    #  no error with prob 88%
    "W00W00": 0.88,
    # X3.I3 error with prob  2%
    "W10W00": 0.02,
    # X3.Z3 error with prob  8%
    "W10W01": 0.08,
    # I3.X3 error with prob  2%
    "W00W10": 0.02,
}

# define Kraus operators based on the above error profile
kraus_list = [
    np.sqrt(prob) * tqm.Weyls(weyl).mat for weyl, prob in error_profile.items()
]

# instantiate a superoperator based on the Kraus operators
superop = tqm.Superop.from_kraus(kraus_list)

# instantiate a device simulator based on the above error profile
device = tqs.Simulator()
device.add_cycle_noise(
    # the 3-qutrit error map is applied to qutrits 0, 1, and 2
    {(0, 1): superop},
    # the error map is only applied to the cycle of interest
    match=tqs.CycleMatch(cycle_of_interest),
    # the error map occurs before the cycle of interest
    cycle_offset=-1,
)
<trueq.simulation.simulator.Simulator object at 0x7fea03171d60>

Now that the device has been instantiated, let’s try to learn the error profile of the cycle_of_interest:

# generate KNR circuits to benchmark the cycle, targeting only single gate supports
knr_circuits = tq.make_knr(
    cycle_of_interest, n_random_cycles=[6, 9, 12], n_circuits=30, subsystems=1
)

# run the circuits on the device
device.run(knr_circuits)

# plot the reconstructed error profile with subsystems=1:
layout = tq.visualization.Graph.linear(2, show_labels=True)  # specify the chip layout
knr_circuits.plot.knr_heatmap(layout)  # plot the heatmap
knr

Notice that for this cycle, some degeneracy orbits contain \(3\) Weyl operators. This observation is expected as tq.Gate.cx3 has a cyclicity of \(3\).

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

Gallery generated by Sphinx-Gallery