# How do I use PEC?#

To apply PEC, you must know how to represent *ideal operations* as linear combinations of *noisy implementable operations*.
This information is exploited by Mitiq to remove the bias error from noisy expectation values.

By

*ideal operations*, we mean noiseless unitary gates applied to specific qubits.By

*noisy implementable operations*, we mean those noisy gates that can be applied to specific qubits by a real backend.

Since real backends are not perfect, the noisy implementable operations do not have an ideal unitary effect and typically correspond to non-unitary quantum channels.

**Note:** In this documentation we often interchange the terms *gate* and *operation* for simplicity. However, in the source code of Mitiq,
we follow the notation in which an operation is a gate applied to specific qubits, while a gate represents an abstract (qubit-independent)
operation.

As with all techniques, PEC is compatible with any frontend supported by Mitiq:

```
import mitiq
mitiq.SUPPORTED_PROGRAM_TYPES.keys()
```

```
['braket', 'cirq', 'pennylane', 'pyquil', 'qibo', 'qiskit']
```

In the next cell you can select the frontend used in this tutorial. For example:

```
frontend = "qiskit"
```

## Problem setup#

We first define the circuit of interest. For simplicity, in this example we use a single-qubit randomized-benchmarking circuit whose ideal (noiseless) execution is equivalent to the identity operation.

```
from mitiq import benchmarks
circuit = benchmarks.generate_rb_circuits(
n_qubits=1, num_cliffords=2, return_type=frontend,
)[0]
print(circuit)
```

```
┌─────────┐┌──────┐┌──────────┐┌────┐┌──────────┐┌──────┐
q: ┤ Ry(π/2) ├┤ √Xdg ├┤ Ry(-π/2) ├┤ √X ├┤ Ry(-π/2) ├┤ √Xdg ├
└─────────┘└──────┘└──────────┘└────┘└──────────┘└──────┘
```

In the code cell above, we used the function `generate_rb_circuits()`

, in which `num_cliffords`

is the
number of random Clifford group elements and is proportional to the depth of the circuit.

We now define an executor which simulates a backend with depolarizing noise and computes the survival probability of the \(|00\rangle\) state.

**Note:** The body of the following function is supposed to be written by the user and depends on the specific
frontend and backend. Here, for simplicity, we first convert the input circuit to the Mitiq internal
representation (Cirq) and then simulate it.

```
from cirq import DensityMatrixSimulator, depolarize
from mitiq.interface import convert_to_mitiq
def execute(circuit, noise_level=0.01):
"""Returns Tr[ρ |0⟩⟨0|] where ρ is the state prepared by the circuit
executed with depolarizing noise.
"""
# Replace with code based on your frontend and backend.
mitiq_circuit, _ = convert_to_mitiq(circuit)
# We add a simple noise model to simulate a noisy backend.
noisy_circuit = mitiq_circuit.with_noise(depolarize(p=noise_level))
rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix
return rho[0, 0].real
```

The function `execute`

can be used to evaluate noisy (unmitigated) expectation values.

```
# Compute the expectation value of the |0⟩⟨0| observable.
noisy_value = execute(circuit)
ideal_value = execute(circuit, noise_level=0.0)
print(f"Error without mitigation: {abs(ideal_value - noisy_value) :.5f}")
```

```
Error without mitigation: 0.03869
```

We now show how to use PEC to reduce this error.

## Appling probabilistic error cancellation (PEC)#

### Represent ideal gates as linear combinations of noisy gates#

To apply PEC, we need to represent some or all of the operations of the ideal `circuit`

as linear combinations
of noisy implementable operations. Such linear combinations are also known as quasi-probability representations (see What is the theory behind PEC?) and, in Mitiq, they correspond to `OperationRepresentation`

objects. More information on the `OperationRepresentation`

class can be found in the section What additional options are available in PEC?.

But how can we obtain the quasi-probability representations that are appropriate for a given backend? There are two main alternative scenarios.

**Case 1:**The noise of the backend can be approximated by a simple noise model, such that quasi-probability representations can be analytically computed [5, 10, 11]. For example, this is possible for depolarizing or amplitude damping noise.**Case 2:**The noise of the backend is too complex and cannot be approximated by a simple noise model.

Depending on the previous two cases, the method to obtain quasi-probability representations is different.

**Method for case 1:**A simple noise model (e.g. depolarizing or amplitude damping) is typically characterized by a single`noise_level`

parameter (or a few parameters) that can be experimentally estimated. Possible techniques for estimating the noise level are randomized-benchmarking experiments or calibration experiments. Often, gate error probabilities are reported by hardware vendors and can be used to obtain a good guess for the`noise_level`

without running any experiments. Given the noise model and the`noise_level`

, one can apply known analytical expressions to compute the quasi-probability representations of arbitrary gates [5, 11].**Method for case 2:**Assuming an over-simplified noise model may be a bad approximation. In this case, the suggested approach is to perform the complete process tomography of a basis set of implementable noisy operations (e.g. the native gate set of the backend). One could also use*gate set tomography*(GST), a noise characterization technique which is robust to state-preparation and measurement errors. Given the superoperators of the noisy implementable operations, one can obtain the quasi-probability representations as solutions of numerical optimization problems [5, 10]. In Mitiq, this is possible through the`find_optimal_representation()`

function as shown the section What additional options are available in PEC?.

In this example, the `execute`

function simulates the effect of depolarizing noise of some fixed `noise_level`

acting after each gate. Therefore, we are in the first scenario defined above and we can generate a list of `OperationRepresentation`

objects associated to all the gates of the `circuit`

as follows:

```
from mitiq.pec.representations.depolarizing import represent_operations_in_circuit_with_local_depolarizing_noise
noise_level = 0.01
reps = represent_operations_in_circuit_with_local_depolarizing_noise(circuit, noise_level)
print(f"{len(reps)} OperationRepresentation objects produced, assuming {100 * noise_level}% depolarizing noise.")
```

```
4 OperationRepresentation objects produced, assuming 1.0% depolarizing noise.
```

As an example, we print the first `OperationRepresentation`

in `reps`

, showing how an ideal gate can be expressed as a linear combination of noisy gates:

```
print(reps[0])
```

```
q_0: ───Ry(0.5π)─── = 1.010*(q_0: ───Ry(0.5π)───)-0.003*(q_0: ───Ry(0.5π)───X───)-0.003*(q_0: ───Ry(0.5π)───Y───)-0.003*(q_0: ───Ry(0.5π)───Z───)
```

**IMPORTANT:** For PEC to work properly, the noise model and the `noise_level`

associated to the `execute`

function must correspond to those used to compute the
`OperationRepresentation`

objects.

### Run PEC#

Once the necessary `OperationRepresentation`

s have been defined,
probabilistic error cancellation can be applied through the `execute_with_pec()`

function as follows:

```
from mitiq import pec
pec_value = pec.execute_with_pec(circuit, execute, representations=reps)
print(f"Error without PEC: {abs(ideal_value - noisy_value) :.5f}")
print(f"Error with PEC: {abs(ideal_value - pec_value) :.5f}")
```

```
Error without PEC: 0.03869
Error with PEC: 0.00415
```

As printed above, PEC reduced the error compared to the unmitigated case.