# Zero-noise extrapolation with Braket on the IonQ backend#

This tutorial shows an example of how to apply zero-noise extrapolation (ZNE) with the Braket frontend to mitigate errors on an IonQ backend. More details on the Mitiq notions of frontends and backends are given here.

Below, we show how to run a simple Braket circuit on an IonQ device.

## Settings#

We import the zero-noise extrapolation module of Mitiq.

```
from mitiq import zne
USE_REAL_HARDWARE = False
```

Note

When `USE_REAL_HARDWARE`

is set to `False`

, a classically simulated noisy backend is used instead of a real quantum computer.

We also set the number of times each quantum circuit is executed and measured using `number_of_shots`

.
Setting a large number of shots improves the accuracy of the results, but also increases the computational cost and the execution time.

```
number_of_shots = 1024
```

## Setup: Defining a circuit in Braket#

For simplicity, we define a single-qubit circuit with 10 \(X\) gates that, in total, is equivalent to the identity operation.

```
import braket
braket_circuit = braket.circuits.Circuit()
for _ in range(10):
braket_circuit.x(0)
print(braket_circuit)
```

```
T : |0|1|2|3|4|5|6|7|8|9|
q0 : -X-X-X-X-X-X-X-X-X-X-
T : |0|1|2|3|4|5|6|7|8|9|
```

We will use the probability of measuring the system in the *zero* state (\(p = \langle 0 | \rho |0\rangle\)) as our expectation value to error-mitigate.
The expectation value evaluates to \(1\) in the noiseless setting, but is usually smaller when estimated on a noisy backend due to errors.

## High-level usage#

To use Mitiq with just a few lines of code, we need to define an *executor*, *i.e.* a function which inputs a circuit and outputs the expectation value to mitigate.
This function will:

Optionally, add measurement(s) to the circuit. (Not in this example).

Run the circuit on a backend.

Convert from raw measurement statistics (or a different output format) to an expectation value.

For information on how to define more advanced executors, see the Executors section of the Mitiq User Guide.

Warning

Using a real IonQ device requires running this notebook within an Amazon Braket cloud session created with a valid AWS account.
A monetary budget (or credits) is necessary.
When `USE_REAL_HARDWARE`

is set to `False`

, this notebook will run on your local machine without costs.

```
def braket_ionq_execute(
braket_circuit: braket.circuits.Circuit,
shots: int = number_of_shots,
noise_level: float = 0.01) -> float:
"""Returns the expectation value to be mitigated.
Args:
circuit: Circuit to run.
shots: Number of times to execute the circuit to compute the expectation value.
noise_level: The level of depolarizing noise.
"""
circuit_to_run = braket_circuit.copy()
if USE_REAL_HARDWARE:
from braket.aws import AwsDevice
backend = AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice")
else:
from braket.devices import LocalSimulator
backend = LocalSimulator("braket_dm")
# Simulate depolarizing noise
circuit_to_run.apply_gate_noise(
braket.circuits.Noise.Depolarizing(noise_level),
# By default, noise is applied to all gates.
# Uncomment next line to add noise only to specific gates, e.g., only to CNOT gates.
# target_gates=braket.circuits.gates.CNot,
)
result = backend.run(circuit_to_run, shots=shots).result()
return result.measurement_probabilities.get("0", 0.0)
```

At this point, the circuit can be executed to return a mitigated expectation value by running `zne.execute_with_zne()`

, as follows.

```
unmitigated = braket_ionq_execute(braket_circuit)
mitigated = zne.execute_with_zne(braket_circuit, executor=braket_ionq_execute)
print(f"Unmitigated result {unmitigated:.3f}")
print(f"Mitigated result {mitigated:.3f}")
```

```
Unmitigated result 0.943
Mitigated result 1.030
```

As long as a circuit and a function for executing the circuit are defined, the `execute_with_zne()`

function can
be called as above to return zero-noise extrapolated expectation value(s).

Warning

