Skip to content

Add measured QROM with cache erasure and shared planning - #64

Open
kvmto wants to merge 5 commits into
NVIDIA:mainfrom
kvmto:pr/measured-qrom
Open

kvmto wants to merge 5 commits into
NVIDIA:mainfrom
kvmto:pr/measured-qrom

Conversation

@kvmto

@kvmto kvmto commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Add measured unary iteration and MeasuredQROM for classical-table XOR lookup with SELECT and SELECT-SWAP, including coherent external control, padded addresses, arbitrary complex outputs, and restoration of the declared clean or borrowed workspace.

Clean SELECT-SWAP supports cache_uncompute="measure": after routing is undone, X-measure and reset the cache, then cancel the outcome-dependent parity phase through a measured phase walk and one-hot cleanup. phase_block_size selects the correction geometry. cache_uncompute="auto" chooses measurement only for a strict logical-Toffoli saving; traversal remains the default and wins cleanup ties. For 256 two-bit entries with eight address bits, B=4 and K=8, this emits 87 Toffolis and 83 measurements on 14 clean workspace qubits, versus 136 Toffolis and 124 measurements for traversal.

The implementation also provides:

  • Shared pure validation, register geometry, operation plans, and immutable stage/resource records, with scalar candidate pricing before emitting the selected plan.
  • One shared CUDA-Q interpreter with invocation-local measurement storage and runtime parity feedback, including outcome slots above 64.
  • Borrowed-bank SELECT-SWAP restoration through a coherent echo, preserving arbitrary bank entanglement. Borrowed banks are never measured; cache measurement requires a known clean initial bank.
  • Explicit controlled=True kernels and the measured walker's logical inverse. Automatic cudaq.control and cudaq.adjoint are outside the measured-channel contract.
  • Guides, a runnable complex-state example, and separate host-proof execution in source CI.

Resource records distinguish tree ANDs, all proven temporary ANDs, routing Toffolis, measurements, clean/borrowed workspace, and persistent classical capacity. The four-T temporary-AND model requires compatible synthesis; these are emitted logical counts, not compiled T counts or hardware costs. Native feedback was validated on CUDA-Q 0.16 qpp-cpu; other backends are not established by these checks.

Validation: 208 host tests passed; 845 native tests passed, with three optional-dependency skips (OpenFermion/Psi4) and two SDK warnings. Independent host tests check every measurement history for small channels, coherent controls, padding, and borrowed-bank restoration. Native trajectory/resource checks, the example, formatting, and Sphinx with warnings as errors passed.

Land this foundation before #65. The published branches currently have sibling histories based on main; #65 includes the shared code and must be updated and revalidated after #64 lands.

Port the measured tree walk with current opcode validation and dispatch
pruning. Expose MeasuredQROM SELECT and SELECT-SWAP without changing
unitary QROM consumers, including explicit controls and an optional
borrowed-bank echo that restores arbitrary entangled bank states.

Separate tree AND, body/routing Toffoli and measurement counts. Document
partial-tree full-address behavior, clean-subspace logical inverses,
theoretical T-count limits, and CUDA-Q 0.16 executor requirements.

Add independent full-history Kraus checks, native complex-input channel
regressions, compiled resource checks, a nonzero coherence observable,
and runnable lookup documentation.

Co-authored-by: Scott Thornton <wsttiger@gmail.com>
Signed-off-by: Kevin Mato <kmato@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Oct 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Fold the measured interpreter into the shared coherent interpreter: the two measured-uncompute opcodes (22, 23) live next to the coherent table and are dispatched behind captured guards, so a coherent tape never carries their arms; the measured module no longer mints kernels or keeps a second keep-alive registry. Count tree ANDs from the tape instead of aliasing the measurement count, keep the lookup tape and add describe(), require plain bools for controlled/dirty_banks, and spell the SELECT-SWAP echo once.

Restore the framework-boundary tests (cudaq.control collapses a superposed control, cudaq.sample rejects direct and nested feedback, cudaq.observe agrees), add an exhaustive measurement-history oracle for MeasuredQROM tapes (SELECT, SELECT-SWAP, dirty banks, controlled), seed the native trajectory tests, drop the zero-measurement parametrizations, pin the SELECT-SWAP price against every candidate tape, and make the vector comparisons strictly absolute.

Document what automatic control does to a measured kernel, narrow the module-wide strictly-unitary claims to the coherent primitives, state the keep-alive registry's real cost, and let the example read CUDAQ_DEFAULT_SIMULATOR through state_from.

Signed-off-by: Kevin Mato <kmato@nvidia.com>
@kvmto
kvmto marked this pull request as ready for review October 13, 2026 22:14
kvmto added 2 commits October 14, 2026 01:03
Signed-off-by: Kevin Mato <kmato@nvidia.com>
Signed-off-by: Kevin Mato <kmato@nvidia.com>
@kvmto
kvmto requested a review from wsttiger October 13, 2026 23:21
Signed-off-by: Kevin Mato <kmato@nvidia.com>
@kvmto kvmto changed the title Add measured unary iteration and QROM with borrowed-bank support Add measured QROM with cache erasure and shared planning Oct 14, 2026
@kvmto

kvmto commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@wsttiger Scott, the completion update is now pushed. This adds whole-cache measurement erasure, outcome-dependent phase correction, measured one-hot cleanup, and the shared planning/feedback infrastructure consumed by #65. The earlier omission of cache erasure is addressed.

Could you review this foundation first, followed by #65? Local validation on the pushed tree: 208 host tests passed; 845 native tests passed, with 3 optional-dependency skips. The description now covers the complete implementation and its execution/resource boundaries.

@wsttiger wsttiger left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kevin, thanks — this is a substantial and careful piece of work. I went through it with a mixed
read-and-run approach: the pure host suite (208 green), every test_primitives_* file on my box (519 green,
2 skipped, 1 failure that is the 0.16-only cudaq.sample pin — expected on my 0.15.1), and #55's
LCUBlockEncoding suite on a tree with #64 merged in (21 green), since #55 builds on
unary_iteration_kernels and QROM. I also re-derived the cache-erasure phase bookkeeping and ran my own
state-vector checks of MeasuredQROM (select, select_swap traverse/measure at K ∈ {1,2,4}, B ∈ {2,4},
multi-bit words, zero words, auto, controlled and uncontrolled, dirty banks) and of the measured walk
with x/y/z/sign and work-register bodies — everything agrees with the ideal channel to ~1e-16, including
relative phases. The host oracle is genuinely independent (expected maps built without the tree or the
measurements, unnormalised branches plus completeness, sentinels that fire), the coherent JSON baselines
reproduce main bit-for-bit, and every resource number in the guide matches the planner. The public
coherent API is source-compatible: #55's imports and _alias_sampling's use of QROM are untouched.

So the physics and the planning are not what I want to discuss. One structural point blocks me, and a
handful of smaller things follow.

