Mitiq paper codeblocks#
Codeblocks from the main text of the Mitiq paper [1].
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/unitaryfund/mitiq)
Mitiq Version: 0.17.0dev
Core Dependencies
-----------------
Cirq Version: 0.14.1
NumPy Version: 1.20.3
SciPy Version: 1.7.3
Optional Dependencies
---------------------
PyQuil Version: 3.0.1
Qiskit Version: 0.36.2
Braket Version: 1.25.2
Python Version: 3.7.9
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.9415782690048214
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.9999961467128782
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: 1.787095612082784
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
folded = zne.scaling.fold_gates_from_left(
circ, scale_factor=2
)
print("Folded circuit:", folded, sep="\n")
Folded circuit:
0: ───H───H───H───@───
│
1: ───────────────X───
Codeblock 10
folded = zne.scaling.fold_gates_from_right(
circ, scale_factor=2
)
print("Folded circuit:", folded, sep="\n")
Folded circuit:
0: ───H───@───@───@───
│ │ │
1: ───────X───X───X───
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.592───Z^(11/14)───
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.009024162004731131
ZNE value via parameter noise scaling: 0.7743791937828062
Codeblock 14
zne_value = zne.execute_with_zne(
circuit,
executor,
scale_noise=zne.scaling.fold_global,
)
print("ZNE value:", zne_value)
ZNE value: 0.9415782690048214
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.8397777080535891
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.9415782690048214
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.9998500777120343
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.9022606164216984
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)),
basis_expansion = {noisy_x: 0.52, noisy_z: -0.48}
)
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.Z(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.9999972208991856
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.9999972208991856
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.4000000539078014
Codeblock 30
import qiskit
provider = qiskit.BasicAer # Use of a simulator as backend.
# provider = qiskit.IBMQ.load_account() # Alternative way to run the blocks, with saved credentials.
def execute(
circuit: qiskit.QuantumCircuit,
backend_name: str = "qasm_simulator",
shots: int = 1024
) -> float:
job = qiskit.execute(
experiments=circuit,
backend=provider.get_backend(backend_name),
optimization_level=0,
shots=shots,
)
counts = job.result().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.4775390625
References#
[1] Mitiq: A software package for error mitigation on noisy quantum computers, Ryan LaRose, Andrea Mari, Sarah Kaiser, Peter J. Karalekas, Andre A. Alves, Piotr Czarnik, Mohamed El Mandouh, Max H. Gordon, Yousef Hindy, Aaron Robertson, Purva Thakre, Nathan Shammah, and William J. Zeng, https://arxiv.org/abs/2009.04417