Repository navigation
Add LCUBlockEncoding: alias-sampled PREPARE, unary-iteration SELECT - #55
Merged
Merged
Conversation
wsttiger
marked this pull request as draft
October 8, 2026 16:14
This was referenced Oct 8, 2026
Add a generic linear-combination-of-unitaries block encoding (cudaq_algorithms.LCUBlockEncoding) that block-encodes H / alpha for a Hamiltonian written as a weighted sum of Hermitian involutory unitaries H = sum_k c_k U_k. The caller supplies the term weights |c_k| and a SELECT body per term describing U_k in the unary-iteration gate vocabulary; the encoding assembles U_A = PREPARE-dagger . SELECT . PREPARE from the merged coherent-alias-sampling PREPARE (AliasSamplingPrepare) and unary-iteration SELECT (unary_iteration_kernels) primitives, and alpha is the term one-norm sum_k |c_k|. The encoding satisfies the BlockEncoding protocol, so it drives the existing Walk and QSVT consumers directly. Walk reflections cover the full ancilla register (index + alias garbage + SELECT work) because SELECT leaves the PREPARE garbage entangled with the system; select_observable is available only when the caller supplies the odd-moment observable (valid when the U_k are computational-frame Pauli words). Tests validate against independent dense references over a linear combination of Pauli words, two ways: the circuit against the exactly-encoded matrix (alias-table weights, dense unitaries) at simulator precision, and the exactly-encoded matrix against the ideal Hamiltonian within the derived discretization bound; plus the one-norm, the self-adjoint involution, Walk Chebyshev powers, controlled-apply conventions, a QSVT phase sequence, protocol conformance, and factory-boundary validation. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
wsttiger
force-pushed
the
features/lcu_alias_qrom_select
branch
from
October 12, 2026 23:08
1260727 to
a4e924a
Compare
…ment The walk step was W = R U_A with no-op prepare/unprepare hooks (the zero frame), and `select_observable` could be supplied at construction. In that frame `Walk.moment` measures the observable on (R U_A)^p |0, psi>, where the documented sum_k sign_k |k><k| (x) P_k returns the first term alone (checked at mu=2: order 1 gave -0.855 against a true -0.174), so no odd-moment observable existed. The step is now defined as `PauliLCU` defines it, W = PREPARE R PREPARE-dagger SELECT (SELECT, then reflect about the PREPARE state), with the alias PREPARE and its adjoint as the prepare/unprepare hooks, at the same per-step gate count. The odd-moment observable is then the SELECT operator itself on the index and system registers (identity on the alias garbage): it agrees with the SELECT circuit on every state the walk produces, because those keep the ladder and work scratch clean. `select_observable` derives it from the bodies whenever every term is a Pauli word and raises `NotImplementedError` for bodies in the extended vocabulary; the constructor no longer accepts an observable. `walk_kernel` wraps PREPARE, steps, PREPARE-dagger, so its block is unchanged, as are `apply_kernel` and QSVT. Body ops are validated at the factory boundary (known op, operand count, non-negative integer operands, system targets below `num_system`) with a `ValueError` naming the term and op, instead of an `IndexError` from the work-width scan or a runtime index error inside the kernel; a Pauli-word body whose ops compose to a +-i phase is rejected as non-Hermitian. The work width is derived from the unary-iteration operand tables. Integer parameters (`num_system`, `mu`, `num_select_work`, renamed from `num_work` to match the property) reject fractional values instead of truncating them. Tests: the full `Walk.moments` sequence (orders 0-5) on a one-qubit instance and odd moments on the two-qubit fixture at mu=2; the controlled walk-step kernels, previously untested, pinned directly (roundtrip identity at control |0> and |1>; a single step is the identity at |0> and has block -H/alpha at |1>) and through `Walk.controlled_roundtrip_kernel`; a CZ term built on the SELECT work register, uncontrolled and controlled; the extended-vocabulary hook raise; the Hermiticity, target, arity, operand-type and integer-parameter guards; pinned register geometry. Docs: `LCUBlockEncoding` in the Python API reference and the block-encodings guide (what the caller supplies, the ancilla formula and how `mu` drives it, the moment frame); the protocol docstring and the qubitization guide state the PREPARE-frame contract the odd-moment path relies on; the Walk/QSVT docstrings name both provided encodings. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
This was referenced Oct 21, 2026
smcardleQ
reviewed
Oct 23, 2026
smcardleQ
reviewed
Oct 23, 2026
smcardleQ
approved these changes
Oct 23, 2026
smcardleQ
left a comment
Collaborator
There was a problem hiding this comment.
Approve overall; I think we should revisit the naming convention of 'PauliLCU' vs 'LCUBlockEncoding' (both of which assume Pauli terms). The real distinction is in how PREP and SEL are implemented. I think ideally there would be one 'PauliLCU' class, with arguments that choose how PREP and SEL are implemented.
wsttiger
marked this pull request as ready for review
October 26, 2026 20:31
The kernels addressed the ancilla register through one-letter offsets (`m`, `g`, `w`, `l0`) and inline slice arithmetic, repeated in seven kernels and twice over for the controlled variants. Each kernel now carves named views once — `index`, `garbage`, `ladder`, `work` (and `control`) — and calls PREPARE and SELECT on them. Same gates, same layout: [index | garbage | work], with SELECT's ladder reusing the PREPARE QROM ladder inside the garbage view. The guide's opening paragraph presented `PauliLCU` and `LCUBlockEncoding` as two parallel Pauli encodings. It now states their roles: `PauliLCU` is the front door for a Pauli sum; `LCUBlockEncoding` is the body-programmed primitive whose terms are SELECT bodies in the unary-iteration vocabulary, not necessarily Pauli words, which the double-factorized and sparse-access encodings program directly. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
wsttiger
added a commit
that referenced
this pull request
Oct 27, 2026
> **Stacked on #55** (`LCUBlockEncoding`). Until #55 merges this PR shows its commits > too; it will be rebased onto `main` and squashed to a single commit once #55 lands. > Opened as a draft for parallel review. Adds an opt-in `backend` parameter to `PauliLCU`. The default `backend="exact"` is unchanged — the exact multiplexed-Ry amplitude PREPARE and the address-indexed controlled-Pauli SELECT. `backend="alias"` instead builds the encoding from the alias-sampled PREPARE and unary-iteration (QROM) SELECT primitives through #55's `LCUBlockEncoding`: the Pauli-sum term weights become the alias weights and each term's Pauli word becomes a unary-iteration SELECT body (with a `("sign",)` op for a negative coefficient). `mu` sets the alias keep precision and is accepted with this backend only. This trades the exact PREPARE's rotations for Toffoli-style gates, at the cost of extra ancillas (index + alias garbage + SELECT work: `3m + 2mu + 3` qubits for `m` index bits, so 20 rather than 3 for the five-term test fixture at `mu = 4`) and a rounding of each term weight to `mu` bits (within `alpha · discretization_bound`; `alpha` stays the exact one-norm). **Thin-adapter delegation.** On the alias backend `PauliLCU` captures the weights and bodies, builds an `LCUBlockEncoding`, and delegates `num_ancilla`, every kernel factory (`encode_kernel` / `walk_kernel` / `apply_kernel` and the `BlockEncoding` protocol factories) and `select_observable` to it. The walk therefore runs in the PREPARE frame on both backends, and `Walk.moment` serves odd orders through the derived SELECT observable; the module-level `select_observable(encoding)` dispatches the same way. The exact backend's code paths are untouched. **Boundaries.** - `kernel_args` (the exact backend's flattened kernel arrays) is exact-backend only and raises `NotImplementedError` on the alias backend, which composes primitives rather than the module-level kernels. - Even `Walk.moment` orders use the geometry-only reflection observable, whose `2^num_ancilla` terms are practical only at small `mu` on the alias backend. - Input guards raise `ValueError`: an unknown backend name, `mu` given with the exact backend, or a fractional / bool / sub-1 `mu`. **Validation.** `tests/python/test_pauli_lcu_alias.py` (19 tests), green on CI (cudaq 0.16), exercise the alias backend through the `PauliLCU` API against independent dense references: the circuit vs the exactly-encoded matrix at simulator precision (1e-10) and vs the ideal Hamiltonian within the discretization bound; the one-norm and pinned ancilla geometry; single-term, single-negative-term, identity-only, negative-identity and identity-excluded Hamiltonians; `SpinOperator` input; `state_prep` injection; walk Chebyshev powers; the full `Walk.moments` sequence (orders 0–5) and multi-qubit-word odd moments; plain and controlled consumer roundtrips; QSVT sequences with forward and adjoint walk directions; protocol conformance with every factory delegating by identity; and the backend / `mu` guards. The exact-backend suite and the consumer suites are unchanged. **Docs.** The block-encodings guide gains a construction line and a short section on the alias backend; the qubitization guide notes that `select_observable` serves both backends. ## Files - `python/cudaq_algorithms/pauli_lcu.py` (opt-in backend) - `tests/python/test_pauli_lcu_alias.py` (new) - `docs/sphinx/guide/block_encodings.rst`, `docs/sphinx/guide/qubitization_qsvt.rst` --------- Signed-off-by: Scott Thornton <wsttiger@gmail.com>
wsttiger
added a commit
to wsttiger/cudaq-algorithms
that referenced
this pull request
Oct 30, 2026
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
cudaq_algorithms.LCUBlockEncoding, a generic linear-combination-of-unitariesblock encoding of
H / alphafor a Hamiltonian written as a weighted sum of Hermitianinvolutory unitaries
H = sum_k c_k U_k. It assembles the merged primitives — coherentalias sampling (
AliasSamplingPrepare, #48) as PREPARE and unary iteration(
unary_iteration_kernels, #40) as SELECT — into the standardU_A = PREPARE† · SELECT · PREPAREstructure.alphais the term one-normsum_k |c_k|.What the caller supplies. The term weights
|c_k|and one SELECT body per termdescribing
U_kin the unary-iteration gate vocabulary (("x"|"y"|"z", target),("sign",)for a negativec_k, and the free / AND-ladder work ops for multi-controlledterm unitaries). This keeps the primitive agnostic to how a problem decomposes into terms.
Every body op is validated at construction (known op, operand count, non-negative integer
operands, system targets below
num_system, and a Hermitian product for Pauli-wordbodies) with a
ValueErrornaming the term and op; integer parameters are not silentlytruncated.
What it provides.
BlockEncodingprotocol, so it drives the existingWalkandQSVTconsumers unchanged;
encode_kernel/walk_kernelconvenience factories withoptional
state_prepinjection.PauliLCU's does(
W = PREPARE · R · PREPARE† · SELECT, realprepare_kernel/unprepare_kernel), soselect_observable— the SELECT operatorsum_k sign_k |k><k|_index ⊗ P_k, identity onthe alias garbage — is derived from the bodies whenever every term is a Pauli word, and
Walk.momentserves odd orders. Bodies in the extended vocabulary raiseNotImplementedErrorthere (even orders remain available).alpha,num_system,num_ancilla(= index + alias garbage + SELECT work), plusnum_index/num_garbage/num_select_work, the termweights,discretized_weights,discretization_bound, and the underlyingpreparation.Contracts / boundaries.
U_kmust be a Hermitian involution; SELECT is then a Hermitian involution andU_Ais exactly self-adjoint on the all-|0…0>ancilla subspace (the domain everyconsumer supplies).
the alias-PREPARE garbage entangled with the system, so an index-only reflection would
misidentify the block.
"select"QROM variant so SELECT can reuse its cleanQROM ladder; PREPARE is never placed under
cudaq.control(only SELECT and thereflection carry the external control).
index(m) + garbage(2m + 2mu + 2) + workqubits, somudrives the width: the five-term, two-qubit test fixture is 22 qubits at
mu = 4and30 at the default
mu = 8, sized for resource estimation rather than for the CIsimulators.
Validation.
tests/python/test_lcu_block_encoding.py(21 tests), green on CI(cudaq 0.16) — exercised through a linear combination of Pauli words against independent
dense references, two ways: the circuit against the exactly-encoded matrix (alias-table
weights, dense unitaries) at simulator precision (1e-10), and the exactly-encoded matrix
against the ideal Hamiltonian within the derived discretization bound. Plus the one-norm,
the self-adjoint involution, Walk Chebyshev powers (T1, T2 of −H/alpha), the full
Walk.momentssequence (orders 0–5) and multi-qubit-word odd moments on small instances,the controlled-apply and controlled walk-step control conventions (directly and through
Walk.controlled_roundtrip_kernel), a body on the SELECT work register (CZ), a QSVT phasesequence against the signal model, protocol conformance, and the factory-boundary guards.
Docs.
LCUBlockEncodingin the Python API reference and the block-encodings guide(what the caller supplies, the ancilla formula, the moment frame); the protocol and
qubitization guide state the PREPARE-frame contract the odd-moment path relies on.
Follow-up (separate PR).
PauliLCUopt-in params to route its PREPARE/SELECT throughthis primitive (alias PREPARE + QROM/unary SELECT alongside today's exact
multiplexed-Ry + controlled-Pauli path); its term unitaries are Pauli words, so
select_observable/Walk.momentcarry over by delegation.