Mitiq paper codeblocks#

Codeblocks from the main text of the Mitiq whitepaper [72] 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.36.0

Core Dependencies
-----------------
Cirq Version:	1.3.0
NumPy Version:	1.26.4
SciPy Version:	1.13.0

Optional Dependencies
---------------------
PyQuil Version:	3.5.4
Qiskit Version:	Not installed
Braket Version:	1.69.1

Python Version:	3.11.6
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.1021933441122005

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.595───Z^0.738───

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.009024212731721345
ZNE value via parameter noise scaling: 0.8325094118714328

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.11/site-packages/mitiq/pec/types/types.py:189: 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.X(cirq.LineQubit(0)),
    ),
    cirq.Moment(
        cirq.Z(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.500000066852893

Codeblock 30

import qiskit
from qiskit_aer import QasmSimulator
from qiskit_ibm_provider import IBMProvider


def execute(
    circuit: qiskit.QuantumCircuit,
    backend_name: str = "qasm_simulator",
    shots: int = 1024
) -> float:
    backend = QasmSimulator()  # Use of a simulator as backend.
    # backend = IBMProvider().get_backend("backend_name")  # Alternative way to run the blocks, with saved credentials.
    
    job = backend.run(
        circuit,
        backend=backend,
        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:
      ┌───┐     ┌─┐   
q1_0: ┤ H ├──■──┤M├───
      └───┘┌─┴─┐└╥┘┌─┐
q1_1: ─────┤ X ├─╫─┤M├
           └───┘ ║ └╥┘
c0: 2/═══════════╩══╩═
                 0  1 
0.5126953125