Statevector testing utilities

Download Notebook - statevector_testing.ipynb

Simulation lets you check quantum amplitudes, complete operations, and individual measurement branches. These examples introduce the helpers for each task.

  • Inspect a state: compare its amplitudes and relative phases.

  • Extract an operation: check its action on every computational-basis input.

  • Post-select a branch: inspect the state or operation conditioned on chosen outcomes.

  • Replay measurements: exercise success, failure, and correction paths predictably.

Run the cells in order; later examples reuse earlier imports and definitions.

from guppylang.std.quantum import (
    discard_array,
    discard,
    x,
    ry, 
    cx,
    qubit,
    h
)
from guppylang.std.angles import angle
from guppylang import guppy
from guppylang.std.debug import state_output
from guppylang.std.builtins import comptime, array, nat
from guppyalgos.utils import qarray
import numpy as np

Inspect a statevector

Use get_statevector to inspect the complex amplitudes of a prepared state:

\[ |\psi\rangle = \sum_{x=0}^{2^n-1} a_x |x\rangle, \qquad P(x)=|a_x|^2. \]
  • Record the state with state_output("result_state", state) before discarding the qubits.

  • The tag must be "result_state"; this is a simulation snapshot, not a measurement.

  • The returned NumPy array contains the amplitudes \(a_x\), including their relative phases.

The example below applies the same Y rotation to each of four qubits.

n_qubits = 4
@guppy
def main_sv() -> None:
    state = qarray(n_qubits)

    for i in range(n_qubits):
        ry(state[i], angle(3.14/2))

    state_output("result_state", state)
    discard_array(state)
from guppyalgos.tests.helpers import get_statevector, get_unitary
get_statevector(main_sv, n_qubits)
array([ 0.37096824-0.00000000e+00j, -0.2972016 +1.81983495e-17j,
       -0.2972016 +1.81983495e-17j,  0.23810338-2.91592544e-17j,
       -0.2972016 +1.81983495e-17j,  0.23810338-2.91592544e-17j,
        0.23810338-2.91592544e-17j, -0.19075678+3.50414518e-17j,
       -0.2972016 +1.81983495e-17j,  0.23810338-2.91592544e-17j,
        0.23810338-2.91592544e-17j, -0.19075678+3.50414518e-17j,
        0.23810338-2.91592544e-17j, -0.19075678+3.50414518e-17j,
       -0.19075678+3.50414518e-17j,  0.152825  -3.74313289e-17j])

Extract a unitary matrix

Use get_unitary to check a whole operation. Each column is the output for one computational-basis input:

\[ U_{yx} = \langle y|U|x\rangle. \]
  • Supply a circuit that accepts one qubit array and leaves it available to its caller.

  • The helper prepares the basis inputs and records the outputs for you; no state_output call is needed.

  • Compare complex matrix entries when relative phases matter. Measurement probabilities alone cannot detect every phase error.

@guppy
def main_unitary[n_s: nat](state: array[qubit, n_s]) -> None:

    for i in range(n_qubits):
        ry(state[i], angle(3.14/2))
