Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
27 changes: 27 additions & 0 deletions guppylang/std/_internal/compiler/futures.py
Copy link
Contributor Author

@narmlu narmlu Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are Marks changes in #1075. I'm happy to rebase to that branch if you want, but I'm inclined to just wait until that's merged.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from collections.abc import Callable, Sequence

from hugr import ops
from hugr import tys as ht

from guppylang.error import InternalGuppyError
from guppylang.std._internal.compiler.tket2_exts import FUTURES_EXTENSION
from guppylang.tys.arg import Argument, TypeArg
from guppylang.tys.subst import Inst


def future_to_hugr(type_args: Sequence[Argument]) -> ht.Type:
match type_args:
case [TypeArg(ty)]:
type_def = FUTURES_EXTENSION.get_type("Future")
return type_def.instantiate([ht.TypeTypeArg(ty.to_hugr())])
case _:
raise InternalGuppyError("Invalid type args for Future type")


def future_op(op_name: str) -> Callable[[ht.FunctionType, Inst], ops.DataflowOp]:
op_def = FUTURES_EXTENSION.get_op(op_name)

def op(concrete: ht.FunctionType, args: Inst) -> ops.DataflowOp:
return op_def.instantiate([arg.to_hugr() for arg in args], concrete)

return op
30 changes: 30 additions & 0 deletions guppylang/std/futures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Generic, no_type_check

from guppylang.decorator import guppy
from guppylang.std._internal.compiler.futures import future_op, future_to_hugr
from guppylang.std.lang import owned
from guppylang.tys.param import TypeParam

T = guppy.type_var("T", copyable=False, droppable=False)

_future_params = [TypeParam(0, "T", must_be_copyable=False, must_be_droppable=False)]


@guppy.type(future_to_hugr, copyable=False, droppable=False, params=_future_params)
class Future(Generic[T]): # type: ignore[misc]
"""A value of type `T` that is computed asynchronously."""

@guppy.hugr_op(future_op("Read"))
@no_type_check
def read(self: "Future[T]" @ owned) -> T:
"""Reads a value from a future, consuming it."""

@guppy.hugr_op(future_op("Dup"))
@no_type_check
def copy(self: "Future[T]") -> "Future[T]":
"""Duplicate a future."""

@guppy.hugr_op(future_op("Free"))
@no_type_check
def discard(self: "Future[T]" @ owned) -> None:
"""Discards a future without reading it."""
48 changes: 48 additions & 0 deletions guppylang/std/qsystem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from guppylang.std._internal.util import quantum_op
from guppylang.std.angles import angle, pi
from guppylang.std.builtins import owned
from guppylang.std.futures import Future
from guppylang.std.option import Option, nothing, some
from guppylang.std.quantum import qubit


Expand Down Expand Up @@ -74,6 +76,52 @@ def reset(q: qubit) -> None: ...
def qfree(q: qubit @ owned) -> None: ...


@guppy.hugr_op(quantum_op("LazyMeasureLeaked", ext=QSYSTEM_EXTENSION))
@no_type_check
def _measure_leaked(q: qubit @ owned) -> Future[int]:
"""Measure the quibit or return 2 if it is leaked."""


@guppy
@no_type_check
def measure_leaked(q: qubit @ owned) -> "MaybeLeaked":
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a dosctring here?

fm = _measure_leaked(q)
return MaybeLeaked(fm)


@guppy.struct
@no_type_check
class MaybeLeaked:
"""A class representing a measurement that may have leaked.

This is used to represent the result of `measure_leaked`, which can either
return a boolean measurement result or indicate that the qubit has leaked.
"""

_measurement: Future[int] # type: ignore[type-arg]

@guppy
@no_type_check
def is_leaked(self: "MaybeLeaked") -> bool:
"""Check if the measurement indicates a leak."""
return self._measurement.copy().read() == 2

@guppy
@no_type_check
def to_result(self: "MaybeLeaked @ owned") -> Option[bool]:
"""Get the measurement result if not leaked."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this would be more accurate?

Suggested change
"""Get the measurement result if not leaked."""
"""Returns the measurement result or `nothing` if leaked."""

int_value: int = self._measurement.read()
if int_value == 2:
return nothing()
measurement = int_value == 1
return some(measurement)

@guppy
@no_type_check
def discard(self: "MaybeLeaked @ owned") -> None:
self._measurement.discard()


# ------------------------------------------------------
# --------- Internal definitions -----------------------
# ------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ miette-py = { workspace = true }

# Uncomment these to test the latest dependency version during development
# hugr = { git = "https://github.com/CQCL/hugr", subdirectory = "hugr-py", tag = "hugr-v0.20.2" }
# tket2-exts = { git = "https://github.com/CQCL/tket2", subdirectory = "tket2-exts", rev = "652a7d0" }
tket2-exts = { git = "https://github.com/CQCL/tket2", subdirectory = "tket2-exts", rev = "38d1c6f" }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this rev dependency still needed? If yes we should wait for a tket-exts release before merging

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good catch, we can wait until then (corresponding tket2 release PR CQCL/tket2#952)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the one you need: CQCL/tket2#951

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, exts. Got it, thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now released

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumped exts to the new release. Will merge when you remove wait to merge in case there are any other gotchas :).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only concern is selene support, @jake-arkinstall thoughts on merging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From @jake-arkinstall in teams:

I have no concerns, merge away. It won’t work with Selene and it’ll link error if used, but it won’t break existing usage (if it isn’t used it isn’t emitted). Selene can play catch up next week.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ss2165 - I think this is ready to merge. I'll do so tomorrow morning (Colorado time) unless you tell me otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

# tket2 = { git = "https://github.com/CQCL/tket2", subdirectory = "tket2-py", rev = "fcb2131" }


Expand Down
15 changes: 15 additions & 0 deletions tests/integration/std/test_futures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from guppylang.decorator import guppy
from guppylang.std.futures import Future
from guppylang.std.lang import owned


def test_read(validate):
@guppy
def main(x: Future[int], y: Future[bool] @ owned) -> int:
z = x.copy()
if y.read():
return z.read()
z.discard()
return 0

validate(guppy.compile(main))
20 changes: 20 additions & 0 deletions tests/integration/test_qsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
from guppylang.decorator import guppy
from guppylang.std.angles import angle
from guppylang.std.builtins import owned, array
from guppylang.std.option import Option
from guppylang.std.qsystem.random import make_discrete_distribution, RNG

from guppylang.std.qsystem import MaybeLeaked, measure_leaked
from guppylang.std.qsystem.utils import get_current_shot
from guppylang.std.quantum import qubit, measure_array
from guppylang.std.qsystem.functional import (
Expand Down Expand Up @@ -59,3 +62,20 @@ def test() -> tuple[int, float, int, int, angle, angle]:
return rint, rfloat, rint_bnd, rint_discrete, rangle, rcangle

validate(guppy.compile(test))


def test_measure_leaked(validate): # type: ignore[no-untyped-def]
"""Compile the measure_leaked operation."""

@guppy
def test(q: qubit @ owned) -> bool:
ml: MaybeLeaked = measure_leaked(q)
if ml.is_leaked():
ml.discard()
q = qubit()
m = measure(q)
return m
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the intention of this code pattern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't represent a "real" pattern. It is intended to make sure that things type check and that the variable q was properly consumed when measure_leaked(q) was called.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to document that. Or remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

b: bool = ml.to_result().unwrap()
return b

validate(guppy.compile(test))
Loading
Loading