Skip to content

Commit

Permalink
fix: hseries ops use floats instead of angles (#483)
Browse files Browse the repository at this point in the history
Adds unit tests to generate and validate each operation in
`prelude.quantum`.

Closes #477
  • Loading branch information
aborgna-q authored Sep 11, 2024
1 parent a89f225 commit 7ed3853
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 39 deletions.
40 changes: 36 additions & 4 deletions guppylang/prelude/quantum.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# mypy: disable-error-code="empty-body, misc"

import typing

from hugr import tys as ht

from guppylang.decorator import guppy
Expand Down Expand Up @@ -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"))
Expand All @@ -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.
"""
19 changes: 0 additions & 19 deletions tests/integration/test_qalloc.py

This file was deleted.

116 changes: 116 additions & 0 deletions tests/integration/test_quantum.py
Original file line number Diff line number Diff line change
@@ -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)
23 changes: 8 additions & 15 deletions tests/integration/test_tket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7ed3853

Please sign in to comment.