Getting Started

Improving the performance of your quantum programs is only a few lines of code away.

This getting started shows examples using cirq cirq and qiskit. We’ll first test mitiq by running against the noisy simulator built into cirq. The qiskit example work similarly as you will see in Qiskit Mitigation.

Multi-platform Framework

In mitiq, a “back-end” is a function that executes quantum programs. A “front-end” is a library/language that constructs quantum programs. mitiq lets you mix and match these. For example, you could write a quantum program in qiskit and then execute it using a cirq backend, or vice versa.

Back-ends are abstracted to functions called executors that always accept a quantum program, sometimes accept other arguments, and always return an expectation value as a float. You can see some examples of different executors for common packages here and in this getting started. If your quantum programming interface of choice can be used to make a Python function with this type, then it can be used with mitiq.

Error Mitigation with Zero-Noise Extrapolation

We define some functions that make it simpler to simulate noise in cirq. These don’t have to do with mitiq directly.

import numpy as np
from cirq import Circuit, depolarize
from cirq import LineQubit, X, DensityMatrixSimulator

SIMULATOR = DensityMatrixSimulator()
# 0.1% depolarizing noise
NOISE = 0.001

def noisy_simulation(circ: Circuit) -> float:
    """ Simulates a circuit with depolarizing noise at level NOISE.
    Args:
        circ: The quantum program as a cirq object.

    Returns:
        The expectation value of the |0> state.
    """
    circuit = circ.with_noise(depolarize(p=NOISE))
    rho = SIMULATOR.simulate(circuit).final_density_matrix
    # define the computational basis observable
    obs = np.diag([1, 0])
    expectation = np.real(np.trace(rho @ obs))
    return expectation

Now we can look at our example. We’ll test single qubit circuits with even numbers of X gates. As there are an even number of X gates, they should all evaluate to an expectation of 1 in the computational basis if there was no noise.

from cirq import Circuit, LineQubit, X

qbit = LineQubit(0)
circ = Circuit(X(qbit) for _ in range(80))
unmitigated = noisy_simulation(circ)
exact = 1
print(f"Error in simulation is {exact - unmitigated:.{3}}")
Error in simulation is 0.0506

This shows the impact the noise has had. Let’s use mitiq to improve this performance.

from mitiq import execute_with_zne

mitigated = execute_with_zne(circ, noisy_simulation)
print(f"Error in simulation is {exact - mitigated:.{3}}")
Error in simulation is 0.000519
print(f"Mitigation provides a {(exact - unmitigated) / (exact - mitigated):.{3}} factor of improvement.")
Mitigation provides a 97.6 factor of improvement.

You can also use mitiq to wrap your backend execution function into an error-mitigated version.

from mitiq import mitigate_executor

run_mitigated = mitigate_executor(noisy_simulation)
mitigated = run_mitigated(circ)
print(round(mitigated,5))
0.99948

Note

As shown here, mitiq wraps executor functions that have a specific type: they take quantum programs as input and return expectation values. However, one often has an execution function with other arguments such as the number of shots, the observable to measure, or the noise level of a noisy simulation. It is still easy to use these with mitiq by using partial function application. Here’s a pseudo-code example:

from functools import partial

def shot_executor(qprogram, n_shots) -> float:
    ...
# we partially apply the n_shots argument to get a function that just
# takes a quantum program
mitigated = execute_with_zne(circ, partial(shot_executor, n_shots=100))

You can read more about functools partial application here.

The default implementation uses Richardson extrapolation to extrapolate the expectation value to the zero noise limit [1]. Mitiq comes equipped with other extrapolation methods as well. Different methods of extrapolation are packaged into Factory objects. It is easy to try different ones.

from mitiq import execute_with_zne
from mitiq.zne.inference import LinearFactory

fac = LinearFactory(scale_factors=[1.0, 2.0, 2.5])
linear = execute_with_zne(circ, noisy_simulation, factory=fac)
print(f"Mitigated error with the linear method is {exact - linear:.{3}}")
Mitigated error with the linear method is 0.00638

You can read more about the Factory objects that are built into mitiq and how to create your own here.

Another key step in zero-noise extrapolation is to choose how your circuit is transformed to scale the noise. You can read more about the noise scaling methods built into mitiq and how to create your own here.

Qiskit Mitigation

Mitiq is designed to be agnostic to the stack that you are using. Thus for qiskit things work in the same manner as before. Since we are now using qiskit, we want to run the error mitigated programs on a qiskit backend. Let’s define the new backend that accepts qiskit circuits. In this case it is a simulator, but you could also use a QPU.

import qiskit
from qiskit import QuantumCircuit

# Noise simulation packages
from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.aer.noise.errors.standard_errors import depolarizing_error

# 0.1% depolarizing noise
NOISE = 0.001

QISKIT_SIMULATOR = qiskit.Aer.get_backend("qasm_simulator")

def qs_noisy_simulation(circuit: QuantumCircuit, shots: int = 4096) -> float:
    """Runs the quantum circuit with a depolarizing channel noise model at
    level NOISE.

    Args:
        circuit (qiskit.QuantumCircuit): Ideal quantum circuit.
        shots (int): Number of shots to run the circuit
                     on the back-end.

    Returns:
        expval: expected values.
    """
    # initialize a qiskit noise model
    noise_model = NoiseModel()

    # we assume a depolarizing error for each
    # gate of the standard IBM basis
    noise_model.add_all_qubit_quantum_error(depolarizing_error(NOISE, 1), ["u1", "u2", "u3"])

    # execution of the experiment
    job = qiskit.execute(
        circuit,
        backend=QISKIT_SIMULATOR,
        basis_gates=["u1", "u2", "u3"],
        # we want all gates to be actually applied,
        # so we skip any circuit optimization
        optimization_level=0,
        noise_model=noise_model,
        shots=shots
    )
    results = job.result()
    counts = results.get_counts()
    expval = counts["0"] / shots
    return expval

We can then use this backend for our mitigation.

from qiskit import QuantumCircuit
from mitiq import execute_with_zne

circ = QuantumCircuit(1, 1)
for __ in range(120):
     _ = circ.x(0)
_ = circ.measure(0, 0)

unmitigated = qs_noisy_simulation(circ)
mitigated = execute_with_zne(circ, qs_noisy_simulation)
exact = 1
# The mitigation should improve the result.
print(abs(exact - mitigated) < abs(exact - unmitigated))
True

Note that we don’t need to even redefine factories for different stacks. Once you have a Factory it can be used with different front and backends.