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.testing 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.testing 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

Test custom unitary modifiers

Use assert_unitary_modifiers for a circuit that accepts a single qubit array and provides custom modifier implementations. One invocation checks:

  • Forward: the extracted matrix is unitary and matches the supplied reference up to one overall global phase.

  • Dagger: the matrix is unitary and matches the forward adjoint.

  • Control and controlled dagger: check the full matrix including the control qubit against identity on the inactive branch and the reference or its adjoint on the active branch, preserving relative phase.

The rotation below defines all four implementations explicitly. Automatically generated modifiers are the compiler’s testing responsibility.

from guppylang.std.builtins import control
from guppylang.std.quantum import rz
from guppyalgos.testing import assert_unitary_modifiers


@guppy.unitary
class custom_rotation:
    @guppy
    def __call__(qs: array[qubit, 1]) -> None:
        rz(qs[0], angle(0.25))

    @guppy
    def daggered(qs: array[qubit, 1]) -> None:
        rz(qs[0], angle(-0.25))

    @guppy
    def controlled[n: nat](qs: array[qubit, 1], controls: array[qubit, n]) -> None:
        with control(controls):
            rz(qs[0], angle(0.25))

    @guppy
    def ctrl_daggered[n: nat](qs: array[qubit, 1], controls: array[qubit, n]) -> None:
        with control(controls):
            rz(qs[0], angle(-0.25))


# angle(0.25) is a quarter half-turn: pi/4 radians.
expected_rotation = np.diag(np.exp(1j * np.array([-np.pi / 8, np.pi / 8])))
assert_unitary_modifiers(custom_rotation, 1, expected_unitary=expected_rotation)

A successful assertion returns without output. Failures identify the mode and distinguish a nonunitary extracted matrix from an incorrect unitary operation. The supplied independent reference checks the full forward unitary up to global phase and preserves the intended relative phase in full controlled-matrix checks. The reference is required so compiler-dependent forward phases cannot determine the controlled expectation.

Use this helper for small systems because full-matrix extraction scales exponentially. Pass n_extra_qubits for internal work qubits. Reset is compatible when those qubits are correctly uncomputed and disentangled. Measurement-based cleanup also needs separate superposition and measurement-outcome checks: basis-input runs can hide lost coherence or combine outcome-dependent phases into inconsistent column phases. Specialized tests can be added case by case.

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.testing 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.testing 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.testing 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.testing 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.testing 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