get_unitary(main_unitary, n_qubits)
array([[ 0.37096824+0.00000000e+00j,  0.2972016 +2.27152533e-17j,
         0.2972016 +2.27152533e-17j,  0.23810338+3.63966991e-17j,
         0.2972016 +2.27152533e-17j,  0.23810338+3.63966991e-17j,
         0.23810338+3.63966991e-17j,  0.19075678+4.37388816e-17j,
         0.2972016 +2.27152533e-17j,  0.23810338+3.63966991e-17j,
         0.23810338+3.63966991e-17j,  0.19075678+4.37388816e-17j,
         0.23810338+3.63966991e-17j,  0.19075678+4.37388816e-17j,
         0.19075678+4.37388816e-17j,  0.152825  +4.67219358e-17j],
       [-0.2972016 +1.81983495e-17j,  0.37096824-4.09136029e-17j,
        -0.23810338-3.61872232e-18j,  0.2972016 -1.00627234e-17j,
        -0.23810338-3.61872232e-18j,  0.2972016 -1.00627234e-17j,
        -0.19075678-1.74787705e-17j,  0.23810338+1.01365879e-17j,
        -0.23810338-3.61872232e-18j,  0.2972016 -1.00627234e-17j,
        -0.19075678-1.74787705e-17j,  0.23810338+1.01365879e-17j,
        -0.19075678-1.74787705e-17j,  0.23810338+1.01365879e-17j,
        -0.152825  -2.56836196e-17j,  0.19075678+2.27005655e-17j],
       [-0.2972016 +1.81983495e-17j, -0.23810338-3.61872232e-18j,
         0.37096824-4.09136029e-17j,  0.2972016 -1.00627234e-17j,
        -0.23810338-3.61872232e-18j, -0.19075678-1.74787705e-17j,
         0.2972016 -1.00627234e-17j,  0.23810338+1.01365879e-17j,
        -0.23810338-3.61872232e-18j, -0.19075678-1.74787705e-17j,
         0.2972016 -1.00627234e-17j,  0.23810338+1.01365879e-17j,
        -0.19075678-1.74787705e-17j, -0.152825  -2.56836196e-17j,
         0.23810338+1.01365879e-17j,  0.19075678+2.27005655e-17j],
       [ 0.23810338-2.91592544e-17j, -0.2972016 +5.09763263e-17j,
        -0.2972016 +5.09763263e-17j,  0.37096824-8.18272057e-17j,
         0.19075678-8.78134068e-18j, -0.23810338+2.26413888e-17j,
        -0.23810338+2.26413888e-17j,  0.2972016 -4.28407001e-17j,
         0.19075678-8.78134068e-18j, -0.23810338+2.26413888e-17j,
        -0.23810338+2.26413888e-17j,  0.2972016 -4.28407001e-17j,
         0.152825  +4.64530345e-18j, -0.19075678+3.55954569e-18j,
        -0.19075678+3.55954569e-18j,  0.23810338-1.61235232e-17j],
       [-0.2972016 +1.81983495e-17j, -0.23810338-3.61872232e-18j,
        -0.23810338-3.61872232e-18j, -0.19075678-1.74787705e-17j,
         0.37096824-4.09136029e-17j,  0.2972016 -1.00627234e-17j,
         0.2972016 -1.00627234e-17j,  0.23810338+1.01365879e-17j,
        -0.23810338-3.61872232e-18j, -0.19075678-1.74787705e-17j,
        -0.19075678-1.74787705e-17j, -0.152825  -2.56836196e-17j,
         0.2972016 -1.00627234e-17j,  0.23810338+1.01365879e-17j,
         0.23810338+1.01365879e-17j,  0.19075678+2.27005655e-17j],
       [ 0.23810338-2.91592544e-17j, -0.2972016 +5.09763263e-17j,
         0.19075678-8.78134068e-18j, -0.23810338+2.26413888e-17j,
        -0.2972016 +5.09763263e-17j,  0.37096824-8.18272057e-17j,
        -0.23810338+2.26413888e-17j,  0.2972016 -4.28407001e-17j,
         0.19075678-8.78134068e-18j, -0.23810338+2.26413888e-17j,
         0.152825  +4.64530345e-18j, -0.19075678+3.55954569e-18j,
        -0.23810338+2.26413888e-17j,  0.2972016 -4.28407001e-17j,
        -0.19075678+3.55954569e-18j,  0.23810338-1.61235232e-17j],
       [ 0.23810338-2.91592544e-17j,  0.19075678-8.78134068e-18j,
        -0.2972016 +5.09763263e-17j, -0.23810338+2.26413888e-17j,
        -0.2972016 +5.09763263e-17j, -0.23810338+2.26413888e-17j,
         0.37096824-8.18272057e-17j,  0.2972016 -4.28407001e-17j,
         0.19075678-8.78134068e-18j,  0.152825  +4.64530345e-18j,
        -0.23810338+2.26413888e-17j, -0.19075678+3.55954569e-18j,
        -0.23810338+2.26413888e-17j, -0.19075678+3.55954569e-18j,
         0.2972016 -4.28407001e-17j,  0.23810338-1.61235232e-17j],
       [-0.19075678+3.50414518e-17j,  0.23810338-5.54193656e-17j,
         0.23810338-5.54193656e-17j, -0.2972016 +8.37543030e-17j,
         0.23810338-5.54193656e-17j, -0.2972016 +8.37543030e-17j,
        -0.2972016 +8.37543030e-17j,  0.37096824-1.22740809e-16j,
        -0.152825  +1.63930127e-17j,  0.19075678-2.98196568e-17j,
         0.19075678-2.98196568e-17j, -0.23810338+4.89015000e-17j,
         0.19075678-2.98196568e-17j, -0.23810338+4.89015000e-17j,
        -0.23810338+4.89015000e-17j,  0.2972016 -7.56186769e-17j],
       [-0.2972016 +1.81983495e-17j, -0.23810338-3.61872232e-18j,
        -0.23810338-3.61872232e-18j, -0.19075678-1.74787705e-17j,
        -0.23810338-3.61872232e-18j, -0.19075678-1.74787705e-17j,
        -0.19075678-1.74787705e-17j, -0.152825  -2.56836196e-17j,
         0.37096824-4.09136029e-17j,  0.2972016 -1.00627234e-17j,
         0.2972016 -1.00627234e-17j,  0.23810338+1.01365879e-17j,
         0.2972016 -1.00627234e-17j,  0.23810338+1.01365879e-17j,
         0.23810338+1.01365879e-17j,  0.19075678+2.27005655e-17j],
       [ 0.23810338-2.91592544e-17j, -0.2972016 +5.09763263e-17j,
         0.19075678-8.78134068e-18j, -0.23810338+2.26413888e-17j,
         0.19075678-8.78134068e-18j, -0.23810338+2.26413888e-17j,
         0.152825  +4.64530345e-18j, -0.19075678+3.55954569e-18j,
        -0.2972016 +5.09763263e-17j,  0.37096824-8.18272057e-17j,
        -0.23810338+2.26413888e-17j,  0.2972016 -4.28407001e-17j,
        -0.23810338+2.26413888e-17j,  0.2972016 -4.28407001e-17j,
        -0.19075678+3.55954569e-18j,  0.23810338-1.61235232e-17j],
       [ 0.23810338-2.91592544e-17j,  0.19075678-8.78134068e-18j,
        -0.2972016 +5.09763263e-17j, -0.23810338+2.26413888e-17j,
         0.19075678-8.78134068e-18j,  0.152825  +4.64530345e-18j,
        -0.23810338+2.26413888e-17j, -0.19075678+3.55954569e-18j,
        -0.2972016 +5.09763263e-17j, -0.23810338+2.26413888e-17j,
         0.37096824-8.18272057e-17j,  0.2972016 -4.28407001e-17j,
        -0.23810338+2.26413888e-17j, -0.19075678+3.55954569e-18j,
         0.2972016 -4.28407001e-17j,  0.23810338-1.61235232e-17j],
       [-0.19075678+3.50414518e-17j,  0.23810338-5.54193656e-17j,
         0.23810338-5.54193656e-17j, -0.2972016 +8.37543030e-17j,
        -0.152825  +1.63930127e-17j,  0.19075678-2.98196568e-17j,
         0.19075678-2.98196568e-17j, -0.23810338+4.89015000e-17j,
         0.23810338-5.54193656e-17j, -0.2972016 +8.37543030e-17j,
        -0.2972016 +8.37543030e-17j,  0.37096824-1.22740809e-16j,
         0.19075678-2.98196568e-17j, -0.23810338+4.89015000e-17j,
        -0.23810338+4.89015000e-17j,  0.2972016 -7.56186769e-17j],
       [ 0.23810338-2.91592544e-17j,  0.19075678-8.78134068e-18j,
         0.19075678-8.78134068e-18j,  0.152825  +4.64530345e-18j,
        -0.2972016 +5.09763263e-17j, -0.23810338+2.26413888e-17j,
        -0.23810338+2.26413888e-17j, -0.19075678+3.55954569e-18j,
        -0.2972016 +5.09763263e-17j, -0.23810338+2.26413888e-17j,
        -0.23810338+2.26413888e-17j, -0.19075678+3.55954569e-18j,
         0.37096824-8.18272057e-17j,  0.2972016 -4.28407001e-17j,
         0.2972016 -4.28407001e-17j,  0.23810338-1.61235232e-17j],
       [-0.19075678+3.50414518e-17j,  0.23810338-5.54193656e-17j,
        -0.152825  +1.63930127e-17j,  0.19075678-2.98196568e-17j,
         0.23810338-5.54193656e-17j, -0.2972016 +8.37543030e-17j,
         0.19075678-2.98196568e-17j, -0.23810338+4.89015000e-17j,
         0.23810338-5.54193656e-17j, -0.2972016 +8.37543030e-17j,
         0.19075678-2.98196568e-17j, -0.23810338+4.89015000e-17j,
        -0.2972016 +8.37543030e-17j,  0.37096824-1.22740809e-16j,
        -0.23810338+4.89015000e-17j,  0.2972016 -7.56186769e-17j],
       [-0.19075678+3.50414518e-17j, -0.152825  +1.63930127e-17j,
         0.23810338-5.54193656e-17j,  0.19075678-2.98196568e-17j,
         0.23810338-5.54193656e-17j,  0.19075678-2.98196568e-17j,
        -0.2972016 +8.37543030e-17j, -0.23810338+4.89015000e-17j,
         0.23810338-5.54193656e-17j,  0.19075678-2.98196568e-17j,
        -0.2972016 +8.37543030e-17j, -0.23810338+4.89015000e-17j,
        -0.2972016 +8.37543030e-17j, -0.23810338+4.89015000e-17j,
         0.37096824-1.22740809e-16j,  0.2972016 -7.56186769e-17j],
       [ 0.152825  -3.74313289e-17j, -0.19075678+5.60797680e-17j,
        -0.19075678+5.60797680e-17j,  0.23810338-8.16794767e-17j,
        -0.19075678+5.60797680e-17j,  0.23810338-8.16794767e-17j,
         0.23810338-8.16794767e-17j, -0.2972016 +1.16532280e-16j,
        -0.19075678+5.60797680e-17j,  0.23810338-8.16794767e-17j,
         0.23810338-8.16794767e-17j, -0.2972016 +1.16532280e-16j,
         0.23810338-8.16794767e-17j, -0.2972016 +1.16532280e-16j,
        -0.2972016 +1.16532280e-16j,  0.37096824-1.63654411e-16j]])

