qml.transforms.mitigate_with_zne

mitigate_with_zne(tape, scale_factors, folding, extrapolate, folding_kwargs=None, extrapolate_kwargs=None, reps_per_factor=1)[source]

Mitigate an input circuit using zero-noise extrapolation.

Error mitigation is a precursor to error correction and is compatible with near-term quantum devices. It aims to lower the impact of noise when evaluating a circuit on a quantum device by evaluating multiple variations of the circuit and post-processing the results into a noise-reduced estimate. This transform implements the zero-noise extrapolation (ZNE) method originally introduced by Temme et al. and Li et al..

Details on the functions passed to the folding and extrapolate arguments of this transform can be found in the usage details. This transform is compatible with functionality from the Mitiq package (version 0.11.0 and above), see the example and usage details for further information.

Parameters
  • tape (QNode or QuantumTape) – the quantum circuit to be error-mitigated

  • scale_factors (Sequence[float]) – the range of noise scale factors used

  • folding (callable) – a function that returns a folded circuit for a specified scale factor

  • extrapolate (callable) – a function that returns an extrapolated result when provided a range of scale factors and corresponding results

  • folding_kwargs (dict) – optional keyword arguments passed to the folding function

  • extrapolate_kwargs (dict) – optional keyword arguments passed to the extrapolate function

  • reps_per_factor (int) – Number of circuits generated for each scale factor. Useful when the folding function is stochastic.

Returns

The transformed circuit as described in qml.transform. Executing this circuit will provide the mitigated results in the form of a tensor of a tensor, a tuple, or a nested tuple depending upon the nesting structure of measurements in the original circuit.

Return type

qnode (QNode) or tuple[List[QuantumTape], function]

Example:

We first create a noisy device using default.mixed by adding AmplitudeDamping to each gate of circuits executed on the device using the add_noise() transform:

import pennylane as qml

dev = qml.device("default.mixed", wires=2)

fcond = qml.noise.wires_in(dev.wires)
noise = qml.noise.partial_wires(qml.AmplitudeDamping, 0.05)
noise_model = qml.NoiseModel({fcond: noise})

noisy_dev = qml.add_noise(dev, noise_model)

Note

The add_noise() transform should be used on the device instead of the circuit if the defined noise_model contains a Channel instance. This is to prevent mitigate_with_zne from computing the adjoint of the channel operation during folding, which is currently not supported.

We can now set up a mitigated QNode by first decomposing it into a target gate set via decompose() and then applying this transform by passing folding and extrapolate functions. PennyLane provides native functions fold_global() and poly_extrapolate(), or richardson_extrapolate(), that allow for differentiating through them. Custom functions, as well as functionalities from the Mitiq package are supported as well (see usage details below).

import numpy as np
from functools import partial
from pennylane import qnode
from pennylane.transforms import fold_global, poly_extrapolate

n_wires = 2
n_layers = 2

shapes = qml.SimplifiedTwoDesign.shape(n_layers, n_wires)
np.random.seed(0)
w1, w2 = [np.random.random(s) for s in shapes]

@partial(
    qml.transforms.mitigate_with_zne,
    scale_factors=[1., 2., 3.],
    folding=fold_global,
    extrapolate=poly_extrapolate,
    extrapolate_kwargs={'order' : 2},
)
@partial(qml.transforms.decompose, gate_set = ["RY", "CZ"])
@qnode(noisy_dev)
def circuit(w1, w2):
    qml.SimplifiedTwoDesign(w1, w2, wires=range(2))
    return qml.expval(qml.Z(0))

Executions of circuit will now be mitigated:

>>> circuit(w1, w2)
0.19113067088978522

The unmitigated circuit result is 0.33652776 while the ideal circuit result is 0.23688169 and we can hence see that mitigation has helped reduce our estimation error.

This mitigated qnode can be differentiated like any other qnode.

>>> qml.grad(circuit)(w1, w2)
(array([-0.89319941,  0.37949841]),
 array([[[-7.04121596e-01,  3.00073104e-01]],
        [[-6.41155176e-01,  8.32667268e-17]]]))

Note

As of PennyLane v0.39, the native function fold_global() no longer decomposes the circuit as part of the folding procedure. Users are encouraged to use decompose() to unroll the circuit into a target gateset before folding, when using this transform.

Theoretical details

A summary of ZNE can be found in LaRose et al.. The method works by assuming that the amount of noise present when a circuit is run on a noisy device is enumerated by a parameter \(\gamma\). Suppose we have an input circuit that experiences an amount of noise equal to \(\gamma = \gamma_{0}\) when executed. Ideally, we would like to evaluate the result of the circuit in the \(\gamma = 0\) noise-free setting.

To do this, we create a family of equivalent circuits whose ideal noise-free value is the same as our input circuit. However, when run on a noisy device, each circuit experiences a noise equal to \(\gamma = s \gamma_{0}\) for some scale factor \(s\). By evaluating the noisy outputs of each circuit, we can extrapolate to \(s=0\) to estimate the result of running a noise-free circuit.

A key element of ZNE is the ability to run equivalent circuits for a range of scale factors \(s\). When the noise present in a circuit scales with the number of gates, \(s\) can be varied using unitary folding. Unitary folding works by noticing that a unitary \(U\) is equivalent to \(U U^{\dagger} U\). This type of transform can be applied to individual gates in the circuit or to the whole circuit. When no folding occurs, the scale factor is \(s=1\) and we are running our input circuit. On the other hand, when each gate has been folded once, we have tripled the amount of noise in the circuit so that \(s=3\). For \(s \geq 3\), each gate in the circuit will be folded more than once. A typical choice of scale parameters is \((1, 2, 3)\).

Unitary folding

This transform applies ZNE to an input circuit using the unitary folding approach. It requires a callable to be passed as the folding argument with signature

fn(circuit, scale_factor, **folding_kwargs)

where

  • circuit is a quantum tape,

  • scale_factor is a float, and

  • folding_kwargs are optional keyword arguments.

The output of the function should be the folded circuit as a quantum tape. Folding functionality is available from the Mitiq package (version 0.11.0 and above) in the zne.scaling.folding module.

Warning

Calculating the gradient of mitigated circuits is not supported when using the Mitiq package as a backend for folding or extrapolation.

Extrapolation

This transform also requires a callable to be passed to the extrapolate argument that returns the extrapolated value(s). Its function should be

fn(scale_factors, results, **extrapolate_kwargs)

where

  • scale_factors are the ZNE scale factors,

  • results are the execution results of the circuit at the specified scale factors of shape (len(scale_factors), len(qnode_returns)), and

  • extrapolate_kwargs are optional keyword arguments.

The output of the extrapolate fn should be a flat array of length len(qnode_returns).

Extrapolation functionality is available using extrapolate methods of the factories in the mitiq.zne.inference module.