Repository navigation
Add the sparse-access block encoding - #53
Conversation
Promote the sparse branch's unary-iteration emitter into a package subpackage and upgrade the circuit: sibling transitions reuse the parent ladder line with a CNOT and deeper transitions fuse the uncompute of the old path into the compute of the new one through a single guard Toffoli on CNOT-conjugated address wires (arXiv:1805.03662, Fig. 7). A full uncontrolled tree of N addresses now costs exactly 3N/2 - 5 Toffolis (3N/2 - 1 controlled) instead of the previous 2N - 4; exhaustive circuit search over CNOT/Toffoli circuits (arbitrary conjugated controls, dirty address wires, extra ancillas) shows these counts are optimal at the small sizes it can cover. The paper's N - 1 assumes measurement-based AND uncomputation, which this library's strictly unitary primitives do not use; the gap is documented where the costs are stated. The factory surface, the trace-time body-callback emitter, the extended body vocabulary (free/AND-ladder/work/sign gates), the controlled variant, the hand-written reversed-tape inverse and the keep-alive kernel registry are kept from the seed. Tests verify the emitted tape classically and exhaustively (every width up to 6, every partial-tree size, controlled and uncontrolled, via Boolean truth tables), pin the count formulas, and pin the minted kernels on the statevector against a dense NumPy SELECT reference plus the seed's behavioral suite. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
One QROM(data, address_bits, output_bits, *, variant, block_size) surface over two constructions behind the same (address, ladder, output) kernel signature: the plain unary-iteration lookup (arXiv:1805.03662) and SELECT-SWAP/QROAM (arXiv:1812.00954) — a fused walk over the block index writes each visited block into clean block registers carried in the ladder view, a binary cswap network on the low address bits routes the selected register into slot 0, CNOTs copy it out, and the routing and writes are undone unitarily. Both variants are exactly self-inverse. Toffoli accounting is a documented contract: W(address_bits, N) for select and 2 W(high_bits, ceil(N/B)) + 2 b (B - 1) for select_swap (Fredkin = one Toffoli + two CNOTs; both the writes and the routing run twice because uncomputation stays unitary, unlike the papers' measurement-assisted headline counts). variant="auto" prices every power-of-two block size with the exact model and takes the cheapest; the default block size therefore optimizes the coherent cost (~sqrt(3N/2b)) rather than the papers' ~sqrt(N/b). Tests read out both variants exhaustively on random tables (including out-of-range addresses of short tables, which must read zero), pin the self-inverse property on superposed addresses, check select_swap equals select amplitude-for-amplitude on the same table, and cover the auto dispatch and the loud factory validation. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
cudaq.estimate_resources counts the ccx operations actually synthesized
(via Resources.count_controls("x", 2) — count("ccx") is a known trap
returning 0) and must equal both the emitter's bookkeeping and the
documented closed forms: 3N/2 - 5 on full uncontrolled trees (3N/2 - 1
controlled), exact 3N/2 per-doubling increments (increments, not
ratios — affine costs approach the asymptote from above), the QROAM
formula 2 W(high_bits, blocks) + 2 b (B - 1) for every block size, and
the auto dispatch beating the losing variant on both sides of the
select/select_swap crossover. Skipif-gated on estimate_resources
availability; every assertion is an exact integer comparison.
Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The Toffoli comparison (3N/2 fused vs 2N naive unitary) flatters the fused walk: its dirty-target Toffolis cost 7 T where clean-target ANDs cost 4 T, so in T gates the coherent advantage is ~7.5N vs ~8N (~6%), against the measured walk's 4N. Stating both metrics in the docstring keeps comparisons against measured-accounting papers honest; a t_count attribute is planned. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The minted kernel is a generic interpreter, so reading its source shows nothing about any particular circuit; the tape it replays is the circuit. UnaryIterationKernels now retains the tape and describe() decodes it one gate per line in execution order, with the Toffolis tagged so the listing reconciles against toffoli_count by eye. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Superposed (phased) address, one application: the state must be the sum over addresses of each branch's own payload unitary acting on a random complex product state — every payload an independently-built dense 64x64 NumPy product of non-commuting Paulis on a 6-qubit target. Order, phase, and ladder-residue errors all misassemble the sum. Rides along: payload size must not change the Toffoli count (leaf-controlled Paulis are two-qubit gates), pinned against emitter and compiler. The file parametrizes over every walker importable on the branch, so the same file serves the coherent and measured branches without conflict. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
- Materialize QROM data before validation: a single-pass iterable was consumed by the int conversion, so the float check ran on an empty iterator and silently truncated (1.5 -> 1). Regression-tested both ways (rejection and valid generator). - Reject variant='select_swap' at address_bits=1 with a clear error instead of a bare min()-of-empty ValueError. - Make QROM.kernel a property, matching every sibling accessor. - Add cudaq_algorithms.primitives to the Sphinx API reference. - Drop the payload test's import scaffolding for an unshipped module and the docstring's promise of a planned t_count attribute. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The four minted interpreter kernels compared the tape opcode against bare integer literals. CUDA-Q kernel code cannot reference Python enums or module globals, but it does capture enclosing-scope ints, so bind the module's _OP_* constants as factory locals and dispatch on the names. No functional change; the compiled kernels are identical. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Bare |0> in a docstring parses as an unterminated RST substitution reference, failing the warnings-as-errors docs build now that the primitives subpackage is on the API reference page. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Review follow-ups (PR 40): every documented body gate gets a behavioral pin — each test states its expected action as a bits -> (bits, phase) function applied at the active address, checked on a phased superposed address exhaustively over target basis states, with work registers verified back to |0>. The free-gate tests double as pins of the conjugation-cancellation soundness condition on inactive addresses. A resource test holds toffoli_count equal to the compiler's count when the body itself contributes Toffolis. External control: the same walk built plain-then-wrapped in cudaq.control and built with controlled=True must produce identical states on a superposed control — pinned against each other and against the analytic reference, making the docstring's 'cudaq.control- compatible by construction' a tested contract. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Review feedback: 'ladder' reads as the walk's ladder but for select_swap it bundles the walk lines with the block registers, and the ladder-to-ladder CNOT opcode serves three distinct jobs (sibling crossings, rebased block writes, Fredkin outer CNOTs). Spell out the register layout, the rebase step, the opcode reuse, and the five-step W S C S^-1 W program in the module docstring. Docs only. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Review feedback on _mint_interpreter: the four variants dispatch overlapping opcode subsets, and an opcode outside the minted variant's set would be silently skipped by the if-chain — the worst available failure mode. To the agreed bar: - Declare the dispatch coverage as module data (_BASE_OPS plus the _CONTROL_OPS/_WORK_OPS bundles a signature carries) and reject out-of-set tapes at mint time with a ValueError. - Replace the numeric opcode literals in the truth-table verifier with the module's _OP_* names, so the test no longer keeps its own copy of the numbering. - Add per-opcode semantic pins (test_primitives_interpreter.py): each of the 21 opcodes runs as a one-instruction tape under every signature variant that supports it, on every basis state of the touched qubits, against its documented action including phases — 70 (opcode, variant) cases. A missing dispatch arm in any variant now fails a pin, and a spec-vs-sets consistency check ensures new opcodes land in both places. The four fixed signatures stay as they are (CUDA-Q kernels cannot take optional or empty register views). Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The local format script run silently no-oped (macOS bash 3.2 lacks mapfile) and the local yapf was 0.40.2; CI pins 0.43.0. Formatting change only. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The reviewer is right that the fused unitary walk is not novel: it is the select(V) construction of Childs et al. (arXiv:1711.10980, Appendix G.4, Lemma G.7) — same in-place fused transitions, same 3N/2 + O(1) count, no measurement — verified against the paper. The module docstring now attributes it there and drops the exhaustive-search narrative; what remains ours is the presentation, the priced controlled variant, and non-power-of-2 handling. Also per review: document what body stores (the SELECT payload table, sum_k |k><k| (x) U_k) at the factory docstring; name the transition helpers (sibling_flip, unguarded_root_crossing) so _emit_walk reads as its three cases; strengthen the adjoint test with an explicit non-involution premise assertion (forward-forward != identity, so the reversed-tape check cannot silently become vacuous); and run the controlled-variant test on a superposed control (ry(0.7)) alongside the classical branches, where a branch-relative phase error would be invisible. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
…e docstring Second review round: state the Childs et al. attribution at the package docstring too; replace the double-negation ceiling division with the additive idiom; rewrite the select_swap rebase comment to say what the construction IS (textbook walk-over-blocks + low-bit routing network) and why the emitter is reused instead of a minted kernel (one flat tape — CUDA-Q kernels cannot call kernels), with 'ladder' explicitly named as the wire bundle rather than a role; note at the routing network that the Fredkin outer CNOTs act purely between data blocks. Docs only. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
CDKM/Cuccaro ripple-carry family (register and constant add/subtract, >=-constant comparator) and the ancilla-free Draper QFT family (qft/iqft, Fourier-basis constant add/subtract, the comparator pair with its hand-written adjoint), all little-endian in-place kernels with hand-written inverses. Tests are exhaustive over all inputs for widths up to 5 via the superposition harness, pin every inverse by op-then-inverse identity, and hold the documented gate prices against the compiler with cudaq.estimate_resources: exactly 2n Toffolis for every CDKM operation (0 at K = 0), and zero Toffolis for the QFT family with exact controlled-r1 / r1 / h budgets (n(n-1) + n for the adder, n(n+1) + n + 1 per comparator side). Review fixes: document the 0 <= K <= 2^n precondition on cmp_ge_constant_qft (the borrow wraps mod 2^(n+1) for larger K) and the exact constant_bits/complement_bits length preconditions on add_constant and cmp_ge_constant; note in the module docstring that operands of any one op must be pairwise disjoint; version-scope the cudaq.adjoint limitation (broken as of CUDA-Q 0.15, still unresolved on the 0.16 pre-release) instead of stating it timelessly. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
…amily cmp_ge_register(a, b, carry, out) XOR-loads (a >= b) (unsigned, equal-width little-endian registers) into the out qubit and restores a, b and the carry ancilla: complement b in place (2^n - b = ~b + 1, carry-in set to 1), MAJ sweep, copy the top ripple carry into out, reverse the sweep and undo the complement. Because b doubles as the constant-load register, no n-qubit work register is needed — only the one-qubit carry — and the price is the family contract of exactly 2n Toffolis, pinned against cudaq.estimate_resources with runtime-argument harnesses at widths 1-5 and 8. cmp_gt_register is the free strict variant (a > b <=> not (b >= a): one X plus the >= comparator with the roles swapped), same price. Both are self-inverse (compute-copy- uncompute operand action, XOR-accumulated flag), pinned by apply-twice-is-identity tests on random entangled states, alongside exhaustive truth tables (widths 1-4, flag initially 0 and 1) and superposed-register statevector checks against dense NumPy references. This is the comparator the alias-sampling conditional (keep-value vs alt-value test) and the first-quantized momentum comparisons need (colleague review feedback; GAP_first_quantized_lcu.md). Signed-off-by: Scott Thornton <wsttiger@gmail.com>
AliasSamplingPrepare: the Babbush-style (arXiv:1805.03662, Sec. III.D) PREPARE-with-garbage whose index-register marginal realizes the integer-Vose table distribution exactly (per-bin discretization bound 1.5 / (K 2^mu), derived not assumed), with a hand-written adjoint. The (alias, keep) lookup is the variants QROM under variant="auto": the ladder slice inside the garbage register is qrom.num_ladder wide, so num_garbage = m + 2mu + 4 + qrom.num_ladder (the historical 2m + 2mu + 4 whenever the plain select walk wins the pricing). No variant is pinned - every variant restores its ladder and self-inverts on the clean-ladder sector, which is what the adjoint's second lookup uses. The new qrom property exposes the priced lookup. Tests pin the index marginal against an independent brute-force branch enumeration, the table distribution against the ideal within the derived bound, prepare-then-adjoint identity, degenerate weights and validation messages, plus compiler-pinned resource contracts: each PREPARE side costs exactly qrom.toffoli_count + 4 (mu + 1) Toffolis (the lookup at the QROM's own reported price plus the two CDKM comparator adders) and num_index Fredkins, adjoint == forward gate-for-gate. Review fixes: pass variant/block_size through to the QROM constructor as keyword-only parameters (they shape num_garbage via qrom.num_ladder; QROM validation errors propagate as-is) and pin the variant-agnostic claim with a forced select_swap test asserting the exact marginal, the prepare-then-adjoint identity and the Toffoli cost identity; reject bool mu explicitly while keeping integral numpy ints; document that the per-bin garbage vectors are not mutually orthogonal (only the index amplitude magnitudes are guaranteed - exactly the qubitization <0|PREP' SELECT PREP|0> contract); say "the available QROM variants" instead of naming constructions this base does not ship; version-scope the cudaq.control nested-call rejection (through CUDA-Q 0.15, lifted in 0.16); mark the h-count assertion as incidental and the toffoli_count cross-check as a deliberate bookkeeping-vs-compiler pin. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Bare |0...0> kets and the |table - ideal| absolute value in the AliasSamplingPrepare docstrings parse as unterminated RST substitution references and fail the warnings-as-errors docs build now that the class is on the API reference page. Same fix as the unary-iteration module received on the QROM PR. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Implements the reviewer-suggested consolidation: the PREPARE's keep-vs-reference conditional now calls the arithmetic family's cmp_ge_register (flag <- (ref >= keep) on the bare mu-bit operands) instead of the bespoke two-adder weave (subtract keep on a (mu+1)-bit extension, copy the borrow, negate, add back). - Toffoli count per PREPARE side drops from 4(mu+1) to 2mu: the cost identity is now qrom.toffoli_count + 2 mu Toffolis + num_index Fredkins, re-derived in the docstrings and re-pinned by the resource tests against cudaq.estimate_resources. - The (mu+1)-bit extension's two pad qubits (keep_pad, ref_pad) are gone: cmp_ge_register complements its second operand in place and needs only the one-qubit carry the layout already had. num_garbage shrinks by 2 to num_index + 2 mu + 2 + qrom.num_ladder, and ladder_offset moves down accordingly. - The hand-written adjoint stays the literal gate-reversal: cmp_ge_register is palindromic around its self-inverse flag copy, so re-applying it is the reversed gate sequence. All marginal/bound/identity tests pass unchanged; only the layout and cost accounting assertions are re-derived. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Re-applies the novel content of experimental/sparse_access_block_encoding (tip bb54617) onto features/primitives_alias_sampling, where unary iteration, QROM, alias sampling, and reversible arithmetic now live as cudaq_algorithms.primitives. The branch's four seed modules (sparse/_unary_iteration, _qrom, _alias_sampling, _arithmetic) are not carried over; the sparse package keeps only the encodings themselves. SparseOracleEncoding (V1) block-encodes a real symmetric d-sparse matrix from user location/value oracles as U = T-dagger S T over a slot register, a flag pair, and a dual index register: exactly self-adjoint and involutory, controlled variants for free (only the encoding-owned S factor carries the control). Signs ride an oracle-supplied sign/upper bit pair as +-i phases; negative diagonals are unrepresentable, with from_general_oracles (Hermitian dilation) as the documented escape hatch. banded_oracles compiles band offsets/values into flat oracle kernels (inlined Draper adders/comparators, since cudaq.control cannot synthesize control variants of kernels that call other kernels). SparseLCUEncoding (V2) is the alias-sampled LCU encoding: PREPARE over the term weights, unary-iteration SELECT over Hermitian involutory term unitaries, alpha = the term one-norm. Each off-diagonal pair splits into a transposition and its reflected partner at half weight each; each diagonal into identity and reflection halves; signs ride the SELECT branch as a leaf-line Z phase. U_A = PREPARE-dagger SELECT PREPARE is self-adjoint and involutory on clean-ancilla states. qrom_oracles compiles matrix entries into an OracleKernels bundle via greedy edge coloring into matchings (every slot map an involution), and encode_sparse prices both constructions classically and dispatches to the cheaper one, with prefer= overrides and a pricing report on the returned encoding. Adaptations for the promoted primitives: - _sparse_lcu and _from_data import AliasSamplingPrepare / unary_iteration_kernels from cudaq_algorithms.primitives. - SparseLCUEncoding pins AliasSamplingPrepare(..., variant="select"), freezing num_garbage = 2*num_index + 2*mu + 4 and the ladder_offset/num_ladder == num_index geometry that sparse_lcu_apply relies on when SELECT reuses the PREPARE QROM ladder — and that _factory's closed register-budget formula prices (commented there). Adopting variant="auto" would require pricing via the QROM cost model. - sparse/__init__ exports only the encodings; the primitives' canonical import path is cudaq_algorithms.primitives. - Docstrings no longer quote the naive walk's 2(K-1) tree-Toffoli figure (the promoted fused walk is cheaper); they defer to the primitives' documented counts and toffoli_count. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Re-applies the qubitization guard from experimental/sparse_access_block_encoding (originally part of 32e252d) onto the promoted-primitives base, resolving the one-hunk textual conflict with the base's reformatted docstring. The reflection observable expands to 2^num_ancilla Pauli terms, and past ~20 ancillas building it hangs rather than erroring. reflection_observable now raises with a pointer at walk_kernel powers — the route wide-ancilla encodings (e.g. the sparse-access family) must take for their Chebyshev data. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Re-applies the branch tests from experimental/sparse_access_block_encoding that cover the novel encodings: test_sparse_oracle.py and test_sparse_factory.py verbatim, test_sparse_lcu.py plus one relocated test. The seed-primitive test files (test_sparse_alias_sampling, test_sparse_arithmetic, test_sparse_qrom, and the walk-cost portion of test_sparse_resources) are not carried over — they are superseded by the test_primitives_* suites already on this base. The tests validate against exact dense references at 1e-10 and against the ideal matrices within derived discretization/value_bits bounds, pin the involution and controlled conventions, the classical term enumeration, the encode_sparse dispatch decision table and priced qubit budgets, and drive both encodings through Walk and QSVT. test_wide_ancilla_even_moment_refuses_fast (originally in test_sparse_resources.py, from bb54617) moves into test_sparse_lcu.py: it pins the reflection_observable ancilla guard through SparseLCUEncoding's genuinely wide ancilla register. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Bare |0> kets, |H_ij| norms and the pipe-separated ancilla layout parse as broken RST substitution references / unterminated inline literals the moment these modules join the API reference; verified clean by building the docs with the subpackage temporarily exposed to autodoc. Same fix the primitives modules received on the QROM and alias PRs. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Repair the RST damage the notation sweep itself introduced (backticks inside a literal block, nested inline literals) — re-verified by building the docs with the subpackage temporarily exposed to autodoc, zero warnings. Add the two missing state_prep-injection tests (LCU encode_kernel and oracle walk_kernel both pinned against their dense references on a product-state prep), and document that from_general_oracles requires flat oracle kernels through CUDA-Q 0.15, so qrom_oracles bundles cannot feed the dilation path. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The PREPARE conditional now consumes the family register comparator (one carry qubit, no pad pair), so AliasSamplingPrepare's num_garbage for the pinned select variant is 2*num_index + 2*mu + 2; the classical qubit budget in encode_sparse follows. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Re-export the sparse public API (SparseOracleEncoding, SparseLCUEncoding, OracleKernels, banded_oracles, qrom_oracles, encode_sparse) from cudaq_algorithms, alongside the existing cudaq_algorithms.sparse subpackage, matching PauliLCU / BlockEncoding. Drop the 'experimental' framing from the subpackage docstring.
The SparseLCUEncoding class docstring listed the PREPARE garbage register as 2m + 2*mu + 4 qubits; the actual width (and the encode_sparse budget formula in _factory.py) is 2m + 2*mu + 2. Documentation only.
test_apply_is_involution only fed the |0...0>-ancilla block, so it did not operationally distinguish SparseOracleEncoding's *global* involution (U_A^2 = I on the full register) from SparseLCUEncoding's, which holds only on the |0>-ancilla domain. Add a companion that loads an arbitrary dense state over system and ancilla (scratch qubits non-zero) and checks U_A^2 restores it exactly.
…t check, reflection docstring Three findings from an external review of the sparse-access encodings: - encode_sparse mis-classified a uniformly tiny but genuinely asymmetric matrix as symmetric: the symmetry tolerance had a fixed 1e-12 absolute floor (max(1.0, |value|)), so matrices with all entries below it were symmetrized (LCU) or crashed on a missing mirror entry (oracle KeyError). _first_asymmetry now scales the tolerance to max|H| and treats missing mirror support as an asymmetry, so such inputs are dilated. Test added at three overall scales x both paths. - The LCU register width was priced by building every SELECT gate body even when the term count already exceeded max_terms (4160 bodies for a 64x64 all-ones input at max_terms=1). The budget is now checked on the cheap term count first; bodies are materialized only for an eligible path. Over-budget qubits are reported as None. Test pins zero bodies built. - The num_ancilla / module docstrings claimed the full-register reflection equals an index-only reflection. It does not: SELECT entangles the PREPARE garbage with the system, so the index-|0> sector is not garbage-free and the full reflection is required (the code already reflects the full register). Corrected, and the difference from _sparse_oracle -- whose scratch is a pure function of the system -- is now stated.
…ings The oracle construction is the standard sparse-access input model; attribute it in the SparseOracleEncoding docstring (Berry-Childs-Kothari 1501.01715, Low-Chuang qubitization 1610.06546, Gilyen-Su-Low-Wiebe QSVT 1806.01838). Cite Camps, Lin, Van Beeumen and Yang (2203.10236) in banded_oracles as the explicit-circuit reference for structured/banded sparse matrices, the layer these prefab oracles occupy above the generic interface.
Attribute the PREPARE-dagger . SELECT . PREPARE structure to the LCU block encoding (Childs-Wiebe 1202.5822; Berry-Childs-Cleve-Kothari-Somma 1412.4687), and point to the primitives layer where the coherent alias sampling and unary iteration gadgets (Babbush et al. 1805.03662) are cited.
The SparseOracleEncoding assembler follows Camps, Lin, Van Beeumen and Yang (arXiv:2203.10236): T is their diffusion . O_A . O_c sandwich (Sec. 4), S the O-dagger . SWAP . O Hermitian symmetrization (Thm. 5.1 / 8.1), and alpha = d_padded * h their sparsity normalization s. banded_oracles implements their Sec. 4.2 banded construction (shift/adder O_c + Ry O_A). Demote Berry-Childs-Kothari / Low-Chuang / Gilyen to the oracle-model and block-encoding/QSVT context they actually supply. The earlier docstrings framed Camps as banded-only related work and mis-credited the construction to the oracle-model ancestors.
Take main's reviewed primitives (renamed QFT comparator, corrected Toffoli counts, dispatch pruning) and their tests wholesale; the sparse package consumes only unary_iteration_kernels and AliasSamplingPrepare, both unchanged in name.
… document The oracle construction's involution was documented as a global matrix identity and the table oracles as their own inverses. That holds for flat, work-free oracles (banded_oracles) but not for qrom_oracles bundles: their oracles are unary-iteration walks, which invert only when the ladder and work scratch enter |0>, so U_A^2 = I fails on a dirty work register (defect ~2e-3 on a 4x4 example). Walk and QSVT only ever see clean scratch, so nothing they compute was wrong; the docstrings, the OracleKernels contract and the LCU module's comparison now state the real guarantee, and the dirty-ancilla tests cover a qrom_oracles bundle (dirty block ancillas, clean work) and a fully dirty banded bundle. Three inputs that silently produced a different operator now raise: - encode_sparse Hermitian-dilated any non-symmetric input, including a symmetric matrix stored as one triangle and one with rounding noise above 1e-12 * max|H|, doubling the system register with only report["dilated"] as a signal, while the constructors raised for the same input. Dilation is now opt-in (dilate=True); the default raises naming the asymmetry and the way out. - An OracleKernels bundle carried no width, so one built for n system qubits was accepted by an encoding on n+1 and ran as a block-diagonal tiling. The bundle now records num_system (set by banded_oracles, qrom_oracles and the dilation) and the encoding rejects a mismatch. - A 3-tuple was always read as (rows, cols, vals), so a dense matrix given as a tuple of three rows became a single entry. Triples are recognized by shape and integer index dtype; anything else is a dense matrix. The pricing report moves onto the public classes (report is None on direct constructions, with the repr suffix only when set), replaces the ambiguous "d" with max_row_nnz and slots, and quantized_angle is exported as part of the oracle contract. Tests: the LCU ideal-matrix check used alpha * num_bins * bound, which at mu=2 admits the zero matrix; the alias tables are now pinned bin by bin against the ideal weights, off-diagonal elements against the two-term bound and diagonals against the all-terms bound, plus a mu sweep. The oracle references use a test-local quantizer instead of importing the one under test. New: controlled walk at control |0> and |1> for both encodings, an exact alpha tie resolved towards the oracle path, the K3 report case, the non-symmetric and width guards, and the tuple-of-rows case. Column-by-column loops become single dense actions and value_bits drops from 12 to 10, cutting the suite from ~10 to ~5 minutes. Docs: the sparse family in the API reference, a guide section (which construction when, encode_sparse and its report, the oracle contract, widths and moments), a conventions entry, a README line; the protocol docstring and the bring-your-own text no longer present sparse-oracle encodings as hypothetical. Stale text fixed: the "+4" garbage comment, a reference to a docs file that does not exist, plan-stage labels, and compiler-version narrative in the flatness rationale. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
`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>
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).
|
SparseOracleEncoding looks great, and is architected in a natural way - see minor comments below. Requested changes1. Fix scale-dependent symmetry validation in
|
…om data The sparse package offers one construction, SparseOracleEncoding (the sparse-access oracle encoding of Camps et al.), fed by banded_oracles, qrom_oracles or a hand-written OracleKernels bundle. The linear combination of transposition and reflection unitaries that SparseLCUEncoding built has no algorithm in the literature behind it and no demonstrated regime of efficiency, so it goes, together with its tests (review of NVIDIA#53). `encode_sparse(matrix, value_bits=, dim=, dilate=, max_entries=)` is now the data-level constructor for the oracle encoding: it validates the entries, dilates on request, prices alpha = d_padded * h and the register budget classically, refuses negative diagonals and non-symmetric input with the way out (shift the diagonal, or dilate=True, under which every entry sits off the diagonal), bounds the QROM table by `max_entries`, and attaches `report` (alpha, qubits, nnz, max_row_nnz, slots, dim, dilated, value_bits). The matrix-input helpers it shares with qrom_oracles live in sparse/_entries.py. Tests: the factory file pins the report and the encoded block for a uniform band, skewed weights, a negative diagonal through the dilation, non-symmetric input through the dilation, the tiny-scale asymmetry detection, the table budget and argument validation. Guide, conventions, README and the API page describe the one construction. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
…r input checks, one slot per entry The banded adder and comparator rotations took the whole offset (or the comparator constant) as a float: 2 pi K / 2^(t+1) for K near 2^53 loses the low bits and the location oracle lands on the wrong row. Each constant is now reduced per bit as an integer, K mod 2^(t+1), the form primitives.phase_add_constant already uses, so any offset is exact; `_residues` holds the table and a test checks 2^30 + 1 and 2^53 + 1 against exact rational phases, builds the 54-qubit bundle, and pins the clipped band at offsets near the register limit through the circuit. Band symmetry is judged relative to the band scale (1e-12 * max |value|), as the matrix-data check already was: lopsided bands at 1e-13 encoded a zero block before. Offsets must be integers (0.9 silently selected the diagonal), a matrix entry must be finite in both parts before the real tolerance is applied (complex(1, nan) passed as 1.0), and alpha = d_padded * h must be finite (1e308 * I produced an infinite alpha), checked in the encoding and before any kernel is minted in encode_sparse. The oracle contract states that every nonzero entry belongs to exactly one slot and that a mapping which merely completes a slot's permutation loads zero, even where the matrix entry is nonzero and owned by another slot; the previous wording loaded H[x, cinv(s, x)] unconditionally and counted such entries twice. A hand-written four-by-four oracle pins the rule, with the double-counting reading as the counterexample, and a banded case with coinciding modular shifts but different supports pins the comparator gating. Review of NVIDIA#53 (smcardleQ), items 1-4. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
… and the dilation A new test file exercises the sparse-access encoding through CUDA-Q rather than through construction metadata: QROM-backed oracles at every shape (1x1, 2x2, diagonal-only, 3x3, 5x5) with padded rows and columns zero; the location and value oracles on basis states (locations and inverses, angle / sign / upper loads for assigned entries and zero for fillers and padded slots, clipped band boundaries, work qubits clean); controlled application, walk powers and QSVT of a QROM-backed encoding against dense references; two nontrivial reverse-slot pairs and the one-qubit slot register pair; the Hermitian dilation with padded slots and signed non-symmetric input under controlled application, the walk, QSVT and the factory's dilation path; and a control in superposition against the full reference state. `state_from(ket, num_qubits=None)` gains the width check the kernel boundary cannot make: a kernel that allocates `cudaq.qvector(state)` takes whatever width the data implies, so an oversized ket silently widens the system register and an undersized one fails inside the simulator with an opaque error; with `num_qubits` given, any other length raises ValueError. The encodings' state-taking kernels and the guide say so. Review of NVIDIA#53 (smcardleQ), additional circuit tests. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
|
Thanks for the thorough pass, and for the reproducers: all four ran here exactly as you described, on CUDA-Q 0.15.1 and 0.16.
1. Symmetry tolerance. Band symmetry is now judged relative to the band scale ( 2. Integer precision. The adder and comparator rotations take each constant reduced per bit as an integer, 3. Contract. The module docstring now states that every nonzero entry belongs to exactly one slot, that a mapping which merely completes a slot's permutation loads zero even where the matrix entry is nonzero and owned by another slot, and that the assignment must be consistent across reverse slots. 4. Validation. Offsets must be integers ( Circuit tests. A new file, Input width. The kernel boundary cannot check it (a kernel allocates whatever All of it runs through CUDA-Q on both supported versions; the branch is also merged with current main. |
Brings in NVIDIA#57 (PhaseEstimation and the sim_utils package). Conflict: the package import list (union).
Summary
Adds
SparseOracleEncoding, the sparse-access block encoding of real sparse symmetricmatrices, three ways to feed it (
banded_oracles,qrom_oracles, a hand-writtenOracleKernelsbundle), andencode_sparse(), the data-level constructor with validation,optional Hermitian dilation and a cost report.
The implementation uses
cudaq_algorithms.primitivesand satisfies theBlockEncodingprotocol, so
WalkandQSVTconsume it unchanged. The public API is available from bothcudaq_algorithms.sparseand the package root.The encoding
Builds
U_A = T† S Tfrom location and value oracles supplied throughOracleKernels(Camps, Lin, Van Beeumen & Yang, arXiv:2203.10236). Its normalization is
alpha = d_padded * h, withd_paddedthe padded number of structure slots andha boundon the largest matrix-element magnitude.
U_Ais self-adjoint and involutory on every state whose work scratch enters|0>, thedomain the oracle contract guarantees and every consumer supplies; flat, work-free oracles
(
banded_oracles) make the involution a global matrix identity, table oracles built onunary-iteration walks (
qrom_oracles) do not. Controlled application controls only themiddle 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_oraclesbuildsbundles 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 byconstant-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 formprimitives.phase_add_constantuses. Band symmetry is judged relative to the band scale.
qrom_oracles: table-based oracles from the entries; a matching-based slot assignmentmakes 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 squareAon one extra system qubit, from flat oracles ofA.OracleKernelsrecords the system width it was built for; the encoding rejects amismatched 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, pricesalphaand the register budget classically, builds the encoding overqrom_oracles, andattaches
encoding.report(alpha,qubits,nnz,max_row_nnz,slots,dim,dilated,value_bits). A non-symmetric input or a negative diagonal raises, naming theway out;
dilate=Trueencodes the Hermitian dilation at the data level, where every entrysits off the diagonal.
max_entriesbounds the QROM table;alphamust be finite.Resource accounting
num_ancillaincludes all ancillas, scratch included;num_block_ancilla/num_scratchexpose the breakdown. The encoding provides no
select_observable, soWalk.momentserveseven orders only, and the shared
reflection_observableraises above 20 ancillas rather thanexpanding into
2^num_ancillaPauli terms; Chebyshev data at these widths comes fromwalk_kernelpowers.state_from(ket, num_qubits=None)gains an input-width check: withnum_qubitsgiven, a ketof 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 againstT_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_sparsereports, dilation and guards (fractionaloffsets, 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
would remove the 20-ancilla guard for every wide encoding.
qrom_oraclesbundles forfrom_general_oracleson top of thecontrolled QROM lookup (Add an externally controlled
QROMlookup #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); the sparse-access
input model of Berry, Childs & Kothari (arXiv:1501.01715);
Low & Chuang (arXiv:1610.06546); Gilyén, Su, Low & Wiebe
(arXiv:1806.01838).