Core concepts¶
Most code in the library is built from four ideas: quantum registers, functions, higher-order functions, and structs.
Quantum registers with qarray¶
A quantum register is usually an array of qubits. The qarray helper
allocates that quantum register inside a Guppy function:
from guppylang import guppy
from guppylang.std.quantum import discard_array
from guppyalgos.utils import qarray
@guppy
def allocate_register() -> None:
qreg = qarray(4)
discard_array(qreg)
qarray(4)returns anarray[qubit, 4]containing four fresh qubits.The size is known at compile time and is part of the quantum register’s type.
Qubits are linear resources: the program must use, measure, return, or discard every allocated qubit.
Functions on quantum registers¶
Algorithm primitives are Guppy functions whose signatures state which quantum registers they accept. A generic size allows one definition to work for any quantum-register width:
from guppylang.std.builtins import array, bool, nat, owned
from guppylang.std.quantum import collect_measurements, cx, h, measure_array, qubit, rz
from guppyalgos.utils import transversal
@guppy
def prepare_plus[n: nat](qreg: array[qubit, n]) -> None:
transversal(h, qreg)
@guppy
def measure_register[n: nat](
qreg: array[qubit, n] @ owned,
) -> array[bool, n]:
return collect_measurements(measure_array(qreg))
nis inferred from the quantum register supplied by the caller.Guppy checks the quantum-register shape and qubit ownership at compile time.
Function arguments are borrowed by default, as in
prepare_plus.measure_registeruses@ ownedbecause measurement consumes the qubits; the measured quantum register cannot be used afterward.Repository primitives use this style for operations such as state preparation, arithmetic, and Hamiltonian simulation.
Higher-order functions¶
A higher-order function accepts another function as a value. Function
records the required Guppy signature, allowing an implementation to be
replaced without changing the surrounding algorithm:
from guppylang.std.builtins import Function
@guppy
def apply_between_registers[n: nat](
operation: Function[
[array[qubit, n], array[qubit, n]], None
],
left_qreg: array[qubit, n],
right_qreg: array[qubit, n],
) -> None:
operation(left_qreg, right_qreg)
The injected operation receives both complete quantum registers.
It decides how the two quantum registers interact; the wrapper only defines the required signature and forwards them.
An operation with an incompatible argument or return type is rejected at compile time.
Use Guppy’s
Function, rather thantyping.Callable, for function values inside Guppy code.
Generic quantum-register types¶
A generic quantum-register type stands for a complete quantum-register shape. The same function can then work with arrays, tuples, or structs:
@guppy
def apply_register_operation[Regs](
operation: Function[[Regs], None],
qregs: Regs,
) -> None:
operation(qregs)
Regs can be a single qubit array:
@guppy
def array_operation[n: nat](qreg: array[qubit, n]) -> None:
transversal(h, qreg)
It can also be a fixed tuple. The repository uses this shape, for example, for two-qubit Givens-rotation targets:
@guppy
def tuple_operation(qregs: tuple[qubit, qubit]) -> None:
h(qregs[0])
cx(qregs[0], qregs[1])
Starting from \(|00\rangle\), this tuple operation prepares a Bell pair: