fastqml 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastqml-0.2.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric A. F. Reinhardt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fastqml-0.2.1/PKG-INFO ADDED
@@ -0,0 +1,428 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastqml
3
+ Version: 0.2.1
4
+ Summary: Fast PyTorch quantum-circuit primitives via Kronecker-product layer fusion and edge coloring
5
+ Author: Eric A. F. Reinhardt
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Eric A. F. Reinhardt
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/ereinha/fastQML
29
+ Project-URL: Repository, https://github.com/ereinha/fastQML
30
+ Project-URL: Issues, https://github.com/ereinha/fastQML/issues
31
+ Keywords: quantum-computing,quantum-machine-learning,pytorch,gate-fusion,cuda-graphs
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Topic :: Scientific/Engineering :: Physics
40
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: torch<3.0,>=2.6
45
+ Requires-Dist: numpy<3.0,>=1.24
46
+ Provides-Extra: test
47
+ Requires-Dist: pennylane<1.0,>=0.40; extra == "test"
48
+ Requires-Dist: pennylane-lightning<1.0,>=0.40; extra == "test"
49
+ Dynamic: license-file
50
+
51
+ # fastQML
52
+
53
+ A small PyTorch quantum-circuit simulation toolkit built around two ideas:
54
+
55
+ 1. **Layer-fusion via Kronecker product + matmul.** When a circuit applies a single-qubit gate to many qubits in parallel, the combined operator is the Kronecker product of those gates. Build the product once, apply via a single matmul on the flat state — replaces N strided per-qubit kernels with one Tensor-Core matmul.
56
+
57
+ 2. **Disjoint multi-qubit-layer fusion via edge coloring.** A layer that applies 2- or 3-qubit gates to many tuples is partitioned into "colors" — each color is a set of tuples that act on pairwise-disjoint qubits AND commute with the gates in later colors. Each color becomes one matmul.
58
+
59
+ Both shapes collapse to the same kernel: `(B, 2^nq) @ (2^nq, 2^nq)`. On a modern GPU this saturates Tensor Cores, and the whole forward+backward fits cleanly into a CUDA graph.
60
+
61
+ The result: a per-sample throughput **~36× faster than PennyLane lightning.qubit** (C++ backend) for the same circuit at `n_qubits=8, n_layers=3` — see benchmark below.
62
+
63
+ ---
64
+
65
+ ## What's in here
66
+
67
+ | File | Purpose |
68
+ |---|---|
69
+ | `fastqml/gate_fusion.py` | Gate matrix builders (RX/RY/RZ/Rot/PhaseShift, X/Y/Z/H/S/T, CRX/CRY/CRZ/CPhase/IsingXX/YY/ZZ, CNOT/CY/CZ/SWAP/ISWAP, Toffoli, CSWAP) + fused layer-apply functions + edge coloring with commutation predicates |
70
+ | `fastqml/torch_sim_real.py` | Raw per-gate kernels in `(B, 2, 2, ..., 2)` state layout, plus state-utility helpers, block-encoding subroutines, and the Hadamard-test overlap primitive |
71
+ | `examples/minimal_cuda_graph_model.py` | A small variational-quantum-circuit-style `nn.Module`, plus benchmarks for CPU eager / CPU `torch.compile` / CUDA eager / CUDA graph / PennyLane `lightning.qubit` |
72
+ | `fastqml/register_ops.py` | Multiplexed (address-controlled) rotations for single large registers — angle indexed by computational-basis address bits — with optional Triton kernels (autograd included), pure-torch fallback, and dense composition helpers (`compose_dense` / `apply_dense`) |
73
+ | `fastqml/circulant.py` | Translation-equivariant circulant layers for lattice registers: momentum-block operators `M = F' (sum_k U(k)) F` as dense matrices or FFT-style applies |
74
+ | `examples/register_ops_bench.py` | Benchmarks for the multiplexer (Triton / eager / per-batch chain), circulant applies, and dense composition |
75
+ | `test_register_ops.py` | 15 numerical tests: multiplexers vs explicit dense references at fp64, Triton forward/backward vs eager autograd, circulant unitarity / dense-vs-k-space equality / translation equivariance |
76
+ | `test_pennylane_equivalence.py` | 32 numerical equivalence tests against PennyLane `default.qubit` covering every gate, plus multi-pair colors, chain ordering, and per-batch chains. Every test must pass at fp64 noise floor (max diff = 0 or ~1e-17) |
77
+
78
+ ### Which module should I use?
79
+
80
+ In almost all training / inference workloads, **use `fastqml.gate_fusion` first**. It's the fast path — every common gate is exposed as a layer-fusing builder + apply pair, and the kron-product + matmul pattern is what makes the CUDA-graph + per-sample throughput numbers in the benchmark below work. If you're building an `nn.Module`, building a VQC, or anything that runs many gates on many qubits per forward, this is the right tool.
81
+
82
+ **Reach for `fastqml.torch_sim_real`** when one of these applies:
83
+
84
+ - **One-off single-gate ops.** Building a full `(nq, 2, 2)` gate stack to use `apply_su2_layer` for a single non-identity gate is wasteful. `apply_ry(re, im, theta, qubit, nq)` etc. apply one gate directly via `movedim` + stack ops — fewer launches when the work isn't layer-shaped.
85
+ - **State / amplitude / probability conversion.** `_amp_to_state_real(amp, nq)` initializes the `(B, 2, 2, ..., 2)` state from an amplitude vector. `_state_to_probs_real(re, im)` flattens `|ψ|²` to a probability vector. `_renormalize_real(re, im, nq)` re-normalizes after non-unitary operations. `gate_fusion` deliberately doesn't ship these — they aren't gates.
86
+ - **Block-encoded operators.** `apply_block_encoding_real(amp, ry_layers, cry_anchor, cry_data, ry_final, nq)` and `apply_block_encoding_complex(...)` post-select an ancilla qubit to encode a learnable sub-norm-1 linear operator. Useful for quantum LCU-style circuits or learned non-unitary maps.
87
+ - **Hadamard test for overlaps.** `hadamard_test_overlap(re_Q, im_Q, re_K, im_K, nq)` returns `Re<ψ_Q | ψ_K>` via the standard 1-ancilla Hadamard-test circuit — useful whenever you need a differentiable estimate of an inner product between two state vectors.
88
+
89
+ If you're just doing standard parametric circuits with rotations and entanglers, you only need `fastqml.gate_fusion`. For more exotic QML primitives (LCU, overlap-based losses, hybrid amplitude/probability workflows), you'll want both.
90
+
91
+ **Reach for `fastqml.register_ops` / `fastqml.circulant`** when your model is a *single large register* rather than many small ones — e.g. a lattice register where sites are basis states. The regime rule: many small registers are launch-bound (gate_fusion + CUDA graphs is the fast path); one big register is bandwidth-bound, and the fast path is minimizing passes over the state. `register_ops` provides the multiplexed rotation (angle indexed by basis-address bits — the uniformly controlled rotation of circuit synthesis), which per-batch gate primitives cannot express without a batch-fold that costs ~150x (see benchmark). `circulant` provides translation-equivariant lattice layers; combined with `compose_dense` for parameters that are static across many evaluations, whole gate cascades collapse to one matmul per block.
92
+
93
+ ---
94
+
95
+ ## Installation
96
+
97
+ ### Python packages
98
+
99
+ For NVIDIA GPUs (Windows/Linux), pin a CUDA-matched PyTorch wheel first:
100
+
101
+ ```bash
102
+ pip install torch --index-url https://download.pytorch.org/whl/cu128
103
+ ```
104
+
105
+ (Use `cu124` / `cu121` / etc. for older CUDA toolkits, or skip this and let `pip install fastqml` pull the default wheel for CPU / Apple silicon.)
106
+
107
+ Then install fastqml:
108
+
109
+ ```bash
110
+ pip install fastqml
111
+ # or directly from GitHub for the latest:
112
+ pip install git+https://github.com/ereinha/fastQML.git
113
+ ```
114
+
115
+ For editable / contributor install:
116
+
117
+ ```bash
118
+ git clone https://github.com/ereinha/fastQML.git && cd fastQML
119
+ pip install -e . # core
120
+ pip install -e .[test] # also install pennylane + pennylane-lightning for the equivalence tests
121
+ ```
122
+
123
+ The core toolkit (`fastqml.gate_fusion` + `fastqml.torch_sim_real`) needs only torch + numpy. PennyLane is an optional dep used for `test_pennylane_equivalence.py` and the PennyLane row in the example benchmark.
124
+
125
+ > **Note:** Triton is *not* a dependency. The gate_fusion fast path uses CUDA graphs (`torch.cuda.make_graphed_callables`), which capture raw CUDA kernel launches and do no codegen. `fastqml.register_ops` will use Triton kernels for its multiplexed rotations when Triton is importable (~4x over its pure-torch fallback) and falls back to pure torch otherwise. On Linux/macOS Triton ships transitively with `torch`; on Windows `pip install triton-windows`.
126
+
127
+ ### C++ compiler (only needed for `torch.compile` on CPU)
128
+
129
+ CUDA users don't need any compiler — `torch.cuda.make_graphed_callables` works out of the box.
130
+
131
+ For CPU `torch.compile`, Inductor calls a C++ compiler to emit fused kernels.
132
+
133
+ **Linux** — install GCC if you don't have it:
134
+
135
+ ```bash
136
+ sudo apt install build-essential # Debian/Ubuntu
137
+ sudo dnf groupinstall "Development Tools" # Fedora/RHEL
138
+ ```
139
+
140
+ **macOS** — install the Xcode Command Line Tools:
141
+
142
+ ```bash
143
+ xcode-select --install
144
+ ```
145
+
146
+ This puts `clang`/`clang++` on PATH; PyTorch picks them up automatically.
147
+
148
+ **Windows** — install Visual Studio 2022 Build Tools' C++ workload:
149
+
150
+ ```powershell
151
+ winget install Microsoft.VisualStudio.2022.BuildTools --override `
152
+ "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.22621 --includeRecommended"
153
+ ```
154
+
155
+ (~3 GB download.) After install, launch your shell from a **"Developer Command Prompt for VS 2022"** so `cl.exe` is on PATH, OR call `vcvars64.bat` first:
156
+
157
+ ```powershell
158
+ cmd /c '"%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && python examples\minimal_cuda_graph_model.py'
159
+ ```
160
+
161
+ If `cl.exe` isn't found at run-time the example's CPU compile path gracefully skips with the error and continues with the eager + CUDA paths.
162
+
163
+ ---
164
+
165
+ ## Quick example
166
+
167
+ ### Apply a single layer of mixed single-qubit gates
168
+
169
+ ```python
170
+ import torch
171
+ from fastqml import gate_fusion as gf
172
+
173
+ device, dtype = torch.device("cuda"), torch.float32
174
+ nq, B = 6, 32
175
+
176
+ state_re = torch.rand(B, *([2] * nq), device=device, dtype=dtype)
177
+ state_im = torch.zeros_like(state_re)
178
+
179
+ ry_angles = torch.randn(nq, device=device, dtype=dtype)
180
+ rz_angles = torch.randn(nq, device=device, dtype=dtype)
181
+
182
+ G_re, G_im = gf.build_ry_rz_gates(ry_angles, rz_angles)
183
+ state_re, state_im = gf.apply_su2_layer(state_re, state_im, G_re, G_im)
184
+ ```
185
+
186
+ That's one (B, 64) @ (64, 64) matmul replacing 12 strided per-qubit ops (6 RY + 6 RZ).
187
+
188
+ ### Apply a layer of CRY gates with edge coloring
189
+
190
+ ```python
191
+ edges = [(0, 1), (2, 3), (4, 5), (1, 2)]
192
+ angles = torch.randn(len(edges), device=device, dtype=dtype)
193
+
194
+ plans = gf.compute_two_qubit_plans(edges, nq, device, dtype, commute_fn=gf.cry_commute)
195
+ for plan in plans:
196
+ color_angles = angles.index_select(0, plan['k_idx'])
197
+ G_re, G_im = gf.build_cry_4x4(color_angles)
198
+ state_re, state_im = gf.apply_disjoint_two_qubit_layer(state_re, state_im, plan, G_re, G_im)
199
+ ```
200
+
201
+ For these 4 edges the coloring produces 2 colors: `[(0,1), (2,3), (4,5)]` (3 disjoint pairs in one matmul) and `[(1,2)]` (must be its own color — non-commuting with the first via target/control overlap).
202
+
203
+ ### Apply a Toffoli + a CSWAP
204
+
205
+ ```python
206
+ plans = gf.compute_three_qubit_plans([(0, 1, 2)], nq, device, dtype)
207
+ G_re, G_im = gf.build_toffoli_8x8(1, device, dtype)
208
+ state_re, state_im = gf.apply_disjoint_three_qubit_layer(state_re, state_im, plans[0], G_re, G_im)
209
+ ```
210
+
211
+ The full list of builders and apply functions is in `fastqml.gate_fusion`. They all return `(re, im)` real/imaginary pairs and operate on state tensors with shape `(B, 2, 2, ..., 2)` where axis 1 is qubit 0 (MSB of the flat basis index).
212
+
213
+ ### Use it inside an `nn.Module` for training
214
+
215
+ See `examples/minimal_cuda_graph_model.py` for the full pattern. The key rules:
216
+
217
+ 1. **Build constant gates in `__init__`** and stash via `register_buffer`. Never build constants in `forward()` — Python-list → tensor allocates on the host and breaks CUDA graph capture.
218
+ 2. **Compute plans in `__init__`** (`compute_two_qubit_plans`, `compute_three_qubit_plans`) so the per-color `k_idx` and permutations are precomputed.
219
+ 3. **Parametric builders go in `forward()`** — `build_ry_rz_gates(...)` etc. are pure GPU-tensor ops, autograd flows through them, and they're safe under capture.
220
+ 4. **Wrap the model once with `torch.cuda.make_graphed_callables`** (CUDA) or `torch.compile` (CPU). The toolkit is graph-safe; the wrapping is one line in your training script.
221
+
222
+ ---
223
+
224
+ ## Benchmarks
225
+
226
+ The example file runs four variants of the same `TinyVQC(n_qubits=8, n_layers=3)` model on a batch of 32 states, against PennyLane `lightning.qubit` running the same circuit topology.
227
+
228
+ System: Windows 11, NVIDIA RTX 5090, CPU compile via MSVC 14.44.
229
+
230
+ ```
231
+ === CPU (16 threads) ===
232
+ forward (no_grad):
233
+ eager: 28.95 ms/call
234
+ torch.compile: 6.79 ms/call speedup: 4.26x (compile cost: 58s)
235
+ training step (fwd + bwd + opt):
236
+ eager: 73.58 ms/step
237
+ torch.compile: 15.37 ms/step speedup: 4.79x (compile cost: 107s)
238
+
239
+ === CUDA (RTX 5090) ===
240
+ forward (no_grad):
241
+ eager: 18.43 ms/call
242
+ cuda-graph: 1.46 ms/call speedup: 12.64x
243
+ training step (fwd + bwd + opt):
244
+ eager: 51.55 ms/step
245
+ cuda-graph: 4.54 ms/step speedup: 11.35x
246
+
247
+ === PennyLane lightning.qubit (C++ backend) ===
248
+ per-circuit eval (B=1): 1.63 ms/call
249
+ batch of 32 sequential: 52.33 ms (1.64 ms/sample)
250
+
251
+ === TorchQuantum 0.3.0 (CUDA eager, batched) ===
252
+ forward (no_grad): 13.22 ms/call (0.41 ms/sample)
253
+ training step (fwd+bwd+opt): 37.44 ms/step
254
+ torch.compile: fails (InductorError: complex-dtype states)
255
+ cuda-graph capture: fails (gate matrices built dynamically per call)
256
+ ```
257
+
258
+ The TorchQuantum run is the identical circuit (cross-checked to 2.5e-10 on output probabilities, `examples/torchquantum_bench.py`). Its batched eager forward beats our eager path; the gap opens at the fast paths, neither of which TorchQuantum can enter — CUDA-graph capture fails on its dynamic gate construction, and `torch.compile` fails on its complex-dtype states (the two constraints the compile-safe `(re, im)` design of this toolkit exists to satisfy).
259
+
260
+ ### Per-sample throughput
261
+
262
+ Our forward times divided by `B=32` for direct comparison with PennyLane's per-sample number:
263
+
264
+ | Backend | ms/sample | vs PennyLane |
265
+ |---|---:|---:|
266
+ | PennyLane `lightning.qubit` (C++) | 1.64 | 1.0× |
267
+ | toolkit CPU eager | 0.91 | 1.8× faster |
268
+ | toolkit CPU `torch.compile` | 0.21 | **7.7× faster** |
269
+ | TorchQuantum (CUDA eager, batched) | 0.41 | 4.0× faster |
270
+ | toolkit CUDA eager | 0.58 | 2.8× faster |
271
+ | toolkit CUDA graphs | 0.046 | **35.6× faster** |
272
+
273
+ The gap comes from gate_fusion processing `B` samples as one batched matmul; PennyLane's lightning evaluates one circuit per sample.
274
+
275
+ ### Register-ops benchmark
276
+
277
+ `examples/register_ops_bench.py`, RTX 5090, fp32, TF32 off.
278
+
279
+ Multiplexed RyRz (`B=8192`, `nq=8`, 64 select values), three routes to the identical op:
280
+
281
+ | Route | ms/call |
282
+ |---|---:|
283
+ | pure-torch fallback + `torch.compile` | 0.024 |
284
+ | `register_ops` Triton (in-place) | 0.109 |
285
+ | `register_ops` pure-torch fallback | 0.375 |
286
+ | batch-fold + `apply_per_batch_su2_chain` | 10.80 |
287
+
288
+ In an eager loop, the compiled fallback is fastest (Inductor fuses trig + rotation + stacking into fewer kernels than the hand-written Triton path launches). Inside an outer CUDA graph the ordering flips: capture removes launch overhead for every route, and the in-place Triton kernel's single zero-copy pass wins. Compile and graphs are mutually exclusive here (see Limitations), so pick per context.
289
+
290
+ The per-batch-chain route is the only way to express address-indexed angles with gate_fusion primitives; the ~160x gap is permute/copy traffic plus half-a-million tiny batched matmuls, and is the reason the multiplexer exists as its own kernel.
291
+
292
+ Circulant block apply (`A=4` ancilla channels):
293
+
294
+ | Lattice | dim | B | dense (4 GEMMs) | k-space | k-space + `torch.compile` |
295
+ |---|---:|---:|---:|---:|---:|
296
+ | 8x8 | 256 | 8192 | 0.142 ms | 0.831 ms | 0.460 ms |
297
+ | 16x16 | 1024 | 1024 | 0.238 ms | 0.858 ms | 0.358 ms |
298
+
299
+ Dense wins at both sizes on this hardware — the k-space route has ~10x fewer FLOPs but loses to launch/bandwidth overhead at these dims; it is kept for larger registers.
300
+
301
+ `compose_dense` (8 fixed layers, `nq=8`, `B=8192`): sequential applies 0.874 ms vs composed-once single apply 0.140 ms (**6.2x**) — when parameters are static across many evaluations, compose once and amortize.
302
+
303
+ ### Reproducing
304
+
305
+ ```bash
306
+ .venv/Scripts/python.exe examples/minimal_cuda_graph_model.py
307
+ .venv/Scripts/python.exe examples/register_ops_bench.py
308
+ .venv/Scripts/python.exe examples/torchquantum_bench.py
309
+ ```
310
+
311
+ The TorchQuantum comparison needs `pip install git+https://github.com/mit-han-lab/torchquantum.git --no-deps` plus `opt_einsum qiskit qiskit-ibm-runtime qiskit-aer torchpack torchdiffeq pathos` (the PyPI torchquantum release is pinned to removed qiskit APIs).
312
+
313
+ On Linux/macOS, `torch.compile`'s CPU backend picks up `gcc`/`clang` automatically. On Windows, run from a Visual Studio Developer Command Prompt (or call `vcvars64.bat`) so `cl.exe` is on `PATH`; otherwise the CPU compile path gracefully skips with the error.
314
+
315
+ ---
316
+
317
+ ## Equivalence tests
318
+
319
+ ```bash
320
+ .venv/Scripts/python.exe test_pennylane_equivalence.py
321
+ ```
322
+
323
+ Output (32 tests, all at fp64 noise floor):
324
+
325
+ ```
326
+ === Single-qubit gates ===
327
+ OK single-qubit RY/RZ/RX/PhaseShift/H/X/Y/Z/S/T max |diff| = 0 or ~1e-17
328
+ OK Rot(phi,theta,omega) per qubit layer max |diff| = 1.5e-16
329
+ OK apply_su2_layer (RY then RZ per qubit) max |diff| = 1.2e-16
330
+
331
+ === 2-qubit gates (single) ===
332
+ OK CRY / CRX / CRZ / CPhase max |diff| = 0
333
+ OK IsingZZ / IsingXX / IsingYY max |diff| = 0
334
+ OK CNOT / CY / CZ / SWAP / ISWAP max |diff| = 0
335
+
336
+ === 2-qubit gates (multi-pair colors) ===
337
+ OK 3 disjoint CRYs in one color max |diff| = 2.8e-17
338
+ OK chain CRY (3 edges, 3 colors) max |diff| = 0
339
+ OK 5 overlapping CPhase (3 colors via diag commute) max |diff| = 6.2e-17
340
+
341
+ === 3-qubit gates ===
342
+ OK Toffoli, CSWAP, 2 disjoint Toffolis, chain Toffoli max |diff| = 0
343
+
344
+ === Per-batch ===
345
+ OK apply_per_batch_su2_chain (B=4, nq=5): all batches match
346
+ ```
347
+
348
+ ### Register-ops tests
349
+
350
+ ```bash
351
+ .venv/Scripts/python.exe test_register_ops.py
352
+ ```
353
+
354
+ Output (15 tests; multiplexers checked against explicit fp64 dense references, Triton backward against eager autograd):
355
+
356
+ ```
357
+ === register_ops: multiplexed rotations ===
358
+ OK mux_ry_rz vs dense reference (nq=5, t=1, S=4) max |diff| = 2.4e-07
359
+ OK mux_ry_rz in-place == out-of-place max |diff| = 2.4e-07
360
+ OK mux_ry vs dense reference (nq=5, t=1, S=4) max |diff| = 1.6e-07
361
+ OK mux_ry in-place == out-of-place max |diff| = 2.4e-07
362
+ OK mux_ry_rz vs dense reference (nq=8, t=0, S=64) max |diff| = 3.3e-07
363
+ OK mux_ry_rz in-place == out-of-place max |diff| = 3.6e-07
364
+ OK triton fwd == eager fwd (nq=8, S=64) max |diff| = 4.8e-07
365
+ OK mux_ry_rz autograd d_re/d_im/d_ty/d_tz (triton bwd vs eager autograd) max |diff| = 7.2e-07
366
+ === register_ops: dense composition ===
367
+ OK compose_dense == sequential apply_dense max |diff| = 7.2e-07
368
+ === circulant ===
369
+ OK circulant_dense unitary for unitary U(k) (L=4x4, A=4) max |diff| = 3.2e-08
370
+ OK circulant dense apply == k-space apply max |diff| = 7.2e-07
371
+ OK circulant commutes with lattice translations max |diff| = 7.2e-07
372
+
373
+ 15/15 passed
374
+ ```
375
+
376
+ ---
377
+
378
+ ## Conventions
379
+
380
+ **State layout.** A state on `nq` qubits is a pair `(re, im)` of float tensors with shape `(B, 2, 2, ..., 2)` — one batch axis and `nq` qubit axes. Axis 1 is qubit 0 (the MSB of the flat basis index), matching PennyLane's default `qml.state()` ordering.
381
+
382
+ **Gate matrices.** All gate builders return `(G_re, G_im)`. Single-qubit shape: `(nq, 2, 2)` for layer apply, `(B, nq, 2, 2)` for per-batch chains. Two-qubit: `(n_pairs, 4, 4)`. Three-qubit: `(n_triples, 8, 8)`.
383
+
384
+ **Two-qubit basis.** `(c, t)` with `c` as MSB, so the 4-state basis order is `|c=0,t=0>, |c=0,t=1>, |c=1,t=0>, |c=1,t=1>`. For CRY/CRX/CRZ the matrix is identity on `|c=0>...`, the rotation on `|c=1>...`.
385
+
386
+ **Three-qubit basis.** `(q1, q2, q3)` with `q1` as MSB; the 8-state basis order matches.
387
+
388
+ **register_ops layout.** Multiplexer state is flat `(B, 2^nq)` with qubit 0 as MSB. `apply_mux_ry(re, im, angles, target, nq, select_bits)`: angles have shape `(B, 2^select_bits)` and are indexed by the lowest `select_bits` bits of the basis address; the select bits must lie below the target qubit (other layouts reduce to this by a basis permutation). In-place variants (`apply_mux_ry_`, `apply_mux_ry_rz_`) skip autograd for sampling-style loops. `circulant` register index is `a * N + site` with sites row-major on the `L1 x L2` torus; momentum blocks `Uk` have shape `(L1, L2, A, A)`.
389
+
390
+ **Commutation predicates.** Used by the edge-coloring algorithms to decide when reordering across colors is safe.
391
+ - `cry_commute(e1, e2)` — CRX/CRY/CRZ family: don't commute when one's target is the other's control
392
+ - `diagonal_commute(e1, e2)` — CZ/CPhase/IsingZZ: always commute (just need disjoint qubits within a color)
393
+ - `symmetric_commute(e1, e2)` — SWAP/ISWAP/IsingXX/YY: don't commute when sharing any qubit
394
+ - `toffoli_commute(t1, t2)` — Toffoli: target of one ↔ any control of the other
395
+ - `cswap_commute(t1, t2)` — share any qubit ⇒ don't commute
396
+
397
+ Pick the right predicate when calling `compute_two_qubit_plans(...)` / `compute_three_qubit_plans(...)` for your gate.
398
+
399
+ ---
400
+
401
+ ## Citation
402
+
403
+ If you use fastQML in published research, please cite it:
404
+
405
+ ```bibtex
406
+ @software{Reinhardt_fastQML_2026,
407
+ author = {Reinhardt, Eric A. F.},
408
+ title = {{fastQML}: Fast {PyTorch} quantum-circuit primitives via {K}ronecker-product layer fusion},
409
+ year = {2026},
410
+ version = {0.2.1},
411
+ url = {https://github.com/ereinha/fastQML}
412
+ }
413
+ ```
414
+
415
+ The multiplexed rotation is the uniformly controlled rotation of quantum-circuit synthesis (Mottonen, Vartiainen, Bergholm & Salomaa, PRL 93, 130502, 2004; Shende, Bullock & Markov, IEEE TCAD 25, 1000, 2006). The optional Triton kernels use Triton (Tillet, Kung & Cox, MAPL 2019). Gate fusion in statevector simulation follows the lineage of qHiPSTER (Smelyanskiy et al., arXiv:1601.07195) and Haner & Steiger (SC'17, arXiv:1704.01127); the equivalence tests and benchmarks use PennyLane (Bergholm et al., arXiv:1811.04968) and TorchQuantum (Wang et al., HPCA 2022, arXiv:2107.10845).
416
+
417
+ A machine-readable [`CITATION.cff`](CITATION.cff) is also included — GitHub renders a "Cite this repository" button on the repo page that copies the citation in BibTeX, APA, or other formats.
418
+
419
+ ## License
420
+
421
+ MIT — see [`LICENSE`](LICENSE).
422
+
423
+ ## Limitations
424
+
425
+ - **Per-batch unitaries don't fuse.** When a layer's gate angles depend on per-batch quantities (e.g. data re-uploading), the full `(B, 2^nq, 2^nq)` per-batch unitary is too large to materialize for `nq=10` (256 MB at B=64, fp32). `apply_per_batch_su2_chain` falls back to a sequential per-qubit batched matmul — still fast in CUDA graph mode but not as fast as the static-unitary path. When the data-dependent angle is indexed by basis-address bits rather than free per batch element, use `register_ops.apply_mux_ry` / `apply_mux_ry_rz` instead (see the register-ops benchmark).
426
+ - **CUDA graphs and torch.compile are mutually exclusive in this code.** Both want to own graph capture; trying to combine breaks. Use one or the other.
427
+ - **Windows CPU `torch.compile` needs MSVC.** Install Visual Studio Build Tools' C++ workload, then run from a Developer Command Prompt (or `vcvars64.bat`-load the env). Linux/macOS work with their default `gcc`/`clang`.
428
+ - **Inductor's auto-CUDA-graph (`reduce-overhead` mode) is disabled** in `fastqml.gate_fusion` (`torch._inductor.config.triton.cudagraphs = False`) to avoid fighting an outer `make_graphed_callables`. If you use Inductor's auto-graphs instead, comment that out.