Allow for internal work qubits

  • Pass n_extra_qubits when the circuit allocates work qubits internally.

  • Return those work qubits to \(|0\rangle\) before discarding them, so they are disentangled from the logical register.

The two CX gates below uncompute the work qubit. The remaining logical operation is X:

\[\begin{split} X=\begin{pmatrix}0&1\\1&0\end{pmatrix}. \end{split}\]
from typing import no_type_check

@guppy
@no_type_check
def unitary_with_work_qubit(state: array[qubit, 1]) -> None:
    work = qubit()
    cx(state[0], work)
    cx(state[0], work)
    x(state[0])
    discard(work)

unitary = get_unitary(
    unitary_with_work_qubit,
    n_qubits=1,
    n_extra_qubits=1,
)

expected = np.array([[0, 1], [1, 0]])
assert np.allclose(unitary / unitary[0, 1], expected)
unitary

Compare magnitudes when phase information is unavailable

get_unitary_assumed_phase also accepts n_extra_qubits, but does not retain phase information. Compare np.abs(...) for this helper, as shown below. This checks matrix-entry magnitudes; it does not verify relative phases.

from guppyalgos.tests.helpers import get_unitary_assumed_phase

unitary_assumed_phase = get_unitary_assumed_phase(
    unitary_with_work_qubit,
    n_qubits=1,
    n_extra_qubits=1,
)

