Migrating to Guppy Version 1.0¶
Guppy v1 is the first stable release of the Guppy quantum programming language. It introduces several major new features alongside a number of breaking changes and new behaviours.
Guppy v1 requires Python 3.12 or later: support for Python 3.10 and 3.11 has been removed.
This guide details the key code changes needed to migrate to Guppy v1 from the 0.x series and explains the rationale for the changes. For a summary of all of the new features available in Guppy v1, see the changelog.
The std.quantum.measure function now returns a Measurement rather than a bool¶
A major change in Guppy v1 is that the measure function no longer returns a bool indicating \(\lvert0\rangle\) or \(\lvert1\rangle\). Instead, it returns a Measurement object. This can be resolved to a boolean
by using the read() method.
@guppy
def f(a: array[qubit, 6] @owned) -> None:
for q in a:
result("t", measure(q))
@guppy
def f(a: array[qubit, 6] @owned) -> None:
for q in a:
output("t", measure(q).read())
Note that in this snippet result has been changed to output in line with the deprecation mentioned later.
This change is to emphasize that forcing measurements can have a significant effect on a program’s performance.
In Selene and Quantinuum systems it is recommended to use the value of measurements as late as possible to allow more opportunities for parallelism during the runtime of the program. Resolving measurements with the read() method makes the behaviour more explicit and allows us to avoid accidentally forcing a sequence of quantum gates to be performed earlier than necessary.
The project_z function also now returns a Measurement object.
For more on how measurements work in Guppy v1, consult the measurements section of the language guide.
Guppy structs are now mutable by default¶
In Guppy v1, structs are now mutable and affine by default. Prior to the v1 release, Guppy structs were always immutable and therefore implicitly copyable.
Mutable - The values of struct fields can be modified after the struct is initialized.
Affine - A mutable struct value can be used at most once. Guppy’s type system only allows a single reference to mutable objects.
Users can indicate that a struct should be immutable by specifying the frozen=True keyword argument in the @guppy.struct decorator (as in Python dataclasses).
When migrating to Guppy v1, users will need to add this frozen=True keyword argument to keep the v0.x behaviour of their structs.
# Grid is immutable by default
@guppy.struct
class Grid:
shape: array[int, 2]
n_filled_sites: int
# Use frozen=True
# to make Grid immutable
@guppy.struct(frozen=True)
class Grid:
shape: array[int, 2]
n_filled_sites: int
Allowing mutable structs means that Guppy users can define certain data structures in a much more streamlined fashion. For example, a mutable struct can be used to define a “counter” structure to track the number of gates applied during the runtime of the program.
Note that because structs are now affine by default, they cannot be implicitly copied.
Here is an example of an implicit struct copy which would type-check with Guppy 0.x but gives a type error with Guppy v1.
from guppylang import guppy
@guppy.struct
class MyStruct:
x: int
y: float
@guppy
def add(self) -> float:
return self.x + self.y
@guppy
def main() -> None:
s0 = MyStruct(7, 1.4)
s1 = s0
s0.add() # Copy violation as we are using s0 after a move
main.check() # Fails type check in v1, valid in 0.x
Error: Copy violation (at <In[1]>:16:3)
|
14 | s0 = MyStruct(7, 1.4)
15 | s1 = s0
16 | s0.add() # Copy violation as we are using s0 after a move
| ^^ Variable `s0` cannot be borrowed ...
Note:
|
14 | s0 = MyStruct(7, 1.4)
15 | s1 = s0
| -- ... since variable `s0` with non-copyable type `MyStruct`
| was already moved here
Guppy compilation failed due to 1 previous error
This code above gives an error in Guppy v1. However if we specified @guppy.struct(frozen=True) then this code would type-check as s1 would be an immutable copy of s0 instead of a reference to s0.
Standard library breakages¶
Internal fields of
collectionstypes are now private
Guppy has a collections module with useful Stack, Queue and PriorityQueue containers. These containers are now implemented as mutable Guppy structs with corresponding methods. The struct methods are the idiomatic way to program with these collections so the struct fields are now private.
The deprecated
quantum_functionalmodule has been removed
The quantum_functional module no longer exists in Guppy v1. Functional quantum operations can be found in the guppylang.std.quantum.functional module instead.
New Function type in Guppy replacing Callable in annotations¶
In Guppy 0.x, the Callable type could be used to annotate Guppy functions passed in type signatures. Guppy v1 has its own Callable type which is a Guppy protocol.
The new Guppy Function type implements this Callable protocol.
This means that these functions are now considered generic. Note that generic functions can’t be compiled directly in guppy v1. In order to write a non-generic function that takes a function argument, use the new Function type instead of Callable.
This is a breaking change as functions with Callable in their signatures will no longer compile in Guppy v1.
Usage of Callable for functions should be replaced with the new Function type as shown below.
from typing import Callable
@guppy
def prepare_choi_state(
unitary: Callable[[array[qubit, N]], None],
) -> array[array[qubit, N], 2]:
...
from guppylang.std.builtins import Function
@guppy
def prepare_choi_state(
unitary: Function[[array[qubit, N]], None],
) -> array[array[qubit, N], 2]:
...
The result function has been renamed to output¶
In Guppy 0.x the result function was used for tagging values returned from Selene and QSystem execution. This function has now been deprecated and output should be used in its place.
The reason for this change is that the term “result” was overloaded with other distinct classes such as EmulatorResult and std.err.Result.
Note that the result function hasn’t been removed and will still work. The use of output is encouraged for clarity.
This change also applies to the state_result function which is used to get a statevector printout during the simulation of a quantum program. Now state_result is deprecated and state_output should be used instead.
Changes to Guppy libraries¶
Introduced in Guppy v0.21.13, Guppy libraries provide a way to group Guppy functions and structs into a single compilable unit. With the new Guppy v1 release, there are two breakages to Guppy libraries. For up-to-date information on how libraries work in Guppy, refer to the libraries section of the Guppy language guide.
Guppy libraries are now created with
guppylang.library.GuppyLibrary.from_members()
In Guppy 0.21, a GuppyLibrary was created using the guppy.library API. In Guppy v1, a GuppyLibrary is instead created using the from_members() method.
# Create a GuppyLibrary from
# three function definitions
lib = guppy.library(
first_func,
second_func,
third_func,
)
# Create a GuppyLibrary from
# three function definitions
lib = GuppyLibrary.from_members(
first_func,
second_func,
third_func,
)
This change means that the entire API for Guppy libraries is self-contained within the guppylang.library module.
link_nameis now its own decorator
Guppy libraries can also be used to link function declarations against function calls, by associating a link_name with a function definition. In Guppy 0.21, this was accomplished by specifying link_name as a keyword argument in the @guppy decorator. In Guppy v1, link_name() is its own decorator.
This was changed to keep the keyword arguments to @guppy related to compilation and to provide better error messages to the user.
@guppy(link_name="my.lib.func")
def my_func() -> None:
pass
@guppy
@link_name("my.lib.func")
def my_func() -> None:
pass