1. The measured arms live in the same interpreter kernel as every coherent walk. _mint_interpreter
now dispatches _BASE_OPS | _MEASURED_OPS | _FEEDBACK_OPS in one kernel body, so the source of
QROM.kernel, unary_iteration_kernels().kernel and (through #55) every LCU SELECT contains h; mz; if outcomes[b]: x arms. On 0.15.1 a plain QROM([3,0,1,2,1], 3, 2).kernel already shows 3 quake.mz,
3 quake.h and 43 cc.if in its MLIR; the pipeline folds them away there, but your own docstring says
that on 0.16 the captured guards are lifted into kernel arguments and the arms stay. That makes the
coherent primitives' cudaq.control-compatibility on 0.16 depend on cuda-quantum#5257 — the control
variant generator silently skipping measurements — which is a bug, and one that NVIDIA/cuda-quantum#5630
(in review now) fixes by rejecting control variants of any kernel whose IR contains a measurement,
dead arm or not. When that ships, every coherent primitive minted here stops being controllable, the
module docstring's promise at lines 48-53 breaks, and test_primitives_body_gates.py:284 /
test_primitives_interpreter.py:340 fail. It also re-opens the #5280 CSE hazard the guard pruning was
built to avoid, for the five new multi-control arms. The clean fix is a separate interpreter for the
measured/feedback opcodes, used only by measured_unary_iteration_kernels and MeasuredQROM, so the
coherent kernels' IR is byte-identical on every CUDA-Q version. If you'd rather keep one interpreter, I'd
need at least a 0.16 test that translates an entry kernel calling QROM(...).kernel and asserts there is
no mz in the IR, plus the two docstrings reconciled (see the inline note). Details inline.

2. Scope. A few #65-only paths ship here without being called out: cache_register='output' and
opcode 25, the opcodes reserved for pr/qrom-select-copy (21, 28-31) that the host oracle already
decodes, and two module names in test_no_cudaq that do not exist in this PR. Either drop them from the
foundation or say in the description that they are forward hooks.

3. Coverage of the headline path. Every state-level check of cache_uncompute="measure" runs at
K = 1 or 2 (the K = 4 case tests the reference's own model). I checked production at K = 4 over all
histories and it is right, so this is cheap: a K = 4 host case and one native K = 8 trajectory test
(n=5, B=2, b=4 is 21 qubits).

4. SDK-boundary tests. Three tests pin CUDA-Q behaviour rather than this package;
test_cudaq_sample_rejects_nested_measurement_feedback fails hard on 0.15.1. The repo convention is a
feature-probe skipif; grouping them in an explicitly named module would also make their purpose clear.

Smaller items inline: real widths in the tape validation, the unused _select_swap_cost/_VARIANTS/import
block, exporting QROMResources, a few "existing consumers continue to…" phrasings in user-facing text
(we try to say what a thing is), the 'none' string vs None, the bool-vs-coerce policy, error
messages that drop the offending value, SPDX headers on the new files, a test module importing from
another test module, and the AST-exec test. None of those are blockers.

One process note: CI has not run on the NVIDIA runners yet (copy-pr-bot vetting), so we'll want a 0.16 run on the final shape before merging.

supported = _BASE_OPS
from . import _unary_iteration_ir as ir

supported = _BASE_OPS | _MEASURED_OPS | ir._FEEDBACK_OPS

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supported = _BASE_OPS | _MEASURED_OPS | ir._FEEDBACK_OPS

This is the line I'd like to change the shape of. Because the four kernel variants below dispatch the
measured and feedback opcodes too, every coherent kernel minted here carries h; mz; if outcomes[b]: x
arms in its source. On my 0.15.1 box the minted QROM([3,0,1,2,1], 3, 2).kernel MLIR has 3 quake.mz,
3 quake.h and 43 cc.if (none before this PR); the compile pipeline folds them away on 0.15, and your
docstring at 356-361 says on 0.16 it does not. The consequence is that cudaq.control of coherent
primitives on 0.16 works only because the control-variant generator skips measurements silently
(cuda-quantum#5257). NVIDIA/cuda-quantum#5630 rejects control variants of any kernel whose IR contains a
measurement, and these arms cannot be accepted by its idiom analysis (Z-basis h; mz, measured qubit is
an argument, inside a conditional) — so once it lands, test_primitives_body_gates.py:284 and
test_primitives_interpreter.py:340 fail and the promise at 48-53 breaks. Suggestion: keep this
interpreter strictly coherent and mint the measured/feedback arms in a separate _mint_measured_interpreter
used only by the measured walk and MeasuredQROM. The measured variants could then come from one template
instead of five copies of the arm block.

in ``_unary_iteration_measured`` is their only producer. On CUDA-Q 0.16 the
captured guards are lifted into kernel arguments, so the generic kernel
source still mentions ``mz`` for coherent walks; the runtime's
measurement-feedback checks look at entry kernels, and the tests pin that

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the runtime's measurement-feedback checks look at entry kernels"

_unary_iteration_measured.py:33-35 says the opposite: "CUDA-Q 0.16's compiler rejects measurement-feedback
kernels, direct or nested, under cudaq.sample". If the nested check is real, then on 0.16 a sampled
entry kernel calling a coherent walk also carries nested mz+feedback in its IR and would be rejected;
if the check is entry-only, the measured docstring is wrong. Which did you observe on 0.16? The test that
backs the coherent claim (test_coherent_tapes_keep_sample_control_and_adjoint) is a two-op tape asserting
behaviour, not IR, so it cannot tell us.

f"interpreter variant's dispatch set (controlled={controlled}, "
f"has_work={has_work}); the dispatch loop would silently skip "
"them")
# Infer only the minimum wire bounds here; public plans validate against

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Infer only the minimum wire bounds here; public plans validate against their actual geometry.

Inferring num_address/num_ladder/num_output/num_work from the tape and then validating the tape against
them means these bounds checks cannot fail. MeasuredQROM knows the real widths (_qrom_measured.py:143-147)
— passing them (or validating in _plan_measured_qrom against BlockGeometry) would catch a wrong
rebase_block_walk offset at mint time. The same tape is also re-validated in _qrom_cache_erasure.py:131-138
and :183-190, and the opcode set is checked here and again in validate_feedback_ops.

cache_bits: int,
*,
cache_offset: int,
cache_register: Literal['ladder', 'output'] = 'ladder') -> QROMPlan:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache_register: Literal['ladder', 'output'] = 'ladder'

'output' (opcode 25, _OP_MEASURE_RESET_OUTPUT) has no production caller — _emit_selection always
erases a ladder cache — yet the arm is compiled into all four kernels. If this is for #65, I'd rather it
arrive with #65, or be named as a forward hook in the description.

_OP_CX_ADDR_ADDR = 18 # cx(address[a], address[b])
_OP_CCX_ADDR_ADDR = 19 # x.ctrl(address[a], address[b], ladder[c])
_OP_CX_LADDER_TARGET = 20 # cx(ladder[a], target[b])
# 21 is reserved for the coherent SELECT-COPY extension (pr/qrom-select-copy).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# 21 is reserved for the coherent SELECT-COPY extension (pr/qrom-select-copy).

Same point for the reservations (21 here, _OP_CCX_LADDER_LADDER_TARGET at 440-441 with its table rows,
"28--31 remain reserved" at 492): they reference a branch that is not in this repository, and the host
oracle already decodes 21. Drop or declare.


__all__ = ["MeasuredQROM"]

_VARIANTS = ("auto", "select", "select_swap")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_VARIANTS = ("auto", "select", "select_swap")

Dead code in this block: _VARIANTS, _MEASURED_ADDRESS_OPERANDS, _select_swap_cost (planning lives in
_qrom_measured_plan._price_candidates) and the _ADDRESS_OPERANDS import routed through _qrom. Also
_unary_iteration_measured.py:52-56 imports 15 names of which 9 are unused and :61 redefines
_MEASURED_OPCODES from _unary_iteration_ir.py:438; _qrom.py:261/328 still assign an unused
_toffoli_count; validate_qrom_data runs twice (_qrom_measured.py:119, _qrom_measured_plan.py:161);
and _qrom_measured.py:86-88 mentions a QROM "SELECT-COPY variant" that does not exist.

@@ -51,11 +55,17 @@
subtract_constant, subtract_constant_qft,
subtract_register)
from ._qrom import QROM

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from ._qrom import QROM

QROM.resources and MeasuredQROM.resources return QROMResources (with QROMStageResources records) and
the guide cites their fields (measured_qrom.rst:171-174), but neither type is exported here, so
automodule :members: cannot render them. Export both (or document the fields in the guide).

little-endian views. The ``ladder`` view contains ``num_clean`` clean qubits followed by
``num_dirty`` borrowed qubits, with ``num_ladder`` their sum. The default
requires all workspace clean; blocked lookups also include cache registers.
Existing ``QROM`` consumers continue to use unitary kernels.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Existing QROM consumers continue to use unitary kernels."

House style in the docs is to say what a thing is rather than how it relates to what came before — e.g.
"QROM and unary_iteration_kernels are strictly unitary; MeasuredQROM and
measured_unary_iteration_kernels opt into measurement." Same for :125 ("preserves previous
choices"), primitives/__init__.py:15 ("stay strictly unitary"), and _qrom_measured.py:5-6, 20
("remain unitary", "preserves the original selection").

raise ValueError(
"variant must be one of ('auto', 'select', 'select_swap'), "
f"got {variant!r}")
if not isinstance(controlled, bool):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not isinstance(controlled, bool):

unary_iteration_kernels and measured_unary_iteration_kernels coerce with bool(...); this rejects
np.True_ and 1. One policy for the package, please (coercion matches the existing factories). Nearby:
:51 resolves cache_uncompute to the string 'none' for SELECT, which is not an accepted input and
shows up in __repr__ — None, like phase_block_size, reads better; :183 drops the offending value
("block_size must be a power of two >= 2", where QROM echoes it), and :216-217 reports
"exceeds available cache or high-address width" even when the cause is K > 2^(h-1) for the chosen B.