assert np.allclose(np.abs(unitary_assumed_phase), expected)
unitary_assumed_phase

Post-select a state

Post-selection keeps the branch associated with chosen measurement outcomes. For an ancilla outcome \(b\), the remaining system state is

\[ |\widetilde{\psi}_b\rangle = (\langle b|_{\mathrm{ancilla}}\otimes I_{\mathrm{system}}) |\Psi\rangle. \]

Its squared norm gives the outcome probability. When that probability is nonzero, normalization gives the conditional state:

\[ p_b=\lVert\widetilde{\psi}_b\rVert^2, \qquad |\psi_b\rangle=\frac{|\widetilde{\psi}_b\rangle}{\sqrt{p_b}}. \]
  • Tag the registers with state_output and use those names in the selection dictionary.

  • False selects \(0\); True selects \(1\).

  • get_statevector_projected returns the state of the unprojected qubits.

  • Use renormalize=True for the conditional state, or False to preserve the branch weight.

This example selects \(|000\rangle\) on the ancilla register tagged "projection".

n_state_qubits = 2
n_ancilla_qubits = 3
n_total_qubits = n_state_qubits + n_ancilla_qubits
@guppy
def main_sv_project() -> None:
    state = qarray(n_state_qubits)
    ancilla = qarray(n_ancilla_qubits)

    for i in range(n_state_qubits):
        ry(state[i], angle(3.14/2))

    for i in range(n_ancilla_qubits):
        ry(ancilla[i], angle(3.14/2))

    for i in range(n_ancilla_qubits):
        for j in range(n_state_qubits):
            cx(state[j], ancilla[i])

    # projection qubits to be measured
    state_output("projection", ancilla)
    state_output("system", state)
    
    discard_array(state)
    discard_array(ancilla)
