-
Notifications
You must be signed in to change notification settings - Fork 13
QCDL registers: report a register name that is allocated twice #73
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
base: main
Are you sure you want to change the base?
Changes from all commits
c329fb6
588b900
eeed807
57c46de
51c1299
145a0b6
cbb8f2c
e594c9c
b8b8adc
66eb3e5
e8aaa5c
04a4f44
15e1b97
a31a4f9
27bfabd
b555b98
5d6364c
a05bac7
dbd7f52
719ab26
b6058b2
be2ce1e
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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,7 +28,7 @@ | |||||||||||||||||||||
| import types | ||||||||||||||||||||||
| from collections.abc import Mapping, Sequence, Set | ||||||||||||||||||||||
| from contextlib import contextmanager | ||||||||||||||||||||||
| from typing import TYPE_CHECKING, Any, Callable, Iterator | ||||||||||||||||||||||
| from typing import TYPE_CHECKING, Any, Callable, Iterator, NamedTuple | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -76,6 +76,21 @@ def default(self, obj: Any) -> Any: | |||||||||||||||||||||
| return str(obj) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class RegisterAllocation(NamedTuple): | ||||||||||||||||||||||
| """One register a circuit has allocated, held under its module and name. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Args: | ||||||||||||||||||||||
| dtype: ``"int"`` or ``"float"``. | ||||||||||||||||||||||
| procedure: Procedure that made the allocation. Its name is reported | ||||||||||||||||||||||
| when a later declaration of the same register name clashes, and | ||||||||||||||||||||||
| comparing it against the declaring procedure separates a re-run of | ||||||||||||||||||||||
| that same procedure from a genuine re-declaration. | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| dtype: str | ||||||||||||||||||||||
| procedure: Procedure | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class Procedure(IndexerMixin): | ||||||||||||||||||||||
| """A QCDL procedure. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -241,6 +256,93 @@ def register_module_used(self, module_name: str | None) -> None: | |||||||||||||||||||||
| if module not in self.modules_used: | ||||||||||||||||||||||
| self.modules_used.append(module) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def register_memory_allocation( | ||||||||||||||||||||||
| self, | ||||||||||||||||||||||
| modules: Sequence[QCDLModule], | ||||||||||||||||||||||
| name: str, | ||||||||||||||||||||||
| dtype: str, | ||||||||||||||||||||||
| allow_existing: bool = False, | ||||||||||||||||||||||
| initial_value_specified: bool = False, | ||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||
| """Record a register allocation, rejecting a silent re-declaration. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
qci-amos marked this conversation as resolved.
|
||||||||||||||||||||||
| This method is mostly intended for use by developers of QCDL; the | ||||||||||||||||||||||
| :class:`~dwave.gate.qcdl.registers.Register` and | ||||||||||||||||||||||
| :class:`~dwave.gate.qcdl.registers.FixedPointRegister` classes call it | ||||||||||||||||||||||
| for you. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Register allocation and initialization happens at compile time, not run | ||||||||||||||||||||||
| time, and hence are global to the circuit rather than local to a | ||||||||||||||||||||||
| procedure. Consequently, the compiler keeps the *first* allocation of a | ||||||||||||||||||||||
| name it finds when traversing the procedures, so a second declaration of | ||||||||||||||||||||||
| the same name on the same module is a no-op: its initial value would not | ||||||||||||||||||||||
| be used. That is almost always a mistake, so it is reported here | ||||||||||||||||||||||
| instead. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| A procedure body is re-executed on every call while the program is being | ||||||||||||||||||||||
| built, but is emitted once in the QCDLProgram, so a declaration reached | ||||||||||||||||||||||
| through a later run of the *same* procedure is not a re-declaration and | ||||||||||||||||||||||
| is not reported. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Re-declaring the name is allowed when the caller asked for it. This can | ||||||||||||||||||||||
| be used to obtain new :class:`~dwave.gate.qcdl.registers.Register` or | ||||||||||||||||||||||
| :class:`~dwave.gate.qcdl.registers.FixedPointRegister` instances which | ||||||||||||||||||||||
| are useful for creating additional expressions on the previously | ||||||||||||||||||||||
| allocated memory. To avoid re-declarations that would attempt to | ||||||||||||||||||||||
| reallocate memory, this use case is opt-in using allow_existing and if | ||||||||||||||||||||||
| initial_value_specified is False. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Args: | ||||||||||||||||||||||
| modules: Modules the register is allocated on. | ||||||||||||||||||||||
|
Collaborator
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.
Suggested change
Collaborator
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. I want to keep it abstract as "modules" here because it could be couplers and we want to hide these details in the abstraction.
Collaborator
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. We have been using the phrase with the "typically" for exactly that purpose for this first release. The idea being to help a new user understand what a module might be (we never replace "module" with just "qubit" in such places). |
||||||||||||||||||||||
| name: Name of the register. | ||||||||||||||||||||||
| dtype: ``"int"`` or ``"float"``. | ||||||||||||||||||||||
|
Collaborator
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.
Suggested change
This is my guess, the intention is to let the user know what each of the dtypes are meant for.
Collaborator
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. It's also used for
Collaborator
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. Sure |
||||||||||||||||||||||
| allow_existing: If True, an existing allocation of ``name`` is | ||||||||||||||||||||||
| accepted as long as no initial value was given. Set by the | ||||||||||||||||||||||
| ``alias`` and ``ignore_reallocation`` arguments of a register. | ||||||||||||||||||||||
| It has no effect when ``name`` is not already allocated. | ||||||||||||||||||||||
|
Comment on lines
+299
to
+302
Collaborator
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.
Suggested change
Collaborator
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. |
||||||||||||||||||||||
| initial_value_specified: Whether the caller gave an initial value | ||||||||||||||||||||||
| for this register. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Raises: | ||||||||||||||||||||||
| :exception:`~dwave.gate.qcdl.exceptions.QCDLUserError`: If ``name`` | ||||||||||||||||||||||
| is already allocated on one of ``modules`` and either | ||||||||||||||||||||||
| ``allow_existing`` is False or an initial value was given. | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| for module in modules: | ||||||||||||||||||||||
| allocated = self.state.allocated_registers.setdefault( | ||||||||||||||||||||||
| module.qcdl_module_name, {} | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| previous = allocated.get(name) | ||||||||||||||||||||||
| if previous is not None and self._is_rerun_of(previous.procedure): | ||||||||||||||||||||||
| previous = None | ||||||||||||||||||||||
| if previous is not None and not ( | ||||||||||||||||||||||
| allow_existing and not initial_value_specified | ||||||||||||||||||||||
| ): | ||||||||||||||||||||||
| where = ( | ||||||||||||||||||||||
| f"register {name!r} is already allocated on" | ||||||||||||||||||||||
| f" {module.qcdl_module_name} with dtype {previous.dtype}" | ||||||||||||||||||||||
| f" in procedure {previous.procedure.name}" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| if allow_existing: | ||||||||||||||||||||||
| raise QCDLUserError( | ||||||||||||||||||||||
| f"{where}, so this initial value would never reach the" | ||||||||||||||||||||||
| f" qubit; drop the initial value" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| raise QCDLUserError( | ||||||||||||||||||||||
| f"{where}, so this declaration would be discarded; reuse" | ||||||||||||||||||||||
| f" that register, pick another name, or pass alias=True or" | ||||||||||||||||||||||
| f" ignore_reallocation=True with no initial value" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| allocated[name] = RegisterAllocation(dtype, self) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _is_rerun_of(self, other: Procedure) -> bool: | ||||||||||||||||||||||
| """Whether ``other`` is an earlier run of the user's same @procedure | ||||||||||||||||||||||
| decorated Python code. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| This logic is useful for tracking register allocations. | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| return other is not self and other.proc_name == self.proc_name | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @property | ||||||||||||||||||||||
| def expression_queue(self) -> list | None: | ||||||||||||||||||||||
| """Create an expression queue. | ||||||||||||||||||||||
|
|
@@ -1234,7 +1336,11 @@ def all_to_all_use(q0, q1): | |||||||||||||||||||||
| sc = Scope(q0, q1) | ||||||||||||||||||||||
| r1 = sc.Register(name="r1") | ||||||||||||||||||||||
| h(q0) | ||||||||||||||||||||||
| measure(q0, register=q0.Register(name="r1")) | ||||||||||||||||||||||
| # alias=True reuses the memory r1 already allocated, so this | ||||||||||||||||||||||
| # example illustrates a way to store this outcome only on q0 | ||||||||||||||||||||||
| # rather than mirrored to all the qubits in the Scope where | ||||||||||||||||||||||
| # r1 was originally allocated. | ||||||||||||||||||||||
| measure(q0, register=q0.Register(name="r1", alias=True)) | ||||||||||||||||||||||
| sc.all_to_all(send=r1==1, reduce_op="&") | ||||||||||||||||||||||
| with sc.If(None): | ||||||||||||||||||||||
| x(q1) | ||||||||||||||||||||||
|
|
@@ -1820,7 +1926,7 @@ def from_rewrapping(m: QCDLModule, new_proc: Procedure) -> QCDLModule: | |||||||||||||||||||||
| return QCDLModule(m.qcdl_module_name, proc) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @property | ||||||||||||||||||||||
| def qcdl_modules(self) -> tuple[QcdlModule]: | ||||||||||||||||||||||
| def qcdl_modules(self) -> tuple[QCDLModule]: | ||||||||||||||||||||||
| """The :class:`~dwave.gate.qcdl.QCDLModule` this | ||||||||||||||||||||||
| container holds. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| --- | ||
| upgrade: | ||
| - | | ||
| Allocating a register name twice on the same module now raises | ||
| ``QCDLUserError``. Register names are global to a circuit rather than local | ||
| to a procedure, and the compiler keeps only the first allocation of a name, | ||
| so a second declaration was previously discarded without a word. A program | ||
| that re-declared a name to get another handle on the same memory now has to | ||
| say so: pass ``alias=True`` to reuse the memory the name already has, or | ||
| ``ignore_reallocation=True`` with no initial value. A declaration reached | ||
| through a later run of the *same* procedure is not a re-declaration and is | ||
| still accepted. | ||
| - | | ||
| The ``initial_value`` argument of ``Register`` and ``FixedPointRegister`` | ||
| now defaults to ``None`` rather than to ``0`` and ``0.0``, so that a value | ||
| the caller gave can be told from one that was omitted. Omitting it still | ||
| allocates zero. Passing ``0`` or ``0.0`` explicitly now counts as giving a | ||
| value, and so is rejected for a name that is already allocated. | ||
| fixes: | ||
| - | | ||
| An ``initial_value`` that could never reach the qubit is now reported rather | ||
| than silently dropped. An ``alias`` never allocates memory, so it never | ||
| takes a value at all, and ``ignore_reallocation`` accepts a name that is | ||
| already allocated only when no value is given. A name that is not yet | ||
| allocated is allocated as usual and may carry a value. | ||
| features: | ||
| - | | ||
| Add the ``QCDLCircuit.allocated_registers`` property, which reports the | ||
| register names allocated so far by module name and then register name, | ||
| together with each one's dtype and the procedure that allocated it. |
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 my guess, the intention is to let the user know what each of the dtypes are meant for.
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.
There's also
Array... I think it's ok to leave it non-specific as "register"?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.
Sure