Qermit

Qermit is a python module for running error-mitigation protocols. It contains an assortment of ready to use error-mitigation schemes. Qermit supports straightforward composition of these existing error-mitigation methods, and the development of new error-mitigation schemes.

Qermit is an extension to the pytket quantum software development kit. Qermit uses the pytket Backend class, and so supports an array of backends.

Getting Started

pip install qermit

Please visit the github repository if you would like to install from source. We also welcome contributions to Qermit there!

If you notice any bugs while using Qermit we would much appreciate you raising them as issues at that repository.

If you would like an further support with Qermit please contact tket-support@quantinuum.com.

Key Notions

Error-mitigation methods in Qermit are either one of two types:

  • MitRes methods which modify a distribution of counts.

  • MitEx methods which modify the expectation value of some observable.

While Qermit includes many advanced error-mitigation schemes, in their basic capacity, MitRes and MitEx objects will run experiments without error-mitigation, as in the following example.

from qermit import MitRes, CircuitShots
from pytket import Circuit
from pytket.extensions.qiskit import AerBackend

# Define the experiment to be run.
# In this case, a Bell pair is prepared and measured 1000 times.
circ = Circuit(2,2).H(0).CX(0,1).measure_all()
circ_shots = CircuitShots(Circuit=circ, Shots=1000)

# Define the way the experiment will be run.
# In this case the default MitRes, without error-mitigation,
# will be performed.
mitres = MitRes(backend = AerBackend())

# Finally, run the experiment.
results = mitres.run([circ_shots])
results[0].get_counts()
Counter({(1, 1): 513, (0, 0): 487})

Here we have introduced the CircuitShots. This can be thought of a defining experiment to run to generated a distribution of shots. In this case the Bell state preparation circuit will be run 1000 times to generate the desired distribution. The MitRes definition can then be thought of as specifying how the experiment will run, in this case without error-mitigation, and by using AerBackend. The input to the run() method is then a list CircuitShots, with the output being a list of BackendResult.

MitRes and MitEx are built from a graph of MitTask objects. A MitTask object is a pure function which computes some basic step in an experiment. These MitTask are composed into a TaskGraph which defined how inputs and outputs pass between the tasks. Indeed, MitRes and MitEx are instances of a TaskGraph.

In its default construction, a MitRes object will simply run each circuit through the backend it is defined by. In the following we see one task, CircuitsToHandles, submitting the circuits and generating handles for those experiments, and a second, HandlesToResults, retrieving the results using those handles. The information passed along the central wire is simply those handles.

mitres.get_task_graph()
_images/6a610d1449d2247d5cf62a621c9603ca72a925845291c651060d7a3c65b88195.svg

Similarly, in its default construction a MitEx object will simply estimate the expectation of each observable desired without applying any mitigation method.

from qermit import (
   MitEx,
   AnsatzCircuit,
   ObservableExperiment,
   ObservableTracker,
   SymbolsDict,
)
from pytket import Qubit
from pytket.pauli import Pauli, QubitPauliString
from pytket.utils import QubitPauliOperator

# Define the experiment to be conducted. In this case that consists
# of two parts.
# 1.  The circuit to run. In the case of MitEx we use AnsatzCircuit
#     which allows parametrised circuit. In this case we do not use
#     this capability however.
ansatz_circuit = AnsatzCircuit(
   Circuit=Circuit(3,3).X(0).X(1),
   Shots=50,
   SymbolsDict=SymbolsDict(),
)

# 2.  The observable to be measured. In this case the observable
#     is only the ZZ observable on qubits 1 and 2.
qubit_pauli_string = QubitPauliString([Qubit(1), Qubit(2)], [Pauli.Z, Pauli.Z])
qubit_pauli_operator = QubitPauliOperator({qubit_pauli_string: 1.0})

# These are combined to define the experiment.
experiment = ObservableExperiment(
   AnsatzCircuit = ansatz_circuit,
   ObservableTracker = ObservableTracker(qubit_pauli_operator)
)

# Now we can define how the experiment is run.
# In this case the default MitEx, without error-mitigation, is performed.
mitex = MitEx(backend = AerBackend())
mitex.run([experiment])
[{(Zq[1], Zq[2]): -1.00000000000000}]

The run() method takes a list of ObservableExperiment as an argument. Each ObservableExperiment contains the basic information required to estimate the expectation value of an observable; a state preparation circuit, a dictionary between symbols and parameter values (where appropriate), a QubitPauliOperator detailing the operator being measured and used for preparing measurement circuits, and the number of shots to run for each measurement circuit.

Each experiment returns a QubitPauliOperator containing an expectation value for each internal QubitPauliString. In its default version, this is achieved by appending a measurement circuit for each QubitPauliString to the ansatz circuit and executing through the Backend the MitEx object is defined by.

mitex.get_task_graph()
_images/f83025e34b40ea15802e488ecc20336c7deae4c16ad5fa05e7acb266c85d1b9f.svg

Contents

API Reference: