Repository navigation
fix: Record measurements into named classical registers #411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 31 commits
861817b
9455208
4b6eccb
ddb881f
cb7e203
cd2cf88
a198744
200c2e1
dceec56
c284e50
3c245f5
20fc3d1
e21bfcd
d7ec08a
25135a7
9e4f61d
985f866
caa86c0
eb7f5ee
3188d2d
ecf4048
577679b
7318138
fca93ff
8b239ef
ec99a90
e8775e6
6906fbc
1fa35ee
ac559b1
b8bed0e
e8c9103
17d3245
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Iterable, Sequence | ||
| from collections.abc import Sequence | ||
|
|
||
| import numpy as np | ||
|
|
||
|
|
@@ -36,6 +36,11 @@ def __init__(self, name: str | None, size: int, order: int): | |
| def size(self) -> int: | ||
| return len(self.sources) | ||
|
|
||
| @property | ||
| def measured(self) -> bool: | ||
| """Whether any element holds a measurement.""" | ||
| return any(source is not None for source in self.sources) | ||
|
|
||
| def bind(self, element: int, qubit: int) -> None: | ||
| """Record ``qubit`` as the measurement source of ``element``. | ||
|
|
||
|
|
@@ -186,50 +191,32 @@ def measured_qubits(self) -> list[int]: | |
| """The measured qubit of each measurement slot, in ``measurement_slots`` order.""" | ||
| return [qubit for _, _, qubit in self.measurement_slots] | ||
|
|
||
| def add_measure_by_index( | ||
| self, | ||
| target: tuple[int, ...], | ||
| classical_targets: Iterable[int] | None = None, | ||
| allow_remeasure: bool = False, | ||
| ) -> None: | ||
| """Record a measurement by flat classical index into the anonymous register. | ||
| @property | ||
| def measured_registers(self) -> list[ClassicalRegister]: | ||
| """The registers holding at least one measurement, in declaration order.""" | ||
| return [register for register in self.classical_registers if register.measured] | ||
|
|
||
| Temporary entry point for callers that still address measurements by a | ||
| program-wide classical index. It is removed once measurements are routed | ||
| through declared registers via ``add_measure``. | ||
| def validate_single_measured_register(self) -> None: | ||
| """Reject programs whose measurements span more than one register. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, for awareness: Whether this check fires depends on which branches run, so the same program can pass or fail from run to run: qubit[2] q;
bit b;
bit r;
ry(0.3) q[0];
b = measure q[0];
if (b) { r = measure q[1]; }With 10 shots, 16 of 20 seeds return results and 4 raise
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferring the fix to the output PRs. |
||
|
|
||
| Args: | ||
| target (tuple[int, ...]): The qubits measured, in order. | ||
| classical_targets (Iterable[int] | None): The classical index of each | ||
| qubit. ``None`` appends one new index per qubit after the indices | ||
| already in use. | ||
| allow_remeasure (bool): Whether a qubit that is already measured into a | ||
| register element may be measured again (replacing the source of the | ||
| requested index). | ||
| """ | ||
| register = self.anonymous_register() | ||
| classical_targets = list(classical_targets) if classical_targets else None | ||
| for index, qubit in enumerate(target): | ||
| if not allow_remeasure and qubit in self.measured_qubits: | ||
| raise ValueError(f"Qubit {qubit} is already measured or captured.") | ||
| element = ( | ||
| classical_targets[index] | ||
| if classical_targets | ||
| else max(index, sum(source is not None for source in register.sources)) | ||
| ) | ||
| if element >= register.size: | ||
| register.grow(element + 1 - register.size) | ||
| register.bind(element, qubit) | ||
| self.qubit_set.add(qubit) | ||
|
|
||
| @property | ||
| def target_classical_indices(self) -> list[int]: | ||
| """The classical index of each measurement slot, in ``measurement_slots`` order. | ||
| This validation will be removed once `output` is supported. | ||
|
|
||
| Temporary companion of ``add_measure_by_index``: with a single anonymous | ||
| register the element index is the classical index. Removed with it. | ||
| Raises: | ||
| ValueError: If measurements were recorded into more than one register. | ||
| """ | ||
| return [element for _, element, _ in self.measurement_slots] | ||
| measured = self.measured_registers | ||
| if len(measured) > 1: | ||
|
yitchen-tim marked this conversation as resolved.
|
||
| names = ", ".join( | ||
| f"`{register.name}`" | ||
| if register.name is not None | ||
| else "measurements without a destination" | ||
| for register in measured | ||
| ) | ||
| raise ValueError( | ||
| "Measurement results can only be reported for a single classical register, " | ||
| f"but measurements were recorded into {len(measured)}: {names}. " | ||
| "Declare one bit register and measure into it." | ||
| ) | ||
|
|
||
| def add_result(self, result: Results) -> None: | ||
| """ | ||
|
|
||
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a temporary validation and it will be removed once
outputis supported.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To raise awareness: AutoQASM declares a new
bitfor everymeasure()call, so this check rejects most AutoQASM programs, including ones without mid-circuit measurements:Generated OpenQASM:
{'00': 51, '11': 49}ValueError: ... recorded into 2:bit_0,bit_1. Declare one bit register and measure into it.AutoQASM users can't follow that advice because they don't write the declarations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also just for awareness - I have this PR stacked on top of Cody's output PR which makes everything work for AutoQASM programs: #401
We don't need to verify it now, but if/when we do rework output variable support, we should just ensure that PR also gets incorporated appropriately (and, ultimately, that amazon-braket/autoqasm#119 will pass CI once the output-supporting LocalSimulator is released)