Measurement-based Pauli exponentials

Download Notebook - pauli_phasor_depth1.ipynb

  • pauli_exp_depth1 implements the same rotation as pauli_exp:

\[ U_P(\theta)=e^{-i\pi\theta P/2}. \]
  • It replaces the CX ladder with an ancilla-based parity construction, measurements, and conditional corrections.

  • The depth-1 description refers to the parity-extraction construction; the full routine also includes basis changes, a rotation, measurement, and feedback.

  • This example checks every measurement branch for \(P=X_0Y_1\) on \(|00\rangle\).

  • Run from a source checkout with development dependencies installed. The repository default is little endian.

Hide code cell source

import itertools
import numpy as np
import pandas as pd
import zixy.qubit.pauli as zqp
from scipy.linalg import expm
from guppylang import guppy
from guppylang.std.angles import angle
from guppylang.std.debug import state_output
from guppylang.std.quantum import discard_array
from selene_sim import QuantumReplay, Quest
from guppyalgos.primitives.pauli.pauli_exp.pauli_exp_depth1 import pauli_exp_depth1
from guppyalgos.utils import qarray
from guppyalgos.tests.helpers import assert_allclose_ignorephase

1. Choose a nontrivial input

  • Use two qubits and theta = 0.3 half-turns.

  • Since \(X_0Y_1|00\rangle=i|11\rangle\), the expected output has a simple form:

\[ U_{X_0Y_1}(0.3)|00\rangle =\cos(0.15\pi)|00\rangle+\sin(0.15\pi)|11\rangle. \]
  • Compute the reference directly from the Pauli matrix.

n_state_qubits = 2
paulis = zqp.String.from_str("X0 Y1", n_state_qubits)
theta = 0.3
pauli_mat = np.asarray(paulis.to_sparse_matrix(True).todense())
u_mat = expm(-0.5j * np.pi * theta * pauli_mat)
initial_state = np.zeros(2**n_state_qubits)
initial_state[0] = 1
expected_state = u_mat @ initial_state

2. Build the measurement-based gadget

  • The calling circuit supplies only qreg and an angle. The gadget manages its ancillas and corrections internally.

  • Reserve two ancillas in addition to the two system qubits when simulating this example.

pauli_gadget = pauli_exp_depth1(paulis, n_state_qubits)


@guppy
def main() -> None:
    qreg = qarray(n_state_qubits)
    pauli_gadget(qreg, angle(theta))
    state_output("result_state", qreg)
    discard_array(qreg)

3. Check every measurement branch

  • Two ancilla measurements give four possible bit strings. QuantumReplay forces each one so the corrections are exercised explicitly.

  • Every corrected branch must produce the same state, up to global phase. The table reports

\[ F=|\langle\psi_{\mathrm{expected}}|\psi_{\mathrm{branch}}\rangle|^2, \qquad F=1\text{ for an exact match}. \]
  • These replayed branches are a correctness check, not an estimate of their sampling probabilities.

branches = [list(bits) for bits in itertools.product([False, True], repeat=n_state_qubits)]
simulator = QuantumReplay(
    simulator=Quest(random_seed=17),
    resume_with_measurement=True,
    measurements=branches,
)
result = (
    main.emulator(2 * n_state_qubits).with_simulator(simulator)
    .with_shots(len(branches)).run()
)
rows = []
for bits, shot in zip(branches, result.results):
    actual_state = Quest.extract_states_dict(shot)["result_state"].get_single_state()
    assert_allclose_ignorephase(expected_state, actual_state)
    rows.append({
        "Measurement branch": "".join(str(int(bit)) for bit in bits),
        "State fidelity": abs(np.vdot(expected_state, actual_state))**2,
    })
print(pd.DataFrame(rows).to_string(index=False, float_format=lambda value: f"{value:.8f}"))
Measurement branch State fidelity
                00     1.00000000
                01     1.00000000
                10     1.00000000
                11     1.00000000