Skip to content

Commit

Permalink
feat: Allow pytket circuits in py expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-koch committed Jan 18, 2024
1 parent f52a5de commit a427f00
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 1 deletion.
18 changes: 18 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,21 @@ jobs:

- name: Run tests
run: poetry run pytest

- name: Checkout tket2
uses: actions/checkout@v4
with:
name: CQCL/tket2
path: guppy

- name: Build tket2
uses: PyO3/maturin-action@v1
with:
command: build
sccache: 'true'
rust-toolchain: '1.75'
working-directory: tket2/tket2-py

- name: Rerun `py(...)` expression tests with tket2 installed
run: poetry run pytest tests/integration/test_py.py tests/error/test_py_errors.py

22 changes: 22 additions & 0 deletions guppylang/checker/expr_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
NoneType,
Subst,
TupleType,
row_to_type,
unify,
)
from guppylang.nodes import (
Expand Down Expand Up @@ -918,4 +919,25 @@ def python_value_to_guppy_type(
return None
return TupleType(cast(list[GuppyType], tys))
case _:
# Pytket conversion is an optional feature
try:
import pytket

if isinstance(v, pytket.circuit.Circuit):
# We also need tket2 installed
try:
import tket2 # type: ignore[import-untyped] # noqa: F401

qubit = globals.types["Qubit"].build()
return FunctionType(
[qubit] * v.n_qubits, row_to_type([qubit] * v.n_qubits)
)
except ImportError:
raise GuppyError(
"Pytket compatibility requires `tket2` to be installed. "
"See https://github.com/CQCL/tket2/tree/main/tket2-py",
node,
) from None
except ImportError:
pass
return None
12 changes: 12 additions & 0 deletions guppylang/compiler/expr_compiler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ast
import json
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Any
Expand Down Expand Up @@ -314,4 +315,15 @@ def python_value_to_hugr(v: Any) -> val.Value | None:
return None
return val.Tuple(vs=vs)
case _:
# Pytket conversion is an optional feature
try:
import pytket

if isinstance(v, pytket.circuit.Circuit):
from tket2.circuit import Tk2Circuit # type: ignore[import-untyped]

hugr = json.loads(Tk2Circuit(v).to_hugr_json())
return val.FunctionVal(hugr=hugr)
except ImportError:
pass
return None
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mypy = "^1.8.0"
pre-commit = "^3.6.0"
ruff = "^0.1.11"
maturin = "^1.4.0"
pytket = "^1.24.0"

[build-system]
requires = ["poetry-core"]
Expand Down
20 changes: 20 additions & 0 deletions tests/error/py_errors/tket2_not_installed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pytket import Circuit

from guppylang.decorator import guppy
from guppylang.module import GuppyModule
from guppylang.prelude.quantum import quantum, Qubit

circ = Circuit(1)
circ.H(0)

module = GuppyModule("test")
module.load(quantum)


@guppy(module)
def foo(q: Qubit) -> Qubit:
f = py(circ)
return f(q)


module.compile()
17 changes: 16 additions & 1 deletion tests/error/test_py_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@

from tests.error.util import run_error_test


try:
import tket2

tket2_installed = True
except ImportError:
tket2_installed = False


path = pathlib.Path(__file__).parent.resolve() / "py_errors"
files = [
x
for x in path.iterdir()
if x.is_file()
if x.suffix == ".py" and x.name != "__init__.py"
if x.suffix == ".py" and x.name not in ("__init__.py", "tket2_not_installed.py")
]

# Turn paths into strings, otherwise pytest doesn't display the names
Expand All @@ -18,3 +27,9 @@
@pytest.mark.parametrize("file", files)
def test_py_errors(file, capsys):
run_error_test(file, capsys)


@pytest.mark.skipif(tket2_installed, reason="tket2 is installed")
def test_tket2_not_installed(capsys):
path = pathlib.Path(__file__).parent.resolve() / "py_errors" / "tket2_not_installed.py"
run_error_test(str(path), capsys)
51 changes: 51 additions & 0 deletions tests/integration/test_py.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import pytest

from guppylang.decorator import guppy
from guppylang.module import GuppyModule
from guppylang.prelude.quantum import Qubit, quantum
from tests.integration.util import py
from tests.util import compile_guppy


try:
import tket2

tket2_installed = True
except ImportError:
tket2_installed = False


def test_basic(validate):
x = 42

Expand Down Expand Up @@ -60,3 +73,41 @@ def foo() -> int:
return x

validate(foo)


@pytest.mark.skipif(not tket2_installed, reason="Tket2 is not installed")
def test_pytket_single_qubit(validate):
from pytket import Circuit

circ = Circuit(1)
circ.H(0)

module = GuppyModule("test")
module.load(quantum)

@guppy(module)
def foo(q: Qubit) -> Qubit:
f = py(circ)
return f(q)

validate(module.compile())


@pytest.mark.skipif(not tket2_installed, reason="Tket2 is not installed")
def test_pytket_multi_qubit(validate):
from pytket import Circuit

circ = Circuit(3)
circ.CX(0, 1)
circ.H(2)
circ.T(0)
circ.CZ(2, 0)

module = GuppyModule("test")
module.load(quantum)

@guppy(module)
def foo(q1: Qubit, q2:Qubit, q3:Qubit) -> tuple[Qubit, Qubit, Qubit]:
return py(circ)(q1, q2, q3)

validate(module.compile())

0 comments on commit a427f00

Please sign in to comment.