from guppyalgos.tests.helpers import get_statevector_projected

post_selection = {"projection": [False for _ in range(n_ancilla_qubits)]} # postselect on all ancillas being 0
get_statevector_projected(main_sv_project, n_total_qubits, post_selection, renormalize=True)
array([0.7556292 -5.07091231e-16j, 0.31129088+1.81104011e-17j,
       0.31129088+5.43312033e-17j, 0.48499534+2.17324813e-16j])

Select outcomes on several registers

A selection dictionary can include several named registers. The helper applies their projections in dictionary order, allowing you to test a joint success condition, such as the ancilla and phase registers both being zero in a QSVT routine.

Record disjoint registers at the same point in the circuit, with no computation between their snapshots.

n_state_qubits = 2
n_phase_qubits = 3
n_total_qubits = n_state_qubits + n_phase_qubits + 1
state_preparation_angle = np.pi / 4

@guppy
def main() -> None:
    phase_qreg = qarray(n_phase_qubits)
    state_qreg = qarray(n_state_qubits)
    ancilla_qreg = qarray(1)

    # Non-eigenstate input in computational basis.
    ry(state_qreg[0], angle(state_preparation_angle))
    x(state_qreg[1])

    for i in range(n_phase_qubits):
        h(phase_qreg[i])

    h(ancilla_qreg[0])
    for i in range(n_phase_qubits):

        cx(phase_qreg[i], ancilla_qreg[0])

    for i in range(n_state_qubits):
        cx(state_qreg[i], ancilla_qreg[0])  

    h(ancilla_qreg[0])

    state_output("phase", phase_qreg)
    state_output("ancilla", ancilla_qreg)
    state_output("system", state_qreg)
    discard_array(phase_qreg)
    discard_array(state_qreg)
    discard_array(ancilla_qreg)

Select \(|0\rangle\) on "ancilla" and \(|000\rangle\) on "phase". The returned statevector describes the remaining system register.

