Getting Started with Error Mitigation (Mitiq) on Braket
Overview
# --- Setup cell added by QCR (not part of the original tutorial) ---
# Source: amazon-braket/amazon-braket-examples @ 0c0818f315479aab9deebed7e7ed7533ac581923, Apache License 2.0.
# Installs the example's dependencies. If a later cell still reports a missing
# package, restart the runtime/kernel and run again from the top.
%pip install -q amazon-braket-sdk==1.117.3 mitiq
Getting started with error mitigation with Mitiq on Braket
In this notebook we will look at Mitiq, a python library for performing quantum error mitigation, in tandem with performance features from Amazon Braket for quantum computational applications. In subsequent notebooks, we show how to utilize Mitiq with Amazon Braket, specifically focusing on utilizing program sets to orchestrate job submissions, reducing costs and improving accuracies for quantum jobs.
Why do we need error mitigation?
Realistic quantum systems couple to external environments. These interactions can result in information loss or distortion of the system of interest. In digital quantum computing, we can understand this simply as errors affecting our simulation (for instance, bit-flip or phase errors), and the strength of the interaction typically translates to the frequency or strength of noise. In principle, given certain error rates, we can use tools from quantum error correction to fully correct, these errors, but these are beyond the capabilities of systems today.
We can instead try to mitigate errors or harness the noisy signal within a noisy quantum system, up to the decay time of our system. Quantum error mitigation (QEM, see Review) techniques attempts to do just this, similar to improving the signal-to-noise ratio of a noisy signal, generally at the cost of repeated measurements. We define quantum error mitigation, or just error mitigation, as tools or techniques which can reduce or mitigate the effect of quantum errors in a system, using approaches without active feedback. That is, extra copies of states can be used, and post processing channels can be applied to a system, but we do not allow for active feedback. This has helped to theoretically define the limits of quantum error mitigation (see Takagi et al.).
The cost of error mitigation
Error mitigation techniques have been shown (Takagi et al., Quek et al.) to have a fundamentally exponential scaling to obtain unbiased results. That is, to correct all error for arbitrary input states requires an exponential number of shots. This exponential requirement is present in the variance of the estimation, which naively scales as
We include multiple references at the end for the interested reader. Importantly, this cost is reduced based on the quality of the method, the depth of the gates, and the limiting gate fidelities, generally a two qubit error rate.
However, it is critical to know what is worth spending more time and executions on. Utilizing program sets is only half the battle - by batching we can generally reduce costs, seeing up to 100x decreases in cost, but we also want to be aware of where our variances are coming from, and that we have allocated our resources accordingly. This is a challenging task, and generally can vary depending on problems, circuits, devices, and mitigation techniques.
Using Mitiq and Braket
Mitiq is an open-source Python toolkit for implementing error mitigation techniques, which covers a breadth of different methodologies and tools.
The Mitiq library provides numerous error mitigation approaches as pre-implemented strategies. These include (but are not limited to):
- Readout error mitigation (
mitiq.rem) - Zero-noise extrapolation (
mitiq.zne) - Probabilistic Error Cancellation (
mitiq.pec) - Probabilistic Error Amplification (
mitiq.pea) - Pauli Twirling (
mitiq.pt) - Classical Shadows (
mitiq.shadows) - Quantum Subspace Expansions for Stabilizers (
mitiq.qse) - Clifford Data Regression (
mitiq.cdr) - Virtual Distillation (
mitiq.vd) - Digital Dynamical Decoupling (
mitiq.ddd)
and more. See the documentation for a full list of supported methods.
Patterns in Mitiq
Mitiq contains two methods for applying error mitigation generally. The first can be thought of as a simple function call in which one passes a Circuit and the QEM technique parameters into a function mitiq.xyz.execute_with_xyz. This uses an executor object, which is detailed below.
from mitiq.zne import execute_with_zne
zne_result = execute_with_zne(circuit, executor, *args)
The second unpacks this procedure, separating the creation of circuits and their execution and reassembly:
from mitiq.zne import construct_circuits, combine_results
modified_circuits = construct_circuits(circuit)
raw_data = executor(modified_circuits)
zne_result = combine_results(raw_data)
in the mitiq_braket_tools.py file, we show executors which can easily be used with the first pattern, and meets the requirements of a mitiq.executor.Executor object. The second we will explore more in a future notebook, and is develop in the parent tools folder, i.e. in the tools/program_set_tools.py file.
Further details can be found in the Mitiq guide.
Using Verbatim circuits
For most of these approaches, we will want to treat our circuits using the Verbatim box. This creates a more reliable thread between the submitted circuits and the noise structure which is being treated, and also allows us to skip the Braket service compiler.
That is, given a circuit of interest, we may:
- Pass our circuit through a compiler / transpiler to nativize it
- Pass our circuit through Mitiq to create copies or multiple instances
- Run our circuits on the Braket service using Verbatim compilation
For simplicity, we will use the Qiskit-Braket provider to harness the Qiskit transpiler, though alternatively the Braket compiler can be used as well by submitting a job to the service against a compiler and returning the output circuit.
Installing Mitiq
Mitiq is not included by default with the amazon-braket-examples or in the Amazon Braket notebook instances. To install, uncomment the first line in the next code block, and if necessary, restart the notebook. Mitiq utilizes Cirq as the backend, and often times will represent circuits using their representation.
# %pip install mitiq cirq-ionq
try:
import mitiq # noqa: F401
print("Package 'mitiq' is installed.")
except ImportError:
print("Package 'mitiq' is not installed.")Package 'mitiq' is installed.
We can then import other packages. Throughout we use pre-packaged noise models or emulators, which are detailed in the noise_model.py file, and can all be carried out locally. In the final notebook we look at a QPU-related example, which has associated costs.
import os
import sys
from mitiq.observable import Observable, PauliString
sys.path.append(os.path.abspath(os.path.join(os.getcwd(), os.pardir)))
from qiskit_braket_provider import to_braket, to_qiskit
from tools.noise_models import qd_depol
from braket.circuits import CircuitExecutors and Observables
Executors are the main engines for running mitiq. These are quite flexible, and are detailed more on the mitiq page.
Generally these can be user-defined functions which input Circuits and output either floats or Measurement-type results. The braket_expectation_executor and braket_measurement_executor are Executors for Braket Observables or mitiq Observables, respectively.
The Executors are essentially wrappers around ProgramSets, and will fail it you try to submit more executables than the program set limit of a given device.
from mitiq_braket_tools import braket_expectation_executor, braket_measurement_executorBelow we perform a simple zero-noise extrapolation experiment using the braket_expectation_executor executor, i.e. the Executor receives a float output.
import warnings
from mitiq.zne import RichardsonFactory, execute_with_zne
from braket.circuits.observables import Z
warnings.filterwarnings("ignore", category=UserWarning)
base_circ = Circuit().h(0).cnot(0,1)
base_circ = to_braket(to_qiskit(base_circ, add_measurements=False), basis_gates=["rz","rx", "cx"])
print(base_circ)
# initialize executor
executor = braket_expectation_executor(qd_depol, Z(0) @ Z(1), shots = 10000, verbatim= False, batch_if_possible=False)
# noisy result as a reference
noisy_result = executor.evaluate(base_circ)[0]
print(f"Noisy result: {noisy_result}")
# perform the ZNE in one step
factory = RichardsonFactory([1., 3., 5.])
zne_result = execute_with_zne(base_circ, executor, factory=factory)
print(f"Zero-noise extrapolation Result: {zne_result}")
# now, test average performance
noisy, zne = [], []
for i in range(10):
noisy.append(executor.evaluate(base_circ)[0])
zne.append(execute_with_zne(base_circ, executor, factory=factory))
avg_noisy = sum([abs(1-a) for a in noisy])/10
avg_zne = sum([abs(1-a) for a in zne])/10
print(f"{100*(1 - (avg_zne)/(avg_noisy)):.2f}% average reduction in error")T : │ 0 │ 1 │ 2 │ 3 │
┌──────────┐ ┌──────────┐ ┌──────────┐
q0 : ─┤ Rz(1.57) ├─┤ Rx(1.57) ├─┤ Rz(1.57) ├───●───
└──────────┘ └──────────┘ └──────────┘ │
┌─┴─┐
q1 : ────────────────────────────────────────┤ X ├─
└───┘
T : │ 0 │ 1 │ 2 │ 3 │
Noisy result: 0.9454
Zero-noise extrapolation Result: 1.0150499999999996
86.61% average reduction in error
In comparison, the braket_measurement_executor returns a mitiq.MeasurementResult, and can support mitiq-based Observables in the executor.
zz = Observable(PauliString("ZZ", coeff = 1)) #mitiq Observable and PauliString
executor_2 = braket_measurement_executor(qd_depol, 10000, False, batch_if_possible=False)
noisy_result = executor_2.evaluate(base_circ, zz)[0]
print(f"Noisy Result: {noisy_result}")
factory = RichardsonFactory([1., 3., 5.])
zne_result = execute_with_zne(base_circ, executor_2, zz, factory=factory)
print(f"Zero-noise extrapolation Result: {zne_result}")
Noisy Result: (0.9478+0j) Zero-noise extrapolation Result: (1.0002499999999994+0j)
Calibrators
We can also use Mitiq's built in Calibrator, to assess which approaches may be most suitable for a particular Executor, method, or Circuit. These can be defined for certain protocols or backends. The Calibrator below focuses on variations of ZNE. The total cost can be easily calculated as
More details are given on the Calibrator here.
from mitiq.calibration import Calibrator
cal = Calibrator(executor_2, frontend = "braket")
print(cal.get_cost())
{'noisy_executions': 100, 'ideal_executions': 0}
cal.run()Below we can see how the Calibrator performs for variants of ZNE on different types of reference circuits and two-qubit gate counts.
cal.results.log_results_cartesian()┌────────────────────────────────────┬────────────────────────────┬────────────────────────────┬────────────────────────────┬────────────────────────────┐ │ strategy\benchmark │ Type: ghz │ Type: w │ Type: rb │ Type: mirror │ │ │ Num qubits: 2 │ Num qubits: 2 │ Num qubits: 2 │ Num qubits: 2 │ │ │ Circuit depth: 2 │ Circuit depth: 2 │ Circuit depth: 48 │ Circuit depth: 33 │ │ │ Two qubit gate count: 1 │ Two qubit gate count: 2 │ Two qubit gate count: 12 │ Two qubit gate count: 14 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✘ │ ✔ │ ✔ │ ✔ │ │ Factory: Richardson │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0291 │ Mitigated error: 0.4442 │ Mitigated error: 0.1604 │ Mitigated error: 0.1555 │ │ Scale method: fold_global │ Improvement factor: 0.2405 │ Improvement factor: 1.0099 │ Improvement factor: 2.6845 │ Improvement factor: 2.6759 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✘ │ ✘ │ ✔ │ ✔ │ │ Factory: Richardson │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0165 │ Mitigated error: 0.4973 │ Mitigated error: 0.2365 │ Mitigated error: 0.1945 │ │ Scale method: fold_global │ Improvement factor: 0.4249 │ Improvement factor: 0.902 │ Improvement factor: 1.821 │ Improvement factor: 2.1389 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✔ │ ✘ │ ✔ │ ✔ │ │ Factory: Linear │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0007 │ Mitigated error: 0.492 │ Mitigated error: 0.332 │ Mitigated error: 0.2979 │ │ Scale method: fold_global │ Improvement factor: 10.5 │ Improvement factor: 0.9118 │ Improvement factor: 1.297 │ Improvement factor: 1.3969 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✘ │ ✘ │ ✔ │ ✔ │ │ Factory: Linear │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.007 │ Mitigated error: 0.4702 │ Mitigated error: 0.3997 │ Mitigated error: 0.3652 │ │ Scale method: fold_global │ Improvement factor: 0.9941 │ Improvement factor: 0.9541 │ Improvement factor: 1.0773 │ Improvement factor: 1.1393 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✘ │ ✔ │ ✔ │ ✔ │ │ Factory: Richardson │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0431 │ Mitigated error: 0.4271 │ Mitigated error: 0.1293 │ Mitigated error: 0.1197 │ │ Scale method: fold_gates_at_random │ Improvement factor: 0.1624 │ Improvement factor: 1.0503 │ Improvement factor: 3.3302 │ Improvement factor: 3.4762 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✔ │ ✘ │ ✔ │ ✔ │ │ Factory: Richardson │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0044 │ Mitigated error: 0.4934 │ Mitigated error: 0.2145 │ Mitigated error: 0.1958 │ │ Scale method: fold_gates_at_random │ Improvement factor: 1.5819 │ Improvement factor: 0.9093 │ Improvement factor: 2.0076 │ Improvement factor: 2.1255 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✘ │ ✘ │ ✔ │ ✔ │ │ Factory: Linear │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0148 │ Mitigated error: 0.4713 │ Mitigated error: 0.3071 │ Mitigated error: 0.3106 │ │ Scale method: fold_gates_at_random │ Improvement factor: 0.473 │ Improvement factor: 0.9519 │ Improvement factor: 1.402 │ Improvement factor: 1.3397 │ ├────────────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ Technique: ZNE │ ✔ │ ✘ │ ✔ │ ✔ │ │ Factory: Linear │ Noisy error: 0.007 │ Noisy error: 0.4486 │ Noisy error: 0.4306 │ Noisy error: 0.4161 │ │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0003 │ Mitigated error: 0.4732 │ Mitigated error: 0.395 │ Mitigated error: 0.3589 │ │ Scale method: fold_gates_at_random │ Improvement factor: 24.0 │ Improvement factor: 0.9481 │ Improvement factor: 1.09 │ Improvement factor: 1.1595 │ └────────────────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┘
Here we can see that mitiq will perform a scan of multiple methods at a lower accuracy, and provide recommendations for which approach achieves the best. With a particular gate structure, folding strategy, etc., and noise rate, this can provide a hands-off approach for assessing viable approaches.
We can also inspect our Executor, looking at relevant outputs and the total number of circuits run.
executor_2.calls_to_executor104
Summary
In this notebook we saw an overview of mitiq, as well as how one method to utilize mitiq with program sets. Generally, error mitigation allows us to reliably expand what is possible on today's quantum computers, and can help deliver more reliable and useful results for noisy quantum devices.
In subsequent notebooks we will dive into common error mitigation strategies, and provide explicit implementations which you can utilize for systems of interest.
References
- Unitary Foundation, Mitiq Documentation, https://mitiq.readthedocs.io/en/stable/index.html. Accessed 12/1/2025.
- Cai et al., Quantum error mitigation (2023) Rev. Mod. Phys., 95, 045005.
- Wang et al., Amazon Braket introduces program sets enabling customers to run quantum programs up to 24x faster, AWS Quantum Technologies Blog. Accessed 12/1/2025.
- Quek et al., Exponentially tighter bounds on limitations of quantum error mitigation (2024), Nat Phys. 20, 1648-1658.
- Takagi et al., Fundamental limits of quantum error mitigation (2022) npj Quantum Inf. 8, 114.
- Aharonov et al. On the importance of Error Mitigation for Quantum Computation (2025), arxiv:2503.17243.
This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.
Versions
Cite all versions? Use the base QCR ID to always reference the latest version of this entry.
Join the Discussion
Comments (0)
No comments yet. Be the first to share your thoughts!