Positive Register-Incremented and Phase-Gradient Rotations

Download Notebook - register_incremented_rotation.ipynb

This notebook demonstrates the positive-angle convention used by the RotationRegisterIncremented and RotationPhaseGradient Guppy structs for a fixed computational-basis data register.

from typing import no_type_check

import numpy as np
import pandas as pd
from guppylang import comptime, guppy
from guppylang.std.debug import state_result
from guppylang.std.quantum import discard, discard_array, qubit, x
from selene_sim import Quest

from guppyalgos.primitives.rotations import (
    RotationAxisY,
    RotationPhaseGradient,
    RotationRegisterIncremented,
)
from guppyalgos.primitives.state_preparation.phase_gradient import Convention, phase_gradient
from guppyalgos.utils import qarray
from guppyalgos.tests.helpers import assert_allclose_ignorephase

For a little-endian \(n\)-qubit data register with bits \(b_i \in \{0, 1\}\), the positive register-incremented per-bit controlled angles are

\[\theta_i = 2^{i+1-n}\]

and the total positive angle applied to the ancilla is

\[\theta_{\mathrm{total}} = \sum_{i=0}^{n-1} b_i \theta_i = \frac{2}{2^n}\sum_{i=0}^{n-1} b_i 2^i.\]

This is the unscaled full-range fixed-point convention shared with phase-gradient kickback.

bits = [True, False, True, False, True]
n_phase_qubits = len(bits)

@guppy
@no_type_check
def main() -> None:
    idx = comptime(bits)
    phase_qreg = qarray(comptime(n_phase_qubits))
    rotation_target = qubit()

    for bit in range(comptime(n_phase_qubits)):
        if idx[bit]:
            x(phase_qreg[bit])

    rotation = RotationRegisterIncremented[
        comptime(n_phase_qubits), RotationAxisY
    ](RotationAxisY())
    rotation.compose(phase_qreg, rotation_target)

    state_result("ancilla", rotation_target)
    discard_array(phase_qreg)
    discard(rotation_target)

res = main.emulator(n_qubits=n_phase_qubits + 1).run()
states = Quest.extract_states_dict(res.results[0].entries)
ancilla_state = states["ancilla"].get_single_state()
per_bit = [2.0 ** (i + 1 - n_phase_qubits) for i in range(n_phase_qubits)]
contrib = [float(per_bit[i]) if bit else 0.0 for i, bit in enumerate(bits)]

pd.DataFrame(
    {
        "i": list(range(n_phase_qubits)),
        "bit b_i": bits,
        "theta_i = 2^(i+1-n)": per_bit,
        "contribution b_i * theta_i": contrib,
    }
)
expected_theta = sum(contrib)
print(contrib)
phase = np.pi * expected_theta / 2.0
expected_state = np.array([np.cos(phase), np.sin(phase)], dtype=np.complex128)

print(f"bits={bits}, axis=Y")
print(f"expected theta_total = {expected_theta}")
print("simulated ancilla state:", ancilla_state)
print("expected ancilla state :", expected_state)

assert_allclose_ignorephase(ancilla_state, expected_state)
print("Passed: ancilla state matches expected axis rotation up to global phase.")

A positive rotation can also be synthesized by the RotationPhaseGradient struct using a phase-gradient register prepared before initialization. It applies X to the target before and after the controlled Gidney adder. This makes addition happen on the target’s original \(|0\rangle\) branch, so the phase-gradient eigenphase \(\exp(-2\pi i x / 2^d)\) appears on \(|0\rangle\) rather than \(|1\rangle\). Up to global phase, this is a positive rotation with half-turn angle theta = 2 * x / 2**d for the standard-convention integer \(x\) encoded by the same little-endian bits.

phase_gradient_prep = phase_gradient(
    n_phase_qubits,
    convention=Convention.Standard,
)

@guppy
@no_type_check
def phase_gradient_main() -> None:
    idx = comptime(bits)
    phase_qreg = qarray(comptime(n_phase_qubits))
    phase_gradient_state = qarray(comptime(n_phase_qubits))
    rotation_target = qubit()

    for bit in range(comptime(n_phase_qubits)):
        if idx[bit]:
            x(phase_qreg[bit])

    phase_gradient_prep(phase_gradient_state)
    rotation = RotationPhaseGradient(phase_gradient_state, RotationAxisY())
    rotation.compose(phase_qreg, rotation_target)

    state_result("phase_gradient_ancilla", rotation_target)
    discard_array(phase_qreg)
    discard_array(rotation.phase_gradient)
    discard(rotation_target)

phase_gradient_res = phase_gradient_main.emulator(n_qubits=(3 * n_phase_qubits) + 1).run()
phase_gradient_states = Quest.extract_states_dict(phase_gradient_res.results[0].entries)
phase_gradient_ancilla_state = phase_gradient_states["phase_gradient_ancilla"].get_single_state()

encoded_integer = sum((2**i) for i, bit in enumerate(bits) if bit)
expected_phase_gradient_theta = 2.0 * encoded_integer / (2**n_phase_qubits)
phase_gradient_phase = np.pi * expected_phase_gradient_theta / 2.0
expected_phase_gradient_state = np.array(
    [np.cos(phase_gradient_phase), np.sin(phase_gradient_phase)],
    dtype=np.complex128,
)

print(f"phase-gradient theta_total = {expected_phase_gradient_theta}")
print("phase-gradient ancilla state:", phase_gradient_ancilla_state)
print("expected ancilla state      :", expected_phase_gradient_state)

assert_allclose_ignorephase(
    phase_gradient_ancilla_state,
    expected_phase_gradient_state,
)
print("Passed: phase-gradient kickback matches the positive axis rotation convention.")