{ "cells": [ { "cell_type": "markdown", "id": "b6018069", "metadata": {}, "source": [ "# Deferring measurements\n", "\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. " ] }, { "cell_type": "code", "execution_count": 1, "id": "96cb92b8", "metadata": { "execution": { "iopub.execute_input": "2026-03-18T13:41:25.912804Z", "iopub.status.busy": "2026-03-18T13:41:25.912547Z", "iopub.status.idle": "2026-03-18T13:41:27.095129Z", "shell.execute_reply": "2026-03-18T13:41:27.094406Z" } }, "outputs": [], "source": [ "from guppylang import guppy\n", "from guppylang.std.builtins import array, owned\n", "from guppylang.std.quantum import qubit, measure\n", "from guppylang.std.qsystem import lazy_measure\n", "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": { "execution": { "iopub.execute_input": "2026-03-18T13:41:27.096554Z", "iopub.status.busy": "2026-03-18T13:41:27.096397Z", "iopub.status.idle": "2026-03-18T13:41:27.112841Z", "shell.execute_reply": "2026-03-18T13:41:27.112351Z" } }, "outputs": [], "source": [ "N = guppy.nat_var(\"N\")\n", "\n", "@guppy\n", "@no_type_check\n", "def eager_read(qs: array[qubit, N] @ 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()" ] }, { "cell_type": "markdown", "id": "3a33772a", "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", "\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", "\n", "Now consider the same function using `lazy_measure`: " ] }, { "cell_type": "code", "execution_count": 3, "id": "cc4e4a99", "metadata": { "execution": { "iopub.execute_input": "2026-03-18T13:41:27.113931Z", "iopub.status.busy": "2026-03-18T13:41:27.113863Z", "iopub.status.idle": "2026-03-18T13:41:27.439616Z", "shell.execute_reply": "2026-03-18T13:41:27.439229Z" }, "tags": [ "raises-exception" ] }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Error: Invalid call of overloaded function (at :5:15)\n", " | \n", "3 | def lazy_read(qs: array[qubit, N] @ 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", "\n", "Note: Available overloads are:\n", " def result(tag: str @comptime, value: int) -> None\n", " def result(tag: str @comptime, value: nat) -> None\n", " def result(tag: str @comptime, value: bool) -> None\n", " def result(tag: str @comptime, value: float) -> None\n", " def result(tag: str @comptime, value: array[int, n]) -> None\n", " def result(tag: str @comptime, value: array[nat, n]) -> None\n", " def result(tag: str @comptime, value: array[bool, n]) -> None\n", " def result(tag: str @comptime, value: array[float, n]) -> None\n", "\n", "Guppy compilation failed due to 1 previous error\n" ] } ], "source": [ "@guppy\n", "@no_type_check\n", "def lazy_read(qs: array[qubit, N] @ owned) -> None:\n", " for q in qs:\n", " result(\"t\", lazy_measure(q))\n", " # [... more quantum operations ...]\n", "\n", "\n", "lazy_read.check()" ] }, { "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. " ] }, { "cell_type": "code", "execution_count": 4, "id": "0bc3eeab", "metadata": { "execution": { "iopub.execute_input": "2026-03-18T13:41:27.441084Z", "iopub.status.busy": "2026-03-18T13:41:27.441011Z", "iopub.status.idle": "2026-03-18T13:41:27.532841Z", "shell.execute_reply": "2026-03-18T13:41:27.532402Z" } }, "outputs": [], "source": [ "@guppy\n", "@no_type_check\n", "def lazy_read(qs: array[qubit, N] @ owned) -> None:\n", " for q in qs:\n", " result(\"t\", lazy_measure(q).read())\n", " # [... more quantum operations ...]\n", "\n", "lazy_read.check()" ] }, { "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." ] }, { "cell_type": "code", "execution_count": 5, "id": "703c7af1", "metadata": { "execution": { "iopub.execute_input": "2026-03-18T13:41:27.534175Z", "iopub.status.busy": "2026-03-18T13:41:27.534099Z", "iopub.status.idle": "2026-03-18T13:41:27.609062Z", "shell.execute_reply": "2026-03-18T13:41:27.608680Z" } }, "outputs": [], "source": [ "@guppy\n", "@no_type_check\n", "def lazy_read_improved(qs: array[qubit, N] @ owned) -> None:\n", " ms = array(lazy_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()" ] }, { "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:" ] }, { "cell_type": "code", "execution_count": 6, "id": "8731d741", "metadata": { "execution": { "iopub.execute_input": "2026-03-18T13:41:27.610320Z", "iopub.status.busy": "2026-03-18T13:41:27.610248Z", "iopub.status.idle": "2026-03-18T13:41:27.618927Z", "shell.execute_reply": "2026-03-18T13:41:27.618558Z" } }, "outputs": [], "source": [ "@guppy\n", "@no_type_check\n", "def lazy_conditional(q: qubit @ owned) -> None:\n", " if lazy_measure(q):\n", " result(\"t\", 1)\n", " else:\n", " result(\"t\", 0)\n", "\n", "lazy_conditional.check();" ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.2" } }, "nbformat": 4, "nbformat_minor": 5 }