Learning NVIDIA CUDA-Q by writing and
verifying real quantum circuits on a local GPU (RTX 4070, via WSL2 + Miniconda).
Every script here was actually run against the nvidia GPU simulator target and
checked against a known-correct answer (brute force, classical ground truth, or
hand-derived math) rather than just assumed to work.
CUDA-Q has no native Windows build - everything runs inside WSL2 Ubuntu, in a
conda base environment:
wsl -d Ubuntu -- bash -lc "source ~/miniconda3/bin/activate base && python your_script.py"
A from-scratch Bell-pair walkthrough: superposition, entanglement, measurement,
verbose wrapper kernels for h/x/mz (since those are recognized by CUDA-Q's
compiler purely by literal spelling, not as real Python objects), a fixed random
seed for reproducibility, and hand-worked comments explaining the state at each
step.
NVIDIA's official CUDA-Q tutorials, rewritten by hand (not copy-pasted) for readability, each one run and verified:
| File | Topic |
|---|---|
01_building_kernels.py |
Kernel composition, GHZ state, adjoint gates |
02_measuring_kernels.py |
Mid-circuit measurement, classical feed-forward |
03_executing_kernels.py |
sample() vs run() vs observe() vs get_state() |
04_operators.py |
Spin operators, a from-scratch mini-VQE |
05_optimizers_gradients.py |
Adam, SPSA, and SciPy COBYLA on a VQE problem |
06_ptsbe.py |
Pre-Trajectory Sampling with Batch Execution (noisy circuits) |
07_quantum_teleportation.py |
Teleporting a qubit via a Bell pair + classical bits |
08_grovers_algorithm.py |
Grover's search, verified against the classic overshoot curve |
09_shors_algorithm.py |
Shor's algorithm factoring 15 = 3x5, via a custom modular-exponentiation gate |
The mirror image of 07_quantum_teleportation.py: instead of moving 1 unknown
qubit with a Bell pair + 2 classical bits, this moves 2 known classical bits
with a Bell pair + 1 physically-transmitted qubit. Verified against all 4
possible message pairs, every shot, on the nvidia simulator.
A standalone deep-dive into 07_quantum_teleportation.py's protocol, built to
answer a series of "wait, but why" questions rather than just show the happy
path: proves the Bell-pair setup must precede Alice's CNOT (swapping the two
breaks teleportation), proves Bob's qubit carries zero information before the
classical bits arrive (no-signaling), proves both classical bits are
structurally required (dropping either is invisible in one measurement basis
but breaks the other), and isolates Bob's correction step into a fully
standalone proof (1 qubit + 2 bits, no message qubit, no sender qubit, no
entanglement in scope at all).
A real answer to "can you sort qubits while they're in superposition?" A
3-element sorting network (compare (e0,e1), (e1,e2), (e0,e1) again) built
entirely from reversible gates - Toffoli to compute "x > y" into an ancilla,
Fredkin (swap.ctrl) to conditionally swap - so the whole sort is one
unitary with no measurement in the middle. Run on a full superposition over
all 64 possible 3-tuple inputs at once; verified across 2000 shots that
every single sampled shot's output is the correctly sorted version of that
same shot's input, and that the comparator's helper ancillas uncompute back
to clean |0> every time.
A made-up display where each pixel is one electron and its color is its
spin direction on the Bloch sphere: (R,G,B) -> (2R-1,2G-1,2B-1)/sqrt(3).
The 8 corners of the RGB cube (every channel fully on or off) land exactly
on the sphere's surface as genuine pure states; anything else - the demo's
3x3 grid includes 50% gray - needs a real mixed state instead, built the
same way as entanglement_propagation.py's mixed marginals: entangle the
pixel qubit with a throwaway ancilla and never measure it. Verified
exactly via get_state() and a numpy partial trace (no sampling noise),
then rendered to an actual PNG. Gray comes out with |Bloch vector| = 0 -
the maximally mixed state - which is a satisfying, literal way to say
"gray is the color with no spin information left."
Runs electron_spin_monitor.py backwards: instead of "here's an RF pulse
setting, what color does it make," start from the desired color and get
back the RF pulse setting that produces it - via measurement collapse, not
by inverting a function. The product framing: a real monitor controller
would have a factory-calibrated table (pulse setting -> resulting spin
direction -> color); given a target image, a classical controller has to
invert that table, worst case an O(N) scan per pixel. This one instead
marks the one candidate pulse code (out of 8, one per RGB-cube corner)
known to produce the desired color and runs Grover's search - the exact
same oracle+diffusion pattern as
nvidia-cudaq-tutorials/08_grovers_algorithm.py, just retargeted at
3-qubit pulse codes - amplifying it to ~94.5% hit probability in 2
iterations (matching the theoretical prediction exactly). Imports
electron_spin_monitor.py to then actually drive the retrieved codes
through the real spin-preparation circuit and render the result, verifying
a full round trip: a 3x3 checkerboard test pattern goes in, gets retrieved
purely via Grover search + collapse, and the driven picture matches the
original exactly.
"Put the two branches in superposition": instead of classically checking
if n is even/odd and picking n/2 or 3n+1, keep n in superposition and
let each branch's own parity qubit route it through the correct half of
the step, coherently, with no measurement until the very end. The
obstacle: the Collatz step isn't reversible on its own (both 8 and 1 map
to 4), so it can't be a bare unitary permutation - same problem as
classical NAND, fixed the same way as everywhere else in this repo: a
reversible lookup-table oracle per step, writing into a fresh register
instead of overwriting in place, so the previous register survives as a
witness. Tracks 4 starting values (2, 3, 5, 6) through 4 steps - 27 qubits
total (2 selector + 5 x 5-bit value registers), right at the ~1GB
single-precision statevector line, the practical ceiling for exact
simulation used throughout this repo. Verified exactly: every shot's FULL
5-register trajectory, not just the final value, matches that shot's
classical Collatz walk precisely, across 2000 shots, zero mismatches.
The reusable ideas from Gu, Kemeny, Yelin and Lukin's Discovering
state-of-the-art quantum circuits with large language models
(arXiv:2610.22367, included here as 2610.22367v1.pdf), rebuilt at toy
scale and verified. The paper's three design principles - route data instead
of controlling arithmetic, uncompute by decoding branch decisions from the
live state instead of keeping a history, and spend a failure budget on rare
inputs - each get a script. The first three are real CUDA-Q circuits
checked exactly; the last two are classical basis-state simulations (the same
way the paper's own evaluator checks circuits), because the point they
verify is that a decoding rule is exact, not an amplitude effect. None of
this reproduces the paper's headline Toffoli/qubit counts - those need its
full-size circuits.
| File | What it demonstrates |
|---|---|
01_conditional_routing.py |
x += y or x += z by selector: controlled-swap + ONE adder instead of two controlled adders - exactly half the Toffolis (28 -> 14) and 3 fewer qubits, verified on all 1024 inputs in superposition |
02_branch_decoding.py |
A branching computation uncomputed without a history stack: each branch bit is read back out of a register that already holds it (path += branch * 2^j) - 7 qubits instead of 9, exact round trip |
03_measurement_based_uncomputation.py |
Clearing a temporary AND with an X-basis measurement + CZ fix instead of a second Toffoli, shown with an interferometer: exact with the fix, 0.625 without it |
04_berlekamp_massey_routing.py |
Reed-Solomon error location over GF(16): the rescaled "replace" update turns both BM branches into one shared update on swapped operands, and the displaced discrepancy is decoded from constant coefficients - checked on ~29k error patterns |
05_divsteps_modular_inversion.py |
Division-free modular inversion whose every branch bit is decoded from the updated state (the paper's Equation 9), verified on all inputs for every prime < 200 and unwound exactly; plus the stopping-time / stack-size failure-budget statistics at 256 bits |
Custom gates built via cudaq.register_operation or plain kernel
composition, each one analyzed for what it actually does mechanically:
| File | What it demonstrates |
|---|---|
maxwells_demon.py |
A coherent, reversible Maxwell's demon contrasted with an irreversible measurement |
quantum_wall.py |
Quantum tunneling through a wall using real WKB transmission math |
fresnel_lens.py |
A 5-zone Fresnel lens built as a genuine interference/recombination circuit, verified with exact get_state() checks |
A fun one: encodes the real branching life-decisions of Jan Dolski (from the video game The Alters) as an entangled decision tree, and samples it to find "the most likely Jan."
A B2B sales funnel modeled as a chain of conditional quantum coin flips
(Lead -> MQL -> SQL -> Opportunity -> Closed-Won), verified against classical
funnel math - plus a second part reusing schrodingers_detective.py's quantum
eraser mechanism as an honest, explicitly-labeled analogy for multi-touch
marketing attribution ambiguity (same underlying data, different "lens,"
different credit assigned) - not a claim that CRM data behaves quantum
mechanically.
A business-facing summary of what this project's demos actually show about quantum computing's real, current capabilities (true randomness, amplitude amplification, correlated-uncertainty modeling) versus hype - source material for a follow-up slide deck.
Each script pairs a genuine CUDA-Q quantum mechanism (superposition, entangling gates, phase, mid-circuit measurement) with a story that makes the mechanism intuitive - and each one is verified against classical ground truth, brute force, or hand-derived math, not just run once and eyeballed.
| File | What it demonstrates |
|---|---|
schrodingers_christmas.py |
A dynamically-sized entangled "graph" (N kids, each correlated with their own gift box) |
schrodingers_sudoku.py |
Solving a genuinely hard Sudoku via classical backtracking + a real quantum coin flip at each ambiguous decision |
schrodingers_dungeon.py |
A from-scratch, honestly-scoped quantum take on Wave Function Collapse procedural generation |
schrodingers_game_of_life.py |
Conway's Game of Life with a quantum-seeded (not pseudo-random) initial board, rendered live with pygame |
schrodingers_detective.py |
A real quantum eraser: measuring in a different basis destroys a correlation without undoing any gate |
schrodingers_epidemic.py |
Infection spread on a real graph with a cycle - showing gate order along a cycle genuinely changes the outcome |
schrodingers_higher_order.py |
2nd- and 3rd-order "uncertainty about uncertainty," verified via nested conditional probabilities |
schrodingers_interval_scheduling.py |
Weighted interval scheduling solved by a Grover oracle built directly from the interval conflict graph (no DP table) |
schrodingers_shipping.py |
The (NP-hard) multi-berth Berth Allocation Problem - and a real, honest demonstration of Grover's oscillation failure mode when the marked fraction exceeds 50% |
schrodingers_tic_tac_toe.py |
Quantum Tic-Tac-Toe's "spooky mark" cycle collapse, shown to be mechanically identical to a GHZ state |
Two more that don't fit the table (they're not story-first, they're a mechanism-first pair building on each other):
LLM attention translated as literally as possible into gates: a query and 3
key vectors get scored by dot product and softmax'd - plain classical math,
same as a real attention layer - but the softmax weights are then
amplitude-encoded directly into a 2-qubit key register's superposition
(sqrt(weight_i) on each branch, so sampling that register up front IS
sampling from the softmax, exactly). Then one value qubit gets a controlled
RY(arccos(value_i)) rotation per key branch, and - crucially - the key
register is never conditioned on. That makes the value qubit's marginal
state a genuine probabilistic mixture of the 3 branches, and <Z> on that
mixture is provably sum_i softmax(query.key_i) * value_i - the literal
attention output - because expectation values are linear over mixtures.
Verified against the classical softmax-attention formula, and against each
branch's conditional <Z | key=i> individually equaling value_i.
A "hash set" needs no values, just membership - built by importing
quantum_attention.py and treating membership itself as the blended
value: +1.0 for members, -1.0 for non-members. A query vector's
"contains?" answer is the attention output: a softmax-weighted blend of
those +-1 scores by similarity to each stored key. Grounded in a real
result (modern continuous Hopfield networks are provably one attention
step - Ramsauer et al., "Hopfield Networks is All You Need"), not a made-up
analogy. Verified on 3 queries: one that confidently matches a member
(score -> +1), one that confidently matches a non-member (score -> -1),
and one deliberately ambiguous between a member and a non-member (score ->
0) - a graceful "not sure" a classical hash set has no way to express.
Same idea as the interval-scheduling series below, applied to LeetCode problems - CPU reference, real GPU (SIMT) parallelism, then a quantum version, each checked against the one before it:
- 5. Longest Palindromic Substring -
cpu/longest_palindromic_substring.py(the DP recurrencedp[i][j] = (s[i]==s[j]) and (j-i<2 or dp[i+1][j-1])),cuda/longest_palindromic_substring_cuda.py(one GPU thread per cell on a DP diagonal - cells on the same diagonal are mutually independent, the diagonal-to-diagonal loop stays sequential since that's a real data dependency),cuda-q/longest_palindromic_substring_cudaq.py(no DP table at all - one decision qubit per candidate palindrome, a NAND-based oracle marks "two selected at once" infeasible, Grover amplification biased toward longer candidates picks the winner). - 167. Two Sum II (sorted input) -
cpu/two_sum_ii.py(two pointers, O(n), exploiting the sortedness - genuinely optimal for this problem),cuda/two_sum_ii_cuda.py(deliberately throws the sortedness away: every candidate pair checked in parallel by its own GPU thread, O(n^2) work traded for zero inter-thread dependencies),cuda-q/two_sum_ii_cudaq.py(also throws the sortedness away - treats the pairs as an unstructured search space and finds the one that sums to target via Grover's search, the same oracle+diffusion pattern asnvidia-cudaq-tutorials/08_grovers_algorithm.py. Honestly scoped: this is O(sqrt(n^2))=O(n) oracle calls, the same order as the two-pointer method, not a speedup - it's here because "find the index satisfying a checkable condition" is exactly Grover's home turf, not because it beats the classical solution for this particular problem).
A single problem solved three times, each version verified against the one before it - CPU, to real GPU (SIMT) parallelism, to quantum:
01_cpu_interval_scheduling.py- plain sequential Python DP (the reference).02_cuda_simt_interval_scheduling.py- a real CUDA C kernel (compiled via NVRTC, through CuPy'sRawKernel) parallelizes the genuinely-parallelizable part (computing each interval's compatibility pointer), while the DP recurrence itself stays sequential since it has a real data dependency.03_cudaq_interval_scheduling.py- the quantum version: no DP table, an entangling gate per conflicting pair of intervals, Grover amplification.