When using a real device, the previous method may fail because the internal compiler of the device can undo the *unitary folding* transformation that Mitiq applies to the input circuit.
If possible, one should switch off any circuit optimization performed by the hardware device.
If not possible, using *global unitary folding* as shown in the next section can also be a practical way of solving this problem.

## Options#

Different options for noise scaling and extrapolation can be passed into the `execute_with_zne()`

function.
By default, noise is scaled by locally folding gates at random, and the default extrapolation method is Richardson extrapolation.

To specify a different extrapolation technique, we can pass a different `Factory`

object to `execute_with_zne()`

.
The following code block shows an example of using linear extrapolation with five different (noise) scale factors.
Moreover, instead of *local unitary folding*, *global unitary folding* is used to scale noise.
More details on ZNE options are given here.

```
factory = zne.inference.LinearFactory(scale_factors=[1.0, 1.5, 2.0, 2.5, 3.0])
noise_scaling_method = zne.scaling.fold_global
mitigated = zne.execute_with_zne(
braket_circuit,
braket_ionq_execute,
factory=factory,
scale_noise=noise_scaling_method,
)
print(f"Mitigated result {mitigated:.3f}")
```

```
Mitigated result 0.988
```

Let’s visualize the zero-noise extrapolation fit.

```
_ = factory.plot_fit()
```

Any different combination of noise scaling and extrapolation technique can be passed as arguments to `execute_with_zne()`

.

## Lower-level usage#

Here, we show a more detailed usage of the Mitiq library which mimics what happens in the call to
`execute_with_zne()`

used in the previous sections. This low-level approach allows us to have
a better control of the error mitigation workflow.

First, we define factors to scale the circuit length by, folding the circuit using the `fold_global()`

method.

```
scale_factors = [1., 1.5, 2., 2.5, 3.]
folded_circuits = [
zne.scaling.fold_global(braket_circuit, scale)
for scale in scale_factors
]
# Check that the circuit depth is (approximately) scaled as expected
length_in = len(braket_circuit.instructions)
for j, c in enumerate(folded_circuits):
length_out = len(c.instructions)
print(f"Number of gates in folded circuit {j} scaled by: {length_out / length_in:.3f}")
```

```
Number of gates in folded circuit 0 scaled by: 1.000
Number of gates in folded circuit 1 scaled by: 1.400
Number of gates in folded circuit 2 scaled by: 2.000
Number of gates in folded circuit 3 scaled by: 2.600
Number of gates in folded circuit 4 scaled by: 3.000
```

The number of gates has been scaled to approximate the input `scale_factors`

.

For a noiseless simulation, the expectation value should be 1.0 because our circuit compiles to the identity. For a noisy simulation, the value will be smaller than one. Because folding introduces more gates and thus more noise, the result will decrease as the length of the folded circuits increases. By fitting this to a curve, we can extrapolate to the zero-noise limit and obtain a better estimate.

Below we execute the folded circuits using the executor function defined at the start of this example.

```
expectation_values = [braket_ionq_execute(c) for c in folded_circuits]
print(f"Expectation values:\n{expectation_values}")
```

```
Expectation values:
[0.9501953125, 0.904296875, 0.8916015625, 0.861328125, 0.8232421875]
```

Note

Using a *batched* executor which can take as input many circuits at once and potentially run them in parallel could speedup this step.
More details can be found in the Executors section.

We can now see the unmitigated expectation value by printing the first element of `expectation_values`

.
(This value corresponds to a circuit with scale factor one, i.e., the original circuit.)

```
print("Unmitigated expectation value:", round(expectation_values[0], 3))
```

```
Unmitigated expectation value: 0.95
```

Now we can use the static `extrapolate()`

method of `Factory`

objects to extrapolate to the zero-noise limit.
Below we use an exponential fit and print out the extrapolated zero-noise value.

```
zero_noise_value = zne.ExpFactory.extrapolate(scale_factors, expectation_values, asymptote=0.5)
print(f"Extrapolated zero-noise value:", round(zero_noise_value, 3))
```

```
Extrapolated zero-noise value: 1.023
```