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