from guppyalgos.tests.helpers import get_statevector_projected, statevector_projected_selene
import numpy as np

postselect_dict = {"ancilla": [False], "phase": [False, False, False]}

get_statevector_projected(main, n_total_qubits, postselect_dict)
array([ 2.02524568e-17-2.17207347e-32j,  5.77861354e-17-7.47527359e-33j,
       -8.70182863e-17-3.30747719e-01j, -3.04173840e-16-9.43719209e-01j])

Keep a state object for further inspection

Use statevector_projected_selene when you need a SeleneQuestState rather than a NumPy array. The returned_specified_qubits argument marks the qubit IDs of interest for subsequent projections or inspection.

statevector_projected_selene(main, n_total_qubits, postselect_dict, returned_specified_qubits=[3,4])
SubQuestState(state=array([ 2.02524568e-17-2.17207347e-32j,  5.77861354e-17-7.47527359e-33j,
       -8.70182863e-17-3.30747719e-01j, -3.04173840e-16-9.43719209e-01j]), total_qubits=2, specified_qubits=[3, 4])

Extract a post-selected operation

Use get_unitary_projected to extract the operation associated with chosen initial and final ancilla states:

\[ A_{b,a} = (\langle b|\otimes I)\,U\,(|a\rangle\otimes I). \]

This block need not be unitary. Its scale matters: for a normalized input \(|\psi\rangle\), the probability of outcome \(b\) is \(\lVert A_{b,a}|\psi\rangle\rVert^2\).

  • Put projected registers first in the circuit signature, followed by the system register.

  • The selection dictionary’s key order determines the projected-register argument order.

  • The helper records those registers for post-selection.

  • Use matching keys in the pre-selection and post-selection dictionaries; omit pre-selection to initialize all projected registers to zero.

Below, the ancillas start in \(|111\rangle\) and are projected onto \(|000\rangle\).

@guppy
def main_unitary_projected[n_a: nat, n_s: nat](ancilla: array[qubit, n_a], state: array[qubit, n_s]) -> None:
    for i in range(len(state)):
        ry(state[i], angle(3.14/2))

    for i in range(len(ancilla)):
        ry(ancilla[i], angle(3.14/2))

    for i in range(len(state)):
        for j in range(len(ancilla)):
            cx(state[i], ancilla[j])
from guppyalgos.tests.helpers import get_unitary_projected

post_selection = {"ancilla": [False for _ in range(n_ancilla_qubits)]}
pre_selection = {"ancilla": [True for _ in range(n_ancilla_qubits)]}
get_unitary_projected(main_unitary_projected, n_state_qubits, post_selection, pre_selection)
array([[ 6.93889390e-17+0.14887239j,  1.42420307e-17+0.11926928j,
         1.42420307e-17+0.11926928j,  3.94574074e-17+0.09555271j],
       [-1.80411242e-16-0.23194517j,  1.28749977e-16+0.28951489j,
        -6.68895356e-17-0.18582312j,  1.39427393e-16+0.23194517j],
       [-1.66533454e-16-0.23194517j, -6.68895356e-17-0.18582312j,
         2.67527855e-16+0.28951489j,  1.53305181e-16+0.23194517j],
       [-6.93889390e-18+0.09555271j, -7.30313681e-18-0.11926928j,
        -7.30313681e-18-0.11926928j, -3.72795415e-17+0.14887239j]])

Allow for internal work qubits

Pass n_extra_qubits for work qubits allocated inside the circuit, beyond its explicit register arguments. The helper reserves that additional emulator capacity while extracting the logical operation.

As with get_unitary, internal work qubits must return to \(|0\rangle\) before being discarded. The example below leaves the projected register unchanged and applies X to the system.

from typing import no_type_check
from guppyalgos.tests.helpers import get_unitary_projected

@guppy
@no_type_check
def projected_unitary_with_work_qubit(
    projection: array[qubit, 1],
    state: array[qubit, 1],
) -> None:
    work = qubit()
    cx(state[0], work)
    cx(state[0], work)
    x(state[0])
    discard(work)

