Skip to content

Add alias sampling - #48

Merged
wsttiger merged 5 commits into
NVIDIA:mainfrom
wsttiger:features/primitives_alias_sampling
Oct 7, 2026
Merged

wsttiger merged 5 commits into
NVIDIA:mainfrom
wsttiger:features/primitives_alias_sampling

Conversation

@wsttiger

@wsttiger wsttiger commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds AliasSamplingPrepare, an implementation of the PREPARE-with-garbage construction described by Babbush et al. in arXiv:1805.03662, Sec. III.D.

Given nonnegative weights w_k, the primitive prepares a state whose index-register measurement probabilities exactly match an integer-discretized alias table. This construction is a standard state-preparation component for qubitized quantum simulation.

The implementation builds on the arithmetic primitives introduced earlier in this PR stack:

  • QROM loads the (alias, keep) pair associated with each bin.
  • A CDKM comparator compares the keep threshold with a uniform reference register.
  • Controlled swaps replace rejected bins with their aliases.
  • A hand-written adjoint reverses the complete preparation.

This PR is stacked on the reversible-arithmetic PR.

Exact, bounded discretization

The input weights are discretized to mu bits using an integer version of Vose's alias-table algorithm.

The resulting quantum circuit realizes the discretized distribution exactly. Tests compare against exact rational probabilities rather than relying only on floating-point tolerances.

For K bins, the error for each probability is bounded by

1.5 / (K * 2^mu).

The module docstring derives this bound, including why the residual-redistribution cursor cannot wrap around. Zero-weight entries and bins introduced through power-of-two padding receive exactly zero probability.

Resource costs and register layout

The implementation exposes and tests the following resource contract:

  • Toffoli count:

    qrom.toffoli_count + 4 * (mu + 1)

  • Fredkin count:

    num_index

The resource tests validate these formulas against the compiled kernel using cudaq.estimate_resources. They also cross-check the QROM object's reported costs against the compiled QROM circuit.

The garbage-register layout is documented field by field. Its total width is

num_garbage = num_index + 2 * mu + 4 + qrom.num_ladder.

Because qrom.num_ladder depends on the selected QROM implementation, the garbage width automatically reflects the chosen variant.

The internal QROM accepts variant= and block_size= options. The default "auto" mode evaluates the available constructions and selects the lowest-cost option. Tests also force the select_swap variant to verify that both PREPARE and its adjoint are independent of the selected QROM implementation, including clean-ladder reuse during uncomputation.

PREPARE-with-garbage contract

AliasSamplingPrepare guarantees the probability associated with each index value. It does not guarantee clean state preparation of the form

sum_k sqrt(p_k) |k>|0>.

Instead, the prepared state generally has the form

sum_k sqrt(p_k) |k>|G_k>,

where the conditional garbage states |G_k> need not be mutually orthogonal. Consequently, the reduced state of the index register is not necessarily diag(p_k).

This is sufficient for the qubitization identity

<0| PREP^dag SELECT PREP |0> = sum_k p_k U_k.

Because SELECT is diagonal in the index basis, index orthogonality eliminates cross terms regardless of overlaps between the garbage states. The class documentation states this contract explicitly so consumers do not assume clean amplitude preparation or a diagonal reduced index state.

Validation

The test suite includes:

  • Exact index-marginal comparisons against independently generated alias tables.
  • Brute-force enumeration of every (bin, reference) branch and comparison with circuit results.
  • PREPARE-followed-by-adjoint identity checks.
  • Resource-identity checks for both the default and forced QROM variants.
  • Exact checks that zero-weight and padded bins remain zero.
  • An independent adversarial sweep over approximately 4,000 weight tables, including:
    • Dynamic ranges from exp(-18) through exp(4).
    • Irrational weights.
    • Tables containing many zeros.
    • Precision values through mu = 8.

The adversarial sweep found no discretization-bound violations, and padded bins remained exactly zero.

At this branch tip:

  • 26 tests are added by this PR.
  • The complete suite reports 560 passed and 3 skipped.

Deferred work

The following extensions are deliberately left for future changes:

  • Sub-bit-precision variants.
  • Measurement-assisted QROM uncomputation.

Both can be introduced behind the existing QROM interface when a consumer requires the associated resource tradeoff.

Comment thread python/cudaq_algorithms/primitives/_alias_sampling.py Outdated
Comment thread python/cudaq_algorithms/primitives/_alias_sampling.py
Comment thread python/cudaq_algorithms/primitives/_alias_sampling.py Outdated
Comment thread python/cudaq_algorithms/primitives/_alias_sampling.py Outdated
Comment thread python/cudaq_algorithms/primitives/_alias_sampling.py Outdated

@smcardleQ smcardleQ 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.

Minor comments on coherent alias sampling, otherwise looks good to merge. I have not checked the arithmetic primitives yet.

wsttiger added a commit to wsttiger/cudaq-algorithms that referenced this pull request Sep 17, 2026
…M TODO

