Repository navigation
Add an externally controlled QROM lookup - #70
Conversation
…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>
`unary_iteration_kernels` mints externally controlled walks, but `QROM` had no way to ask for one: both variants minted the plain walk. `QROM(..., controlled=True)` now mints the controlled lookup with the walk convention's signature, `(control, address, ladder, output)`, identity at control |0> and the plain lookup at |1>. `controlled` must be a bool (same guard and message as the measured QROM work). Only the walk passes carry the control. In the composed select_swap the walk sub-kernel is minted `controlled=True` and the parent passes the control view to both walk passes, while the routing network and the copy stay the plain kernels: at control |0> a controlled walk on a clean ladder writes nothing, so the block registers stay |0>, the routing Fredkins swap |0> registers, the copy CNOTs fire from a |0> slot, and the un-route and un-write are the identity too. The 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), with the routing and copy costs unchanged, at a fraction of the cost of `cudaq.control` over the composition; `variant="auto"` prices the controlled candidates against each other. The identity at |0> is a clean-ladder statement, the kernel's precondition in any case. Tests: the controlled lookup leaves a superposed address register with a non-zero output word untouched at control |0>, equals the plain lookup and the table itself at |1>, carries both branches at |+>, and is self-inverse under a superposed control, over six geometries (both variants, a one-bit address, a single block-index bit, a zero-padded last block at B = 8); the Toffoli counts follow the documented contract, auto pricing is pinned on a table whose optimum moves between plain and controlled pricing, the bool guard raises, and the resource suite's compiler cross-check covers the controlled lookups. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
3b680d2 to
5a0566b
Compare
|
Rebased onto main now that #52 (composed select_swap) has landed, as agreed on #52. The controlled lookup is now the natural form under the composed construction: the walk sub-kernel is minted |
ivanbasov
left a comment
There was a problem hiding this comment.
Tests and a compiled-Toffoli comparison against cudaq.control run on 0.16.0, all consistent with the description; three optional notes inline.
… layout and the measured price `variant="auto"` prices the controlled candidates against each other on Toffolis alone, so the optimum can sit at a larger block than the plain lookup's and `num_ladder` differs (5 against 4 for seven one-bit entries over three address bits); the `controlled` parameter doc now says to size the ladder from the instance rather than assume the plain and controlled lookups share a layout. The two select-swap parents are deliberate: they differ only in the walk calls and share the route and copy sub-kernels, the plain one keeps the three-view signature of the composition contract, and a dummy control would cost every plain caller a qubit. A comment above them says so. The module paragraph quotes the compiled comparison for eleven 3-bit entries over four address bits: 19 / 26 / 28 native controlled Toffolis (select, select_swap B = 2 and B = 4) against 15 / 18 / 22 plain, with `cudaq.control` over the composition at two to three times the native count, widest on select_swap because the wrapper also controls the Fredkins and the copy CNOTs. Docstrings and comments only. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Brings in NVIDIA#52, NVIDIA#54, NVIDIA#55, NVIDIA#56, NVIDIA#61, NVIDIA#62, NVIDIA#63, NVIDIA#66, NVIDIA#68, NVIDIA#69, NVIDIA#70 and NVIDIA#75. Conflicts: the package docstring and import list (union), the BlockEncoding docstring (names all four provided implementations), and the API page (combinators automodule, then the sparse section).
## Summary Adds `SparseOracleEncoding`, the sparse-access block encoding of real sparse symmetric matrices, three ways to feed it (`banded_oracles`, `qrom_oracles`, a hand-written `OracleKernels` bundle), and `encode_sparse()`, the data-level constructor with validation, optional Hermitian dilation and a cost report. The implementation uses `cudaq_algorithms.primitives` and satisfies the `BlockEncoding` protocol, so `Walk` and `QSVT` consume it unchanged. The public API is available from both `cudaq_algorithms.sparse` and the package root. ## The encoding Builds `U_A = T† S T` from location and value oracles supplied through `OracleKernels` (Camps, Lin, Van Beeumen & Yang, arXiv:2203.10236). Its normalization is `alpha = d_padded * h`, with `d_padded` the padded number of structure slots and `h` a bound on the largest matrix-element magnitude. `U_A` is self-adjoint and involutory on every state whose work scratch enters `|0>`, the domain the oracle contract guarantees and every consumer supplies; flat, work-free oracles (`banded_oracles`) make the involution a global matrix identity, table oracles built on unary-iteration walks (`qrom_oracles`) do not. Controlled application controls only the middle factor, so the supplied oracles never need controlled versions. Negative off-diagonal entries are supported; negative diagonal entries need a diagonal shift or the Hermitian dilation. **The oracle contract.** Every nonzero entry belongs to exactly one slot; a mapping that only completes a slot's permutation loads zero, even where the matrix entry is nonzero and owned by another slot; reverse slots carry mirrored entries consistently. `qrom_oracles` builds bundles that satisfy this by construction, and a hand-written four-by-four oracle pins the rule in the tests. ## Inputs - **`banded_oracles`**: arithmetic location and value oracles for matrices given by constant-valued bands, with boundary comparators preventing wraparound. Offsets of any magnitude are applied exactly: the Fourier-basis adder and comparator take each constant reduced per bit as an integer (`K mod 2^(t+1)`), the form `primitives.phase_add_constant` uses. Band symmetry is judged relative to the band scale. - **`qrom_oracles`**: table-based oracles from the entries; a matching-based slot assignment makes each location map its own inverse, so the same kernel serves as its adjoint. - **`from_general_oracles`**: the Hermitian dilation `[[0, A], [A.T, 0]]` of a real square `A` on one extra system qubit, from flat oracles of `A`. - **`OracleKernels`** records the system width it was built for; the encoding rejects a mismatched width. Data-driven inputs accept dense arrays, SciPy sparse matrices, or integer-indexed `(rows, cols, vals)` triples; entries must be real and finite in both parts. `encode_sparse(matrix, value_bits=, dim=, dilate=, max_entries=)` validates the data, prices `alpha` and the register budget classically, builds the encoding over `qrom_oracles`, and attaches `encoding.report` (`alpha`, `qubits`, `nnz`, `max_row_nnz`, `slots`, `dim`, `dilated`, `value_bits`). A non-symmetric input or a negative diagonal raises, naming the way out; `dilate=True` encodes the Hermitian dilation at the data level, where every entry sits off the diagonal. `max_entries` bounds the QROM table; `alpha` must be finite. ## Resource accounting `num_ancilla` includes all ancillas, scratch included; `num_block_ancilla` / `num_scratch` expose the breakdown. The encoding provides no `select_observable`, so `Walk.moment` serves even orders only, and the shared `reflection_observable` raises above 20 ancillas rather than expanding into `2^num_ancilla` Pauli terms; Chebyshev data at these widths comes from `walk_kernel` powers. `state_from(ket, num_qubits=None)` gains an input-width check: with `num_qubits` given, a ket of any other length raises instead of silently widening the system register (oversized) or failing inside the simulator (undersized). ## Validation Three test files, run through CUDA-Q on 0.15.1 and 0.16. Circuit blocks are compared with the exactly realized (quantized) matrices at 1e-10 and those with the intended matrices within the derived quantization bound. Covered: Laplacian and permutation blocks; the involution on dirty block ancillas with clean scratch (`qrom_oracles`) and on a fully dirty register (`banded_oracles`); walk powers against `T_k(-H/alpha)`; forward/adjoint round trips; controlled application and controlled walks at `|0>`, `|1>` and a control in superposition; QSVT against the signal model; protocol conformance; the dilation with padded slots and signed non-symmetric input under the consumers; QROM-backed oracles at every shape (1×1, 2×2, diagonal-only, non-power-of-two) with padded rows and columns zero; the location and value oracles on basis states (locations, inverses, angle / sign / upper loads, fillers and padded slots, clipped boundaries, clean work); two nontrivial reverse-slot pairs and the one-qubit slot register; band offsets near the register limit and overlapping modular shifts; the per-bit reduction checked analytically at 2^30 + 1 and 2^53 + 1 with the 54-qubit bundle built; the hand-written-oracle contract with the double-counting counterexample; scale-relative band symmetry at 1e-13, 1 and 1e6; `encode_sparse` reports, dilation and guards (fractional offsets, non-finite entries, infinite `alpha`, table budget, input width). ## Docs API reference entries for the family, a "Sparse-access encodings" section in the block-encodings guide, a conventions entry, and a README line. ## Follow-ups - Even moments from the all-zero-ancilla probability instead of a Pauli expansion, which would remove the 20-ancilla guard for every wide encoding. - Natively controlled `qrom_oracles` bundles for `from_general_oracles` on top of the controlled QROM lookup (#70); automatic QROM-variant selection; complex matrices. ### References Camps, Lin, Van Beeumen & Yang, *Explicit quantum circuits for block encodings of certain sparse matrices* ([arXiv:2203.10236](https://arxiv.org/abs/2203.10236)); the sparse-access input model of Berry, Childs & Kothari ([arXiv:1501.01715](https://arxiv.org/abs/1501.01715)); Low & Chuang ([arXiv:1610.06546](https://arxiv.org/abs/1610.06546)); Gilyén, Su, Low & Wiebe ([arXiv:1806.01838](https://arxiv.org/abs/1806.01838)). --------- Signed-off-by: Scott Thornton <wsttiger@gmail.com>
QROMgainscontrolled=True: the externally controlled lookup, with theunary_iteration_kernelsconvention — a leading one-qubitcontrolview,identity at control
|0>, the plain lookup at|1>, self-inverse.controlledmust 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 mintedcontrolled=Trueand the parentpasses 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 writesnothing, so the block registers stay
|0>, the routing swaps|0>registers, thecopy fires from a
|0>slot, and the un-route/un-write are the identity. TheToffoli 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 onselect_swapwithB = 2), withrouting and copy unchanged — a fraction of
cudaq.controlover the composition,which adds a control to every gate.
variant="auto"prices the controlledcandidates 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.controlover the plain composition costs 42/ 77 / 83. Because the optimum moves to larger blocks under control,
num_laddercanexceed the plain lookup's; size the ladder from the instance.
What "identity at
|0>" means. A clean-ladder statement: every ladder write ofa 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.controlof the plain lookup is the identityon 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_oracleswraps the oracles incudaq.control, which excludestable-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): controlconventions at
|0>,|1>and|+>and the involution under a superposed controlover six geometries — both variants, a one-bit address, a single block-index bit
(
B = 2^(address_bits − 1)), a zero-padded last block atB = 8— on superposedaddresses with a preloaded output word, the
|1>branch compared with the tableitself; Toffoli counts against the documented contract;
autopricing pinned on atable whose optimum moves (plain
B = 2, controlledB = 4); the bool guard; andthe 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 cantake the composed
select_swapwith this flag wholesale (MeasuredQROMalreadycarries a
controlledflag of the same shape).Files
python/cudaq_algorithms/primitives/_qrom.pypython/cudaq_algorithms/primitives/__init__.py(docstring line)tests/python/test_primitives_qrom.pytests/python/test_primitives_resources.py