Repository navigation
Conversation
Signed-off-by: Scott Thornton <wsttiger@gmail.com>
The primitives guide described the kernel factories by contrast with QROM.kernel and the pinned QROM variant as a property of "the current LCU implementation". Say what they are: kernel()/adjoint_kernel() are zero-argument methods whose kernels take (index, garbage), and LCUBlockEncoding pins the "select" variant so SELECT reuses the clean ladder. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Signed-off-by: Scott Thornton <wsttiger@gmail.com>
Keep every validation active and always report failures. Preserve student-facing results, resource tables, and approximation errors in default output. Document --verbose in the scripts and example pages. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
kvmto
left a comment
There was a problem hiding this comment.
Merge conflicts and order
This PR, #93 and #94 each create docs/sphinx/guide/primitives.rst and docs/sphinx/examples_rst/primitives.rst, so only the first of the three merges cleanly. I tried the merges locally: #94 and #95 conflict in both files in either order, and #93 conflicts with both. #100 merges cleanly with this PR.
Suggested order: #93, #94, then this one, because AliasSamplingPrepare uses the QROM and cmp_ge_register. This PR then rebases, drops its copy of the page headers (L1-5 of both files) and adds its section below the existing ones, with its own seealso and literalinclude block on the examples page.
After the rebase: K is the number of bins here and the threshold in #94's section of the same page, so one of the two should be renamed. Guide L121 ("for the full script") should name the section. The comparator sentence (L11-12) could link to #94's comparator section.
- Like #94, the script prints the elapsed time on its last line. No example on main does, #100 does not, and it changes on every run. I would drop it.
- #94 and #100 end with a line saying that all checks passed. This script does not, so a default run does not show that the Walk and QSVT checks ran.
- The guide calls the one-norm alpha, as conventions.rst does, but the sampler line printed on L118 says
lambda=. That string comes from the library, so it is a separate small fix.
| [("and_tt", 0, 1, 0), ("z_w", 0), ("and_tt", 0, 1, 0), | ||
| ("sign", )]] | ||
| encoding = LCUBlockEncoding(num_system=2, | ||
| weights=[0.6, 0.4], |
There was a problem hiding this comment.
Can we use weights that do not add up to one? With 0.6 and 0.4, alpha is exactly 1, so dividing by encoding.alpha (L227, L264) changes nothing and the example cannot tell whether the block is H/alpha. I changed both lines to divide by alpha**2 and every check still passed. With 0.9 X_0 - 0.5 CZ_01 (alpha = 1.4, same qubits and runtime) that wrong version fails. The change touches L199, L205, L217, L229 (expect 1.4) and L231 here, and guide L68, L76 and L92-93, where the represented operator becomes 0.875 X_0 - 0.525 CZ_01.
| actual, | ||
| polynomial @ ket, | ||
| verbose=verbose) | ||
| phases = [0.3, -0.4, 0.25] |
There was a problem hiding this comment.
Could this be an odd-degree sequence, for example [0.3, -0.4, 0.25, 0.7]? With three phases the response is an even function of x, so this check gives the same result for H and for -H and cannot see the sign convention the guide describes (T_k(-H_mu/alpha), guide L107). Evaluating the reference model at -x passes today. With the four phases above the example passes as written and the -x version fails.
| bound_check( | ||
| "ideal-versus-table probability error", | ||
| float(np.max(np.abs(table_probabilities - sampler.probabilities))), | ||
| expected_bound + ATOL, | ||
| verbose=verbose) |
There was a problem hiding this comment.
Where are the requested weights compared with what the library built? sampler.probabilities and encoding.discretized_weights come from the library and are then used as the reference. I built the sampler from [1.0, 0.0, 1.2], and separately the encoding from [0.5, 0.5], and every check still passed. Two additions close this: compare sampler.probabilities with [1, 0, sqrt(2), 0] / (1 + sqrt(2)) written out by hand, and check encoding.discretized_weights against the numbers the guide gives (L92-93; [0.875, 0.525] with the weights suggested above).
| Body programs are host-side data compiled at factory time. Here `and_tt` | ||
| computes both system bits into clean work[0]. The leaf-controlled `z_w` | ||
| applies the selected CZ phase, the second AND restores work, and `sign` | ||
| supplies the negative coefficient. The free compute/uncompute gates cancel |
There was a problem hiding this comment.
Could this paragraph say what the operands of and_tt and z_w mean? They are not described anywhere else in the built docs, only in the docstring of the private module _unary_iteration. From running them: ("and_tt", a, b, w) is a Toffoli from system[a] and system[b] onto work[w], and it is not controlled on the index, so the body has to undo it; ("z_w", w) applies Z to work[w] only on this term's branch. Also on L84, and_tt writes the AND of the two system bits into work[0], not "both system bits".
| Weights remain positive; their signs belong in SELECT. The exact one-norm | ||
| `alpha` is preserved, while `discretized_weights` specifies the weights | ||
| actually encoded by the finite tables. At mu=2 this fixture realizes | ||
| `0.625 X_0 - 0.375 CZ_01`. The runnable check builds that operator directly | ||
| from NumPy matrices rather than calibrating it from circuit output. | ||
|
|
||
| The good block of PREPARE/SELECT/UNPREPARE is the represented Hamiltonian | ||
| divided by alpha. After nontrivial SELECT, UNPREPARE generally leaves some | ||
| amplitude outside that block. SELECT work is nevertheless restored. Project | ||
| and reflect using the complete `num_ancilla` register, including index, | ||
| preparation garbage, and SELECT work. |
There was a problem hiding this comment.
The examples page says the reader will learn why the weights stay positive and why the garbage can stay entangled with the index (examples_rst/primitives.rst L19-22), but here both are only stated. Could we add the short reason and the equation? SELECT is sum_l |l><l| (x) U_l and does not touch the garbage, so <0| PREPARE^dagger SELECT PREPARE |0> = sum_l <g_l|g_l> U_l = sum_l p_l U_l, where |g_l> is the garbage state attached to index l. Only the norms of the garbage states enter, so they do not need to be orthogonal, and since the p_l are probabilities a minus sign has to come from U_l. It would also help to spell out "LCU" once and cite the alias sampling paper (Babbush et al., Phys. Rev. X 8, 041015 (2018), arXiv:1805.03662).
Adds a self-verifying alias-sampling tutorial that reconstructs the complete prepared state and exact rational index probabilities from public tables, demonstrates entangled garbage, and verifies complete preparation cleanup. It then composes
0.6 X_0 - 0.4 CZ_01with a work-assisted SELECT body and checks the represented block, clean SELECT work, and shared Walk/QSVT consumers against independent NumPy models. Ideal-versus-discretized error is reported separately from circuit error.Validation on CUDA-Q 0.16.0, Python 3.12.3, qpp-cpu:
python3 -m pytest tests/python/test_examples.py -k alias_sampling_lcu -q: 1 passed.python3 -m sphinx -W --keep-going -b html docs/sphinx docs/sphinx/_build/html: passed.Independent plan 04 PR against main; all documentation/navigation is included. No PauliLCU-demo changes, other proposed tutorials, or new dependencies are required.
Development is hosted in wsttiger/cudaq-algorithms. This replaces #84 after relocating its branch from upstream to the fork. Existing review discussion remains on that linked PR. The latest commit history is preserved unchanged; this relocation does not rerun scientific validation.