Per smcardleQ's review of PR NVIDIA#48:
- drop the instance-specific '~0.19' overlap figure from the garbage caveat
- spell out the QROM-word packing convention (alias low bits, keep above)
- rename terse offsets k0/r0/l0/c0 -> keep_off/ref_off/ladder_off/carry_off
- add a TODO to expose the inverse QROM as an explicit qrom_inverse call,
  since measurement-based uncomputation makes the adjoint PREPARE cheaper

No behavior change; alias-sampling suite 26/26.

Signed-off-by: Scott Thornton <wsttiger@gmail.com>
@wsttiger wsttiger added the enhancement New feature or request label Sep 17, 2026
wsttiger added a commit that referenced this pull request Oct 7, 2026
Adds `cudaq_algorithms.primitives` reversible integer arithmetic device
kernels — little-endian, in-place, with hand-written inverses (no
`cudaq.adjoint`, per cuda-quantum#4897/#4898).

**CDKM/Cuccaro ripple-carry** (arXiv:quant-ph/0410184):
- `add_register` / `subtract_register` — `b ← (b ± a) mod 2ⁿ`, **2n−2**
Toffolis (the unused top carry-out cancels; §4.1's 2n−3 not adopted).
- `add_constant` / `subtract_constant` — constant loaded into a
caller-provided `work`+`carry` register, 2n−2.
- `cmp_ge_constant` — `out ^= (x ≥ K)`, restores `x`, **2n−1** Toffolis.
- `cmp_ge_register` / `cmp_gt_register` — `out ^= (a ≥ b)` / `(a > b)`
on equal-width unsigned registers, 2n−1, no n-qubit work register.

**Draper QFT** (arXiv:quant-ph/0008033, no Toffolis — `r1` rotations
only):
- `add_constant_qft` / `subtract_constant_qft`.
- `cmp_ge_constant_qft_shift` / `cmp_ge_constant_qft_shift_adj` — the
compute half **leaves `x` shifted** by −K (for the #48 alias sampler);
the adjoint restores it. `out` is XOR-loaded, with the argument order
matching the CDKM comparators (`out` last).

**Tests (100 passing; 2 version-gated skips):** operation-pinning
harnesses — fixed-operand superposition for the register ops,
basis-input truth tables for the constant ops, correlated-flag
superposition for the comparators (over `out ∈ {0,1}` and `K ∈ 0..2ⁿ`,
for both comparator families) — op-then-inverse identity checks, direct
`qft`/`iqft` and large-`K` coverage, a public-API resolution check, and
`estimate_resources` contracts against the documented per-kernel counts.

**`cudaq.control`:** composition over these kernels was a 0.15.x
specialization regression — correct on 0.14.2 and fixed on 0.16.0 (no
upstream issue filed). A version-gated superposed-control test pins it
on 0.16 and skips on 0.15.x.

**Follow-ups:** ancilla-light constant adder (Häner, arXiv:1611.07995);
Cuccaro §4.1's 2n−3 adder.

---------

Signed-off-by: Scott Thornton <wsttiger@gmail.com>
wsttiger and others added 5 commits October 7, 2026 16:47
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>
…M TODO

Per smcardleQ's review of PR NVIDIA#48:
- drop the instance-specific '~0.19' overlap figure from the garbage caveat
- spell out the QROM-word packing convention (alias low bits, keep above)
- rename terse offsets k0/r0/l0/c0 -> keep_off/ref_off/ladder_off/carry_off
- add a TODO to expose the inverse QROM as an explicit qrom_inverse call,
  since measurement-based uncomputation makes the adjoint PREPARE cheaper

No behavior change; alias-sampling suite 26/26.

Signed-off-by: Scott Thornton <wsttiger@gmail.com>
PR NVIDIA#47 merged with the one-Toffoli comparator saving (cmp_ge_register is now
2n-1, not 2n). The alias-sampling PREPARE consumes that comparator, so its
documented Toffoli cost drops by one: qrom.toffoli_count + 2*mu - 1 (was
+ 2*mu). Updates the sampler docstring and the resource tests; the 2*mu register
widths/offsets are unchanged. Suite: 26 passed on 0.15.1/qpp-cpu.

Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wsttiger
wsttiger force-pushed the features/primitives_alias_sampling branch from 1cc210b to fd53593 Compare October 7, 2026 17:03
@wsttiger

wsttiger commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main now that #47 (reversible arithmetic) has merged. The two arithmetic commits this branch was stacked on are dropped, so the diff is now alias-sampling only (+707/−0 across _alias_sampling.py, its test, and the primitives/__init__ export).

One re-pricing followed from the merge: #47 landed with the one-Toffoli comparator saving (cmp_ge_register is now 2n−1, not 2n), so the PREPARE's documented Toffoli cost is qrom.toffoli_count + 2·mu − 1 — docstring and resource tests updated accordingly (the 2·mu register widths/offsets are unchanged).

Green: 26/26 test_primitives_alias_sampling.py on 0.15.1 / qpp-cpu, and yapf-clean. Ready for review.

@wsttiger
wsttiger merged commit e46eea9 into NVIDIA:main Oct 7, 2026
22 checks passed
@wsttiger
wsttiger deleted the features/primitives_alias_sampling branch October 7, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants