What happens when I use PT?#

Warning:

Pauli Twirling in Mitiq is still under construction. This users guide will change in the future after some utility functions are introduced.

The workflow of Pauli Twirling (PT) in Mitiq is represented in the figure below.

../_images/pt_workflow.svg

Workflow of the PT technique in Mitiq, detailed in the What happens when I use PT? section.#

  • The user provides a QPROGRAM, (i.e. a quantum circuit defined via any of the supported frontends).

  • Mitiq modifies the input circuit with the insertion of PT gates on noisy operations.

  • The modified circuit is executed via a user-defined Executor.

  • The error mitigated expectation value is returned to the user.

With respect to the workflows of other error-mitigation techniques (e.g. ZNE or PEC), PT involves the generation of a single circuit with random modifications, and subsequently averages over many executions. For this reason, there is no need for a complex final inference step, which is necessary for other techniques, and so this average is instead trivial for PT.

Note

When setting the num_trials option to a value larger than one, multiple circuits are actually generated by Mitiq and the associated results are averaged to obtain the final expectation value. This more general case is not shown in the figure since it can be considered as an average of independent single-circuit workflows.

As shown in How do I use PT?, the function pauli_twirl_circuit() applies PT behind the scenes and returns the different versions of Pauli Twirled circuits. In the next sections instead, we show how one can apply PT at a lower level, i.e., by:

  • Twirling CZ and CNOT gates in the circuit;

  • Executing the modified circuit (still under construction).

  • Estimate expectation value by averaging over randomized twirling circuits

Twirling CZ and CNOT gates in the circuit#

To twirl about particular gates, we need the Pauli group for those gates. These groups are stored as lookup tables, in mitiq.pt.pt.CNOT_twirling_gates and mitiq.pt.pt.CZ_twirling_gates, so that we can randomly select a tuple from the group. Now we’re ready to twirl our gates.

First let’s define our circuit:

from cirq import LineQubit, Circuit, CZ, CNOT

a, b, c, d  = LineQubit.range(4)
circuit = Circuit(
    CNOT.on(a, b),
    CZ.on(b, c),
    CNOT.on(c, d),
)

print(circuit)
0: ───@───────────
      │
1: ───X───@───────
          │
2: ───────@───@───
              │
3: ───────────X───

Now, we can see what happens when we apply the PT functions, through twirl_CNOT_gates() and the subsequent twirl_CZ_gates()

from mitiq import pt

circuit_to_twirl = circuit.copy()
CNOT_twirled_circuits = pt.twirl_CNOT_gates(circuit_to_twirl, num_circuits=10)
twirled_circuits = [
    pt.twirl_CZ_gates(c, num_circuits=1)[0] for c in CNOT_twirled_circuits
]
print("Twirling just the CNOT gates: \n", CNOT_twirled_circuits[0], "\n")
print("Twirling both CNOT and CZ gates: \n" ,twirled_circuits[0])
Twirling just the CNOT gates: 
 0: ───I───@───I───────────────────
          │
1: ───X───X───X───@───────────────
                  │
2: ───────────────@───Y───@───X───
                          │
3: ───────────────────Z───X───Y─── 

Twirling both CNOT and CZ gates: 
 0: ───I───@───I───────────────────────────
          │
1: ───X───X───X───Z───@───Z───────────────
                      │
2: ───────────────I───@───I───Y───@───X───
                                  │
3: ───────────────────────────Z───X───Y───

We see that we return lists of the randomly twirled circuits, and so we must take a simple average over their expectation values.

Executing the modified circuits#

Warning:

Pauli Twirling in Mitiq is still under construction. Some lines in the code blocks below are commented out as intended behavior is currently a WIP.

Now that we have our twirled circuits, let’s simulate some noise and execute those circuits, using the mitiq.Executor to collect the results.

from cirq import DensityMatrixSimulator, amplitude_damp
from mitiq import Executor


def execute(circuit, noise_level=0.003):
    """Returns Tr[ρ |00..⟩⟨00..|] where ρ is the state prepared by the circuit
    executed with depolarizing noise.
    """
    noisy_circuit = circuit.with_noise(amplitude_damp(noise_level))
    rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix
    return rho[0, 0].real

executor = Executor(execute)
# expvals = executor.evaluate(twirled_circuits, None)

Estimate expectation value by averaging over randomized twirling circuits#

Pauli Twirling doesn’t require running the circuit at different noise levels or with different noise models. It applies a randomized sequence of Pauli operations within the same quantum circuit and averages the results to reduce the effect of the noise.

# import numpy as np
# from typing import cast

# average = cast(float, np.average(expvals))
# print(average)

Keep in mind, ths code is for illustration and that the noise level, type of noise (here amplitude damping), and the observable need to be adapted to the specific experiment.

If executed on a noiseless backend, a given circuit_with_pt and circuit are equivalent. On a real backend, they have a different sensitivity to noise. The core idea of the PT technique is that, circuits_with_pt (hopefully) tailors the noise into stochastic Pauli channels, such that a simple average over results will return a mitigated result.

As a final remark, we stress that the low-level procedure that we have shown is exactly what pauli_twirl_circuit() does behind the scenes. Let’s verify this fact:

# np.isclose(
#  pt.pauli_twirl_circuit(circuit, executor),
#  average,
# )