projected = get_unitary_projected(
    projected_unitary_with_work_qubit,
    n_state_qubits=1,
    post_select_dict={"projection": [False]},
    n_extra_qubits=1,
)

expected = np.array([[0, 1], [1, 0]])
assert np.allclose(projected / projected[0, 1], expected)
projected

Inspect only selected registers

Use get_total_state_on_only_specified_registers to inspect a collection of tagged registers while excluding unused ancillas.

  • Identify the registers by their state_output tags.

  • Record the snapshots together, with no computation between them.

  • The selected registers must be disentangled from everything excluded. They may be entangled with each other.

  • The helper returns a state object and a mapping from register tags to qubit IDs.

Here, the control and target registers contain six qubits altogether, so their state has \(2^6=64\) amplitudes. The separate ancilla is excluded.

from guppyalgos.utils import transversal
from selene_sim import Quest
from guppyalgos.tests.helpers import get_total_state_on_only_specified_registers

@guppy
def main_get_total_state_on_only_specified_registers() -> None:
    control_reg = qarray(3)
    target_reg = qarray(3)
    ancilla = qubit()
    x(control_reg[1])
    transversal(cx, control_reg, target_reg)
    # state results for desired registers
    state_output("control", control_reg)
    state_output("target", target_reg)
    discard_array(control_reg)
    discard_array(target_reg)
    discard(ancilla)

res = main_get_total_state_on_only_specified_registers.emulator(7).run()
states = Quest.extract_states_dict(res.results[0].entries)

# returns the state and a dictionary mapping from the tags to the qubit ids for each register
non_ancilla_state, spec_qubit_dict = get_total_state_on_only_specified_registers(
    states, ["target", "control"]
)
control_qubits = spec_qubit_dict["control"]
target_qubits = spec_qubit_dict["target"]

# length of non_ancilla_state will exclude the ancilla, so be 2**6=64,
print(len(non_ancilla_state.state)) 
64

Replay measurement outcomes

Selene’s QuantumReplay lets you specify measurement outcomes in advance. This makes tests of mid-circuit measurements, feedforward, and retries reproducible.

The example below uses rotation resource states until a success flag is measured. It checks two paths:

  • [True]: success on the first attempt.

  • [False, False, True]: two failures followed by success.

After each failure, the correction angle doubles; attempt \(k\) uses \(2^{k-1}\theta\). Comparing the final rotation across paths checks that the corrections produce the intended result.

Requested outcomes must have non-negligible probability in the simulated state. Replay selects a possible branch rather than assigning an arbitrary quantum state.

from selene_sim import QuantumReplay
from guppylang.std.quantum import measure, h, rz, s, sdg
import numpy as np

@guppy
def repeat_until_success_ry_example() -> None:
    """Perform repeat-until-success Ry rotation."""
    q = qubit()
    sdg(q)
    h(q)
    theta = angle(0.1)
    attempts = 0
    while True:
        attempts += 1
        a = qubit()
        h(a)
        rz(a, theta * (2 ** (attempts - 1)))
        cx(q, a)
        if not measure(a).read():
            continue
        break
    h(q)
    s(q)
    state_output("result_state",q)
    discard(q)

# set up the replay plugin with desired measurements and Quest as a backend
n_shots = 2
rus_replay_sim = QuantumReplay(simulator=Quest(), measurements=[[True], [False, False, True]])


em_result = (
        repeat_until_success_ry_example.emulator(2).with_simulator(rus_replay_sim).with_shots(n_shots).run()
    )
# print the rotation angle applied in each case
for shot_result in em_result.results:
    states = Quest.extract_states_dict(shot_result)
    sv = states["result_state"].state
    res = abs(sv[3]) ** 2
    theta = 2 * np.arcsin(np.sqrt(res)) / np.pi
    print(theta)
0.10000000000000005
0.10000000000000002