From 20a4ca456fe05402f0c7e80488c8a1aa2e8aff59 Mon Sep 17 00:00:00 2001 From: Robert Fisher Date: Mon, 9 Feb 2026 16:58:08 -0600 Subject: [PATCH 1/4] Address Issue 1078: Added Transversal overloads of builtin quantum operations --- .../guppylang_internals/tracing/function.py | 14 +- .../src/guppylang/std/quantum/__init__.py | 369 +++++++++++++----- .../src/guppylang/std/quantum/functional.py | 273 +++++++++++-- tests/error/modifier_errors/higher_order.err | 10 +- tests/error/poly_errors/non_linear2.err | 5 +- tests/integration/test_quantum.py | 107 +++++ 6 files changed, 639 insertions(+), 139 deletions(-) diff --git a/guppylang-internals/src/guppylang_internals/tracing/function.py b/guppylang-internals/src/guppylang_internals/tracing/function.py index a74c84779..71922059d 100644 --- a/guppylang-internals/src/guppylang_internals/tracing/function.py +++ b/guppylang-internals/src/guppylang_internals/tracing/function.py @@ -11,10 +11,11 @@ from guppylang_internals.checker.errors.type_errors import TypeMismatchError from guppylang_internals.compiler.core import CompilerContext, DFContainer from guppylang_internals.compiler.expr_compiler import ExprCompiler +from guppylang_internals.definition.overloaded import OverloadedFunctionDef from guppylang_internals.definition.value import CallableDef from guppylang_internals.diagnostic import Error from guppylang_internals.error import GuppyComptimeError, GuppyError, exception_hook -from guppylang_internals.nodes import PlaceNode +from guppylang_internals.nodes import GlobalCall, PlaceNode from guppylang_internals.tracing.builtins_mock import mock_builtins from guppylang_internals.tracing.object import GuppyObject from guppylang_internals.tracing.state import ( @@ -173,8 +174,15 @@ def trace_call(func: CallableDef, *args: Any) -> Any: # Update inouts # If the input types of the function aren't known, we can't check this. # This is the case for functions with a custom checker and no type annotations. - if len(func.ty.inputs) != 0: - for inp, arg, var in zip(func.ty.inputs, args, arg_vars, strict=True): + # For overloaded functions, func.ty is a dummy type with no inputs, so we + # resolve the actual variant from the call node to get the correct input flags. + func_inputs = func.ty.inputs + if isinstance(func, OverloadedFunctionDef) and isinstance(call_node, GlobalCall): + resolved_def = state.globals[call_node.def_id] + if isinstance(resolved_def, CallableDef): + func_inputs = resolved_def.ty.inputs + if len(func_inputs) != 0: + for inp, arg, var in zip(func_inputs, args, arg_vars, strict=True): if InputFlags.Inout in inp.flags: # Note that `inp.ty` could refer to bound variables in the function # signature. Instead, make sure to use `var.ty` which will always be a diff --git a/guppylang/src/guppylang/std/quantum/__init__.py b/guppylang/src/guppylang/std/quantum/__init__.py index 33018e685..bc2cea6f7 100644 --- a/guppylang/src/guppylang/std/quantum/__init__.py +++ b/guppylang/src/guppylang/std/quantum/__init__.py @@ -1,6 +1,6 @@ """Guppy standard module for quantum operations.""" -# mypy: disable-error-code="empty-body, misc, valid-type" +# mypy: disable-error-code="empty-body, misc, valid-type, no-untyped-def" from typing import no_type_check @@ -49,84 +49,53 @@ def maybe_qubit() -> Option[qubit]: if allocation succeeds or `nothing` if it fails.""" -@hugr_op(quantum_op("H"), unitary_flags=UnitaryFlags.Unitary) -@no_type_check -def h(q: qubit) -> None: - r"""Hadamard gate command +# --------------------------------------------------------------------------- +# Single-qubit gates (scalar + array overloads) +# --------------------------------------------------------------------------- - .. math:: - \mathrm{H}= \frac{1}{\sqrt{2}} - \begin{pmatrix} - 1 & 1 \\ - 1 & -1 - \end{pmatrix} - """ +N = guppy.nat_var("N") -@hugr_op(quantum_op("CZ"), unitary_flags=UnitaryFlags.Unitary) +@hugr_op(quantum_op("H"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def cz(control: qubit, target: qubit) -> None: - r"""Controlled-Z gate command. - - cz(control, target) - - Qubit ordering: [control, target] - - .. math:: - \mathrm{CZ}= - \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & -1 - \end{pmatrix} - """ +def _h(q: qubit) -> None: ... -@hugr_op(quantum_op("CY"), unitary_flags=UnitaryFlags.Unitary) +@guppy @no_type_check -def cy(control: qubit, target: qubit) -> None: - r"""Controlled-Y gate command. +def _h_array(qs: array[qubit, N]) -> None: + for i in range(N): + _h(qs[i]) - cy(control, target) - Qubit ordering: [control, target] +@guppy.overload(_h, _h_array) +def h(q) -> None: + r"""Hadamard gate command. Accepts a single qubit or an array of qubits. .. math:: - \mathrm{CY}= + \mathrm{H}= \frac{1}{\sqrt{2}} \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & -i \\ - 0 & 0 & i & 0 + 1 & 1 \\ + 1 & -1 \end{pmatrix} """ -@hugr_op(quantum_op("CX"), unitary_flags=UnitaryFlags.Unitary) +@hugr_op(quantum_op("T"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def cx(control: qubit, target: qubit) -> None: - r"""Controlled-X gate command. - - cx(control, target) +def _t(q: qubit) -> None: ... - Qubit ordering: [control, target] - .. math:: - \mathrm{CX}= - \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - 0 & 0 & 1 & 0 - \end{pmatrix} - """ +@guppy +@no_type_check +def _t_array(qs: array[qubit, N]) -> None: + for i in range(N): + _t(qs[i]) -@hugr_op(quantum_op("T"), unitary_flags=UnitaryFlags.Unitary) -@no_type_check -def t(q: qubit) -> None: - r"""T gate. +@guppy.overload(_t, _t_array) +def t(q) -> None: + r"""T gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{T}= @@ -140,8 +109,19 @@ def t(q: qubit) -> None: @hugr_op(quantum_op("S"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def s(q: qubit) -> None: - r"""S gate. +def _s(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _s_array(qs: array[qubit, N]) -> None: + for i in range(N): + _s(qs[i]) + + +@guppy.overload(_s, _s_array) +def s(q) -> None: + r"""S gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{S}= @@ -155,8 +135,19 @@ def s(q: qubit) -> None: @hugr_op(quantum_op("V"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def v(q: qubit) -> None: - r"""V gate. +def _v(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _v_array(qs: array[qubit, N]) -> None: + for i in range(N): + _v(qs[i]) + + +@guppy.overload(_v, _v_array) +def v(q) -> None: + r"""V gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{V}= \frac{1}{\sqrt{2}} @@ -170,8 +161,19 @@ def v(q: qubit) -> None: @hugr_op(quantum_op("X"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def x(q: qubit) -> None: - r"""X gate. +def _x(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _x_array(qs: array[qubit, N]) -> None: + for i in range(N): + _x(qs[i]) + + +@guppy.overload(_x, _x_array) +def x(q) -> None: + r"""X gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{X}= @@ -185,8 +187,19 @@ def x(q: qubit) -> None: @hugr_op(quantum_op("Y"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def y(q: qubit) -> None: - r"""Y gate. +def _y(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _y_array(qs: array[qubit, N]) -> None: + for i in range(N): + _y(qs[i]) + + +@guppy.overload(_y, _y_array) +def y(q) -> None: + r"""Y gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{Y}= @@ -200,8 +213,19 @@ def y(q: qubit) -> None: @hugr_op(quantum_op("Z"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def z(q: qubit) -> None: - r"""Z gate. +def _z(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _z_array(qs: array[qubit, N]) -> None: + for i in range(N): + _z(qs[i]) + + +@guppy.overload(_z, _z_array) +def z(q) -> None: + r"""Z gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{Z}= @@ -215,8 +239,19 @@ def z(q: qubit) -> None: @hugr_op(quantum_op("Tdg"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def tdg(q: qubit) -> None: - r"""Tdg gate. +def _tdg(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _tdg_array(qs: array[qubit, N]) -> None: + for i in range(N): + _tdg(qs[i]) + + +@guppy.overload(_tdg, _tdg_array) +def tdg(q) -> None: + r"""Tdg gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{T}^\dagger= @@ -230,8 +265,19 @@ def tdg(q: qubit) -> None: @hugr_op(quantum_op("Sdg"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def sdg(q: qubit) -> None: - r"""Sdg gate. +def _sdg(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _sdg_array(qs: array[qubit, N]) -> None: + for i in range(N): + _sdg(qs[i]) + + +@guppy.overload(_sdg, _sdg_array) +def sdg(q) -> None: + r"""Sdg gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{S}^\dagger= @@ -245,8 +291,19 @@ def sdg(q: qubit) -> None: @hugr_op(quantum_op("Vdg"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def vdg(q: qubit) -> None: - r"""Vdg gate. +def _vdg(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _vdg_array(qs: array[qubit, N]) -> None: + for i in range(N): + _vdg(qs[i]) + + +@guppy.overload(_vdg, _vdg_array) +def vdg(q) -> None: + r"""Vdg gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{V}^\dagger= \frac{1}{\sqrt{2}} @@ -258,6 +315,11 @@ def vdg(q: qubit) -> None: """ +# --------------------------------------------------------------------------- +# Rotation gates (no array overloads for now) +# --------------------------------------------------------------------------- + + @custom_function(RotationCompiler("Rz"), unitary_flags=UnitaryFlags.Unitary) @no_type_check def rz(q: qubit, angle: angle) -> None: @@ -324,6 +386,109 @@ def crz(control: qubit, target: qubit, angle: angle) -> None: """ +# --------------------------------------------------------------------------- +# Two-qubit gates (scalar + array overloads) +# --------------------------------------------------------------------------- + + +@hugr_op(quantum_op("CZ"), unitary_flags=UnitaryFlags.Unitary) +@no_type_check +def _cz(control: qubit, target: qubit) -> None: ... + + +@guppy +@no_type_check +def _cz_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: + for i in range(N): + _cz(controls[i], targets[i]) + + +@guppy.overload(_cz, _cz_array) +def cz(control, target) -> None: + r"""Controlled-Z gate command. Accepts single qubits or arrays of qubits. + + cz(control, target) + + Qubit ordering: [control, target] + + .. math:: + \mathrm{CZ}= + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{pmatrix} + """ + + +@hugr_op(quantum_op("CY"), unitary_flags=UnitaryFlags.Unitary) +@no_type_check +def _cy(control: qubit, target: qubit) -> None: ... + + +@guppy +@no_type_check +def _cy_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: + for i in range(N): + _cy(controls[i], targets[i]) + + +@guppy.overload(_cy, _cy_array) +def cy(control, target) -> None: + r"""Controlled-Y gate command. Accepts single qubits or arrays of qubits. + + cy(control, target) + + Qubit ordering: [control, target] + + .. math:: + \mathrm{CY}= + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & -i \\ + 0 & 0 & i & 0 + \end{pmatrix} + """ + + +@hugr_op(quantum_op("CX"), unitary_flags=UnitaryFlags.Unitary) +@no_type_check +def _cx(control: qubit, target: qubit) -> None: ... + + +@guppy +@no_type_check +def _cx_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: + for i in range(N): + _cx(controls[i], targets[i]) + + +@guppy.overload(_cx, _cx_array) +def cx(control, target) -> None: + r"""Controlled-X gate command. Accepts single qubits or arrays of qubits. + + cx(control, target) + + Qubit ordering: [control, target] + + .. math:: + \mathrm{CX}= + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 + \end{pmatrix} + """ + + +# --------------------------------------------------------------------------- +# Other gates (no array overloads) +# --------------------------------------------------------------------------- + + @hugr_op(quantum_op("Toffoli"), unitary_flags=UnitaryFlags.Unitary) @no_type_check def toffoli(control1: qubit, control2: qubit, target: qubit) -> None: @@ -354,40 +519,64 @@ def project_z(q: qubit) -> bool: """Project a single qubit into the Z-basis (a non-destructive measurement).""" +# --------------------------------------------------------------------------- +# Measure, discard, reset (scalar + array overloads) +# --------------------------------------------------------------------------- + + @hugr_op(quantum_op("QFree")) @no_type_check -def discard(q: qubit @ owned) -> None: - """Discard a single qubit.""" +def _discard(q: qubit @ owned) -> None: ... + + +@guppy +@no_type_check +def _discard_array(qubits: array[qubit, N] @ owned) -> None: + for q in qubits: + _discard(q) + + +@guppy.overload(_discard, _discard_array) +def discard(q) -> None: + """Discard a single qubit or an array of qubits.""" @hugr_op(quantum_op("MeasureFree")) @no_type_check -def measure(q: qubit @ owned) -> bool: - """Measure a single qubit destructively.""" +def _measure(q: qubit @ owned) -> bool: ... -@hugr_op(quantum_op("Reset")) +@guppy @no_type_check -def reset(q: qubit) -> None: - """Reset a single qubit to the :math:`|0\rangle` state.""" +def _measure_array(qubits: array[qubit, N] @ owned) -> array[bool, N]: + return array(_measure(q) for q in qubits) -N = guppy.nat_var("N") +@guppy.overload(_measure, _measure_array) +def measure(q): + """Measure a single qubit or an array of qubits destructively.""" -@guppy +@hugr_op(quantum_op("Reset")) @no_type_check -def measure_array(qubits: array[qubit, N] @ owned) -> array[bool, N]: - """Measure an array of qubits, returning an array of bools.""" - return array(measure(q) for q in qubits) +def _reset(q: qubit) -> None: ... @guppy @no_type_check -def discard_array(qubits: array[qubit, N] @ owned) -> None: - """Discard an array of qubits.""" - for q in qubits: - discard(q) +def _reset_array(qs: array[qubit, N]) -> None: + for i in range(N): + _reset(qs[i]) + + +@guppy.overload(_reset, _reset_array) +def reset(q) -> None: + """Reset a single qubit or an array of qubits to the :math:`|0\\rangle` state.""" + + +# Backward-compatible aliases +measure_array = _measure_array +discard_array = _discard_array # -------NON-PRIMITIVE------- diff --git a/guppylang/src/guppylang/std/quantum/functional.py b/guppylang/src/guppylang/std/quantum/functional.py index 58cebf98b..cd374934d 100644 --- a/guppylang/src/guppylang/std/quantum/functional.py +++ b/guppylang/src/guppylang/std/quantum/functional.py @@ -9,116 +9,225 @@ import guppylang.std.quantum as quantum from guppylang.decorator import guppy -# mypy: disable-error-code="empty-body, misc, valid-type" +# mypy: disable-error-code="empty-body, misc, valid-type, no-untyped-def" from guppylang.std.angles import angle +from guppylang.std.array import array from guppylang.std.lang import owned from guppylang.std.quantum import qubit +N = guppy.nat_var("N") + + +# --------------------------------------------------------------------------- +# Single-qubit gates (scalar + array overloads) +# --------------------------------------------------------------------------- + @guppy @no_type_check -def h(q: qubit @ owned) -> qubit: - """Functional Hadamard gate command.""" +def _h(q: qubit @ owned) -> qubit: quantum.h(q) return q @guppy @no_type_check -def cz(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - """Functional CZ gate command.""" - quantum.cz(control, target) - return control, target +def _h_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.h(qs[i]) + return qs + + +@guppy.overload(_h, _h_array) +def h(q): + """Functional Hadamard gate command. Accepts a single qubit or an array.""" @guppy @no_type_check -def cx(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - """Functional CX gate command.""" - quantum.cx(control, target) - return control, target +def _t(q: qubit @ owned) -> qubit: + quantum.t(q) + return q @guppy @no_type_check -def cy(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - """Functional CY gate command.""" - quantum.cy(control, target) - return control, target +def _t_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.t(qs[i]) + return qs + + +@guppy.overload(_t, _t_array) +def t(q): + """Functional T gate command. Accepts a single qubit or an array.""" @guppy @no_type_check -def t(q: qubit @ owned) -> qubit: - """Functional T gate command.""" - quantum.t(q) +def _s(q: qubit @ owned) -> qubit: + quantum.s(q) return q @guppy @no_type_check -def s(q: qubit @ owned) -> qubit: - """Functional S gate command.""" - quantum.s(q) - return q +def _s_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.s(qs[i]) + return qs + + +@guppy.overload(_s, _s_array) +def s(q): + """Functional S gate command. Accepts a single qubit or an array.""" @guppy @no_type_check -def v(q: qubit @ owned) -> qubit: - """Functional V gate command.""" +def _v(q: qubit @ owned) -> qubit: quantum.v(q) return q @guppy @no_type_check -def x(q: qubit @ owned) -> qubit: - """Functional X gate command.""" +def _v_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.v(qs[i]) + return qs + + +@guppy.overload(_v, _v_array) +def v(q): + """Functional V gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _x(q: qubit @ owned) -> qubit: quantum.x(q) return q @guppy @no_type_check -def y(q: qubit @ owned) -> qubit: - """Functional Y gate command.""" +def _x_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.x(qs[i]) + return qs + + +@guppy.overload(_x, _x_array) +def x(q): + """Functional X gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _y(q: qubit @ owned) -> qubit: quantum.y(q) return q @guppy @no_type_check -def z(q: qubit @ owned) -> qubit: - """Functional Z gate command.""" +def _y_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.y(qs[i]) + return qs + + +@guppy.overload(_y, _y_array) +def y(q): + """Functional Y gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _z(q: qubit @ owned) -> qubit: quantum.z(q) return q @guppy @no_type_check -def tdg(q: qubit @ owned) -> qubit: - """Functional Tdg gate command.""" +def _z_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.z(qs[i]) + return qs + + +@guppy.overload(_z, _z_array) +def z(q): + """Functional Z gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _tdg(q: qubit @ owned) -> qubit: quantum.tdg(q) return q @guppy @no_type_check -def sdg(q: qubit @ owned) -> qubit: - """Functional Sdg gate command.""" +def _tdg_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.tdg(qs[i]) + return qs + + +@guppy.overload(_tdg, _tdg_array) +def tdg(q): + """Functional Tdg gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _sdg(q: qubit @ owned) -> qubit: quantum.sdg(q) return q @guppy @no_type_check -def vdg(q: qubit @ owned) -> qubit: - """Functional Vdg gate command.""" +def _sdg_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.sdg(qs[i]) + return qs + + +@guppy.overload(_sdg, _sdg_array) +def sdg(q): + """Functional Sdg gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _vdg(q: qubit @ owned) -> qubit: quantum.vdg(q) return q +@guppy +@no_type_check +def _vdg_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.vdg(qs[i]) + return qs + + +@guppy.overload(_vdg, _vdg_array) +def vdg(q): + """Functional Vdg gate command. Accepts a single qubit or an array.""" + + +# --------------------------------------------------------------------------- +# Rotation gates (no array overloads for now) +# --------------------------------------------------------------------------- + + @guppy @no_type_check def rz(q: qubit @ owned, angle: angle) -> qubit: @@ -153,6 +262,82 @@ def crz( return control, target +# --------------------------------------------------------------------------- +# Two-qubit gates (scalar + array overloads) +# --------------------------------------------------------------------------- + + +@guppy +@no_type_check +def _cz(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: + quantum.cz(control, target) + return control, target + + +@guppy +@no_type_check +def _cz_array( + controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned +) -> tuple[array[qubit, N], array[qubit, N]]: + for i in range(N): + quantum.cz(controls[i], targets[i]) + return controls, targets + + +@guppy.overload(_cz, _cz_array) +def cz(control, target): + """Functional CZ gate command. Accepts single qubits or arrays.""" + + +@guppy +@no_type_check +def _cx(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: + quantum.cx(control, target) + return control, target + + +@guppy +@no_type_check +def _cx_array( + controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned +) -> tuple[array[qubit, N], array[qubit, N]]: + for i in range(N): + quantum.cx(controls[i], targets[i]) + return controls, targets + + +@guppy.overload(_cx, _cx_array) +def cx(control, target): + """Functional CX gate command. Accepts single qubits or arrays.""" + + +@guppy +@no_type_check +def _cy(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: + quantum.cy(control, target) + return control, target + + +@guppy +@no_type_check +def _cy_array( + controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned +) -> tuple[array[qubit, N], array[qubit, N]]: + for i in range(N): + quantum.cy(controls[i], targets[i]) + return controls, targets + + +@guppy.overload(_cy, _cy_array) +def cy(control, target): + """Functional CY gate command. Accepts single qubits or arrays.""" + + +# --------------------------------------------------------------------------- +# Other gates (no array overloads) +# --------------------------------------------------------------------------- + + @guppy @no_type_check def toffoli( @@ -165,12 +350,24 @@ def toffoli( @guppy @no_type_check -def reset(q: qubit @ owned) -> qubit: - """Functional Reset command.""" +def _reset(q: qubit @ owned) -> qubit: quantum.reset(q) return q +@guppy +@no_type_check +def _reset_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: + for i in range(N): + quantum.reset(qs[i]) + return qs + + +@guppy.overload(_reset, _reset_array) +def reset(q): + """Functional Reset command. Accepts a single qubit or an array.""" + + @guppy @no_type_check def project_z(q: qubit @ owned) -> tuple[qubit, bool]: diff --git a/tests/error/modifier_errors/higher_order.err b/tests/error/modifier_errors/higher_order.err index 94f82424b..6cfa05747 100644 --- a/tests/error/modifier_errors/higher_order.err +++ b/tests/error/modifier_errors/higher_order.err @@ -1,8 +1,8 @@ -Error: Unitary constraint violation (at $FILE:10:4) +Error: Type mismatch (at $FILE:17:16) | - 8 | def test_ho(f: Callable[[qubit], None], q: qubit) -> None: - 9 | # There is no way to use specify flags for f -10 | f(q) - | ^^^^ This function cannot be called in a dagger context +15 | q = qubit() +16 | with dagger: +17 | test_ho(h, q) + | ^ Expected argument of type `qubit -> None`, got `() -> None` Guppy compilation failed due to 1 previous error diff --git a/tests/error/poly_errors/non_linear2.err b/tests/error/poly_errors/non_linear2.err index 006751cdf..f7325bb3a 100644 --- a/tests/error/poly_errors/non_linear2.err +++ b/tests/error/poly_errors/non_linear2.err @@ -1,9 +1,8 @@ -Error: Not defined for linear argument (at $FILE:15:4) +Error: Type mismatch (at $FILE:15:8) | 13 | @guppy 14 | def main() -> None: 15 | foo(h) - | ^^^^^^ Cannot instantiate copyable type parameter `T` in type - | `forall T. (T -> T) -> None` with non-copyable type `qubit` + | ^ Expected argument of type `?T -> ?T`, got `() -> None` Guppy compilation failed due to 1 previous error diff --git a/tests/integration/test_quantum.py b/tests/integration/test_quantum.py index 060690206..7a96fdf9a 100644 --- a/tests/integration/test_quantum.py +++ b/tests/integration/test_quantum.py @@ -145,6 +145,113 @@ def test() -> None: validate(test.compile_function()) +def test_1qb_op_array(validate): + """Apply single-qubit gates to arrays of qubits.""" + + @guppy + @no_type_check + def test() -> None: + qs = array(qubit() for _ in range(5)) + q.h(qs) + q.t(qs) + q.s(qs) + q.v(qs) + q.x(qs) + q.y(qs) + q.z(qs) + q.tdg(qs) + q.sdg(qs) + q.vdg(qs) + q.reset(qs) + discard_array(qs) + + validate(test.compile_function()) + + +def test_2qb_op_array(validate): + """Apply two-qubit gates to arrays of qubits.""" + + @guppy + @no_type_check + def test() -> None: + qs1 = array(qubit() for _ in range(5)) + qs2 = array(qubit() for _ in range(5)) + q.cx(qs1, qs2) + q.cy(qs1, qs2) + q.cz(qs1, qs2) + discard_array(qs1) + discard_array(qs2) + + validate(test.compile_function()) + + +def test_measure_overload(validate): + """Measure an array of qubits using the overloaded measure function.""" + + @guppy + def test() -> array[bool, 10]: + qs = array(qubit() for _ in range(10)) + return measure(qs) + + validate(test.compile_function()) + + +def test_discard_overload(validate): + """Discard an array of qubits using the overloaded discard function.""" + + @guppy + def test() -> None: + qs = array(qubit() for _ in range(10)) + discard(qs) + + validate(test.compile_function()) + + +def test_functional_1qb_array(validate): + """Functional single-qubit gates on arrays.""" + + @guppy + def test() -> array[bool, 5]: + qs = array(qubit() for _ in range(5)) + qs = h(qs) + qs = x(qs) + qs = z(qs) + return measure(qs) + + validate(test.compile_function()) + + +def test_functional_2qb_array(validate): + """Functional two-qubit gates on arrays.""" + + @guppy + def test() -> None: + qs1 = array(qubit() for _ in range(5)) + qs2 = array(qubit() for _ in range(5)) + qs1, qs2 = cx(qs1, qs2) + qs1, qs2 = cy(qs1, qs2) + qs1, qs2 = cz(qs1, qs2) + discard(qs1) + discard(qs2) + + validate(test.compile_function()) + + +def test_scalar_still_works(validate): + """Ensure scalar usage of gates still works after overloading.""" + + @guppy + def test() -> bool: + q1 = qubit() + q2 = qubit() + q.h(q1) + q.cx(q1, q2) + discard(q1) + return measure(q2) + + validate(test.compile_function()) + + def test_panic_discard(validate): """Panic while discarding qubit.""" From 70c4e3a0a95c6e56b50c4cdf64e77a50082e31bf Mon Sep 17 00:00:00 2001 From: Robert Fisher Date: Mon, 9 Feb 2026 17:06:00 -0600 Subject: [PATCH 2/4] Added @no_type_check to transversal quantum operations to address mypy check --- .../src/guppylang/std/quantum/__init__.py | 18 +++++++++++++++++- .../src/guppylang/std/quantum/functional.py | 16 +++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/guppylang/src/guppylang/std/quantum/__init__.py b/guppylang/src/guppylang/std/quantum/__init__.py index bc2cea6f7..a885cb1c6 100644 --- a/guppylang/src/guppylang/std/quantum/__init__.py +++ b/guppylang/src/guppylang/std/quantum/__init__.py @@ -1,6 +1,6 @@ """Guppy standard module for quantum operations.""" -# mypy: disable-error-code="empty-body, misc, valid-type, no-untyped-def" +# mypy: disable-error-code="empty-body, misc, valid-type" from typing import no_type_check @@ -69,6 +69,7 @@ def _h_array(qs: array[qubit, N]) -> None: @guppy.overload(_h, _h_array) +@no_type_check def h(q) -> None: r"""Hadamard gate command. Accepts a single qubit or an array of qubits. @@ -94,6 +95,7 @@ def _t_array(qs: array[qubit, N]) -> None: @guppy.overload(_t, _t_array) +@no_type_check def t(q) -> None: r"""T gate. Accepts a single qubit or an array of qubits. @@ -120,6 +122,7 @@ def _s_array(qs: array[qubit, N]) -> None: @guppy.overload(_s, _s_array) +@no_type_check def s(q) -> None: r"""S gate. Accepts a single qubit or an array of qubits. @@ -146,6 +149,7 @@ def _v_array(qs: array[qubit, N]) -> None: @guppy.overload(_v, _v_array) +@no_type_check def v(q) -> None: r"""V gate. Accepts a single qubit or an array of qubits. @@ -172,6 +176,7 @@ def _x_array(qs: array[qubit, N]) -> None: @guppy.overload(_x, _x_array) +@no_type_check def x(q) -> None: r"""X gate. Accepts a single qubit or an array of qubits. @@ -198,6 +203,7 @@ def _y_array(qs: array[qubit, N]) -> None: @guppy.overload(_y, _y_array) +@no_type_check def y(q) -> None: r"""Y gate. Accepts a single qubit or an array of qubits. @@ -224,6 +230,7 @@ def _z_array(qs: array[qubit, N]) -> None: @guppy.overload(_z, _z_array) +@no_type_check def z(q) -> None: r"""Z gate. Accepts a single qubit or an array of qubits. @@ -250,6 +257,7 @@ def _tdg_array(qs: array[qubit, N]) -> None: @guppy.overload(_tdg, _tdg_array) +@no_type_check def tdg(q) -> None: r"""Tdg gate. Accepts a single qubit or an array of qubits. @@ -276,6 +284,7 @@ def _sdg_array(qs: array[qubit, N]) -> None: @guppy.overload(_sdg, _sdg_array) +@no_type_check def sdg(q) -> None: r"""Sdg gate. Accepts a single qubit or an array of qubits. @@ -302,6 +311,7 @@ def _vdg_array(qs: array[qubit, N]) -> None: @guppy.overload(_vdg, _vdg_array) +@no_type_check def vdg(q) -> None: r"""Vdg gate. Accepts a single qubit or an array of qubits. @@ -404,6 +414,7 @@ def _cz_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: @guppy.overload(_cz, _cz_array) +@no_type_check def cz(control, target) -> None: r"""Controlled-Z gate command. Accepts single qubits or arrays of qubits. @@ -435,6 +446,7 @@ def _cy_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: @guppy.overload(_cy, _cy_array) +@no_type_check def cy(control, target) -> None: r"""Controlled-Y gate command. Accepts single qubits or arrays of qubits. @@ -466,6 +478,7 @@ def _cx_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: @guppy.overload(_cx, _cx_array) +@no_type_check def cx(control, target) -> None: r"""Controlled-X gate command. Accepts single qubits or arrays of qubits. @@ -537,6 +550,7 @@ def _discard_array(qubits: array[qubit, N] @ owned) -> None: @guppy.overload(_discard, _discard_array) +@no_type_check def discard(q) -> None: """Discard a single qubit or an array of qubits.""" @@ -553,6 +567,7 @@ def _measure_array(qubits: array[qubit, N] @ owned) -> array[bool, N]: @guppy.overload(_measure, _measure_array) +@no_type_check def measure(q): """Measure a single qubit or an array of qubits destructively.""" @@ -570,6 +585,7 @@ def _reset_array(qs: array[qubit, N]) -> None: @guppy.overload(_reset, _reset_array) +@no_type_check def reset(q) -> None: """Reset a single qubit or an array of qubits to the :math:`|0\\rangle` state.""" diff --git a/guppylang/src/guppylang/std/quantum/functional.py b/guppylang/src/guppylang/std/quantum/functional.py index cd374934d..241352e9f 100644 --- a/guppylang/src/guppylang/std/quantum/functional.py +++ b/guppylang/src/guppylang/std/quantum/functional.py @@ -9,7 +9,7 @@ import guppylang.std.quantum as quantum from guppylang.decorator import guppy -# mypy: disable-error-code="empty-body, misc, valid-type, no-untyped-def" +# mypy: disable-error-code="empty-body, misc, valid-type" from guppylang.std.angles import angle from guppylang.std.array import array from guppylang.std.lang import owned @@ -39,6 +39,7 @@ def _h_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_h, _h_array) +@no_type_check def h(q): """Functional Hadamard gate command. Accepts a single qubit or an array.""" @@ -59,6 +60,7 @@ def _t_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_t, _t_array) +@no_type_check def t(q): """Functional T gate command. Accepts a single qubit or an array.""" @@ -79,6 +81,7 @@ def _s_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_s, _s_array) +@no_type_check def s(q): """Functional S gate command. Accepts a single qubit or an array.""" @@ -99,6 +102,7 @@ def _v_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_v, _v_array) +@no_type_check def v(q): """Functional V gate command. Accepts a single qubit or an array.""" @@ -119,6 +123,7 @@ def _x_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_x, _x_array) +@no_type_check def x(q): """Functional X gate command. Accepts a single qubit or an array.""" @@ -139,6 +144,7 @@ def _y_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_y, _y_array) +@no_type_check def y(q): """Functional Y gate command. Accepts a single qubit or an array.""" @@ -159,6 +165,7 @@ def _z_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_z, _z_array) +@no_type_check def z(q): """Functional Z gate command. Accepts a single qubit or an array.""" @@ -179,6 +186,7 @@ def _tdg_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_tdg, _tdg_array) +@no_type_check def tdg(q): """Functional Tdg gate command. Accepts a single qubit or an array.""" @@ -199,6 +207,7 @@ def _sdg_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_sdg, _sdg_array) +@no_type_check def sdg(q): """Functional Sdg gate command. Accepts a single qubit or an array.""" @@ -219,6 +228,7 @@ def _vdg_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_vdg, _vdg_array) +@no_type_check def vdg(q): """Functional Vdg gate command. Accepts a single qubit or an array.""" @@ -285,6 +295,7 @@ def _cz_array( @guppy.overload(_cz, _cz_array) +@no_type_check def cz(control, target): """Functional CZ gate command. Accepts single qubits or arrays.""" @@ -307,6 +318,7 @@ def _cx_array( @guppy.overload(_cx, _cx_array) +@no_type_check def cx(control, target): """Functional CX gate command. Accepts single qubits or arrays.""" @@ -329,6 +341,7 @@ def _cy_array( @guppy.overload(_cy, _cy_array) +@no_type_check def cy(control, target): """Functional CY gate command. Accepts single qubits or arrays.""" @@ -364,6 +377,7 @@ def _reset_array(qs: array[qubit, N] @ owned) -> array[qubit, N]: @guppy.overload(_reset, _reset_array) +@no_type_check def reset(q): """Functional Reset command. Accepts a single qubit or an array.""" From 91e48b5861eba7b2fd9e9e2dc9bdfe84ed52c921 Mon Sep 17 00:00:00 2001 From: Robert Fisher Date: Wed, 11 Feb 2026 17:00:36 -0600 Subject: [PATCH 3/4] Reorganzing order of gate functions to match original order --- .../src/guppylang/std/quantum/__init__.py | 216 ++++++++---------- .../src/guppylang/std/quantum/functional.py | 158 ++++++------- 2 files changed, 165 insertions(+), 209 deletions(-) diff --git a/guppylang/src/guppylang/std/quantum/__init__.py b/guppylang/src/guppylang/std/quantum/__init__.py index a885cb1c6..29d60b8f6 100644 --- a/guppylang/src/guppylang/std/quantum/__init__.py +++ b/guppylang/src/guppylang/std/quantum/__init__.py @@ -49,10 +49,6 @@ def maybe_qubit() -> Option[qubit]: if allocation succeeds or `nothing` if it fails.""" -# --------------------------------------------------------------------------- -# Single-qubit gates (scalar + array overloads) -# --------------------------------------------------------------------------- - N = guppy.nat_var("N") @@ -82,6 +78,102 @@ def h(q) -> None: """ +@hugr_op(quantum_op("CZ"), unitary_flags=UnitaryFlags.Unitary) +@no_type_check +def _cz(control: qubit, target: qubit) -> None: ... + + +@guppy +@no_type_check +def _cz_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: + for i in range(N): + _cz(controls[i], targets[i]) + + +@guppy.overload(_cz, _cz_array) +@no_type_check +def cz(control, target) -> None: + r"""Controlled-Z gate command. Accepts single qubits or arrays of qubits. + + cz(control, target) + + Qubit ordering: [control, target] + + .. math:: + \mathrm{CZ}= + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{pmatrix} + """ + + +@hugr_op(quantum_op("CY"), unitary_flags=UnitaryFlags.Unitary) +@no_type_check +def _cy(control: qubit, target: qubit) -> None: ... + + +@guppy +@no_type_check +def _cy_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: + for i in range(N): + _cy(controls[i], targets[i]) + + +@guppy.overload(_cy, _cy_array) +@no_type_check +def cy(control, target) -> None: + r"""Controlled-Y gate command. Accepts single qubits or arrays of qubits. + + cy(control, target) + + Qubit ordering: [control, target] + + .. math:: + \mathrm{CY}= + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & -i \\ + 0 & 0 & i & 0 + \end{pmatrix} + """ + + +@hugr_op(quantum_op("CX"), unitary_flags=UnitaryFlags.Unitary) +@no_type_check +def _cx(control: qubit, target: qubit) -> None: ... + + +@guppy +@no_type_check +def _cx_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: + for i in range(N): + _cx(controls[i], targets[i]) + + +@guppy.overload(_cx, _cx_array) +@no_type_check +def cx(control, target) -> None: + r"""Controlled-X gate command. Accepts single qubits or arrays of qubits. + + cx(control, target) + + Qubit ordering: [control, target] + + .. math:: + \mathrm{CX}= + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 + \end{pmatrix} + """ + + @hugr_op(quantum_op("T"), unitary_flags=UnitaryFlags.Unitary) @no_type_check def _t(q: qubit) -> None: ... @@ -325,11 +417,6 @@ def vdg(q) -> None: """ -# --------------------------------------------------------------------------- -# Rotation gates (no array overloads for now) -# --------------------------------------------------------------------------- - - @custom_function(RotationCompiler("Rz"), unitary_flags=UnitaryFlags.Unitary) @no_type_check def rz(q: qubit, angle: angle) -> None: @@ -396,112 +483,6 @@ def crz(control: qubit, target: qubit, angle: angle) -> None: """ -# --------------------------------------------------------------------------- -# Two-qubit gates (scalar + array overloads) -# --------------------------------------------------------------------------- - - -@hugr_op(quantum_op("CZ"), unitary_flags=UnitaryFlags.Unitary) -@no_type_check -def _cz(control: qubit, target: qubit) -> None: ... - - -@guppy -@no_type_check -def _cz_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: - for i in range(N): - _cz(controls[i], targets[i]) - - -@guppy.overload(_cz, _cz_array) -@no_type_check -def cz(control, target) -> None: - r"""Controlled-Z gate command. Accepts single qubits or arrays of qubits. - - cz(control, target) - - Qubit ordering: [control, target] - - .. math:: - \mathrm{CZ}= - \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & -1 - \end{pmatrix} - """ - - -@hugr_op(quantum_op("CY"), unitary_flags=UnitaryFlags.Unitary) -@no_type_check -def _cy(control: qubit, target: qubit) -> None: ... - - -@guppy -@no_type_check -def _cy_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: - for i in range(N): - _cy(controls[i], targets[i]) - - -@guppy.overload(_cy, _cy_array) -@no_type_check -def cy(control, target) -> None: - r"""Controlled-Y gate command. Accepts single qubits or arrays of qubits. - - cy(control, target) - - Qubit ordering: [control, target] - - .. math:: - \mathrm{CY}= - \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & -i \\ - 0 & 0 & i & 0 - \end{pmatrix} - """ - - -@hugr_op(quantum_op("CX"), unitary_flags=UnitaryFlags.Unitary) -@no_type_check -def _cx(control: qubit, target: qubit) -> None: ... - - -@guppy -@no_type_check -def _cx_array(controls: array[qubit, N], targets: array[qubit, N]) -> None: - for i in range(N): - _cx(controls[i], targets[i]) - - -@guppy.overload(_cx, _cx_array) -@no_type_check -def cx(control, target) -> None: - r"""Controlled-X gate command. Accepts single qubits or arrays of qubits. - - cx(control, target) - - Qubit ordering: [control, target] - - .. math:: - \mathrm{CX}= - \begin{pmatrix} - 1 & 0 & 0 & 0 \\ - 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 1 \\ - 0 & 0 & 1 & 0 - \end{pmatrix} - """ - - -# --------------------------------------------------------------------------- -# Other gates (no array overloads) -# --------------------------------------------------------------------------- - - @hugr_op(quantum_op("Toffoli"), unitary_flags=UnitaryFlags.Unitary) @no_type_check def toffoli(control1: qubit, control2: qubit, target: qubit) -> None: @@ -532,11 +513,6 @@ def project_z(q: qubit) -> bool: """Project a single qubit into the Z-basis (a non-destructive measurement).""" -# --------------------------------------------------------------------------- -# Measure, discard, reset (scalar + array overloads) -# --------------------------------------------------------------------------- - - @hugr_op(quantum_op("QFree")) @no_type_check def _discard(q: qubit @ owned) -> None: ... diff --git a/guppylang/src/guppylang/std/quantum/functional.py b/guppylang/src/guppylang/std/quantum/functional.py index 241352e9f..611f81f40 100644 --- a/guppylang/src/guppylang/std/quantum/functional.py +++ b/guppylang/src/guppylang/std/quantum/functional.py @@ -18,11 +18,6 @@ N = guppy.nat_var("N") -# --------------------------------------------------------------------------- -# Single-qubit gates (scalar + array overloads) -# --------------------------------------------------------------------------- - - @guppy @no_type_check def _h(q: qubit @ owned) -> qubit: @@ -44,6 +39,75 @@ def h(q): """Functional Hadamard gate command. Accepts a single qubit or an array.""" +@guppy +@no_type_check +def _cz(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: + quantum.cz(control, target) + return control, target + + +@guppy +@no_type_check +def _cz_array( + controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned +) -> tuple[array[qubit, N], array[qubit, N]]: + for i in range(N): + quantum.cz(controls[i], targets[i]) + return controls, targets + + +@guppy.overload(_cz, _cz_array) +@no_type_check +def cz(control, target): + """Functional CZ gate command. Accepts single qubits or arrays.""" + + +@guppy +@no_type_check +def _cx(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: + quantum.cx(control, target) + return control, target + + +@guppy +@no_type_check +def _cx_array( + controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned +) -> tuple[array[qubit, N], array[qubit, N]]: + for i in range(N): + quantum.cx(controls[i], targets[i]) + return controls, targets + + +@guppy.overload(_cx, _cx_array) +@no_type_check +def cx(control, target): + """Functional CX gate command. Accepts single qubits or arrays.""" + + +@guppy +@no_type_check +def _cy(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: + quantum.cy(control, target) + return control, target + + +@guppy +@no_type_check +def _cy_array( + controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned +) -> tuple[array[qubit, N], array[qubit, N]]: + for i in range(N): + quantum.cy(controls[i], targets[i]) + return controls, targets + + +@guppy.overload(_cy, _cy_array) +@no_type_check +def cy(control, target): + """Functional CY gate command. Accepts single qubits or arrays.""" + + @guppy @no_type_check def _t(q: qubit @ owned) -> qubit: @@ -233,11 +297,6 @@ def vdg(q): """Functional Vdg gate command. Accepts a single qubit or an array.""" -# --------------------------------------------------------------------------- -# Rotation gates (no array overloads for now) -# --------------------------------------------------------------------------- - - @guppy @no_type_check def rz(q: qubit @ owned, angle: angle) -> qubit: @@ -272,85 +331,6 @@ def crz( return control, target -# --------------------------------------------------------------------------- -# Two-qubit gates (scalar + array overloads) -# --------------------------------------------------------------------------- - - -@guppy -@no_type_check -def _cz(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - quantum.cz(control, target) - return control, target - - -@guppy -@no_type_check -def _cz_array( - controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned -) -> tuple[array[qubit, N], array[qubit, N]]: - for i in range(N): - quantum.cz(controls[i], targets[i]) - return controls, targets - - -@guppy.overload(_cz, _cz_array) -@no_type_check -def cz(control, target): - """Functional CZ gate command. Accepts single qubits or arrays.""" - - -@guppy -@no_type_check -def _cx(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - quantum.cx(control, target) - return control, target - - -@guppy -@no_type_check -def _cx_array( - controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned -) -> tuple[array[qubit, N], array[qubit, N]]: - for i in range(N): - quantum.cx(controls[i], targets[i]) - return controls, targets - - -@guppy.overload(_cx, _cx_array) -@no_type_check -def cx(control, target): - """Functional CX gate command. Accepts single qubits or arrays.""" - - -@guppy -@no_type_check -def _cy(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - quantum.cy(control, target) - return control, target - - -@guppy -@no_type_check -def _cy_array( - controls: array[qubit, N] @ owned, targets: array[qubit, N] @ owned -) -> tuple[array[qubit, N], array[qubit, N]]: - for i in range(N): - quantum.cy(controls[i], targets[i]) - return controls, targets - - -@guppy.overload(_cy, _cy_array) -@no_type_check -def cy(control, target): - """Functional CY gate command. Accepts single qubits or arrays.""" - - -# --------------------------------------------------------------------------- -# Other gates (no array overloads) -# --------------------------------------------------------------------------- - - @guppy @no_type_check def toffoli( From f48592f6bac24670a784ceee536f73c3f2db7399 Mon Sep 17 00:00:00 2001 From: Robert Fisher Date: Wed, 11 Feb 2026 17:24:22 -0600 Subject: [PATCH 4/4] Added reset_array alias and removed 'test_scalar_still_works' test --- guppylang/src/guppylang/std/quantum/__init__.py | 1 + tests/integration/test_quantum.py | 15 --------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/guppylang/src/guppylang/std/quantum/__init__.py b/guppylang/src/guppylang/std/quantum/__init__.py index 29d60b8f6..27ce456cc 100644 --- a/guppylang/src/guppylang/std/quantum/__init__.py +++ b/guppylang/src/guppylang/std/quantum/__init__.py @@ -569,6 +569,7 @@ def reset(q) -> None: # Backward-compatible aliases measure_array = _measure_array discard_array = _discard_array +reset_array = _reset_array # -------NON-PRIMITIVE------- diff --git a/tests/integration/test_quantum.py b/tests/integration/test_quantum.py index 7a96fdf9a..ed833536c 100644 --- a/tests/integration/test_quantum.py +++ b/tests/integration/test_quantum.py @@ -237,21 +237,6 @@ def test() -> None: validate(test.compile_function()) -def test_scalar_still_works(validate): - """Ensure scalar usage of gates still works after overloading.""" - - @guppy - def test() -> bool: - q1 = qubit() - q2 = qubit() - q.h(q1) - q.cx(q1, q2) - discard(q1) - return measure(q2) - - validate(test.compile_function()) - - def test_panic_discard(validate): """Panic while discarding qubit."""