# 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.

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: ───I───X───I───@───────────────
│
2: ───────────────@───Y───@───X───
│
3: ───────────────────Y───X───Z───
Twirling both CNOT and CZ gates:
0: ───I───@───I───────────────────────────
│
1: ───I───X───I───Z───@───Z───────────────
│
2: ───────────────Z───@───Z───Y───@───X───
│
3: ───────────────────────────Y───X───Z───
```

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,
# )
```