Mitiq paper codeblocks#
Codeblocks from the main text of the Mitiq whitepaper [84] published in Quantum.
Codeblock 1
# !pip install mitiq --quiet
Codeblock 2
import mitiq
mitiq.about()
Mitiq: A Python toolkit for implementing error mitigation on quantum computers
==============================================================================
Authored by: Mitiq team, 2020 & later (https://github.com/unitaryfoundation/mitiq)
Mitiq Version: 1.0.0
Core Dependencies
-----------------
Cirq Version: 1.6.1
NumPy Version: 2.2.6
SciPy Version: 1.17.1
Optional Dependencies
---------------------
PyQuil Version: 4.17.0
Qiskit Version: 2.1.2
Braket Version: 1.113.1
Python Version: 3.12.10
Platform Info: Linux (x86_64)
Codeblock 4
Note: The paper just shows the signature of an executor function, but here we explicitly define one to use in examples.
import cirq
from mitiq.interface import accept_any_qprogram_as_input
@accept_any_qprogram_as_input
def executor(circuit: mitiq.QPROGRAM) -> float:
return cirq.DensityMatrixSimulator().simulate(
circuit.with_noise(cirq.depolarize(p=0.01))
).final_density_matrix[0, 0].real
Codeblock 5
from mitiq import zne
circuit = cirq.Circuit([cirq.X.on(cirq.LineQubit(0))] * 50)
zne_value = zne.execute_with_zne(circuit, executor)
print("ZNE value:", zne_value)
ZNE value: 0.9415791034698486
Codeblock 6
Note: The paper shows pseudocode; here we show an example.
zne_value = zne.execute_with_zne(
circuit,
executor,
scale_noise=zne.scaling.fold_global,
factory=zne.inference.ExpFactory(scale_factors=[1.0, 3.0, 5.0, 7.0, 9.0])
)
print("ZNE value:", zne_value)
ZNE value: 0.9999995687370389
Codeblock 7
Note: The paper shows pseudocode; here we show an example.
from mitiq import pec
representation = pec.represent_operation_with_local_depolarizing_noise(
ideal_operation=cirq.Circuit(circuit[0]), noise_level=0.01
)
print("Representation of ideal operation:", representation, sep="\n\n")
pec_value = pec.execute_with_pec(
circuit,
executor,
representations=[representation],
num_samples=10, # Remove argument or increase for better accuracy.
)
print("\n\nPEC value:", pec_value)
Representation of ideal operation:
0: ───X─── = 1.010*(0: ───X───)-0.003*(0: ───X───X───)-0.003*(0: ───X───Y───)-0.003*(0: ───X───Z───)
PEC value: 0.8285169807908908
Codeblock 8
qreg = cirq.LineQubit.range(2)
circ = cirq.Circuit(
cirq.ops.H.on(qreg[0]),
cirq.ops.CNOT.on(qreg[0] , qreg[1])
)
print("Original circuit:", circ, sep="\n")
Original circuit:
0: ───H───@───
│
1: ───────X───
Codeblock 9
Warning
The method zne.scaling.fold_gates_from_left has been removed as of v0.36.0.
folded = zne.scaling.fold_gates_from_left(
circ, scale_factor=2
)
print("Folded circuit:", folded, sep="\n")
Codeblock 10
Warning
The method zne.scaling.fold_gates_from_right has been removed as of v0.36.0.
folded = zne.scaling.fold_gates_from_right(
circ, scale_factor=2
)
print("Folded circuit:", folded, sep="\n")
Codeblock 11
folded = zne.scaling.fold_global(circ, scale_factor=3.)
print("Folded circuit:", folded, sep="\n")
Folded circuit:
0: ───H───@───@───H───H───@───
│ │ │
1: ───────X───X───────────X───
Codeblock 12
Note: The paper shows pseudocode; here we show an example.
# Example of parameter-noise scaling.
q = cirq.LineQubit(0)
circuit2 = cirq.Circuit(cirq.X.on(q) ** (3 / 5), cirq.Z.on(q) ** (4 / 5))
print("Circuit:", circuit2, sep="\n")
scaled = zne.scaling.scale_parameters(circuit2, scale_factor=2.0, base_variance=0.01)
print("\nScaled circuit:", scaled, sep="\n")
Circuit:
0: ───X^0.6───Z^0.8───
Scaled circuit:
0: ───X^0.617───Z^0.816───
Codeblock 13
Note: The paper shows pseudocode; here we show an example.
from functools import partial
from mitiq.zne.scaling import compute_parameter_variance, scale_parameters
# Estimate base level of parameter noise
base_variance = compute_parameter_variance(executor, cirq.X, cirq.LineQubit(0))
print("Estimation of parameter noise variance:", base_variance)
scale_param_noise = partial(scale_parameters, base_variance=base_variance)
zne_value = zne.execute_with_zne(
circuit,
executor,
scale_noise=scale_param_noise,
num_to_average=10,
)
# Parameter-noise mitigation is designed for random, coherent noise. For simplicity, we use depolarizing noise here, so we don't expect the best performance.
print("ZNE value via parameter noise scaling:", zne_value)
Estimation of parameter noise variance:
0.009024178
ZNE value via parameter noise scaling: 0.9003973215818409
Codeblock 14
zne_value = zne.execute_with_zne(
circuit,
executor,
scale_noise=zne.scaling.fold_global,
)
print("ZNE value:", zne_value)
ZNE value: 0.9415791034698486
Codeblock 15
linear_factory = zne.inference.LinearFactory(scale_factors=[1.0, 2.0, 3.0])
Codeblock 16
zne_value = zne.execute_with_zne(
circuit,
executor,
factory=linear_factory,
)
print("ZNE value:", zne_value)
ZNE value: 0.8397779464721679
Codeblock 17
zne_value = zne.execute_with_zne(
circuit,
executor,
factory=zne.inference.PolyFactory(
scale_factors=[1.0, 2.0, 3.0], order=2
),
)
print("ZNE value:", zne_value)
ZNE value: 0.9415791034698486
Codeblock 18
zne_value = zne.execute_with_zne(
circuit,
executor,
factory=zne.inference.AdaExpFactory(
scale_factor=2.0, steps=5
),
)
print("ZNE value:", zne_value)
ZNE value: 0.9998546479905079
Codeblock 19
from mitiq.zne.inference import BatchedFactory, PolyFactory
import numpy as np
class MyFactory(BatchedFactory):
@staticmethod
def extrapolate(scale_factors, exp_values, full_output):
zne_result, *extras = PolyFactory.extrapolate(
scale_factors, exp_values, order=2, full_output=True
)
zne_result = np.clip(zne_result, -1.0, 1.0)
return zne_result if not full_output else (zne_result, *extras)
# Example usage.
zne_value = zne.execute_with_zne(
circuit,
executor,
factory=MyFactory(scale_factors=[1, 3, 5])
)
print("ZNE value:", zne_value)
ZNE value: 0.9022610038518891
Codeblock 20
from mitiq import pec
noisy_x = pec.NoisyOperation(
circuit=cirq.Circuit(cirq.X.on(q)),
channel_matrix=np.random.randn(4, 4),
)
noisy_z = pec.NoisyOperation(
circuit=cirq.Circuit(cirq.Z.on(q)),
channel_matrix=np.random.randn(4, 4),
)
Codeblock 21 & 22
h_rep = pec.OperationRepresentation(
ideal=cirq.Circuit(cirq.H.on(q)),
noisy_operations=[noisy_x, noisy_z],
coeffs=[0.52, -0.48],
)
/home/docs/checkouts/readthedocs.org/user_builds/mitiq/checkouts/stable/mitiq/pec/types/types.py:185: UserWarning: The sum of the coefficients is different from 1.
warnings.warn("The sum of the coefficients is different from 1.")
Codeblock 23
noisy_op, sign, coeff = h_rep.sample()
Codeblock 24
circuit3 = cirq.Circuit([cirq.H.on(q)] * 5)
sampled, sign, norm = pec.sample_circuit(circuit3, representations=[h_rep])
print("Sampled circuit:", sampled, sep="\n") # Run many times to see different sampled circuits!
Sampled circuit:
[cirq.Circuit([
cirq.Moment(
cirq.X(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.X(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.X(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.X(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.X(cirq.LineQubit(0)),
),
])]
Note: For a runnable code block in which PEC is applied to estimate an expectation value, see Codeblock 7.
Codeblock 25 & 26
See https://mitiq.readthedocs.io/en/stable/examples/cdr_api.html.
Codeblock 27
mitigated_executor = zne.mitigate_executor(
executor, scale_noise=zne.scaling.fold_global, factory=zne.inference.ExpFactory(scale_factors=[1, 3, 5, 7])
)
zne_value = mitigated_executor(circuit)
print("ZNE value:", zne_value)
ZNE value: 0.9999994906573442
Codeblock 28
@zne.zne_decorator(factory=zne.inference.ExpFactory(scale_factors=[1, 3, 5, 7]), scale_noise=zne.scaling.fold_gates_at_random)
def execute(circuit: cirq.Circuit) -> float:
return cirq.DensityMatrixSimulator().simulate(
circuit.with_noise(cirq.depolarize(p=0.01))
).final_density_matrix[0, 0].real
zne_value = execute(circuit)
print("ZNE value:", zne_value)
ZNE value: 0.9999994906573442
Codeblock 29
@pec.pec_decorator(representations=[h_rep], num_samples=10)
@zne.zne_decorator(
factory=zne.inference.ExpFactory(scale_factors=[1, 3, 5, 7]),
scale_noise=zne.scaling.fold_gates_at_random
)
def execute(circuit: cirq.Circuit) -> float:
return cirq.DensityMatrixSimulator().simulate(
circuit.with_noise(cirq.depolarize(p=0.01))
).final_density_matrix[0, 0].real
zne_then_pec_value = execute(circuit3)
print("ZNE then PEC value:", zne_then_pec_value) # Note this is not accurate (bad representation).
ZNE then PEC value: -0.400000034780663
Codeblock 30
import qiskit
from qiskit_aer import QasmSimulator
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
def execute(
circuit: qiskit.QuantumCircuit,
backend_name: str = "qasm_simulator",
shots: int = 1024
) -> float:
backend = QasmSimulator() # Use of a simulator as backend.
# backend = QiskitRuntimeService().least_busy(operational=True, simulator=False) # Alternative way to run the blocks, with saved credentials.
pm = generate_preset_pass_manager(
backend=backend,
optimization_level=0,
)
exec_circuit = pm.run(circuit)
if not isinstance(exec_circuit, list):
exec_circuit = [exec_circuit]
sampler = Sampler(backend)
job = sampler.run(
exec_circuit,
shots=shots,
)
result = job.result()[0]
counts = result.join_data().get_counts()
return counts.get("00", 0.0) / shots
# Example usage.
qreg = qiskit.QuantumRegister(2)
creg = qiskit.ClassicalRegister(2)
circ = qiskit.QuantumCircuit(qreg, creg)
circ.h(qreg[0])
circ.cx(*qreg)
circ.measure(qreg, creg)
print("Circuit:")
print(circ)
execute(circ)
Circuit:
┌───┐ ┌─┐
q0_0: ┤ H ├──■──┤M├───
└───┘┌─┴─┐└╥┘┌─┐
q0_1: ─────┤ X ├─╫─┤M├
└───┘ ║ └╥┘
c0: 2/═══════════╩══╩═
0 1
0.509765625