diff --git a/guppylang/prelude/quantum.py b/guppylang/prelude/quantum.py index 5ad4861b..58b0fe8d 100644 --- a/guppylang/prelude/quantum.py +++ b/guppylang/prelude/quantum.py @@ -2,6 +2,8 @@ # mypy: disable-error-code="empty-body, misc" +import typing + from hugr import tys as ht from guppylang.decorator import guppy @@ -84,12 +86,19 @@ def rz(q: qubit, angle: angle) -> qubit: ... def rx(q: qubit, angle: angle) -> qubit: ... -@guppy.hugr_op(quantum, quantum_op("PhasedX", ext=HSERIES_EXTENSION)) -def phased_x(q: qubit, angle1: angle, angle2: angle) -> qubit: ... +@guppy(quantum) +@typing.no_type_check +def phased_x(q: qubit, angle1: angle, angle2: angle) -> qubit: + f1 = float(angle1) + f2 = float(angle2) + return _phased_x(q, f1, f2) -@guppy.hugr_op(quantum, quantum_op("ZZPhase", ext=HSERIES_EXTENSION)) -def zz_phase(q1: qubit, q2: qubit, angle: angle) -> tuple[qubit, qubit]: ... +@guppy(quantum) +@typing.no_type_check +def zz_phase(q1: qubit, q2: qubit, angle: angle) -> tuple[qubit, qubit]: + f = float(angle) + return _zz_phase(q1, q2, f) @guppy.hugr_op(quantum, quantum_op("QFree")) @@ -102,3 +111,26 @@ def reset(q: qubit) -> qubit: ... @guppy.custom(quantum, MeasureCompiler()) def measure(q: qubit) -> bool: ... + + +# ------------------------------------------------------ +# --------- Internal definitions ----------------------- +# ------------------------------------------------------ + + +@guppy.hugr_op(quantum, quantum_op("PhasedX", ext=HSERIES_EXTENSION)) +def _phased_x(q: qubit, angle1: float, angle2: float) -> qubit: + """PhasedX operation from the hseries extension. + + See `guppylang.prelude.quantum.phased_x` for a public definition that + accepts angle parameters. + """ + + +@guppy.hugr_op(quantum, quantum_op("ZZPhase", ext=HSERIES_EXTENSION)) +def _zz_phase(q1: qubit, q2: qubit, angle: float) -> tuple[qubit, qubit]: + """ZZPhase operation from the hseries extension. + + See `guppylang.prelude.quantum.phased_x` for a public definition that + accepts angle parameters. + """ diff --git a/tests/integration/test_qalloc.py b/tests/integration/test_qalloc.py deleted file mode 100644 index 97506d5a..00000000 --- a/tests/integration/test_qalloc.py +++ /dev/null @@ -1,19 +0,0 @@ -from guppylang.decorator import guppy -from guppylang.module import GuppyModule -from guppylang.prelude.quantum import qubit - -import guppylang.prelude.quantum as quantum -from guppylang.prelude.quantum import cx, measure, dirty_qubit - - -def test_dirty_qubit(validate): - module = GuppyModule("test") - module.load_all(quantum) - - @guppy(module) - def test() -> tuple[bool, bool]: - q1, q2 = qubit(), dirty_qubit() - q1, q2 = cx(q1, q2) - return (measure(q1), measure(q2)) - - validate(module.compile()) diff --git a/tests/integration/test_quantum.py b/tests/integration/test_quantum.py new file mode 100644 index 00000000..be9fafcb --- /dev/null +++ b/tests/integration/test_quantum.py @@ -0,0 +1,116 @@ +"""Various tests for the functions defined in `guppylang.prelude.quantum`.""" + +import pytest + +from hugr.ext import Package + +import guppylang.decorator +from guppylang.decorator import guppy +from guppylang.module import GuppyModule +from guppylang.prelude.angles import angle + +from guppylang.prelude.builtins import py +import guppylang.prelude.quantum as quantum +from guppylang.prelude.quantum import ( + cx, + cz, + h, + t, + s, + x, + y, + z, + tdg, + sdg, + zz_max, + phased_x, + qubit, + rx, + rz, + zz_phase, + discard, + measure, + measure_return, + dirty_qubit, + reset, +) + + +def compile_quantum_guppy(fn) -> Package: + """A decorator that combines @guppy with HUGR compilation. + + Modified version of `tests.util.compile_guppy` that loads the quantum module. + """ + assert not isinstance( + fn, + GuppyModule, + ), "`@compile_quantum_guppy` does not support extra arguments." + + module = GuppyModule("module") + module.load(angle) + module.load_all(quantum) + guppylang.decorator.guppy(module)(fn) + return module.compile() + + +def test_dirty_qubit(validate): + @compile_quantum_guppy + def test() -> tuple[bool, bool]: + q1, q2 = qubit(), dirty_qubit() + q1, q2 = cx(q1, q2) + return (measure(q1), measure(q2)) + + validate(test) + + +def test_1qb_op(validate): + @compile_quantum_guppy + def test(q: qubit) -> qubit: + q = h(q) + q = t(q) + q = s(q) + q = x(q) + q = y(q) + q = z(q) + q = tdg(q) + q = sdg(q) + return q + + validate(test) + + +def test_2qb_op(validate): + @compile_quantum_guppy + def test(q1: qubit, q2: qubit) -> tuple[qubit, qubit]: + q1, q2 = cx(q1, q2) + q1, q2 = cz(q1, q2) + q1, q2 = zz_max(q1, q2) + return (q1, q2) + + validate(test) + + +def test_measure_ops(validate): + """Compile various measurement-related operations.""" + + @compile_quantum_guppy + def test(q1: qubit, q2: qubit) -> tuple[bool, bool]: + q1, b1 = measure_return(q1) + q1 = discard(q1) + q2 = reset(q2) + b2 = measure(q2) + return (b1, b2) + + validate(test) + + +def test_parametric(validate): + """Compile various parametric operations.""" + + @compile_quantum_guppy + def test(q1: qubit, q2: qubit, a1: angle, a2: angle) -> tuple[qubit, qubit]: + q1 = rx(q1, a1) + q2 = rz(q2, a2) + q1 = phased_x(q1, a1, a2) + q1, q2 = zz_phase(q1, q2, a1) + return (q1, q2) diff --git a/tests/integration/test_tket.py b/tests/integration/test_tket.py index 7e9449d9..10197845 100644 --- a/tests/integration/test_tket.py +++ b/tests/integration/test_tket.py @@ -49,15 +49,13 @@ def my_func( return (q0, q1) circ = guppy_to_circuit(my_func) - assert circ.num_operations() == 8 tk1 = circ.to_tket1() assert tk1.n_qubits == 2 - # TODO: rz and phased_x do not currently emit tket2 operations, - # so they don't get lowered to tket1 gates - # assert tk1.n_gates == 7 - # gates = list(tk1) - # assert gates[4].op.type == pytket.circuit.OpType.ZZMax + # TODO: The tket1 conversion needs to be updated with all the hugr ops changes + # before we can test the translated ops + # ops = {g.op.type for g in tk1} + # assert pytket.circuit.OpType.ZZMax in ops @pytest.mark.skipif(not tket2_installed, reason="Tket2 is not installed") @@ -83,14 +81,9 @@ def my_func( circ = guppy_to_circuit(my_func) - # The 7 operations in the function, plus two implicit QFree, plus one angle - # division op (only counted once since it's in a function?) - assert circ.num_operations() == 10 - tk1 = circ.to_tket1() assert tk1.n_qubits == 2 - # TODO: rz and phased_x do not currently emit tket2 operations, - # so they don't get lowered to tket1 gates - # assert tk1.n_gates == 7 - # gates = list(tk1) - # assert gates[4].op.type == pytket.circuit.OpType.ZZMax + # TODO: The tket1 conversion needs to be updated with all the hugr ops changes + # before we can test the translated ops + # ops = {g.op.type for g in tk1} + # assert pytket.circuit.OpType.ZZMax in ops diff --git a/uv.lock b/uv.lock index 5041e473..845ea84b 100644 --- a/uv.lock +++ b/uv.lock @@ -518,7 +518,7 @@ wheels = [ [[package]] name = "guppylang" -version = "0.9.0" +version = "0.10.0" source = { editable = "." } dependencies = [ { name = "graphviz" },