Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .github/workflows/benchmarks_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
steps:
- uses: actions/checkout@v6

# TODO: remove this when the required tket2 wheel is released
- uses: quantinuum/hugrverse-env/install-hugrenv-action@main

- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ jobs:

steps:
- uses: actions/checkout@v6

# TODO: remove this when the required tket2 wheel is released
- uses: quantinuum/hugrverse-env/install-hugrenv-action@main

- name: Run sccache-cache
uses: mozilla-actions/[email protected]
- name: Install rust toolchain
Expand Down Expand Up @@ -68,6 +72,10 @@ jobs:

steps:
- uses: actions/checkout@v6

# TODO: remove this when the required tket2 wheel is released
- uses: quantinuum/hugrverse-env/install-hugrenv-action@main

- name: Run sccache-cache
uses: mozilla-actions/[email protected]
- name: Install rust toolchain
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/semver-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

# TODO: remove this when the required tket2 wheel is released
- uses: quantinuum/hugrverse-env/install-hugrenv-action@main

- name: Run sccache-cache
uses: mozilla-actions/[email protected]
- name: Install rust toolchain
Expand Down
4 changes: 2 additions & 2 deletions examples/canonical-qpe.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"source": [
"from guppylang import guppy\n",
"from guppylang.std.angles import pi\n",
"from guppylang.std.quantum import qubit, h, crz, x, measure_array, discard_array\n",
"from guppylang.std.quantum import qubit, h, crz, x, measure_array, discard_array, collect_measurements\n",
"from guppylang.std.builtins import result, array \n",
"from guppylang.std.mem import mem_swap\n",
"\n",
Expand Down Expand Up @@ -255,7 +255,7 @@
" discard_array(state)\n",
"\n",
" # Create a result from the measured array\n",
" result(\"c\", measure_array(measured))\n"
" result(\"c\", collect_measurements((measure_array(measured))))\n"
]
},
{
Expand Down
151 changes: 73 additions & 78 deletions examples/deferred_measurement.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
"\n",
"**Download this notebook - {nb-download}`deferred_measurement.ipynb`**\n",
"\n",
"In this example we will illustrate why using `std.qsystem.lazy_measure` can be useful. "
"In this example we will illustrate the `Measurement` type and why the ability to defer measurements can be important for performance."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "96cb92b8",
"metadata": {},
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2026-04-30T10:08:46.776952Z",
"start_time": "2026-04-30T10:08:46.204437Z"
}
},
"source": [
"from guppylang import guppy\n",
"from guppylang.std.builtins import array, owned\n",
Expand All @@ -26,66 +29,57 @@
"from guppylang.std.platform import result\n",
"\n",
"from typing import no_type_check"
]
},
{
"cell_type": "markdown",
"id": "edddb1c3",
"metadata": {},
"source": [
"Let's say we want to measure an array of qubits one by one and output the results. You might do something like `eager_read` below:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3c414f04",
"metadata": {},
],
"outputs": [],
"source": [
"@guppy\n",
"@no_type_check\n",
"def eager_read(qs: array[qubit, 10] @ owned) -> None:\n",
" for q in qs:\n",
" result(\"t\", measure(q)) # forces immediate measurement of q\n",
" # [... more quantum operations ...]\n",
"\n",
"eager_read.check()"
]
"execution_count": 1
},
{
"cell_type": "markdown",
"id": "3a33772a",
"id": "edddb1c3",
"metadata": {},
"source": [
"It is important to understand that while `std.quantum.measure` returns a `bool` on the surface, on Quantinuum systems the physical measurement doesn't always happen immediately. A measurement call requests a measurement, which can then be deferred until it is actually required. At that point, any further execution is blocked, so by delaying it we give the runtime more opportunities to parallelise operations between request and block.\n",
"In previous versions of Guppy, the `measure` function used to return a `bool` directly, which implied a measurement was being performed immediately. However, this was hiding what was actually happening on the hardware, which is that a measurement request is being made when the function is called, and the physical measurement can be deferred until it is actually required.\n",
"\n",
"In the example above, using `result` does mean forcing an immediate measurement of `q`, which isn't the best for performance. However, this isn't obvious from a user perspective. \n",
"Now `measure` (and adjacent functions such as `measure_array`) return a `Measurement` type, which is a handle to the measurement request. In order to block on the request and wait to receive a bool, the `read()` method should be used. This allows the runtime to defer measurements until they are actually needed, which can give more opportunities for parallelism and therefore better performance. Exposing this behaviour allows you to make more informed decisions about how to structure your program for better performance.\n",
"\n",
"Now consider the same function using `lazy_measure`: "
"Consider the following program, where you might be tempted to use the output of `measure` directly in `result`:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "cc4e4a99",
"metadata": {
"tags": [
"raises-exception"
]
],
"ExecuteTime": {
"end_time": "2026-04-30T10:08:46.969299Z",
"start_time": "2026-04-30T10:08:46.782496Z"
}
},
"source": [
"@guppy\n",
"@no_type_check\n",
"def output_result(qs: array[qubit, 10] @ owned) -> None:\n",
" for q in qs:\n",
" result(\"t\", measure(q))\n",
" # [... more quantum operations ...]\n",
"\n",
"\n",
"output_result.check()"
],
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Error: Invalid call of overloaded function (at <In[3]>:5:15)\n",
"Error: Invalid call of overloaded function (at <In[2]>:5:15)\n",
" | \n",
"3 | def lazy_read(qs: array[qubit, 10] @ owned) -> None:\n",
"3 | def output_result(qs: array[qubit, 10] @ owned) -> None:\n",
"4 | for q in qs:\n",
"5 | result(\"t\", lazy_measure(q))\n",
" | ^^^^^^^^^^^^^^^^^^^^ No variant of overloaded function `result` takes arguments\n",
" | `str`, `Measurement`\n",
"5 | result(\"t\", measure(q))\n",
" | ^^^^^^^^^^^^^^^ No variant of overloaded function `result` takes arguments\n",
" | `str`, `Measurement`\n",
"\n",
"Note: Available overloads are:\n",
" def result(tag: str @comptime, value: int) -> None\n",
Expand All @@ -101,100 +95,101 @@
]
}
],
"source": [
"@guppy\n",
"@no_type_check\n",
"def lazy_read(qs: array[qubit, 10] @ owned) -> None:\n",
" for q in qs:\n",
" result(\"t\", lazy_measure(q))\n",
" # [... more quantum operations ...]\n",
"\n",
"\n",
"lazy_read.check()"
]
"execution_count": 2
},
{
"cell_type": "markdown",
"id": "683a89c6",
"metadata": {},
"source": [
"Simply replacing the method results in an error because `lazy_measure` returns a value of type `Measurement`. In order to obtain a `bool`, we have to explicitaly use `read()` on the value. "
"Simply replacing the method results in an error because `result` expects a `bool`. In order to obtain it, we have to explicitly use `read()` on the value of type `Measurement`. This will block until the physical measurement is completed:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0bc3eeab",
"metadata": {},
"outputs": [],
"id": "430b5814",
"metadata": {
"ExecuteTime": {
"end_time": "2026-04-30T10:08:53.815260Z",
"start_time": "2026-04-30T10:08:53.563296Z"
}
},
"source": [
"@guppy\n",
"@no_type_check\n",
"def lazy_read(qs: array[qubit, 10] @ owned) -> None:\n",
"def output_result(qs: array[qubit, 10] @ owned) -> None:\n",
" for q in qs:\n",
" result(\"t\", lazy_measure(q).read())\n",
" # [... more quantum operations ...]\n",
"\n",
"lazy_read.check()"
]
"output_result.check()"
],
"outputs": [],
"execution_count": 3
},
{
"cell_type": "markdown",
"id": "11043eea",
"metadata": {},
"source": [
"The program now type-checks and ends up compiling to the exact same operation order as `eager_read`. We can now see where exactly the `read()` happens, rather than it being done implicitly, so we can try to move it further down the program."
"The program now type-checks and we can see where exactly the `read()` happens, rather than it being done implicitly. Therefore we can try to move it further down the program, which allows for flexibility in when the quantum operations before the blocking read are performed:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "703c7af1",
"metadata": {},
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2026-04-30T10:08:56.647903Z",
"start_time": "2026-04-30T10:08:56.264786Z"
}
},
"source": [
"@guppy\n",
"@no_type_check\n",
"def lazy_read_improved(qs: array[qubit, 10] @ owned) -> None:\n",
" ms = array(lazy_measure(q) for q in qs)\n",
"def output_result_improved(qs: array[qubit, 10] @ owned) -> None:\n",
" ms = array(measure(q) for q in qs)\n",
" # [... more quantum operations ...]\n",
" for m in ms:\n",
" # This `read` call only blocks execution when we are at the end of the program \n",
" result(\"t\", m.read()) \n",
"\n",
"\n",
"lazy_read_improved.check()"
]
"output_result_improved.check()"
],
"outputs": [],
"execution_count": 4
},
{
"cell_type": "markdown",
"id": "6e39f898",
"metadata": {},
"source": [
"Now that each measurement is only read at the end of the program, physical measurements can be deferred to a better point in execution.\n",
"\n",
"Of course in this case we could have also collected the `bool` returns of `measure` or used `measure_array` to achieve the same outcome, however there might be cases where other solutions are less obvious. It can therefore be useful to use `lazy_measure` in programs where you want more control over when measurements should happen.\n",
"\n",
"For convenience, `__bool__` is implemented on the `Measurement` type as syntactic sugar for `read()`, so it is called automatically in conditionals for example:"
"For convenience, `__bool__` is implemented on the `Measurement` type as syntactic sugar for `read()`, so it is called automatically in conditionals, for example:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "8731d741",
"metadata": {},
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2026-04-30T10:08:58.267937Z",
"start_time": "2026-04-30T10:08:58.192851Z"
}
},
"source": [
"@guppy\n",
"@no_type_check\n",
"def lazy_conditional(q: qubit @ owned) -> None:\n",
" if lazy_measure(q):\n",
" if measure(q):\n",
" result(\"t\", 1)\n",
" else:\n",
" result(\"t\", 0)\n",
"\n",
"lazy_conditional.check();"
]
"lazy_conditional.check()"
],
"outputs": [],
"execution_count": 5
}
],
"metadata": {
Expand Down
6 changes: 3 additions & 3 deletions examples/ghz_and_graph.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"from guppylang import guppy\n",
"from guppylang.defs import GuppyFunctionDefinition\n",
"from guppylang.std.builtins import array, comptime, result\n",
"from guppylang.std.quantum import cx, cz, h, measure_array, qubit\n",
"from guppylang.std.quantum import cx, cz, h, measure_array, qubit, collect_measurements\n",
"from guppylang.emulator import EmulatorResult"
]
},
Expand Down Expand Up @@ -80,7 +80,7 @@
"\n",
" build_ghz_state(q)\n",
"\n",
" result(\"c\", measure_array(q))\n",
" result(\"c\", collect_measurements(measure_array(q)))\n",
"\n",
" # return the guppy function\n",
" return main"
Expand Down Expand Up @@ -172,7 +172,7 @@
" # apply CZ along every graph edge\n",
" cz(qs[i], qs[j])\n",
"\n",
" result(\"c\", measure_array(qs))\n",
" result(\"c\", collect_measurements(measure_array(qs)))\n",
"\n",
" return main"
]
Expand Down
Loading