qml.ftqc.get_byproduct_corrections

get_byproduct_corrections(tape, mid_meas, measurement_vals)[source]

Correct sample results offline based on the executed quantum script and the mid-circuit measurement results for each shot. The mid measurement results are first parsed with the quantum script to get the byproduct operations for each Clifford and non-Clifford gates. Note that byproduct operations are stored as a list and accessed in a stack manner. The calculation iteratively pops out the first operation in the tape and applies commutation rules for the first byproduct ops in the byproduct stack and then the results are commutated to the byproduct of the current operations in the tape if it is a Clifford gate. The calculation starts from applying commutate rules for qml.I gate or \(encode\_xz(x,z)=(0,0)\) to the first gate in the tape. The measurement corrections are returned based on the observable operators and the xz recorded.

Parameters:
  • (tape (tape) – qml.tape.QuantumScript): A Clifford quantum tape with X, Y, Z, I, H, S, CNOT and non-Clifford gates (RZ and RotXZX) at the beginning of circuit in the standard circuit formalism. Note that one non-Clifford gate per wire at most is supported.

  • mid_meas (list) – MidMeasurement results per shot.

  • measurement_vals (list) – Raw measurement results.

Returns:

A list of corrected measurement results.

Note This work is to be integrated into the MBQC transform pipeline.

Example:

import pennylane as qml

from pennylane.ftqc import diagonalize_mcms, generate_lattice, measure_x, measure_y
from pennylane.ftqc import GraphStatePrep

from pennylane.ftqc.pauli_tracker import get_byproduct_corrections
import numpy as np


def generate_random_state(n):
    seed_value = 42  # You can use any integer as the seed
    np.random.seed(seed_value)
    input_state = np.random.random(2**n) + 1j * np.random.random(2**n)
    return input_state / np.linalg.norm(input_state)


def generate_rot_gate_graph():
    lattice = generate_lattice([4], "chain")
    return lattice.graph


num_shots = 1000
dev = qml.device("lightning.qubit", shots=num_shots)

@diagonalize_mcms
@qml.qnode(dev, mcm_method="one-shot")
def circ(start_state):
    qml.StatePrep(start_state, wires=[0])
    GraphStatePrep(generate_rot_gate_graph(), wires=[1, 2, 3, 4])
    qml.CZ(wires=[0, 1])
    m0 = measure_x(0, reset=True)
    m1 = measure_y(1, reset=True)
    m2 = measure_y(2, reset=True)
    m3 = measure_y(3, reset=True)

    GraphStatePrep(generate_rot_gate_graph(), wires=[3, 2, 1, 0])
    qml.CZ(wires=[3, 4])
    m4 = measure_x(4, reset=True)
    m5 = measure_y(3, reset=True)
    m6 = measure_y(2, reset=True)
    m7 = measure_y(1, reset=True)

    GraphStatePrep(generate_rot_gate_graph(), wires=[1, 2, 3, 4])
    qml.CZ(wires=[0, 1])
    m8 = measure_x(0, reset=True)
    m9 = measure_y(1, reset=True)
    m10 = measure_y(2, reset=True)
    m11 = measure_y(3, reset=True)

    return (
        qml.sample(wires=[4]),
        qml.sample(m0),
        qml.sample(m1),
        qml.sample(m2),
        qml.sample(m3),
        qml.sample(m4),
        qml.sample(m5),
        qml.sample(m6),
        qml.sample(m7),
        qml.sample(m8), qml.sample(m9), qml.sample(m10), qml.sample(m11)
    )

init_state = generate_random_state(1)
res = circ(init_state)

ops = [qml.H(wires=[0]), qml.H(wires=[0]), qml.H(wires=[0])]
measurements = [qml.sample(qml.Z(0))]

meas_res = res[0]
mid_meas_res = res[1:]
corrected_meas_res = []

script = qml.tape.QuantumScript(ops, measurements, shots=num_shots)

for i in range(num_shots):
    mid_meas = [row[i] for row in mid_meas_res]
    corrected_meas_res.extend(get_byproduct_corrections(script, mid_meas, [meas_res[i]]))

res_corrected = 1 - 2*np.sum(corrected_meas_res) / num_shots

dev_ref = qml.device("default.qubit")

@diagonalize_mcms
@qml.qnode(dev)
def circ_ref(start_state):
    qml.StatePrep(start_state, wires=[0])
    qml.H(0)
    qml.H(0)
    qml.H(0)
    return qml.expval(qml.Z(0))

np.allclose(res_corrected, circ_ref(init_state))