Mitiq paper codeblocks#
Codeblocks from the main text of the Mitiq whitepaper [61] 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/unitaryfund/mitiq)
Mitiq Version: 0.29.0
Core Dependencies
-----------------
Cirq Version: 1.1.0
NumPy Version: 1.23.5
SciPy Version: 1.11.2
Optional Dependencies
---------------------
PyQuil Version: 3.5.4
Qiskit Version: 0.44.1
Braket Version: 1.54.3.post0
Python Version: 3.10.12
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.2397413979202156
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.672───Z^0.839───
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.8061630934476844
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.9998500780863577
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)),
noisy_operations=[noisy_x, noisy_z],
coeffs=[0.52, -0.48],
)
/home/docs/checkouts/readthedocs.org/user_builds/mitiq/envs/stable/lib/python3.10/site-packages/mitiq/pec/types/types.py:188: 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.Z(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.Z(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.Z(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.X(cirq.LineQubit(0)),
),
cirq.Moment(
cirq.Z(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.9999972208991883
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.9999972208991883
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.7000000927430768
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.466796875