@@ -0,0 +1,355 @@
"""Independent little-endian NumPy gates and unnormalized measurement channels.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(no SPDX header)

The seven tests/host/*.py files have no SPDX lines, and _unary_iteration_ir.py, _qrom_plan.py,
_qrom_common.py, _qrom_cache_erasure.py, _qrom_measured_plan.py and
tests/python/test_primitives_measured_cache.py carry only the license line; every other file has both.
While you are in the test tree: test_qrom_measured_selection.py:329 imports check_emitted_plan from
another test module (works only under pytest's default import mode — move it to the reference module),
and test_primitives_qrom_plan.py:80-129 execs production source via AST surgery, which is fragile and
largely duplicates test_default_tapes_match_captured_baseline.

@wsttiger wsttiger added enhancement New feature or request release - v0.2 Targeted for the v0.2 release release - v0.3 Targeted for the v0.3 release labels Oct 21, 2026
@wsttiger wsttiger removed the release - v0.2 Targeted for the v0.2 release label Oct 26, 2026
wsttiger added a commit that referenced this pull request Oct 27, 2026
…ract (cudaq >= 0.16) (#52)

## Summary

Rebuilds the `select_swap` QROM as composed kernels and promises
composition as
a supported surface of the primitives subpackage.

1. **`select_swap` as composed kernels.** The lookup is a parent kernel
that
carves `low`/`high` address views and `lines`/`blocks` ancilla views out
of
   the public `(address, ladder, output)` signature and composes three
sub-kernels, `W S C S⁻¹ W`: a plain `unary_iteration_kernels` walk over
the
block index whose target view is the block registers, a minted Fredkin
routing network over `(low address bits, blocks)` and its reversed-list
inverse, and the copy CNOTs inline. The flat-tape splice — rebasing the
   walk's address operands and smuggling the block registers through the
walk's ladder view — and the `_OP_CX_LADDER_TARGET` opcode are deleted
end
to end. Public signature, `num_ladder` and `toffoli_count` are unchanged
   (`AliasSamplingPrepare` depends on them).

2. **Composition as a supported surface.** The package docstring states
the
contract: minted kernels are building blocks callable from user kernels,
and
`cudaq.control` over the whole composition yields the controlled
operation
(the `controlled=True` factory variants remain the cheaper route for a
controlled walk). `tests/python/test_primitives_composition.py` pins
both
halves: plain composition, and controlled composition three-way against
the
factory's controlled walk and the analytic state on a superposed
control.

3. **Version gate and docstrings.** Controlled composition is pinned on
the
supported CUDA-Q line through a `controlled_composition` pytest marker
(`conftest.py`): the tests skip only below 0.16.0, the package minimum,
and
fail on a 0.16.x that regresses control propagation. Docstrings describe
   what composition and the flat tape are.

## Validation

-
`test_primitives_{qrom,composition,interpreter,unary_iteration,resources,alias_sampling,arithmetic}.py`:
  274 passed on cudaq 0.16.0 (nothing skipped); 270 passed / 4 skipped
  (the marker-gated controlled-composition tests) on cudaq 0.15.1.
- The composed `select_swap` reproduces the previous construction
exactly:
full statevectors identical and equal Toffoli counts on five geometries
(block sizes 2–8, one-high-bit and zero-padded cases, 32 entries over 5
bits).
- `cudaq.estimate_resources` on the composed kernel equals
`toffoli_count`
  (resource suite, block sizes 2, 4, 8).

## Interplay

- #70 (`QROM(..., controlled=True)`) threads its flag through the
flat-tape
  `select_swap`; under this construction the controlled lookup is a
`controlled=True` walk sub-kernel taking `(control, high, lines,
blocks)` with
the routing and copy unchanged, no `cudaq.control` needed (verified:
identical
conventions and the documented controlled Toffoli price). Whichever
lands
second carries that ~15-line rewrite; #70's tests use only the public
API and
  transfer unchanged.
- #64 (measured QROM) rebuilds `_qrom.py` around a flat IR; its coherent
half
can take this composed `select_swap` wholesale, and its measured path
keeps
  its own opcode copies.

## Files
- `python/cudaq_algorithms/primitives/__init__.py`, `_qrom.py`,
`_unary_iteration.py`, `_alias_sampling.py`
- `tests/python/conftest.py`, `test_primitives_composition.py` (new),
`test_primitives_qrom.py`,
  `test_primitives_interpreter.py`, `test_primitives_arithmetic.py`

---------

Signed-off-by: Scott Thornton <wsttiger@gmail.com>
wsttiger added a commit that referenced this pull request Oct 29, 2026
`QROM` gains `controlled=True`: the externally controlled lookup, with
the
`unary_iteration_kernels` convention — a leading one-qubit `control`
view,
identity at control `|0>`, the plain lookup at `|1>`, self-inverse.
`controlled`
must be a `bool` (same guard and message as the measured-QROM work).

**Why it is cheap.** Only the two walk passes carry the control. In the
composed
`select_swap` (#52) the walk sub-kernel is minted `controlled=True` and
the parent
passes the control view to both walk passes; the routing network and the
copy are
the plain kernels. At control `|0>` a controlled walk on a clean ladder
writes
nothing, so the block registers stay `|0>`, the routing swaps `|0>`
registers, the
copy fires from a `|0>` slot, and the un-route/un-write are the
identity. The
Toffoli price is the controlled walk count in place of the plain one
(the root
gadgets become Toffolis and the root crossings lose the unguarded
shortcut: +4 for
the 3-bit, 5-entry test table on `select`, +6 on `select_swap` with `B =
2`), with
routing and copy unchanged — a fraction of `cudaq.control` over the
composition,
which adds a control to every gate. `variant="auto"` prices the
controlled
candidates against each other; the control shifts the optimum toward
larger blocks
on small tables.
Compiled on CUDA-Q 0.16 for eleven 3-bit entries over four address bits,
the native
controlled lookup costs 19 / 26 / 28 Toffolis (select, select_swap `B =
2`, `B = 4`)
against 15 / 18 / 22 plain, where `cudaq.control` over the plain
composition costs 42
/ 77 / 83. Because the optimum moves to larger blocks under control,
`num_ladder` can
exceed the plain lookup's; size the ladder from the instance.

**What "identity at `|0>`" means.** A clean-ladder statement: every
ladder write of
a controlled walk is gated by the control or by a ladder line above it,
so on a
dirty ladder the deeper Toffolis fire. The clean ladder is the kernel's
precondition in any case, but `cudaq.control` of the plain lookup is the
identity
on every input, so the docstrings say which identity this is.

**Who needs it.** The alias PREPARE deliberately never puts QROM under
control, so
the LCU encodings do not. The consumer is the sparse-access oracle path
(#53):
`from_general_oracles` wraps the oracles in `cudaq.control`, which
excludes
table-oracle bundles built on walks; a natively controlled lookup is
what lets that
path accept them (follow-up in #53), under the clean-ladder precondition
those
bundles already carry.

**Tests** (`test_primitives_qrom.py` +9, `test_primitives_resources.py`
+3): control
conventions at `|0>`, `|1>` and `|+>` and the involution under a
superposed control
over six geometries — both variants, a one-bit address, a single
block-index bit
(`B = 2^(address_bits − 1)`), a zero-padded last block at `B = 8` — on
superposed
addresses with a preloaded output word, the `|1>` branch compared with
the table
itself; Toffoli counts against the documented contract; `auto` pricing
pinned on a
table whose optimum moves (plain `B = 2`, controlled `B = 4`); the bool
guard; and
the resource suite's compiler-measured Toffoli cross-check over the
controlled
lookups. Existing QROM, unary-iteration, composition and alias-sampling
behaviour
unchanged.

**Note for #64.** The measured-QROM PR rewrites `_qrom.py`; its coherent
half can
take the composed `select_swap` with this flag wholesale (`MeasuredQROM`
already
carries a `controlled` flag of the same shape).

## Files
- `python/cudaq_algorithms/primitives/_qrom.py`
- `python/cudaq_algorithms/primitives/__init__.py` (docstring line)
- `tests/python/test_primitives_qrom.py`
- `tests/python/test_primitives_resources.py`

---------

Signed-off-by: Scott Thornton <wsttiger@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release - v0.3 Targeted for the v0.3 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants