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..27ce456cc 100644 --- a/guppylang/src/guppylang/std/quantum/__init__.py +++ b/guppylang/src/guppylang/std/quantum/__init__.py @@ -49,10 +49,25 @@ def maybe_qubit() -> Option[qubit]: if allocation succeeds or `nothing` if it fails.""" +N = guppy.nat_var("N") + + @hugr_op(quantum_op("H"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def h(q: qubit) -> None: - r"""Hadamard gate command +def _h(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _h_array(qs: array[qubit, N]) -> None: + for i in range(N): + _h(qs[i]) + + +@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. .. math:: \mathrm{H}= \frac{1}{\sqrt{2}} @@ -65,8 +80,20 @@ def h(q: qubit) -> None: @hugr_op(quantum_op("CZ"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def cz(control: qubit, target: qubit) -> None: - r"""Controlled-Z gate command. +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) @@ -85,8 +112,20 @@ def cz(control: qubit, target: qubit) -> None: @hugr_op(quantum_op("CY"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def cy(control: qubit, target: qubit) -> None: - r"""Controlled-Y gate command. +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) @@ -105,8 +144,20 @@ def cy(control: qubit, target: qubit) -> None: @hugr_op(quantum_op("CX"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def cx(control: qubit, target: qubit) -> None: - r"""Controlled-X gate command. +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) @@ -125,8 +176,20 @@ def cx(control: qubit, target: qubit) -> None: @hugr_op(quantum_op("T"), unitary_flags=UnitaryFlags.Unitary) @no_type_check -def t(q: qubit) -> None: - r"""T gate. +def _t(q: qubit) -> None: ... + + +@guppy +@no_type_check +def _t_array(qs: array[qubit, N]) -> None: + for i in range(N): + _t(qs[i]) + + +@guppy.overload(_t, _t_array) +@no_type_check +def t(q) -> None: + r"""T gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{T}= @@ -140,8 +203,20 @@ 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) +@no_type_check +def s(q) -> None: + r"""S gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{S}= @@ -155,8 +230,20 @@ 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) +@no_type_check +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 +257,20 @@ 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) +@no_type_check +def x(q) -> None: + r"""X gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{X}= @@ -185,8 +284,20 @@ 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) +@no_type_check +def y(q) -> None: + r"""Y gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{Y}= @@ -200,8 +311,20 @@ 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) +@no_type_check +def z(q) -> None: + r"""Z gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{Z}= @@ -215,8 +338,20 @@ 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) +@no_type_check +def tdg(q) -> None: + r"""Tdg gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{T}^\dagger= @@ -230,8 +365,20 @@ 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) +@no_type_check +def sdg(q) -> None: + r"""Sdg gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{S}^\dagger= @@ -245,8 +392,20 @@ 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) +@no_type_check +def vdg(q) -> None: + r"""Vdg gate. Accepts a single qubit or an array of qubits. .. math:: \mathrm{V}^\dagger= \frac{1}{\sqrt{2}} @@ -356,38 +515,61 @@ def project_z(q: qubit) -> bool: @hugr_op(quantum_op("QFree")) @no_type_check -def discard(q: qubit @ owned) -> None: - """Discard a single qubit.""" +def _discard(q: qubit @ owned) -> None: ... -@hugr_op(quantum_op("MeasureFree")) +@guppy @no_type_check -def measure(q: qubit @ owned) -> bool: - """Measure a single qubit destructively.""" +def _discard_array(qubits: array[qubit, N] @ owned) -> None: + for q in qubits: + _discard(q) -@hugr_op(quantum_op("Reset")) +@guppy.overload(_discard, _discard_array) @no_type_check -def reset(q: qubit) -> None: - """Reset a single qubit to the :math:`|0\rangle` state.""" +def discard(q) -> None: + """Discard a single qubit or an array of qubits.""" -N = guppy.nat_var("N") +@hugr_op(quantum_op("MeasureFree")) +@no_type_check +def _measure(q: qubit @ owned) -> bool: ... @guppy @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 _measure_array(qubits: array[qubit, N] @ owned) -> array[bool, N]: + return array(_measure(q) for q in qubits) + + +@guppy.overload(_measure, _measure_array) +@no_type_check +def measure(q): + """Measure a single qubit or an array of qubits destructively.""" + + +@hugr_op(quantum_op("Reset")) +@no_type_check +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) +@no_type_check +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 +reset_array = _reset_array # -------NON-PRIMITIVE------- diff --git a/guppylang/src/guppylang/std/quantum/functional.py b/guppylang/src/guppylang/std/quantum/functional.py index 58cebf98b..611f81f40 100644 --- a/guppylang/src/guppylang/std/quantum/functional.py +++ b/guppylang/src/guppylang/std/quantum/functional.py @@ -11,114 +11,292 @@ # 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 from guppylang.std.quantum import qubit +N = guppy.nat_var("N") + @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.""" +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) +@no_type_check +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 cx(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - """Functional CX gate command.""" +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 cy(control: qubit @ owned, target: qubit @ owned) -> tuple[qubit, qubit]: - """Functional CY gate command.""" +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 t(q: qubit @ owned) -> qubit: - """Functional T gate command.""" +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: quantum.t(q) return q @guppy @no_type_check -def s(q: qubit @ owned) -> qubit: - """Functional S gate command.""" +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) +@no_type_check +def t(q): + """Functional T gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +def _s(q: qubit @ owned) -> qubit: quantum.s(q) return q @guppy @no_type_check -def v(q: qubit @ owned) -> qubit: - """Functional V gate command.""" +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) +@no_type_check +def s(q): + """Functional S gate command. Accepts a single qubit or an array.""" + + +@guppy +@no_type_check +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) +@no_type_check +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) +@no_type_check +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) +@no_type_check +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) +@no_type_check +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) +@no_type_check +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) +@no_type_check +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) +@no_type_check +def vdg(q): + """Functional Vdg gate command. Accepts a single qubit or an array.""" + + @guppy @no_type_check def rz(q: qubit @ owned, angle: angle) -> qubit: @@ -165,12 +343,25 @@ 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) +@no_type_check +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..ed833536c 100644 --- a/tests/integration/test_quantum.py +++ b/tests/integration/test_quantum.py @@ -145,6 +145,98 @@ 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_panic_discard(validate): """Panic while discarding qubit."""