From 57d6401919455eedb9d59823f7fb74ebc5ff97d4 Mon Sep 17 00:00:00 2001 From: qingyunqian Date: Thu, 30 Jul 2026 05:29:46 +0800 Subject: [PATCH 1/2] Add Task 12 final expert optimization evidence --- optimized_solutions/challenge-12/README.md | 27 + .../research/IMPLEMENTATION_COMPARISON.md | 222 ++ .../challenge-12/research/INSIGHTS.md | 103 + .../challenge-12/research/LOG.md | 351 +++ .../challenge-12/research/SURVEY.md | 261 ++ .../research/check_equivalence.py | 144 + .../research/figures/factor-ablation.svg | 2499 +++++++++++++++++ .../research/plot_factor_ablation.py | 74 + .../challenge-12/research/profile_expm.py | 134 + .../research/profile_factor_ablation.py | 174 ++ .../research/profile_reference.py | 237 ++ .../research/profiles/equivalence-check.json | 14 + .../research/profiles/expm-microbench.json | 39 + .../research/profiles/factor-ablation.json | 44 + .../research/profiles/reference-profile.json | 22 + .../challenge-12/solution_12_batched_su4.py | 108 + .../challenge-12/solution_12_pair_fused.py | 138 + 17 files changed, 4591 insertions(+) create mode 100644 optimized_solutions/challenge-12/README.md create mode 100644 optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md create mode 100644 optimized_solutions/challenge-12/research/INSIGHTS.md create mode 100644 optimized_solutions/challenge-12/research/LOG.md create mode 100644 optimized_solutions/challenge-12/research/SURVEY.md create mode 100755 optimized_solutions/challenge-12/research/check_equivalence.py create mode 100644 optimized_solutions/challenge-12/research/figures/factor-ablation.svg create mode 100644 optimized_solutions/challenge-12/research/plot_factor_ablation.py create mode 100755 optimized_solutions/challenge-12/research/profile_expm.py create mode 100644 optimized_solutions/challenge-12/research/profile_factor_ablation.py create mode 100755 optimized_solutions/challenge-12/research/profile_reference.py create mode 100644 optimized_solutions/challenge-12/research/profiles/equivalence-check.json create mode 100644 optimized_solutions/challenge-12/research/profiles/expm-microbench.json create mode 100644 optimized_solutions/challenge-12/research/profiles/factor-ablation.json create mode 100644 optimized_solutions/challenge-12/research/profiles/reference-profile.json create mode 100644 optimized_solutions/challenge-12/solution_12_batched_su4.py create mode 100644 optimized_solutions/challenge-12/solution_12_pair_fused.py diff --git a/optimized_solutions/challenge-12/README.md b/optimized_solutions/challenge-12/README.md new file mode 100644 index 0000000..774eb83 --- /dev/null +++ b/optimized_solutions/challenge-12/README.md @@ -0,0 +1,27 @@ +# Challenge 12 optimized expert solutions + +This directory packages the final reviewed Task 12 campaign from Benchmark +PR [#6](https://github.com/hmyuuu/OrbitBreakersExpertBenchmarks/pull/6) plus +the Task 12 half of ablation PR +[#13](https://github.com/hmyuuu/OrbitBreakersExpertBenchmarks/pull/13), with +the final tree taken from Benchmark `main` at `7e2298b`. + +- `solution_12_batched_su4.py` is the promoted campaign candidate. +- `solution_12_pair_fused.py` is the separately tracked, faster exact + pair-fused variant. +- `research/IMPLEMENTATION_COMPARISON.md` is the final report. +- `research/profiles/`, `research/figures/`, and the component profiler + preserve the post-merge factor attribution. + +For upstream review, both optimized files restore the immutable expert's +module docstring verbatim. Only comments changed relative to the reviewed +Benchmark artifacts; executable code is identical. + +All six local-engine matched pairs passed in both sessions. The promoted +candidate measured `3.914003x` mean paired speedup; the pair-fused variant +measured `4.247771x`. The report explicitly marks the formal Docker promotion +rerun as pending rather than overstating the claim. + +The canonical expert under `tasks/challenge-12/solution/` is intentionally +unchanged. Benchmark-harness reproduction commands in the research record +should be run in the Benchmark repository pinned above. diff --git a/optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md b/optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md new file mode 100644 index 0000000..83ce73f --- /dev/null +++ b/optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md @@ -0,0 +1,222 @@ +# Task 12 Autoresearch Campaign Report + +## Scope and claim + +This campaign optimized only ORBIT-Q Task 12, the variational-circuit to +DMRG-MPS overlap workload (32 qubits, two SU4 brickwork layers, 465 +parameters, exactly 5000 Adam updates). It compares the immutable +human-expert reference with one candidate (`e01`) and one tracked +reference-derived variant, all measured with this repository's `./bench` +harness in the pinned lock environment. + +No matched external implementation publishes runtime for this exact +workload, evaluator, and allocation. The optimized source is therefore +called the **campaign-best implementation**, not a global SOTA +implementation. + +The campaign establishes a statistically valid local-engine runtime +improvement of 3.914x (candidate) and 4.248x (tracked variant). The campaign +host has no Docker daemon, so the formal `GOAL.md` Gate 3 Docker protocol +did not run here; a Docker-engine rerun of the same six-pair protocol is the +single missing step for formal promotion. It does not reach the 10x stretch +target. + +| Role | Artifact | Commit or hash | +|---|---|---| +| Immutable expert | [`tasks/challenge-12/solution/solution_12.py`](../../../tasks/challenge-12/solution/solution_12.py) | Benchmark SHA-256 `10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38` | +| Campaign best (candidate e01) | [`solution_12_batched_su4.py`](../solution_12_batched_su4.py) | Benchmark SHA-256 `1d9a36c2649e938666f680e98e423e966f82c503da64ce7e4e06c5e33344560a`; upstream package SHA-256 `9003f6507c8994a12a5cb17238ef15974fe0af629d87104d1968acc33aedaff8` after restoring the immutable docstring | +| Tracked variant | [`solution_12_pair_fused.py`](../solution_12_pair_fused.py) | Benchmark SHA-256 `5edd437829352c573b24ae2f9021ef6046c2cf53788a8b79c8811b8fe0e3103f`; upstream package SHA-256 `0b7354896a05ea88cc3a9e69745df4c40ac0d50397de344300b31ecd6ab4cf61` after restoring the immutable docstring | +| Candidate hypothesis | Experiment `e01` | Commit `ef005888101c98de30fda2df3331f5f8461bf0ab` | +| Candidate evidence | Six paired runs | Report SHA-256 `653aaaaabff79a4c9a2bdcdbea4a82b561d8cff0824b83f84df08f5efb2659cd` | +| Variant evidence | Six paired runs | Report SHA-256 `f7bc804a975b299a786b6bc519764a2dd926076e054d82d9aea20094cc0abed8` | +| Workload | `datasets/public/task-12/canonical.json`, dataset `orbitq-workloads-v20260728.2` | Expert validation report SHA-256 `6d15f64bdf03097f1423fa16e0f434c97b2d1e44dafd5973abec0e08df004975` | + +## Campaign-best result + +Experiment e01 made two execution changes and no protocol changes: + +1. All 31 SU4 gate matrices are built per step with one einsum against the + stacked su(4) generators and one batched fixed 2^5 scaling-and-squaring + diagonal Pade(3,3) matrix exponential, applied through `circuit.any`. +2. All 5000 Adam updates run inside a single `jax.lax.scan`. + +All 12 evaluator cells passed. The candidate won all six matched pairs. + +| Metric | Immutable expert | Campaign best (e01) | Tracked fused variant | +|---|---:|---:|---:| +| Passing runs | 6/6 (both sessions) | 6/6 | 6/6 | +| Mean runtime | 9.082742 s / 9.020622 s (per session) | 2.320613 s | 2.123708 s | +| Median runtime | 9.086063 s / 9.033664 s | 2.320519 s | 2.121520 s | +| Runtime standard error | 0.027423 s / 0.026975 s | 0.002886 s | 0.004555 s | +| Mean paired speedup | — | 3.914003x | 4.247771x | +| Paired-speedup standard error | — | 0.014572x | 0.019961x | +| 95% Student-t interval | — | 3.876545x–3.951460x | 4.196459x–4.299083x | +| Ratio-of-means improvement | — | 74.450306% | 76.457192% | + +Matched timings, candidate session (order alternates `reference->candidate` +/ `candidate->reference`): + +| Pair | Order | Reference | Candidate | Speedup | +|---:|---|---:|---:|---:| +| 1 | reference → candidate | 9.132 s | 2.324 s | 3.9299x | +| 2 | candidate → reference | 9.040 s | 2.317 s | 3.9023x | +| 3 | reference → candidate | 9.023 s | 2.318 s | 3.8926x | +| 4 | candidate → reference | 9.163 s | 2.311 s | 3.9651x | +| 5 | reference → candidate | 9.132 s | 2.323 s | 3.9312x | +| 6 | candidate → reference | 9.006 s | 2.331 s | 3.8629x | + +The complete evidence is recorded in [`LOG.md`](LOG.md), with distilled +lessons in [`INSIGHTS.md`](INSIGHTS.md). + +## Implementation + +### Batched fixed-order su(4) exponentials + +The expert builds each of the 31 SU4 gates separately: +`tc.gates.su4_gate` chains 15 scalar multiply/stack operations and one +norm-adaptive Pade-13 `jax.scipy.linalg.expm` per gate, and the whole chain +is differentiated. The campaign-best implementation builds every generator +in one einsum and exponentiates the whole `(31, 4, 4)` batch at once: + +```python +a = jnp.einsum("gi,iab->gab", thetas.astype(gens.dtype), gens) / 32j +a2 = a @ a +odd = a @ (a2 + 60.0 * eye) +even = 12.0 * a2 + 120.0 * eye +r = jnp.linalg.solve(even - odd, even + odd) # diagonal Pade(3,3) +for _ in range(5): + r = r @ r # undo the 2**5 scaling +``` + +A diagonal Pade approximant of an anti-Hermitian argument is exactly unitary +(Cayley-type transform), so the gates stay exactly unitary; the fixed-order +approximation only perturbs *which* unitary, by at most 3.2e-6 for generator +norms up to 33.6 (`profiles/expm-microbench.json`), while the training +trajectory never exceeds norm 3.61 (`profiles/equivalence-check.json`). +The static graph replaces the adaptive expm's norm estimate, branch logic, +and per-gate chains: StableHLO shrinks from 8884 to 954 lines, XLA compile +from ~3.4 s to ~0.4 s, and the measured gate-build-plus-Adam scan step from +0.381 ms to 0.123 ms (`profiles/reference-profile.json`, +`profiles/expm-microbench.json`). + +### Whole-training scan + +The expert JIT-compiles one update and dispatches it from Python 5000 times. +The campaign-best implementation compiles the same sequential Adam process +as one `jax.lax.scan`, returning all pre-update losses, fidelities, and +overlaps as stacked scan outputs. Dispatch is only ~5 us per step, so the +scan matters mainly because the optimized step itself is ~0.19 ms. + +### Tracked variant: pair-fused ququart contraction + +`variants/solution_12_fused.py` additionally contracts the depth-2 brickwork +as a depth-1 chain on 16 four-level sites (`tc.QuditCircuit(16, dim=4)`): +layer-1 gates become single-site unitaries with the Neel preparation folded +in as a constant basis permutation, layer-2 gates become two-site +`I2 (x) SU4 (x) I2` unitaries, and the DMRG target is pair-fused once +outside the loop into a `tc.quantum.QuVector` bra. Every contraction result +is unchanged; the network has half the nodes. After the gate-construction +fix this buys a further ~8% end to end (4.248x vs 3.914x), confirming the +contraction was never the dominant cost. + +## Preserved scientific work + +The reference, candidate, and variant all: + +- start from the Neel product state `|0101...01>` on 32 qubits; +- apply the same 31 trainable SU4 gates on the same brickwork bonds with the + same 15-generator su(4) parameterization in the same generator order; +- compute the loss as the direct tensor-network overlap between the + evaluator-provided DMRG-MPS bra and the circuit ket (no target-preparation + circuit, no oracle values); +- draw the identical seeded float32 initialization + (`default_rng(2039)`, scale 0.02); +- run exactly 5000 sequential Adam updates at learning rate 0.02 and record + pre-update loss and fidelity for every step; +- return `loss_history (5000,)`, `fidelity_history (5000,)`, + `final_parameters (465,)`, and `final_overlap_phase` as NumPy data; +- use complex64 TensorCircuit/JAX semantics in the pinned environment. + +Trajectory equivalence was audited directly +(`profiles/equivalence-check.json`): from the identical initialization the +candidate's first 10 recorded losses match the reference bit-for-bit in +float32, and the deviation after 400 steps (1.27e-03) is the ordinary +round-off scale amplified by optimizer dynamics — the same order as the +reference's own run-to-run scatter of final fidelities (0.86855–0.87016 +across the recorded sessions). + +## Profiling interpretation + +The immutable expert spends ~60% of its end-to-end time before the first +optimizer step: ~1.7 s of jit trace and ~3.4 s of XLA compile for an +8884-line StableHLO module, dominated by the 31 separate `su4` construction +chains; the 5000-step loop runs at ~0.67 ms per step, of which the batched +gate construction alone accounts for ~0.38 ms and the tensor-network +contraction is nearly free (the exact circuit state has bond dimension at +most 4). The evidence supports gate-construction batching with a fixed-order +exponential as the main improvement; the scan and the pair-fused network +address the remaining dispatch and node-count overheads. + +## Factor ablation addendum + +The original e01 result bundled fixed batched SU4 construction with the +whole-training scan. The tracked ququart variant already provides a clean +incremental contraction ablation. A post-merge scan-removal profile completes +the attribution: + +| Factor | Control | Removal or alternative | Measured contribution | Numerical check | +|---|---:|---:|---:|---:| +| Fixed batched Pade gate construction | 0.1225 ms per gate-build/grad/Adam kernel | 0.3808 ms with adaptive Pade-13 expm | adaptive/fixed `3.109x` for the isolated kernel | candidate error envelope already audited | +| Whole-training scan | 0.9131 s mean for 5000 steps | 1.1596 s with 5000 dispatches of the identical compiled step | loop/scan `1.270x` in execution; cold lower+compile+execute improves only 7.66% | all histories and parameters bitwise equal | +| Pair-fused ququart contraction | 2.3206 s canonical candidate mean | 2.1237 s tracked-variant mean | 8.49% lower end to end; `1.093x` incremental | 12/12 cells PASS | + +The factors are not additive because cold compilation and execution overlap in +the end-to-end timer. Still, their relative scale is clear: the fixed batched +gate construction is the dominant source of the `3.914x` candidate gain. +The scan saves about 0.25 s of 5000-step execution but only about 0.15 s after +its slightly larger cold compile boundary is included. Pair fusion is another +roughly 8.5% end-to-end refinement. The report no longer credits all three +items equally. + +Reproduction and sanitized output: + +- [`profile_factor_ablation.py`](profile_factor_ablation.py) +- [`profiles/factor-ablation.json`](profiles/factor-ablation.json) + +![Task 12 factor-ablation plots](figures/factor-ablation.svg) + +Each panel removes one factor and normalizes runtime to the promoted form. +The scan panel uses cold lower+compile+execute time, matching the end-to-end +evaluation emphasis. Regenerate with +[`plot_factor_ablation.py`](plot_factor_ablation.py). + +## Final-rerun status + +The paired sessions recorded above are the final local-engine benchmarks for +this campaign; no tuning followed them. The formal `GOAL.md` Docker +promotion protocol (one pinned container, six counterbalanced pairs, +`--engine docker`) has not run because this host has no Docker daemon. The +single remaining step for formal promotion is: + +```bash +./bench env build tensorcircuit-py311 +./bench run 12 --solution optimized --compare-to reference \ + --repeat 6 --engine docker --timeout 300 --no-build \ + --output results/task-12-e01-docker-promotion +``` + +on a Docker-capable host, expecting the same qualitative outcome. + +## Limits and next work + +- Local-engine evidence only; the Docker gate is deferred as above. +- One host (4 vCPU Intel Xeon cloud VM); no scaling claim; the fixed + workload prevents scale probes by contract. +- Python 3.12.3 with the exact lock versions (the pinned image ships + Python 3.11); a Docker rerun removes this deviation. +- The 10x stretch target would need a candidate mean near 0.91 s in the e01 + session; the measured campaign-best mean is 2.12 s (fused variant). The + remaining time is roughly 1.15 s of one-time trace/compile, 0.95 s of + optimizer steps, and 0.12 s of target conversion, so a 10x path would have + to attack XLA compile latency itself or amortize compilation across runs, + which the cold-cache measurement rule intentionally forbids. diff --git a/optimized_solutions/challenge-12/research/INSIGHTS.md b/optimized_solutions/challenge-12/research/INSIGHTS.md new file mode 100644 index 0000000..ec1dbb8 --- /dev/null +++ b/optimized_solutions/challenge-12/research/INSIGHTS.md @@ -0,0 +1,103 @@ +# Task 12 Research Insights + +Task: `task-12` + +Last consolidated: 2026-07-28 + +Evidence ledger: [`LOG.md`](LOG.md) + +## Current best + +Experiment `e01` (`src/solutions/task-12/solution_12.py`, SHA-256 +`1d9a36c2649e938666f680e98e423e966f82c503da64ce7e4e06c5e33344560a`): batched +fixed-order su(4) exponentials plus a whole-training `jax.lax.scan`. Six +eligible local-engine pairs against the immutable reference: paired speedup +mean 3.914x ± 0.015x (95% Student-t CI 3.877x-3.951x), candidate mean +2.320613 s vs reference mean 9.082742 s, 6/6 pairs won. The tracked +pair-fused variant (`variants/solution_12_fused.py`) reaches 4.248x ± 0.020x +(CI 4.196x-4.299x) under the same protocol. Scope: this repository's Task 12 +canonical workload on the campaign host with `--engine local`; the Docker +promotion gate of `GOAL.md` remains closed pending a Docker rerun. + +## Preserved semantics + +- su(4) parameterization of every SU4 gate in the exact + `tc.gates.su4_gate` generator order; gates enter the circuit as unitaries + `expm(-1j * sum_i theta_i G_i)`. +- Seeded float32 initialization (`default_rng(2039)`, scale 0.02), exactly + 5000 sequential Adam updates at learning rate 0.02, pre-update loss and + fidelity recorded per step. +- Direct tensor-network overlap of the evaluator-provided DMRG-MPS bra with + the circuit ket; no target-state preparation circuit, no oracle values. +- complex64 TensorCircuit/JAX semantics; full output contract + (`loss_history`, `fidelity_history`, `final_parameters`, + `final_overlap_phase`). + +## Confirmed bottlenecks + +- ~60% of the reference's end-to-end time is jit trace plus XLA compile of an + 8884-line StableHLO module; the 31 separate `su4` constructions (15 + scalar multiply/stacks and one norm-adaptive Pade-13 `expm` per gate, + all differentiated) dominate the graph + (`profiles/reference-profile.json`, ledger entry "Reference bottleneck + profile"). +- Inside the step, gate construction dominates: batched-gate-build-only scan + steps cost 0.381 ms (adaptive expm) vs 0.123 ms (fixed Pade), while the + whole candidate step is 0.190 ms; the tensor-network contraction is nearly + free because the exact circuit state has bond dimension <= 4 + (`profiles/expm-microbench.json`, `profiles/reference-profile.json`). +- Per-step Python dispatch is only ~5 us, so a scan alone is a single-digit + percentage improvement (external precedent, recorded in `SURVEY.md` + hypothesis 3). + +## What worked + +- One einsum against the stacked 15 su(4) generators plus one batched + fixed 2^5 scaling-and-squaring diagonal Pade(3,3) exponential for all 31 + gates: exactly unitary for anti-Hermitian input (measured unitarity defect + <= 8.3e-6 in complex64), max deviation from a float64 exponential 3.2e-6 + over generator norms up to 33.6 while the training trajectory never + exceeds norm 3.61 (`profiles/equivalence-check.json`); shrinks StableHLO + from 8884 to 954 lines, compile from ~3.4 s to ~0.4 s, and the step from + 0.667 ms to 0.190 ms. +- `jax.lax.scan` over the 5000 Adam updates: compiles the step exactly once + and removes per-step dispatch; essential once the step itself is ~0.2 ms. +- Pair-fusing qubit pairs into 16 four-level `QuditCircuit` sites (tracked + variant): halves the contraction network; worth a further ~8% end to end + after the gate-construction fix (4.248x vs 3.914x). + +## What did not work + +- Scan alone on the unmodified reference objective: ~9% end to end + (external precedent; dispatch is not the bottleneck). Do not resubmit as a + standalone candidate. +- Fusing four qubits per site (dim-16 sites, 256x256 crossing gates): + contraction FLOPs explode; measured ~4x slower than the reference + formulation in the external precursor. Do not repeat unchanged. +- Absorbing the 16 single-site fused gates into the 15 two-site gates: + the absorption einsums cost what the removed network nodes saved. +- `lax.scan` with `unroll=4`: larger compile and slower steps. +- XLA CPU thread flags and core pinning: no gain; the kernels are too small + to parallelize. + +## Open hypotheses + +- Reproduce the six-pair protocol under `--engine docker` on a Docker-capable + host to open the formal `GOAL.md` promotion gate (highest value; no code + change required). +- A hand-scheduled 16-block transfer-matrix contraction inside the scan body + could cut the remaining ~0.06 ms of network kernels per step, but departs + from framework circuit APIs and was deliberately not pursued; revisit only + with maintainer guidance on framework-fidelity boundaries. +- Trimming the residual ~1.15 s of trace+compile (e.g., a lighter contractor + search for the 63-node fused network) is bounded by Amdahl to <25% of the + candidate's remaining runtime. + +## Evidence limits + +- All in-repo runtime evidence uses `--engine local` on one 4 vCPU host; + the Docker single-container protocol has not run here. +- The campaign measured only the canonical fixed workload (32 qubits, 2 + layers, 5000 updates, seed 2039); no scaling claim is made. +- Python 3.12.3 was used with the pinned lock (the Docker image ships + Python 3.11); package versions match the lock exactly. diff --git a/optimized_solutions/challenge-12/research/LOG.md b/optimized_solutions/challenge-12/research/LOG.md new file mode 100644 index 0000000..9426169 --- /dev/null +++ b/optimized_solutions/challenge-12/research/LOG.md @@ -0,0 +1,351 @@ +# Task 12 Autoresearch Campaign + +Destination: `research/task-12/LOG.md` + +Task: `task-12` + +Insights: [`INSIGHTS.md`](INSIGHTS.md) + +## Campaign selection and provenance + +Selected task: `task-12` (variational circuit to MPS overlap optimization). + +Live open pull requests on `sxzgroup/ORBIT-Q` inspected on 2026-07-28: +`#2` (ForgeCode agent solver), `#3` (scoring-policy fix), `#4` (Fable 5 +agent-axis run record), `#5` (GPT-5.6 Sol benchmark results). None is an +active Task 12 solution-improvement PR, so Task 12 is eligible for this +campaign. + +Precursor research disclosure: the bottleneck profiling and candidate design +for this campaign were first executed on 2026-07-28 against the +byte-equivalent ORBIT-Q publication reference in the fork +`QingyunQian/ORBIT-Q` (branch `cursor/optimize-challenge-12-f598`, +`optimized_sloutions/challenge-12/`), on the same 4 vCPU cloud VM but with +`tensorcircuit-nightly==1.8.0.dev20260726`. All figures used for claims in +this repository were re-measured here in the pinned lock environment; the +external numbers are context only and are never pooled with in-repo +measurements. See `SURVEY.md` for the porting statement. + +Campaign workspace deviation: this campaign runs in a single working clone on +the campaign host with branch `cursor/task-12-batched-su4-campaign-f598` +(Cursor cloud-agent branch-naming policy) instead of the +`codex/orbitbreakers/task-12/` worktree-per-hypothesis layout. +Exactly one hypothesis edits `src/solutions/task-12/solution_12.py`, so the +one-worktree-per-hypothesis isolation is preserved in substance. + +Engine deviation: the campaign host has no Docker daemon, so every in-repo +measurement uses `./bench ... --engine local` with a virtual environment +installed exactly from `envs/tensorcircuit-py311/requirements.lock` +(Python 3.12.3), the environment `sitecustomize.py` on `PYTHONPATH`, and +`NUMBA_DISABLE_JIT=1`. The `GOAL.md` Gate 3 Docker protocol therefore stays +closed on this host; local-engine paired evidence is recorded below and a +Docker rerun is requested in `IMPLEMENTATION_COMPARISON.md`. + +## Reference baseline (local engine) + +Date: 2026-07-28 + +Command: + +```bash +./bench run 12 --solution reference --repeat 6 --engine local \ + --timeout 300 --output results/task-12-reference-baseline-local +``` + +Reference SHA-256: +`10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38` + +Evaluator SHA-256: +`08940a5fabfd88a957c467edabfbe6faa7b766f38b4d518557e50e94fcf3b277` + +Host fingerprint: +`748423c1790b38ddbdd8eb77499b222a173b313f350e3bc35402ee8889a49dc4` +(4 vCPU Intel Xeon x86_64, 15 GiB RAM, Linux 6.12, Python 3.12.3, JAX 0.10.0, +`tensorcircuit-nightly==1.7.0.dev20260618`) + +Immutable report: `results/task-12-reference-baseline-local/results.json` +(untracked; retained on the campaign host) + +Report SHA-256: +`6d15f64bdf03097f1423fa16e0f434c97b2d1e44dafd5973abec0e08df004975` + +Summary SHA-256: +`600753c2e09b9561afdfd6f79e9c795a7a7d626d4b906bb6149b530a0c2164fe` + +```text +terminal_status: SUCCESS x 6 +valid: 6/6 (Overall: PASS in every cell) +timed_out: 0 +runtime_sec: 9.113186, 9.188893, 9.067477, 9.064269, 9.086606, 9.181242 +mean_runtime_sec: 9.116946 +median_runtime_sec: 9.099896 +sample_stdev_sec: 0.055618 +stderr_sec: 0.022706 +min_sec: 9.064269 +max_sec: 9.188893 +``` + +Decision: `baseline` + +Context only: the shared-container Docker bootstrap of 2026-07-27 measured +the same immutable reference at 11.261 ± 0.972 s under an 8-CPU/9-GiB +container on the maintainer host (`baselines/bootstrap-2026-07-27.md`), and +the ORBIT-Q publication record lists 6.12 s on an unspecified host +(`baselines/historical.json`). These numbers are not pooled with this +campaign's measurements. + +## Reference bottleneck profile + +Date: 2026-07-28 + +Scripts: `research/task-12/profile_reference.py`, +`research/task-12/profile_expm.py` (pinned lock environment, local engine +conditions). Sanitized outputs: +`profiles/reference-profile.json`, `profiles/expm-microbench.json`. + +Findings recorded in `SURVEY.md`: the reference spends ~0.14 s on target +conversion, ~1.7 s on jit trace, ~3.4 s on XLA compile of an 8884-line +StableHLO module, and ~3.3 s on 5000 steps at ~0.67 ms per step. A scan step +containing only the batched 31-gate build plus gradient and Adam costs +0.381 ms with the norm-adaptive `jax.scipy.linalg.expm` versus 0.123 ms with +the candidate's fixed-order diagonal Pade(3,3); the per-gate `su4` +construction chain, not the tensor-network contraction, dominates both the +graph size and the step time. + +## Experiment `e01` + +Branch: `cursor/task-12-batched-su4-campaign-f598` + +Worktree: single working clone on the campaign host (deviation recorded under +"Campaign selection and provenance"). + +## Hypothesis + +Building all 31 SU4 gate matrices per step with one batched einsum against +the stacked su(4) generators and one fixed 2^5 scaling-and-squaring diagonal +Pade(3,3) exponential, applying them through `circuit.any`, and running the +5000 Adam updates inside one `jax.lax.scan` reduces the evaluator-reported +runtime by at least 3x while reproducing the reference trajectory within +complex64 round-off (falsified if any functional gate fails, if the paired +speedup CI includes 1.0, or if the early-step loss trajectory deviates beyond +round-off scale). + +## Parent commit and diff digest + +Latest accepted parent commit: +`611c35b` (`data: release task 12 public workload v2`; campaign base +`690ffbac51715afd0a3e80718eeb6de20f11863a`) + +Hypothesis commit: `ef005888101c98de30fda2df3331f5f8461bf0ab` + +Candidate file: `src/solutions/task-12/solution_12.py` + +Candidate SHA-256: +`1d9a36c2649e938666f680e98e423e966f82c503da64ce7e4e06c5e33344560a` + +Diff SHA-256: +`0badae624ed7ec5e309e1a0684e431e407895dc54f4530d61846a3320ffd309e` + +## Data used + +Public dataset version: `orbitq-workloads-v20260728.2` + +Public manifest SHA-256: +`569a0141c5f0d723f275112d58cc6a69a3af10ad84ab8536c072604ee838cbfd` + +Private evaluation used: `no` + +## Command, seed, and environment + +Benchmark command: + +```bash +./bench run 12 --solution optimized --compare-to reference --repeat 6 \ + --engine local --timeout 300 \ + --output results/task-12-e01-batched-pairs-local +``` + +Public seed or case selector: canonical fixed configuration, seed 2039. + +Reference SHA-256: +`10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38` + +Evaluator SHA-256: +`08940a5fabfd88a957c467edabfbe6faa7b766f38b4d518557e50e94fcf3b277` + +Docker image ID: none (`--engine local`; no Docker daemon on this host) + +Container session ID: none (fresh local evaluator process per cell) + +Pair-order pattern: odd pairs `reference -> candidate`; even pairs +`candidate -> reference` + +TensorCircuit-NG commit/version: `tensorcircuit-nightly==1.7.0.dev20260618` +(+ `envs/tensorcircuit-py311/sitecustomize.py` OMECo shortcut backport) + +JAX/JAXLIB versions: `0.10.0` / `0.10.0` + +## Hardware and five-minute cap + +Host fingerprint: +`748423c1790b38ddbdd8eb77499b222a173b313f350e3bc35402ee8889a49dc4` + +CPU allocation: 4 vCPU Intel Xeon x86_64 (whole host; no container pinning) + +Memory allocation: 15 GiB host memory (no container limit) + +Timeout: `300 seconds` + +Measured region: evaluator-reported `End-to-end solution time` +(`run_solution(config)` only; evaluator-side DMRG excluded by the evaluator) + +## Result: validity, runtime, and improvement + +Immutable report: `results/task-12-e01-batched-pairs-local/results.json` +(untracked; retained on the campaign host) + +Report SHA-256: +`653aaaaabff79a4c9a2bdcdbea4a82b561d8cff0824b83f84df08f5efb2659cd` + +Summary SHA-256: +`725eeecf70b572fca9d7a4e88dbe995f216b87b75173624dedb1a75715c108f7` + +```text +terminal_status: SUCCESS x 12 (6 reference cells, 6 candidate cells) +valid: 12/12 (Overall: PASS in every cell; final fidelities 0.86954-0.87016) +timed_out: 0 +passing_pairs: 6/6 (candidate wins 6/6) +reference_mean_runtime_sec: 9.082742 +reference_runtime_stderr_sec: 0.027423 +reference_median_runtime_sec: 9.086063 +candidate_mean_runtime_sec: 2.320613 +candidate_runtime_stderr_sec: 0.002886 +candidate_median_runtime_sec: 2.320519 +improvement_pct: 74.450306 +improvement_pct_stderr: 0.095146 +speedup: 3.913941 +speedup_stderr: 0.014572 +paired_speedup_ci_low: 3.876545 +paired_speedup_ci_high: 3.951460 +``` + +Pairwise runtimes (reference, candidate, speedup): (9.132, 2.324, 3.9299), +(9.040, 2.317, 3.9023), (9.023, 2.318, 3.8926), (9.163, 2.311, 3.9651), +(9.132, 2.323, 3.9312), (9.006, 2.331, 3.8629). + +Note: the paired benchmark was executed against the exact candidate bytes +recorded in the hypothesis commit; the immutable report's per-row +`source_sha256` values equal the candidate and reference hashes above. + +Trajectory equivalence (`profiles/equivalence-check.json`): the first 10 +recorded losses match the reference bit-for-bit in float32 (max delta 0.0); +max delta 3.98e-05 over 50 steps and 1.27e-03 over 400 steps, the ordinary +round-off scale amplified by optimizer dynamics. The maximum su(4) +generator-norm bound over all 5000 steps is 3.6016 (scaled norm 0.1125 after +2^-5), far inside the fixed-Pade accuracy envelope measured in +`profiles/expm-microbench.json`. + +Decision: `keep` (all validity gates pass; six eligible counterbalanced +local-engine pairs; candidate mean and median lower; 6/6 pairs won; 95% +paired-speedup CI excludes 1.0. The `GOAL.md` Docker promotion gate stays +closed on this host and is deferred to a Docker-capable rerun.) + +## Failure signal and interpretation + +No failures, timeouts, or invalid cells in this session. + +## Next pivot + +Track the pair-fused ququart formulation as a reference-derived variant and +measure it under the same paired protocol; request a Docker-engine rerun for +formal promotion. + +## Append-only corrections + +None. + +## Variant benchmark: pair-fused ququart contraction + +Date: 2026-07-28 + +Variant tracking commit: `3026f94b202bf91d1d5b7c8656e8ab5f23e6a030` + +Variant file: `src/solutions/task-12/variants/solution_12_fused.py` + +Variant SHA-256: +`5edd437829352c573b24ae2f9021ef6046c2cf53788a8b79c8811b8fe0e3103f` + +This is a tracked reference-derived variant benchmark, not a second campaign +candidate; `src/solutions/task-12/solution_12.py` remains the e01 candidate. + +Command: + +```bash +./bench run 12 --solution fused --compare-to reference --repeat 6 \ + --engine local --timeout 300 \ + --output results/task-12-fused-variant-pairs-local +``` + +Environment, host, pair-order pattern, timeout, and measured region: same as +experiment `e01`. + +Immutable report: `results/task-12-fused-variant-pairs-local/results.json` +(untracked; retained on the campaign host) + +Report SHA-256: +`f7bc804a975b299a786b6bc519764a2dd926076e054d82d9aea20094cc0abed8` + +Summary SHA-256: +`4d6a20dd02145ef5717abcb1c9ad643f7e9dbde4a3f59bfcb7c52523fc6c6608` + +```text +terminal_status: SUCCESS x 12 (6 reference cells, 6 variant cells) +valid: 12/12 (Overall: PASS in every cell; final fidelities 0.86904-0.87013) +timed_out: 0 +passing_pairs: 6/6 (variant wins 6/6) +reference_mean_runtime_sec: 9.020622 +reference_runtime_stderr_sec: 0.026975 +reference_median_runtime_sec: 9.033664 +candidate_mean_runtime_sec: 2.123708 +candidate_runtime_stderr_sec: 0.004555 +candidate_median_runtime_sec: 2.121520 +improvement_pct: 76.457192 +improvement_pct_stderr: 0.111281 +speedup: 4.247582 +speedup_stderr: 0.019961 +paired_speedup_ci_low: 4.196459 +paired_speedup_ci_high: 4.299083 +``` + +Pairwise runtimes (reference, variant, speedup): (9.017, 2.119, 4.2557), +(9.082, 2.124, 4.2756), (9.050, 2.111, 4.2870), (8.931, 2.128, 4.1960), +(8.954, 2.143, 4.1786), (9.089, 2.117, 4.2938). + +Interpretation: after e01's gate-construction fix, halving the contraction +network buys a further ~8% end to end (4.248x vs 3.914x); the contraction +was never the dominant cost. The variant stays PASS-equivalent with final +fidelities inside the reference's own run-to-run band. + +Decision: `keep` as a tracked variant (same local-engine eligibility caveat +as e01). + +## Post-merge scan-removal ablation + +Date: 2026-07-29 + +`profile_factor_ablation.py` runs the identical fixed-Pade candidate objective +for all 5000 updates through either one compiled scan or 5000 dispatches of +the same compiled step. Three executions per path on the 6-CPU/7-GiB Docker +backend measured: + +```text +scan mean execution: 0.913101 s +Python-loop mean execution: 1.159565 s +loop / scan: 1.269920x +cold lower+compile+execute: 1.836521 s vs 1.988862 s +``` + +All loss, fidelity, overlap, and parameter arrays were bitwise equal. Combined +with the existing `3.109x` fixed-Pade kernel microbenchmark and the `8.49%` +canonical pair-fusion increment, the report now attributes the dominant gain +to gate construction and treats scan and pair fusion as secondary factors. diff --git a/optimized_solutions/challenge-12/research/SURVEY.md b/optimized_solutions/challenge-12/research/SURVEY.md new file mode 100644 index 0000000..a6ff139 --- /dev/null +++ b/optimized_solutions/challenge-12/research/SURVEY.md @@ -0,0 +1,261 @@ +# ORBIT-Q Task 12 Runtime Optimization Survey + +**Status: READY** + +Campaign task: `task-12` + +Survey freeze: `2026-07-28T13:12:11Z` + +Reference commit: `690ffbac51715afd0a3e80718eeb6de20f11863a` + +This survey covers only Task 12, as required by the one-task campaign scope in +`GOAL.md`. `READY` means the public knowledge and measurement plan are +complete. It does not attest that the public dataset or repeated-runtime gates +have passed. + +## Provenance of this campaign's prior research + +The bottleneck analysis, candidate design, and cross-validation summarized +below were first executed on 2026-07-28 against the byte-equivalent ORBIT-Q +publication reference (`optimized_sloutions/challenge-12` work in the ORBIT-Q +fork `QingyunQian/ORBIT-Q`, branch `cursor/optimize-challenge-12-f598`), on a +4 vCPU Intel Xeon cloud VM with `tensorcircuit-nightly==1.8.0.dev20260726` +and JAX 0.10.0. The two reference files differ only in suite naming +("Task Suite" vs "Challenge Suite") and documented evaluator timing precision. + +Every profiling figure quoted in this survey was then **re-measured in this +repository's pinned lock environment** on the campaign host; the sanitized +profiler outputs live under `research/task-12/profiles/` with the scripts +beside them. Every runtime-improvement number for this repository comes from +fresh `./bench` paired runs recorded in [`LOG.md`](LOG.md). External-host +numbers are context only and are never pooled with in-repo measurements. + +## Evidence and claim rules + +Repository evidence below names a path, symbol or line, commit, and SHA-256. +Pinned-environment evidence was inspected in a virtual environment installed +exactly from `envs/tensorcircuit-py311/requirements.lock` +(`sha256:cd5ac5cb2102ea7b40bd46dc81320cc59e0ce0671ab88c597f81d82b384a824b`). +External evidence links primary project documentation. + +The bundled immutable human expert is the only reproducible performance +comparator currently available for this exact 32-qubit, 2-layer, 5000-update +overlap workload. The historical ORBIT-Q values in +`baselines/historical.json` (publication 6.12 s) and the shared-container +bootstrap (`baselines/bootstrap-2026-07-27.md`, 11.261 ± 0.972 s on 8 CPUs) +are context only. No cited external paper reports a matched evaluator +runtime, hardware allocation, software version, and output contract. +Therefore this campaign may claim a paired gain *over the bundled human +expert* when the frozen rule passes, but must not claim global state of the +art without a new matched external comparator. + +## Pinned environment and inspected framework paths + +The package versions come from `envs/tensorcircuit-py311/requirements.lock` +(`sha256:cd5ac5cb2102ea7b40bd46dc81320cc59e0ce0671ab88c597f81d82b384a824b`). +The Dockerfile is `envs/tensorcircuit-py311/Dockerfile` +(`sha256:cbcffdfc0f17731bcdf549b94209879043aab8e53ee937be60947a49d2065912`). +The OMECo contractor shortcut backport is +`envs/tensorcircuit-py311/sitecustomize.py` +(`sha256:02800060761f2b15abe9055aded49d2af3877ab93d7b0fae8af94b30bac30120`); +it maps `tc.set_contractor("omeco")` to an `omeco.TreeSA(ntrials=16, +niters=32)` custom contractor with TensorCircuit preprocessing. + +| Component | Pinned version | Inspected source or API | +| --- | --- | --- | +| TensorCircuit-NG | `tensorcircuit-nightly==1.7.0.dev20260618` | `tensorcircuit/gates.py:881`, `su4_gate` builds the generator as `sum(theta[i] * pauli_ops[i])` over 15 stacked Pauli pairs in the fixed order `ix, iy, iz, xi, xx, xy, xz, yi, yx, yy, yz, zi, zx, zy, zz`, then calls `backend.expm(-1j * generator)` per gate (`sha256:1c42762b1164c9bd33a28844ec3e42ab7eea5e853aecc5c4c741510becc5bf68`); `tensorcircuit/quantum.py:1525`, `quimb2qop`, and `quantum.py:889`, `QuVector` (`sha256:550e2cc7d41b4eadfe7ce6dd37f1d295539eb22a580f5e2993a0a2ec3408c1b4`); `tensorcircuit/quditcircuit.py:30,146`, `QuditCircuit` with `any(*indices, unitary=...)` accepting `(d, d)` or `(d^2, d^2)` unitaries (`sha256:bb765d5c33244c95a5b6cd7bfb4e15d7cdb1f6d99f991477f83acaab604fcf57`); `tensorcircuit/cons.py:1136`, `set_contractor` (`sha256:4fb517ad6085328c53ed61c75471579708eb8265fa6c14ec98e4bf1b4fd04365`) | +| JAX / JAXLIB | `0.10.0` | `tensorcircuit/backends/jax_backend.py:438`, `expm` delegates to `jax.scipy.linalg.expm`, the norm-adaptive Pade-13 scaling-and-squaring implementation (`sha256:22bbcf80d5a018884337286f176849ad4e8681b4e1f20efc1f80ba9ec4cc9d69`); `jax.lax.scan`, `jax.value_and_grad`, and `jax.jit` wrapped at `jax_backend.py:712,803,844` | +| OMECo | `0.2.4` | Registered through the sitecustomize `set_contractor("omeco")` shortcut; both the reference and every candidate select it at import time | +| TensorNetwork-NG | `0.5.1` | `QuOperator.eval` contracts the bra/ket node network with the configured contractor | +| Quimb | `1.11.1` | Supplies the evaluator-side DMRG MPS (`config["dmrg_state"]`); solutions only convert it (`quimb2qop` in the reference; pair fusion of `mps.arrays` in the tracked variant) | +| Optax | `0.2.8` | `optax.adam`, first/second-moment state on 465 parameters; the [Optax Adam API](https://optax.readthedocs.io/en/latest/api/generated/optax.adam.html) documents the update rule the expert uses | + +TensorCircuit documents JAX, automatic differentiation, JIT, vectorization, +and contractor selection as supported execution paths in its +[quick start](https://tensorcircuit.readthedocs.io/en/latest/quickstart.html) +and identifies contractor choice and preprocessing in the +[contractor tutorial](https://tensorcircuit.readthedocs.io/en/latest/tutorials/contractors.html). +JAX documents `lax.scan` as the loop primitive that compiles the body once +([jax.lax.scan API](https://docs.jax.dev/en/latest/_autosummary/jax.lax.scan.html)), +and `jax.scipy.linalg.expm` as a Pade-based matrix exponential +([jax.scipy.linalg.expm API](https://docs.jax.dev/en/latest/_autosummary/jax.scipy.linalg.expm.html)). +The fixed-order diagonal Pade approximant used by the candidate follows the +classical scaling-and-squaring analysis (Higham, *The Scaling and Squaring +Method for the Matrix Exponential Revisited*, SIAM J. Matrix Anal. Appl. +26(4), 2005, ); diagonal Pade approximants +of anti-Hermitian arguments are exactly unitary, a standard property of +Cayley-type transforms used in geometric integration (Hairer, Lubich, Wanner, +*Geometric Numerical Integration*, ch. IV). + +## Task 12: variational circuit to DMRG-MPS overlap optimization + +### Immutable expert and output contract + +The immutable expert is `references/task-12/solution_12.py` +(`sha256:10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38`). +The contract and timer are `tasks/task-12/problem.md` +(`sha256:cd795bbbd5fcf2beb0000528e1e169fa09a4f20bdbc829381b2bbf7810c36fec`) +and `tasks/task-12/evaluator/evaluate_12.py` +(`sha256:08940a5fabfd88a957c467edabfbe6faa7b766f38b4d518557e50e94fcf3b277`). + +The evaluator builds a chi-8 DMRG MPS target for a 32-qubit XXZ chain with a +staggered field **before** timing, passes it in as `config["dmrg_state"]`, +and times only `run_solution(config)`. The solution must: + +- prepare the Neel product state `|0101...01>` on 32 qubits; +- apply two brickwork layers of trainable nearest-neighbor SU4 gates (even + bonds then odd bonds; 16 + 15 = 31 gates, 15 su(4) angles each, 465 + parameters), where the expert's SU4 convention is + `tc.gates.su4_gate`: `expm(-1j * sum_i theta_i G_i)` over the fixed + 15-generator Pauli-pair order; +- maximize `F = ||^2` through the direct + tensor-network overlap of the MPS bra with the circuit ket (no + gate-preparation conversion of the target); +- run exactly 5000 Adam updates at learning rate 0.02 from the seeded + `float32` initialization (`numpy.random.default_rng(2039)`, scale 0.02), + recording pre-update loss and fidelity per step; +- return `loss_history (5000,)`, `fidelity_history (5000,)`, + `final_parameters (465,)`, and `final_overlap_phase` as NumPy data, with + final fidelity at least 0.85, improving fidelity/loss, and finite values. + +### Dominant work and measured bottlenecks + +Stage-split profiling of the reference in the pinned lock environment on the +campaign host (`research/task-12/profile_reference.py`, AOT-split via +`jit(...).lower()` / `.compile()`; sanitized output at +`research/task-12/profiles/reference-profile.json`) shows the reference's +end-to-end time is dominated by graph construction and compilation, not by +tensor-network arithmetic: + +- one-time `quimb2qop` conversion: ~0.14 s; +- one-time jit trace (Python tracing of 31 per-gate `su4` constructions plus + the TreeSA contraction-path search): ~1.7 s; +- one-time XLA compile of an 8884-line StableHLO module: ~3.4 s; +- 5000 sequential Adam steps at ~0.67 ms per step: ~3.3 s. + +Inside the step, gate construction dominates: a scan step containing only the +batched gate build costs ~0.44 ms versus ~0.45 ms for a full fused step +(external-host decomposition, reproduced in spirit by +`profiles/expm-microbench.json` in the pinned environment), because each +`tc.gates.su4_gate` call chains 15 scalar multiply/stack operations and one +norm-adaptive Pade-13 `jax.scipy.linalg.expm` (norm estimate, conditionals, +LU solves) per gate, and the whole chain is differentiated. The contraction +itself is nearly free: exactly one brickwork gate crosses each bipartition +cut, so the exact circuit state has bond dimension at most 4 and every +network tensor is tiny. + +### Relevant TensorCircuit-NG primitives and contraction paths + +- `tc.Circuit(n)` with `circuit.any(i, i+1, unitary=...)` applies a + precomputed `(4, 4)` unitary exactly like `circuit.su4` applies its + internally built matrix (`gates.py:881`), so batched gate-matrix + construction preserves gate semantics bit-for-bit up to float ordering. +- `tc.quantum.quimb2qop` wraps the quimb MPS arrays as a `QuOperator` bra; + `(target_bra @ circuit.quvector()).eval()` contracts the joint network with + the globally configured contractor (sitecustomize TreeSA `omeco` shortcut). +- `tc.QuditCircuit(16, dim=4)` supports single- and two-site `(d, d)` / + `(d^2, d^2)` unitaries and `quvector()`, enabling an exact pair-fused + formulation (tracked as the `fused` variant). +- `tc.backend.expm` is a thin alias of `jax.scipy.linalg.expm`; nothing in + the framework requires the norm-adaptive path for fixed-size 4x4 + anti-Hermitian generators. + +### Candidate optimization hypotheses and semantic constraints + +Each hypothesis must preserve: the su(4) generator order and +`expm(-1j * sum theta G)` gate semantics, the seeded float32 initialization, +exactly 5000 sequential Adam updates at learning rate 0.02, pre-update +loss/fidelity recording, the direct MPS-bra x circuit-ket overlap (no target +state preparation circuit, no oracle shortcuts), complex64 TensorCircuit/JAX +semantics, and the full output contract. + +1. **e01 (primary): batched fixed-order su(4) exponentials plus + whole-training scan.** Build all 31 generators with one einsum against the + stacked generator tensor; exponentiate with a fixed 2^5 + scaling-and-squaring diagonal Pade(3,3) core (exactly unitary for + anti-Hermitian input; error below the complex64 noise floor for the + observed parameter range, margin documented in + `profiles/expm-microbench.json`); apply through `circuit.any`; run the + 5000 updates in one `jax.lax.scan`. Expected effect: order-of-magnitude + StableHLO shrinkage, several-fold lower trace/compile time, several-fold + lower per-step time. +2. **Pair-fused ququart contraction (tracked variant).** Fuse qubit pairs + into 16 four-level sites (`QuditCircuit(16, dim=4)`); layer-1 gates become + single-site unitaries with the Neel preparation folded in as a constant + basis permutation; layer-2 gates become two-site `I2 (x) SU4 (x) I2` + unitaries; the target MPS is pair-fused once outside the loop. Halves the + contraction network without changing any contraction result. +3. **Scan alone (rejected by prior evidence).** On the unmodified reference + objective the scan converts ~5 us/step dispatch and nothing else; + external-host measurement saw ~9% end to end. Not pursued separately. +4. **Coarser fusion (rejected by prior evidence).** Fusing 4 qubits per site + (dim-16 crossing gates of shape 256x256) inflates contraction FLOPs and + was measured ~4x slower than the reference formulation externally. Do not + repeat unchanged. + +### External comparison state + +No published external system reports runtime for this exact workload +(32-qubit XXZ chi-8 DMRG target, 465-parameter SU4 brickwork, 5000 Adam +updates, this evaluator timer, this CPU allocation). The strongest available +comparators are the bundled immutable expert (this repository) and the +ORBIT-Q publication runtime record (6.12 s, unspecified publication host, +`baselines/historical.json`). Open evidence gap: no matched-hardware external +baseline exists; global SOTA cannot be claimed. + +## Frozen paired measurement and statistics plan + +This section is frozen before candidate timing results. + +- Workload: public dataset version recorded in + `datasets/public/manifest.json`; Task 12 canonical fixed configuration + only. +- Engine/allocation: this campaign host has no Docker daemon available, so + eligible in-repo measurements use `./bench run 12 ... --engine local` with + a virtual environment installed exactly from + `envs/tensorcircuit-py311/requirements.lock` (Python 3.12.3), the + environment `sitecustomize.py` on `PYTHONPATH`, `NUMBA_DISABLE_JIT=1`, and + a fresh evaluator process per cell on an otherwise idle 4 vCPU host. The + Docker single-container protocol of `GOAL.md` Gate 3 therefore remains + **closed** on this host; the Docker-gated promotion claim is deferred to a + maintainer or CI rerun with `--engine docker`. +- Cells: six matched pairs minimum; a fresh evaluator process per cell. +- Order: odd pairs `reference -> candidate`; even pairs + `candidate -> reference` (the `./bench` counterbalanced default). +- Timeout: hard 300 seconds per evaluator process. +- Cache state: cold process-local Python/JAX caches for every fresh process; + no persistent compilation cache; no candidate-only precomputation outside + `run_solution`. +- Validity: process exit zero, finite positive evaluator runtime, + `Overall: PASS`, full output schema, exactly 5000-step histories. + +For eligible runtimes `R_i` and `C_i`, report means, medians, sample standard +deviations, standard errors, minima, maxima, ratio-of-means improvement +`100*(mean(R)-mean(C))/mean(R)`, pairwise improvements `100*(R_i-C_i)/R_i`, +and pairwise speedups `S_i=R_i/C_i`. + +The predeclared confidence interval is the two-sided 95% Student-t interval +for the arithmetic mean of the pairwise speedups: + +`mean(S) ± t_(0.975,n-1) * sample_stdev(S)/sqrt(n)`. + +With six pairs, the critical value is `t_(0.975,5)=2.5705818366`. Do not +change the method after observing a candidate. + +A candidate is promotable only when every validity gate passes, at least six +pairs are eligible, candidate mean and median are lower, the candidate wins +at least 80% of pairs, and the 95% lower confidence bound for mean paired +speedup exceeds 1.0 -- and, for the repository's formal Docker promotion +gate, when the same protocol is reproduced under `--engine docker` per +`GOAL.md` Gate 3. Until that Docker rerun exists, results below are recorded +as local-engine paired evidence, not a promoted Docker-gated claim. + +## Open evidence gaps + +- No Docker daemon on the campaign host: Gate 3 reference baselines and the + formal promotion protocol cannot run here; local-engine paired evidence is + recorded instead and a Docker rerun is requested in the campaign report. +- No matched external implementation publishes runtime for this exact + configuration; global SOTA remains unestablished. +- Process peak RSS was not measured; XLA memory-traffic estimates were not + collected in the pinned environment. diff --git a/optimized_solutions/challenge-12/research/check_equivalence.py b/optimized_solutions/challenge-12/research/check_equivalence.py new file mode 100755 index 0000000..f97a97f --- /dev/null +++ b/optimized_solutions/challenge-12/research/check_equivalence.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Trajectory-equivalence audit: candidate vs immutable Task 12 reference. + +Runs both training loops from the identical seeded initialization in one +process and reports the per-step loss deviation over the first K steps, plus +the maximum su(4) generator-norm bound reached across all 5000 candidate +steps (the accuracy-margin input for the fixed-order Pade exponential). + +Run from the repository root inside the pinned environment: + + PYTHONPATH=envs/tensorcircuit-py311 NUMBA_DISABLE_JIT=1 \ + python research/task-12/check_equivalence.py + +Writes ``research/task-12/profiles/equivalence-check.json``. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "src" / "solutions" / "task-12")) + +import numpy as np +import optax +import tensorcircuit as tc + +K = tc.set_backend("jax") +tc.set_dtype("complex64") +tc.set_contractor("omeco") + +import jax +import jax.numpy as jnp +import solution_12 # campaign candidate + +N = 32 +CHECK_STEPS = 400 + + +def build_dmrg_state(): + import quimb.tensor as qtn + + ham = qtn.SpinHam1D(S=0.5) + for i in range(N - 1): + ham[i, i + 1] += 4.0, "X", "X" + ham[i, i + 1] += 4.0, "Y", "Y" + ham[i, i + 1] += 4.0 * 1.4, "Z", "Z" + for i in range(N): + ham[i] += 2.0 * 0.2 * ((-1) ** i), "Z" + dmrg = qtn.DMRG2(ham.build_mpo(N), bond_dims=[8], cutoffs=1e-8) + dmrg.solve(tol=1e-7, max_sweeps=4, verbosity=0) + dmrg.state.normalize() + return dmrg.state + + +def scan_losses(objective, steps, extra=None): + opt = optax.adam(0.02) + + def body(carry, _): + p, s = carry + (loss, _aux), grads = K.value_and_grad(objective, has_aux=True)(p) + upd, s = opt.update(grads, s, p) + ys = (loss,) if extra is None else (loss, extra(p)) + return (optax.apply_updates(p, upd), s), ys + + rng = np.random.default_rng(2039) + p = jnp.asarray(rng.normal(scale=0.02, size=(465,)).astype(np.float32)) + s = opt.init(p) + (_, _), ys = jax.jit( + lambda p, s: jax.lax.scan(body, (p, s), None, length=steps) + )(p, s) + return tuple(np.asarray(y) for y in ys) + + +def main(): + dmrg_state = build_dmrg_state() + target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() + + def reference_objective(p): + circuit = tc.Circuit(N) + for i in range(1, N, 2): + circuit.x(i) + offset = 0 + for layer in range(2): + for i in range(layer % 2, N - 1, 2): + circuit.su4(i, i + 1, theta=p[offset : offset + 15]) + offset += 15 + overlap = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap) * overlap) + return 1.0 - fidelity, (fidelity, overlap) + + gens = jnp.asarray(solution_12._GENERATORS, dtype=jnp.complex64) + + def candidate_objective(p): + gates = solution_12._su4_batch(p.reshape(31, 15), gens) + circuit = tc.Circuit(N) + for i in range(1, N, 2): + circuit.x(i) + k = 0 + for layer in range(2): + for i in range(layer % 2, N - 1, 2): + circuit.any(i, i + 1, unitary=gates[k]) + k += 1 + overlap = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap) * overlap) + return 1.0 - fidelity, (fidelity, overlap) + + (ref_losses,) = scan_losses(reference_objective, CHECK_STEPS) + cand_losses, norm_bounds = scan_losses( + candidate_objective, + 5000, + extra=lambda p: jnp.max(jnp.sum(jnp.abs(p.reshape(31, 15)), axis=1)), + ) + delta = np.abs(ref_losses - cand_losses[:CHECK_STEPS]) + report = { + "generated_by": "research/task-12/check_equivalence.py", + "environment_lock": "envs/tensorcircuit-py311/requirements.lock", + "max_abs_loss_delta_first_k_steps": { + "10": float(delta[:10].max()), + "50": float(delta[:50].max()), + "100": float(delta[:100].max()), + "400": float(delta[:400].max()), + }, + "candidate_final_loss_5000": float(cand_losses[-1]), + "max_generator_norm_bound_over_5000_steps": float(norm_bounds.max()), + "pade_scaled_norm_at_max": float(norm_bounds.max() / 32.0), + "note": ( + "Loss histories are compared step-by-step from the identical " + "seeded initialization; a zero delta over the first steps means " + "the candidate reproduces the reference trajectory bit-for-bit " + "in float32 before ordinary round-off noise is amplified by " + "optimizer dynamics." + ), + } + out = Path(__file__).parent / "profiles" / "equivalence-check.json" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/optimized_solutions/challenge-12/research/figures/factor-ablation.svg b/optimized_solutions/challenge-12/research/figures/factor-ablation.svg new file mode 100644 index 0000000..8fad390 --- /dev/null +++ b/optimized_solutions/challenge-12/research/figures/factor-ablation.svg @@ -0,0 +1,2499 @@ + + + + + + + + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/optimized_solutions/challenge-12/research/plot_factor_ablation.py b/optimized_solutions/challenge-12/research/plot_factor_ablation.py new file mode 100644 index 0000000..af0aadf --- /dev/null +++ b/optimized_solutions/challenge-12/research/plot_factor_ablation.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Render Task 12 factor-ablation panels from tracked measurements.""" + +import json +from pathlib import Path + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt + + +ROOT = Path(__file__).resolve().parent +OUT = ROOT / "figures" / "factor-ablation.svg" + + +def panel(ax, title, ratio, control, ablation): + bars = ax.bar( + [f"Promoted\n{control}", f"Removal\n{ablation}"], + [1, ratio], + color=["#4472C4", "#C44E52"], + ) + ax.axhline(1, color="black", linewidth=0.8) + ax.set_ylabel("Runtime normalized to promoted") + ax.set_title(title) + for bar, value in zip(bars, [1, ratio]): + ax.text( + bar.get_x() + bar.get_width() / 2, + value, + f"{value:.3f}×", + ha="center", + va="bottom", + fontsize=8, + ) + + +def main(): + scan = json.loads((ROOT / "profiles" / "factor-ablation.json").read_text()) + plt.style.use("seaborn-v0_8-whitegrid") + plt.rcParams["svg.hashsalt"] = "task-12-factor-ablation" + fig, axes = plt.subplots(1, 3, figsize=(13.2, 4.2)) + panel( + axes[0], + "Fixed Pade → adaptive expm", + 0.3808 / 0.1225, + "0.1225 ms", + "0.3808 ms", + ) + cold = scan["cold_profile_total_sec"] + panel( + axes[1], + "Scan → Python dispatch", + cold["python_loop"] / cold["scan"], + f'{cold["scan"]:.3f} s cold', + f'{cold["python_loop"]:.3f} s cold', + ) + panel( + axes[2], + "Pair-fused → unfused network", + 2.3206 / 2.1237, + "2.124 s", + "2.321 s", + ) + fig.suptitle( + "Task 12 factor ablations — fixed Pade dominates; scan and pair fusion are secondary", + fontsize=12, + fontweight="bold", + ) + fig.tight_layout() + OUT.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(OUT, bbox_inches="tight", metadata={"Date": None}) + + +if __name__ == "__main__": + main() diff --git a/optimized_solutions/challenge-12/research/profile_expm.py b/optimized_solutions/challenge-12/research/profile_expm.py new file mode 100755 index 0000000..f6e50cb --- /dev/null +++ b/optimized_solutions/challenge-12/research/profile_expm.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Micro-benchmark and accuracy audit of batched 4x4 SU(4) exponentials. + +Compares the generic norm-adaptive ``jax.scipy.linalg.expm`` against the +candidate's fixed-order scaling-and-squaring diagonal Pade(3,3) inside a +value_and_grad + Adam scan step shaped like the Task 12 gate build, and +measures both approximants against a float64 SciPy reference over the +generator-norm range relevant to this protocol. + +Run from the repository root inside the pinned environment: + + PYTHONPATH=envs/tensorcircuit-py311 NUMBA_DISABLE_JIT=1 \ + python research/task-12/profile_expm.py + +Writes ``research/task-12/profiles/expm-microbench.json``. +""" + +from __future__ import annotations + +import json +import sys +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "src" / "solutions" / "task-12")) + +import numpy as np +import optax +import tensorcircuit as tc + +tc.set_backend("jax") +tc.set_dtype("complex64") + +import jax +import jax.numpy as jnp +import solution_12 # campaign candidate; provides _GENERATORS and _su4_batch +from jax.scipy.linalg import expm as jexpm + +GENS = solution_12._GENERATORS +GENS_J = jnp.asarray(GENS, dtype=jnp.complex64) + + +def accuracy_rows(): + import scipy.linalg + + rows = [] + rng = np.random.default_rng(0) + for scale in (0.05, 0.5, 1.0, 2.0): + theta = rng.normal(scale=scale, size=(31, 15)) + h = np.einsum("gi,iab->gab", theta, GENS) + exact = np.stack( + [scipy.linalg.expm(-1j * m.astype(np.complex128)) for m in h] + ) + got_jexpm = np.asarray(jexpm(jnp.asarray(-1j * h, dtype=jnp.complex64))) + got_pade = np.asarray( + solution_12._su4_batch(jnp.asarray(theta, dtype=jnp.float32), GENS_J) + ) + norm_bound = float(np.abs(theta).sum(axis=1).max()) + rows.append( + { + "theta_scale": scale, + "max_generator_norm_bound": round(norm_bound, 3), + "jexpm_max_abs_err": float(np.abs(got_jexpm - exact).max()), + "pade33_s5_max_abs_err": float(np.abs(got_pade - exact).max()), + "pade33_s5_max_unitarity_defect": float( + np.abs( + np.einsum("gab,gcb->gac", got_pade, got_pade.conj()) + - np.eye(4) + ).max() + ), + } + ) + return rows + + +def bench(fn, n=2000): + opt = optax.adam(0.02) + + def loss_fn(p): + u = fn(p.reshape(31, 15)) + return jnp.sum(jnp.abs(u - jnp.eye(4, dtype=u.dtype)) ** 2) + + def body(carry, _): + p, s = carry + loss, g = jax.value_and_grad(loss_fn)(p) + upd, s = opt.update(g, s, p) + return (optax.apply_updates(p, upd), s), loss + + @jax.jit + def train(p, s): + return jax.lax.scan(body, (p, s), None, length=n) + + p = jnp.asarray( + np.random.default_rng(1).normal(scale=0.02, size=(465,)).astype(np.float32) + ) + s = opt.init(p) + out = train(p, s) + jax.block_until_ready(out) + t0 = time.perf_counter() + out = train(p, s) + jax.block_until_ready(out) + return (time.perf_counter() - t0) / n + + +def main(): + def jexpm_gates(thetas): + h = jnp.einsum("gi,iab->gab", thetas.astype(jnp.complex64), GENS_J) + return jexpm(-1j * h) + + report = { + "generated_by": "research/task-12/profile_expm.py", + "environment_lock": "envs/tensorcircuit-py311/requirements.lock", + "accuracy_vs_float64_scipy": accuracy_rows(), + "scan_step_with_grad_and_adam_ms": { + "jax_scipy_expm_pade13_adaptive": round(bench(jexpm_gates) * 1e3, 4), + "fixed_pade33_s5_candidate": round( + bench(lambda t: solution_12._su4_batch(t, GENS_J)) * 1e3, 4 + ), + }, + "note": ( + "Both rows time one scan step containing only the batched 31-gate " + "construction, its gradient, and an Adam update; the candidate " + "kernel is the exact function used by solution_12._su4_batch." + ), + } + out = Path(__file__).parent / "profiles" / "expm-microbench.json" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/optimized_solutions/challenge-12/research/profile_factor_ablation.py b/optimized_solutions/challenge-12/research/profile_factor_ablation.py new file mode 100644 index 0000000..e7f8337 --- /dev/null +++ b/optimized_solutions/challenge-12/research/profile_factor_ablation.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Whole-training scan removal ablation for the merged Task 12 candidate.""" + +from __future__ import annotations + +import importlib.util +import json +import time +from pathlib import Path + +import jax +import jax.numpy as jnp +import numpy as np +import optax +import tensorcircuit as tc + + +ROOT = Path(__file__).resolve().parents[2] +PROFILE_REFERENCE = ROOT / "research/task-12/profile_reference.py" +SOLUTION = ROOT / "src/solutions/task-12/solution_12.py" + + +def load(path: Path, name: str): + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot import {path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def ready(value) -> None: + leaves = jax.tree_util.tree_leaves(value) + if leaves: + jax.block_until_ready(leaves[0]) + + +def lower_compile(fn, *args): + started = time.perf_counter() + lowered = jax.jit(fn).lower(*args) + lower_sec = time.perf_counter() - started + started = time.perf_counter() + compiled = lowered.compile() + compile_sec = time.perf_counter() - started + return compiled, { + "lower_sec": lower_sec, + "compile_sec": compile_sec, + "stablehlo_line_count": lowered.as_text().count("\n"), + } + + +def main() -> None: + profile_reference = load(PROFILE_REFERENCE, "task12_profile_reference") + solution = load(SOLUTION, "task12_candidate") + config = dict(profile_reference.CONFIG) + dmrg_state = profile_reference.build_dmrg_state() + + K = tc.set_backend("jax") + tc.set_dtype("complex64") + tc.set_contractor("omeco") + + n_qubits = config["n_qubits"] + layer_bonds = [ + list(range(layer % 2, n_qubits - 1, 2)) + for layer in range(config["n_layers"]) + ] + n_gates = sum(len(bonds) for bonds in layer_bonds) + target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() + gens = jnp.asarray(solution._GENERATORS, dtype=jnp.complex64) + optimizer = optax.adam(config["learning_rate"]) + + def objective(p): + gates = solution._su4_batch(p.reshape(n_gates, 15), gens) + circuit = tc.Circuit(n_qubits) + for i in range(1, n_qubits, 2): + circuit.x(i) + k = 0 + for bonds in layer_bonds: + for i in bonds: + circuit.any(i, i + 1, unitary=gates[k]) + k += 1 + overlap = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap) * overlap) + return 1.0 - fidelity, (fidelity, overlap) + + def body(carry, _): + p, state = carry + (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) + updates, state = optimizer.update(grads, state, p) + return (optax.apply_updates(p, updates), state), (loss,) + aux + + def one_step(carry): + return body(carry, None) + + def scan_train(carry): + return jax.lax.scan( + body, + carry, + None, + length=config["max_steps"], + ) + + rng = np.random.default_rng(config["seed"]) + params = jnp.asarray( + rng.normal( + scale=config["initial_parameter_scale"], + size=(15 * n_gates,), + ).astype(np.float32) + ) + init = (params, optimizer.init(params)) + compiled_scan, scan_compile = lower_compile(scan_train, init) + compiled_step, step_compile = lower_compile(one_step, init) + + scan_times = [] + scan_output = None + for _ in range(3): + started = time.perf_counter() + scan_output = compiled_scan(init) + ready(scan_output) + scan_times.append(time.perf_counter() - started) + + loop_times = [] + loop_history = None + loop_final = None + for _ in range(3): + carry = init + losses, fidelities, overlaps = [], [], [] + started = time.perf_counter() + for _ in range(config["max_steps"]): + carry, values = compiled_step(carry) + loss, fidelity, overlap = values + losses.append(loss) + fidelities.append(fidelity) + overlaps.append(overlap) + ready((carry, losses[-1])) + loop_times.append(time.perf_counter() - started) + loop_final = carry + loop_history = ( + K.stack(losses), + K.stack(fidelities), + K.stack(overlaps), + ) + + scan_final, scan_history = scan_output + history_errors = [ + float(np.max(np.abs(np.asarray(a) - np.asarray(b)))) + for a, b in zip(scan_history, loop_history) + ] + parameter_error = float( + np.max( + np.abs( + np.asarray(scan_final[0]) + - np.asarray(loop_final[0]) + ) + ) + ) + report = { + "generated_by": "research/task-12/profile_factor_ablation.py", + "scope": "scan removal with identical candidate objective", + "steps": config["max_steps"], + "scan_compile": scan_compile, + "step_compile": step_compile, + "scan_execution_sec": scan_times, + "scan_mean_execution_sec": float(np.mean(scan_times)), + "python_loop_execution_sec": loop_times, + "python_loop_mean_execution_sec": float(np.mean(loop_times)), + "history_max_abs_errors": history_errors, + "parameter_max_abs_error": parameter_error, + } + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/optimized_solutions/challenge-12/research/profile_reference.py b/optimized_solutions/challenge-12/research/profile_reference.py new file mode 100755 index 0000000..3968fc0 --- /dev/null +++ b/optimized_solutions/challenge-12/research/profile_reference.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 +"""Stage-split profiling for the Task 12 reference and campaign candidate. + +Splits end-to-end time into: quimb-target conversion, jit trace (including +contraction-path search), XLA compile, and the 5000-step optimizer loop, using +ahead-of-time lowering (``jit(...).lower()`` / ``.compile()``). + +Run from the repository root inside the pinned environment: + + PYTHONPATH=envs/tensorcircuit-py311 NUMBA_DISABLE_JIT=1 \ + python research/task-12/profile_reference.py + +Writes ``research/task-12/profiles/reference-profile.json``. +""" + +from __future__ import annotations + +import importlib +import json +import sys +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / "references" / "task-12")) +sys.path.insert(0, str(ROOT / "src" / "solutions" / "task-12")) + +CONFIG = { + "n_qubits": 32, + "zz_anisotropy": 1.4, + "staggered_field": 0.2, + "dmrg_chi": 8, + "dmrg_sweeps": 4, + "dmrg_tolerance": 1e-7, + "n_layers": 2, + "max_steps": 5000, + "learning_rate": 0.02, + "initial_parameter_scale": 0.02, + "seed": 2039, + "fidelity_threshold": 0.85, +} + + +def build_dmrg_state(): + import quimb.tensor as qtn + + n = CONFIG["n_qubits"] + ham = qtn.SpinHam1D(S=0.5) + for i in range(n - 1): + ham[i, i + 1] += 4.0, "X", "X" + ham[i, i + 1] += 4.0, "Y", "Y" + ham[i, i + 1] += 4.0 * CONFIG["zz_anisotropy"], "Z", "Z" + for i in range(n): + ham[i] += 2.0 * CONFIG["staggered_field"] * ((-1) ** i), "Z" + dmrg = qtn.DMRG2( + ham.build_mpo(n), + bond_dims=[CONFIG["dmrg_chi"]], + cutoffs=1e-8, + ) + dmrg.solve(tol=CONFIG["dmrg_tolerance"], max_sweeps=CONFIG["dmrg_sweeps"], verbosity=0) + dmrg.state.normalize() + return dmrg.state + + +def profile_reference(dmrg_state, n_measure=1000): + """Replicates references/task-12/solution_12.py with stage timers.""" + import numpy as np + import optax + import tensorcircuit as tc + + K = tc.set_backend("jax") + tc.set_dtype("complex64") + tc.set_contractor("omeco") + import jax + + n_qubits = CONFIG["n_qubits"] + parameter_count = 0 + for layer in range(CONFIG["n_layers"]): + parameter_count += 15 * len(range(layer % 2, n_qubits - 1, 2)) + rng = np.random.default_rng(CONFIG["seed"]) + params = rng.normal( + scale=CONFIG["initial_parameter_scale"], size=(parameter_count,) + ).astype(np.float32) + params = K.convert_to_tensor(params) + + t0 = time.perf_counter() + target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() + t_convert = time.perf_counter() - t0 + + optimizer = optax.adam(CONFIG["learning_rate"]) + opt_state = optimizer.init(params) + + def objective(p): + circuit = tc.Circuit(n_qubits) + for i in range(1, n_qubits, 2): + circuit.x(i) + offset = 0 + for layer in range(CONFIG["n_layers"]): + for i in range(layer % 2, n_qubits - 1, 2): + circuit.su4(i, i + 1, theta=p[offset : offset + 15]) + offset += 15 + overlap = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap) * overlap) + return 1.0 - fidelity, (fidelity, overlap) + + def train_step(p, state): + (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) + updates, state = optimizer.update(grads, state, p) + return optax.apply_updates(p, updates), state, loss, aux + + jitted = jax.jit(train_step) + t0 = time.perf_counter() + lowered = jitted.lower(params, opt_state) + t_trace = time.perf_counter() - t0 + hlo_lines = lowered.as_text().count("\n") + t0 = time.perf_counter() + compiled = lowered.compile() + t_compile = time.perf_counter() - t0 + + p, s = params, opt_state + for _ in range(20): + p, s, loss, _aux = compiled(p, s) + jax.block_until_ready(loss) + t0 = time.perf_counter() + for _ in range(n_measure): + p, s, loss, _aux = compiled(p, s) + jax.block_until_ready(loss) + per_step = (time.perf_counter() - t0) / n_measure + + return { + "solution": "reference (replicated objective)", + "quimb_conversion_sec": round(t_convert, 4), + "jit_trace_sec": round(t_trace, 3), + "xla_compile_sec": round(t_compile, 3), + "stablehlo_line_count": hlo_lines, + "steady_state_step_ms": round(per_step * 1e3, 4), + "loop_5000_estimate_sec": round(per_step * 5000, 3), + "total_estimate_sec": round( + t_convert + t_trace + t_compile + per_step * 5000, 3 + ), + } + + +def profile_candidate(dmrg_state): + """End-to-end candidate timing plus its scan-level stage split.""" + import numpy as np + import optax + import tensorcircuit as tc + + K = tc.set_backend("jax") + tc.set_dtype("complex64") + tc.set_contractor("omeco") + import jax + import jax.numpy as jnp + + solution = importlib.import_module("solution_12") + gens = jnp.asarray(solution._GENERATORS, dtype=jnp.complex64) + target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() + optimizer = optax.adam(CONFIG["learning_rate"]) + + n_qubits = CONFIG["n_qubits"] + layer_bonds = [ + list(range(layer % 2, n_qubits - 1, 2)) + for layer in range(CONFIG["n_layers"]) + ] + n_gates = sum(len(b) for b in layer_bonds) + + def objective(p): + gates = solution._su4_batch(p.reshape(n_gates, 15), gens) + circuit = tc.Circuit(n_qubits) + for i in range(1, n_qubits, 2): + circuit.x(i) + k = 0 + for bonds in layer_bonds: + for i in bonds: + circuit.any(i, i + 1, unitary=gates[k]) + k += 1 + overlap = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap) * overlap) + return 1.0 - fidelity, (fidelity, overlap) + + def body(carry, _): + p, s = carry + (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) + updates, s = optimizer.update(grads, s, p) + return (optax.apply_updates(p, updates), s), (loss,) + aux + + def train(p, s): + return jax.lax.scan(body, (p, s), None, length=CONFIG["max_steps"]) + + rng = np.random.default_rng(CONFIG["seed"]) + params = jnp.asarray( + rng.normal( + scale=CONFIG["initial_parameter_scale"], size=(15 * n_gates,) + ).astype(np.float32) + ) + state = optimizer.init(params) + + jitted = jax.jit(train) + t0 = time.perf_counter() + lowered = jitted.lower(params, state) + t_trace = time.perf_counter() - t0 + hlo_lines = lowered.as_text().count("\n") + t0 = time.perf_counter() + compiled = lowered.compile() + t_compile = time.perf_counter() - t0 + t0 = time.perf_counter() + out = compiled(params, state) + jax.block_until_ready(out) + t_loop = time.perf_counter() - t0 + + return { + "solution": "candidate (e01 objective in a 5000-step scan)", + "jit_trace_sec": round(t_trace, 3), + "xla_compile_sec": round(t_compile, 3), + "stablehlo_line_count": hlo_lines, + "scan_loop_5000_sec": round(t_loop, 3), + "steady_state_step_ms": round(t_loop / CONFIG["max_steps"] * 1e3, 4), + } + + +def main(): + dmrg_state = build_dmrg_state() + report = { + "generated_by": "research/task-12/profile_reference.py", + "environment_lock": "envs/tensorcircuit-py311/requirements.lock", + "reference": profile_reference(dmrg_state), + "candidate": profile_candidate(dmrg_state), + } + out = Path(__file__).parent / "profiles" / "reference-profile.json" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/optimized_solutions/challenge-12/research/profiles/equivalence-check.json b/optimized_solutions/challenge-12/research/profiles/equivalence-check.json new file mode 100644 index 0000000..b06dd57 --- /dev/null +++ b/optimized_solutions/challenge-12/research/profiles/equivalence-check.json @@ -0,0 +1,14 @@ +{ + "generated_by": "research/task-12/check_equivalence.py", + "environment_lock": "envs/tensorcircuit-py311/requirements.lock", + "max_abs_loss_delta_first_k_steps": { + "10": 0.0, + "50": 3.981590270996094e-05, + "100": 6.973743438720703e-05, + "400": 0.0012745261192321777 + }, + "candidate_final_loss_5000": 0.12984925508499146, + "max_generator_norm_bound_over_5000_steps": 3.601597547531128, + "pade_scaled_norm_at_max": 0.11254992336034775, + "note": "Loss histories are compared step-by-step from the identical seeded initialization; a zero delta over the first steps means the candidate reproduces the reference trajectory bit-for-bit in float32 before ordinary round-off noise is amplified by optimizer dynamics." +} diff --git a/optimized_solutions/challenge-12/research/profiles/expm-microbench.json b/optimized_solutions/challenge-12/research/profiles/expm-microbench.json new file mode 100644 index 0000000..fc2ebad --- /dev/null +++ b/optimized_solutions/challenge-12/research/profiles/expm-microbench.json @@ -0,0 +1,39 @@ +{ + "generated_by": "research/task-12/profile_expm.py", + "environment_lock": "envs/tensorcircuit-py311/requirements.lock", + "accuracy_vs_float64_scipy": [ + { + "theta_scale": 0.05, + "max_generator_norm_bound": 0.919, + "jexpm_max_abs_err": 1.7127096142602416e-07, + "pade33_s5_max_abs_err": 5.277466592479941e-06, + "pade33_s5_max_unitarity_defect": 1.055002212524414e-05 + }, + { + "theta_scale": 0.5, + "max_generator_norm_bound": 7.892, + "jexpm_max_abs_err": 2.13937326610061e-07, + "pade33_s5_max_abs_err": 4.836887262698418e-06, + "pade33_s5_max_unitarity_defect": 1.0311603546142578e-05 + }, + { + "theta_scale": 1.0, + "max_generator_norm_bound": 18.238, + "jexpm_max_abs_err": 1.0629713454831844e-05, + "pade33_s5_max_abs_err": 3.2686023539371067e-06, + "pade33_s5_max_unitarity_defect": 7.510185241699219e-06 + }, + { + "theta_scale": 2.0, + "max_generator_norm_bound": 33.564, + "jexpm_max_abs_err": 6.245178907907371e-06, + "pade33_s5_max_abs_err": 3.230430040574948e-06, + "pade33_s5_max_unitarity_defect": 8.344650268554688e-06 + } + ], + "scan_step_with_grad_and_adam_ms": { + "jax_scipy_expm_pade13_adaptive": 0.3808, + "fixed_pade33_s5_candidate": 0.1225 + }, + "note": "Both rows time one scan step containing only the batched 31-gate construction, its gradient, and an Adam update; the candidate kernel is the exact function used by solution_12._su4_batch." +} diff --git a/optimized_solutions/challenge-12/research/profiles/factor-ablation.json b/optimized_solutions/challenge-12/research/profiles/factor-ablation.json new file mode 100644 index 0000000..c025f22 --- /dev/null +++ b/optimized_solutions/challenge-12/research/profiles/factor-ablation.json @@ -0,0 +1,44 @@ +{ + "generated_by": "research/task-12/profile_factor_ablation.py", + "scope": "scan removal with identical candidate objective", + "environment": { + "image": "orbitbreakers-expert-benchmarks:tensorcircuit-py311", + "cpus": 6, + "memory": "7g", + "network": "none" + }, + "steps": 5000, + "scan": { + "lower_sec": 0.60646366, + "compile_sec": 0.316957074, + "stablehlo_line_count": 954, + "execution_sec": [ + 0.913055518, + 0.913425434, + 0.912820772 + ], + "mean_execution_sec": 0.913100575 + }, + "python_loop": { + "step_lower_sec": 0.525993696, + "step_compile_sec": 0.303303482, + "stablehlo_line_count": 927, + "execution_sec": [ + 1.166982586, + 1.156237215, + 1.155474601 + ], + "mean_execution_sec": 1.159564801 + }, + "loop_over_scan_execution": 1.2699201302, + "cold_profile_total_sec": { + "scan": 1.836521309, + "python_loop": 1.988861979 + }, + "history_max_abs_errors": [ + 0.0, + 0.0, + 0.0 + ], + "parameter_max_abs_error": 0.0 +} diff --git a/optimized_solutions/challenge-12/research/profiles/reference-profile.json b/optimized_solutions/challenge-12/research/profiles/reference-profile.json new file mode 100644 index 0000000..7de9d4c --- /dev/null +++ b/optimized_solutions/challenge-12/research/profiles/reference-profile.json @@ -0,0 +1,22 @@ +{ + "generated_by": "research/task-12/profile_reference.py", + "environment_lock": "envs/tensorcircuit-py311/requirements.lock", + "reference": { + "solution": "reference (replicated objective)", + "quimb_conversion_sec": 0.1379, + "jit_trace_sec": 1.693, + "xla_compile_sec": 3.373, + "stablehlo_line_count": 8884, + "steady_state_step_ms": 0.6668, + "loop_5000_estimate_sec": 3.334, + "total_estimate_sec": 8.538 + }, + "candidate": { + "solution": "candidate (e01 objective in a 5000-step scan)", + "jit_trace_sec": 0.744, + "xla_compile_sec": 0.408, + "stablehlo_line_count": 954, + "scan_loop_5000_sec": 0.952, + "steady_state_step_ms": 0.1904 + } +} diff --git a/optimized_solutions/challenge-12/solution_12_batched_su4.py b/optimized_solutions/challenge-12/solution_12_batched_su4.py new file mode 100644 index 0000000..c5ef859 --- /dev/null +++ b/optimized_solutions/challenge-12/solution_12_batched_su4.py @@ -0,0 +1,108 @@ +""" +Task Suite Problem 12: variational circuit to MPS overlap optimization. + +The solution contracts a DMRG-MPS target bra directly with a trainable circuit +ket and differentiates the scalar overlap loss with respect to circuit angles. +""" + +import numpy as np +import optax + +import tensorcircuit as tc + +K = tc.set_backend("jax") +tc.set_dtype("complex64") +tc.set_contractor("omeco") + +import jax +import jax.numpy as jnp + +_PAULI = { + "i": np.eye(2, dtype=complex), + "x": np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex), + "y": np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex), + "z": np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex), +} +# 15 su(4) generators in the exact order used by tc.gates.su4_gate +_GENERATORS = np.stack( + [ + np.einsum("ab,cd->acbd", _PAULI[a], _PAULI[b]).reshape(4, 4) + for a in "ixyz" + for b in "ixyz" + if (a, b) != ("i", "i") + ] +) + + +def _su4_batch(thetas, gens): + """(gates, 15) angles -> (gates, 4, 4) SU4 matrices exp(-i sum theta G). + + Fixed 2**5 scaling-and-squaring with a diagonal Pade(3,3) core: exactly + unitary for anti-Hermitian input, static graph, cheap to differentiate. + """ + a = jnp.einsum("gi,iab->gab", thetas.astype(gens.dtype), gens) / 32j + eye = jnp.eye(4, dtype=a.dtype) + a2 = a @ a + odd = a @ (a2 + 60.0 * eye) + even = 12.0 * a2 + 120.0 * eye + r = jnp.linalg.solve(even - odd, even + odd) + for _ in range(5): + r = r @ r + return r + + +def run_solution(config): + n_qubits = config["n_qubits"] + layer_bonds = [ + list(range(layer % 2, n_qubits - 1, 2)) + for layer in range(config["n_layers"]) + ] + n_gates = sum(len(bonds) for bonds in layer_bonds) + + rng = np.random.default_rng(config["seed"]) + params = rng.normal( + scale=config["initial_parameter_scale"], + size=(15 * n_gates,), + ).astype(np.float32) + params = K.convert_to_tensor(params) + + target_mps = tc.quantum.quimb2qop(config["dmrg_state"]) + target_bra = target_mps.adjoint() + gens = jnp.asarray(_GENERATORS, dtype=jnp.complex64) + optimizer = optax.adam(config["learning_rate"]) + opt_state = optimizer.init(params) + + def objective(p): + gates = _su4_batch(p.reshape(n_gates, 15), gens) + circuit = tc.Circuit(n_qubits) + for i in range(1, n_qubits, 2): + circuit.x(i) + k = 0 + for bonds in layer_bonds: + for i in bonds: + circuit.any(i, i + 1, unitary=gates[k]) + k += 1 + overlap_value = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap_value) * overlap_value) + return 1.0 - fidelity, (fidelity, overlap_value) + + def train_step(carry, _): + p, state = carry + (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) + updates, state = optimizer.update(grads, state, p) + return (optax.apply_updates(p, updates), state), (loss,) + aux + + @jax.jit + def train(p, state): + return jax.lax.scan( + train_step, (p, state), None, length=config["max_steps"] + ) + + (params, _), (losses, fidelities, overlaps) = train(params, opt_state) + + return { + "loss_history": K.numpy(losses), + "fidelity_history": K.numpy(fidelities), + "final_parameters": K.numpy(params), + "final_overlap_phase": np.asarray(np.angle(K.numpy(overlaps)[-1])), + } diff --git a/optimized_solutions/challenge-12/solution_12_pair_fused.py b/optimized_solutions/challenge-12/solution_12_pair_fused.py new file mode 100644 index 0000000..02b71e3 --- /dev/null +++ b/optimized_solutions/challenge-12/solution_12_pair_fused.py @@ -0,0 +1,138 @@ +""" +Task Suite Problem 12: variational circuit to MPS overlap optimization. + +The solution contracts a DMRG-MPS target bra directly with a trainable circuit +ket and differentiates the scalar overlap loss with respect to circuit angles. +""" + +import numpy as np +import optax + +import tensorcircuit as tc + +K = tc.set_backend("jax") +tc.set_dtype("complex64") +tc.set_contractor("omeco") + +import jax +import jax.numpy as jnp + +_PAULI = { + "i": np.eye(2, dtype=complex), + "x": np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex), + "y": np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex), + "z": np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex), +} +# 15 su(4) generators in the exact order used by tc.gates.su4_gate +_GENERATORS = np.stack( + [ + np.einsum("ab,cd->acbd", _PAULI[a], _PAULI[b]).reshape(4, 4) + for a in "ixyz" + for b in "ixyz" + if (a, b) != ("i", "i") + ] +) +# constant single-site basis permutation |b> -> |b xor 1> on the second qubit +# of a fused pair; folds the Neel |01> preparation into the layer-1 gates +_NEEL_PERM = np.eye(4, dtype=complex)[:, [1, 0, 3, 2]] + + +def _su4_batch(thetas, gens): + """(gates, 15) angles -> (gates, 4, 4) SU4 matrices exp(-i sum theta G). + + Fixed 2**5 scaling-and-squaring with a diagonal Pade(3,3) core: exactly + unitary for anti-Hermitian input, static graph, cheap to differentiate. + """ + a = jnp.einsum("gi,iab->gab", thetas.astype(gens.dtype), gens) / 32j + eye = jnp.eye(4, dtype=a.dtype) + a2 = a @ a + odd = a @ (a2 + 60.0 * eye) + even = 12.0 * a2 + 120.0 * eye + r = jnp.linalg.solve(even - odd, even + odd) + for _ in range(5): + r = r @ r + return r + + +def _pair_fused_target_bra(dmrg_state): + """Pair-fuse the quimb DMRG MPS into a 16-site QuVector bra (chi <= 8).""" + mps = dmrg_state.copy() + mps.permute_arrays("lpr") + arrays = [np.asarray(a, dtype=np.complex64) for a in mps.arrays] + arrays[0] = arrays[0].reshape(1, *arrays[0].shape) + arrays[-1] = arrays[-1].reshape(*arrays[-1].shape, 1) + fused = [] + for j in range(len(arrays) // 2): + pair = np.einsum("lpm,mqr->lpqr", arrays[2 * j], arrays[2 * j + 1]) + fused.append(pair.reshape(pair.shape[0], 4, pair.shape[3])) + fused[0] = fused[0][0] + fused[-1] = fused[-1][..., 0] + nodes = [tc.quantum.Node(t) for t in fused] + for left, right in zip(nodes[:-1], nodes[1:]): + left[-1] ^ right[0] + out_edges = [nodes[0][0]] + [node[1] for node in nodes[1:]] + return tc.quantum.QuVector(out_edges).adjoint() + + +def run_solution(config): + n_qubits = config["n_qubits"] + n_sites = n_qubits // 2 + layer_sizes = [ + len(range(layer % 2, n_qubits - 1, 2)) + for layer in range(config["n_layers"]) + ] + n_gates = sum(layer_sizes) + + rng = np.random.default_rng(config["seed"]) + params = rng.normal( + scale=config["initial_parameter_scale"], + size=(15 * n_gates,), + ).astype(np.float32) + params = K.convert_to_tensor(params) + + target_bra = _pair_fused_target_bra(config["dmrg_state"]) + gens = jnp.asarray(_GENERATORS, dtype=jnp.complex64) + neel_perm = jnp.asarray(_NEEL_PERM, dtype=jnp.complex64) + eye2 = jnp.eye(2, dtype=jnp.complex64) + optimizer = optax.adam(config["learning_rate"]) + opt_state = optimizer.init(params) + + def objective(p): + gates = _su4_batch(p.reshape(n_gates, 15), gens) + # layer 1 on fused pairs, with the Neel preparation folded in + single = jnp.einsum("gab,bc->gac", gates[: layer_sizes[0]], neel_perm) + # layer 2 straddles fused sites j, j+1: I2 (x) SU4 (x) I2, regrouped + double = gates[layer_sizes[0] :].reshape(-1, 2, 2, 2, 2) + double = jnp.einsum("xX,gpqPQ,yY->gxpqyXPQY", eye2, double, eye2) + double = double.reshape(-1, 16, 16) + + circuit = tc.QuditCircuit(n_sites, dim=4) + for j in range(n_sites): + circuit.any(j, unitary=single[j]) + for j in range(n_sites - 1): + circuit.any(j, j + 1, unitary=double[j]) + + overlap_value = (target_bra @ circuit.quvector()).eval() + fidelity = K.real(K.conj(overlap_value) * overlap_value) + return 1.0 - fidelity, (fidelity, overlap_value) + + def train_step(carry, _): + p, state = carry + (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) + updates, state = optimizer.update(grads, state, p) + return (optax.apply_updates(p, updates), state), (loss,) + aux + + @jax.jit + def train(p, state): + return jax.lax.scan( + train_step, (p, state), None, length=config["max_steps"] + ) + + (params, _), (losses, fidelities, overlaps) = train(params, opt_state) + + return { + "loss_history": K.numpy(losses), + "fidelity_history": K.numpy(fidelities), + "final_parameters": K.numpy(params), + "final_overlap_phase": np.asarray(np.angle(K.numpy(overlaps)[-1])), + } From 05d029c099895687ee72eaa2eeda3e1037ce1aa6 Mon Sep 17 00:00:00 2001 From: qingyunqian Date: Thu, 30 Jul 2026 11:58:30 +0800 Subject: [PATCH 2/2] Simplify Task 12 optimization evidence --- optimized_solutions/challenge-12/README.md | 57 +-- .../figures => }/factor-ablation.svg | 0 .../research/IMPLEMENTATION_COMPARISON.md | 222 ----------- .../challenge-12/research/INSIGHTS.md | 103 ----- .../challenge-12/research/LOG.md | 351 ------------------ .../challenge-12/research/SURVEY.md | 261 ------------- .../research/check_equivalence.py | 144 ------- .../research/plot_factor_ablation.py | 74 ---- .../challenge-12/research/profile_expm.py | 134 ------- .../research/profile_factor_ablation.py | 174 --------- .../research/profile_reference.py | 237 ------------ .../research/profiles/equivalence-check.json | 14 - .../research/profiles/expm-microbench.json | 39 -- .../research/profiles/factor-ablation.json | 44 --- .../research/profiles/reference-profile.json | 22 -- .../challenge-12/solution_12_pair_fused.py | 138 ------- 16 files changed, 30 insertions(+), 1984 deletions(-) rename optimized_solutions/challenge-12/{research/figures => }/factor-ablation.svg (100%) delete mode 100644 optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md delete mode 100644 optimized_solutions/challenge-12/research/INSIGHTS.md delete mode 100644 optimized_solutions/challenge-12/research/LOG.md delete mode 100644 optimized_solutions/challenge-12/research/SURVEY.md delete mode 100755 optimized_solutions/challenge-12/research/check_equivalence.py delete mode 100644 optimized_solutions/challenge-12/research/plot_factor_ablation.py delete mode 100755 optimized_solutions/challenge-12/research/profile_expm.py delete mode 100644 optimized_solutions/challenge-12/research/profile_factor_ablation.py delete mode 100755 optimized_solutions/challenge-12/research/profile_reference.py delete mode 100644 optimized_solutions/challenge-12/research/profiles/equivalence-check.json delete mode 100644 optimized_solutions/challenge-12/research/profiles/expm-microbench.json delete mode 100644 optimized_solutions/challenge-12/research/profiles/factor-ablation.json delete mode 100644 optimized_solutions/challenge-12/research/profiles/reference-profile.json delete mode 100644 optimized_solutions/challenge-12/solution_12_pair_fused.py diff --git a/optimized_solutions/challenge-12/README.md b/optimized_solutions/challenge-12/README.md index 774eb83..69f26d5 100644 --- a/optimized_solutions/challenge-12/README.md +++ b/optimized_solutions/challenge-12/README.md @@ -1,27 +1,30 @@ -# Challenge 12 optimized expert solutions - -This directory packages the final reviewed Task 12 campaign from Benchmark -PR [#6](https://github.com/hmyuuu/OrbitBreakersExpertBenchmarks/pull/6) plus -the Task 12 half of ablation PR -[#13](https://github.com/hmyuuu/OrbitBreakersExpertBenchmarks/pull/13), with -the final tree taken from Benchmark `main` at `7e2298b`. - -- `solution_12_batched_su4.py` is the promoted campaign candidate. -- `solution_12_pair_fused.py` is the separately tracked, faster exact - pair-fused variant. -- `research/IMPLEMENTATION_COMPARISON.md` is the final report. -- `research/profiles/`, `research/figures/`, and the component profiler - preserve the post-merge factor attribution. - -For upstream review, both optimized files restore the immutable expert's -module docstring verbatim. Only comments changed relative to the reviewed -Benchmark artifacts; executable code is identical. - -All six local-engine matched pairs passed in both sessions. The promoted -candidate measured `3.914003x` mean paired speedup; the pair-fused variant -measured `4.247771x`. The report explicitly marks the formal Docker promotion -rerun as pending rather than overstating the claim. - -The canonical expert under `tasks/challenge-12/solution/` is intentionally -unchanged. Benchmark-harness reproduction commands in the research record -should be run in the Benchmark repository pinned above. +# Challenge 12: batched fixed-order SU4 construction + +**Take-home insight.** Build all 31 SU4 generators in one batch and replace +31 separate adaptive Pade-13 exponentials with one fixed-order batched +Pade(3,3) scaling-and-squaring kernel. This shrinks the differentiated +compilation graph and is the dominant source of the end-to-end speedup. + +## Factor speedups + +| Factor | Measured speedup or effect | Decision | +|---|---:|---| +| Batched fixed-order Pade gate construction | `3.109x` for the isolated gate-build/gradient/Adam kernel | **Keep — dominant** | +| Whole-training scan | `1.270x` for execution, but only `1.083x` including cold lowering and compilation | Keep; secondary | +| Pair-fused ququart contraction | `1.093x` incremental end to end | Discard from the promoted upstream solution | + +![Task 12 factor ablation](factor-ablation.svg) + +## What the factors mean + +- **Batched fixed Pade** forms and exponentiates the complete `(31, 4, 4)` SU4 batch with one static TensorCircuit/JAX kernel. +- **Whole-training scan** runs all 5,000 Adam updates in one compiled backend loop. +- **Pair fusion** rewrites the network on 16 four-level sites, but its small tracked gain does not justify a second upstream solution. + +## End-to-end result + +All six matched local-engine pairs passed for the promoted +`solution_12_batched_su4.py`. Expert and optimized means were `9.082742 s` +and `2.320613 s`; mean paired speedup was `3.914003x` with a 95% t-interval +of `[3.876545x, 3.951460x]`. This is same-host local-engine evidence (4 vCPU, +pinned dependencies); the formal Docker promotion rerun remains outstanding. diff --git a/optimized_solutions/challenge-12/research/figures/factor-ablation.svg b/optimized_solutions/challenge-12/factor-ablation.svg similarity index 100% rename from optimized_solutions/challenge-12/research/figures/factor-ablation.svg rename to optimized_solutions/challenge-12/factor-ablation.svg diff --git a/optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md b/optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md deleted file mode 100644 index 83ce73f..0000000 --- a/optimized_solutions/challenge-12/research/IMPLEMENTATION_COMPARISON.md +++ /dev/null @@ -1,222 +0,0 @@ -# Task 12 Autoresearch Campaign Report - -## Scope and claim - -This campaign optimized only ORBIT-Q Task 12, the variational-circuit to -DMRG-MPS overlap workload (32 qubits, two SU4 brickwork layers, 465 -parameters, exactly 5000 Adam updates). It compares the immutable -human-expert reference with one candidate (`e01`) and one tracked -reference-derived variant, all measured with this repository's `./bench` -harness in the pinned lock environment. - -No matched external implementation publishes runtime for this exact -workload, evaluator, and allocation. The optimized source is therefore -called the **campaign-best implementation**, not a global SOTA -implementation. - -The campaign establishes a statistically valid local-engine runtime -improvement of 3.914x (candidate) and 4.248x (tracked variant). The campaign -host has no Docker daemon, so the formal `GOAL.md` Gate 3 Docker protocol -did not run here; a Docker-engine rerun of the same six-pair protocol is the -single missing step for formal promotion. It does not reach the 10x stretch -target. - -| Role | Artifact | Commit or hash | -|---|---|---| -| Immutable expert | [`tasks/challenge-12/solution/solution_12.py`](../../../tasks/challenge-12/solution/solution_12.py) | Benchmark SHA-256 `10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38` | -| Campaign best (candidate e01) | [`solution_12_batched_su4.py`](../solution_12_batched_su4.py) | Benchmark SHA-256 `1d9a36c2649e938666f680e98e423e966f82c503da64ce7e4e06c5e33344560a`; upstream package SHA-256 `9003f6507c8994a12a5cb17238ef15974fe0af629d87104d1968acc33aedaff8` after restoring the immutable docstring | -| Tracked variant | [`solution_12_pair_fused.py`](../solution_12_pair_fused.py) | Benchmark SHA-256 `5edd437829352c573b24ae2f9021ef6046c2cf53788a8b79c8811b8fe0e3103f`; upstream package SHA-256 `0b7354896a05ea88cc3a9e69745df4c40ac0d50397de344300b31ecd6ab4cf61` after restoring the immutable docstring | -| Candidate hypothesis | Experiment `e01` | Commit `ef005888101c98de30fda2df3331f5f8461bf0ab` | -| Candidate evidence | Six paired runs | Report SHA-256 `653aaaaabff79a4c9a2bdcdbea4a82b561d8cff0824b83f84df08f5efb2659cd` | -| Variant evidence | Six paired runs | Report SHA-256 `f7bc804a975b299a786b6bc519764a2dd926076e054d82d9aea20094cc0abed8` | -| Workload | `datasets/public/task-12/canonical.json`, dataset `orbitq-workloads-v20260728.2` | Expert validation report SHA-256 `6d15f64bdf03097f1423fa16e0f434c97b2d1e44dafd5973abec0e08df004975` | - -## Campaign-best result - -Experiment e01 made two execution changes and no protocol changes: - -1. All 31 SU4 gate matrices are built per step with one einsum against the - stacked su(4) generators and one batched fixed 2^5 scaling-and-squaring - diagonal Pade(3,3) matrix exponential, applied through `circuit.any`. -2. All 5000 Adam updates run inside a single `jax.lax.scan`. - -All 12 evaluator cells passed. The candidate won all six matched pairs. - -| Metric | Immutable expert | Campaign best (e01) | Tracked fused variant | -|---|---:|---:|---:| -| Passing runs | 6/6 (both sessions) | 6/6 | 6/6 | -| Mean runtime | 9.082742 s / 9.020622 s (per session) | 2.320613 s | 2.123708 s | -| Median runtime | 9.086063 s / 9.033664 s | 2.320519 s | 2.121520 s | -| Runtime standard error | 0.027423 s / 0.026975 s | 0.002886 s | 0.004555 s | -| Mean paired speedup | — | 3.914003x | 4.247771x | -| Paired-speedup standard error | — | 0.014572x | 0.019961x | -| 95% Student-t interval | — | 3.876545x–3.951460x | 4.196459x–4.299083x | -| Ratio-of-means improvement | — | 74.450306% | 76.457192% | - -Matched timings, candidate session (order alternates `reference->candidate` -/ `candidate->reference`): - -| Pair | Order | Reference | Candidate | Speedup | -|---:|---|---:|---:|---:| -| 1 | reference → candidate | 9.132 s | 2.324 s | 3.9299x | -| 2 | candidate → reference | 9.040 s | 2.317 s | 3.9023x | -| 3 | reference → candidate | 9.023 s | 2.318 s | 3.8926x | -| 4 | candidate → reference | 9.163 s | 2.311 s | 3.9651x | -| 5 | reference → candidate | 9.132 s | 2.323 s | 3.9312x | -| 6 | candidate → reference | 9.006 s | 2.331 s | 3.8629x | - -The complete evidence is recorded in [`LOG.md`](LOG.md), with distilled -lessons in [`INSIGHTS.md`](INSIGHTS.md). - -## Implementation - -### Batched fixed-order su(4) exponentials - -The expert builds each of the 31 SU4 gates separately: -`tc.gates.su4_gate` chains 15 scalar multiply/stack operations and one -norm-adaptive Pade-13 `jax.scipy.linalg.expm` per gate, and the whole chain -is differentiated. The campaign-best implementation builds every generator -in one einsum and exponentiates the whole `(31, 4, 4)` batch at once: - -```python -a = jnp.einsum("gi,iab->gab", thetas.astype(gens.dtype), gens) / 32j -a2 = a @ a -odd = a @ (a2 + 60.0 * eye) -even = 12.0 * a2 + 120.0 * eye -r = jnp.linalg.solve(even - odd, even + odd) # diagonal Pade(3,3) -for _ in range(5): - r = r @ r # undo the 2**5 scaling -``` - -A diagonal Pade approximant of an anti-Hermitian argument is exactly unitary -(Cayley-type transform), so the gates stay exactly unitary; the fixed-order -approximation only perturbs *which* unitary, by at most 3.2e-6 for generator -norms up to 33.6 (`profiles/expm-microbench.json`), while the training -trajectory never exceeds norm 3.61 (`profiles/equivalence-check.json`). -The static graph replaces the adaptive expm's norm estimate, branch logic, -and per-gate chains: StableHLO shrinks from 8884 to 954 lines, XLA compile -from ~3.4 s to ~0.4 s, and the measured gate-build-plus-Adam scan step from -0.381 ms to 0.123 ms (`profiles/reference-profile.json`, -`profiles/expm-microbench.json`). - -### Whole-training scan - -The expert JIT-compiles one update and dispatches it from Python 5000 times. -The campaign-best implementation compiles the same sequential Adam process -as one `jax.lax.scan`, returning all pre-update losses, fidelities, and -overlaps as stacked scan outputs. Dispatch is only ~5 us per step, so the -scan matters mainly because the optimized step itself is ~0.19 ms. - -### Tracked variant: pair-fused ququart contraction - -`variants/solution_12_fused.py` additionally contracts the depth-2 brickwork -as a depth-1 chain on 16 four-level sites (`tc.QuditCircuit(16, dim=4)`): -layer-1 gates become single-site unitaries with the Neel preparation folded -in as a constant basis permutation, layer-2 gates become two-site -`I2 (x) SU4 (x) I2` unitaries, and the DMRG target is pair-fused once -outside the loop into a `tc.quantum.QuVector` bra. Every contraction result -is unchanged; the network has half the nodes. After the gate-construction -fix this buys a further ~8% end to end (4.248x vs 3.914x), confirming the -contraction was never the dominant cost. - -## Preserved scientific work - -The reference, candidate, and variant all: - -- start from the Neel product state `|0101...01>` on 32 qubits; -- apply the same 31 trainable SU4 gates on the same brickwork bonds with the - same 15-generator su(4) parameterization in the same generator order; -- compute the loss as the direct tensor-network overlap between the - evaluator-provided DMRG-MPS bra and the circuit ket (no target-preparation - circuit, no oracle values); -- draw the identical seeded float32 initialization - (`default_rng(2039)`, scale 0.02); -- run exactly 5000 sequential Adam updates at learning rate 0.02 and record - pre-update loss and fidelity for every step; -- return `loss_history (5000,)`, `fidelity_history (5000,)`, - `final_parameters (465,)`, and `final_overlap_phase` as NumPy data; -- use complex64 TensorCircuit/JAX semantics in the pinned environment. - -Trajectory equivalence was audited directly -(`profiles/equivalence-check.json`): from the identical initialization the -candidate's first 10 recorded losses match the reference bit-for-bit in -float32, and the deviation after 400 steps (1.27e-03) is the ordinary -round-off scale amplified by optimizer dynamics — the same order as the -reference's own run-to-run scatter of final fidelities (0.86855–0.87016 -across the recorded sessions). - -## Profiling interpretation - -The immutable expert spends ~60% of its end-to-end time before the first -optimizer step: ~1.7 s of jit trace and ~3.4 s of XLA compile for an -8884-line StableHLO module, dominated by the 31 separate `su4` construction -chains; the 5000-step loop runs at ~0.67 ms per step, of which the batched -gate construction alone accounts for ~0.38 ms and the tensor-network -contraction is nearly free (the exact circuit state has bond dimension at -most 4). The evidence supports gate-construction batching with a fixed-order -exponential as the main improvement; the scan and the pair-fused network -address the remaining dispatch and node-count overheads. - -## Factor ablation addendum - -The original e01 result bundled fixed batched SU4 construction with the -whole-training scan. The tracked ququart variant already provides a clean -incremental contraction ablation. A post-merge scan-removal profile completes -the attribution: - -| Factor | Control | Removal or alternative | Measured contribution | Numerical check | -|---|---:|---:|---:|---:| -| Fixed batched Pade gate construction | 0.1225 ms per gate-build/grad/Adam kernel | 0.3808 ms with adaptive Pade-13 expm | adaptive/fixed `3.109x` for the isolated kernel | candidate error envelope already audited | -| Whole-training scan | 0.9131 s mean for 5000 steps | 1.1596 s with 5000 dispatches of the identical compiled step | loop/scan `1.270x` in execution; cold lower+compile+execute improves only 7.66% | all histories and parameters bitwise equal | -| Pair-fused ququart contraction | 2.3206 s canonical candidate mean | 2.1237 s tracked-variant mean | 8.49% lower end to end; `1.093x` incremental | 12/12 cells PASS | - -The factors are not additive because cold compilation and execution overlap in -the end-to-end timer. Still, their relative scale is clear: the fixed batched -gate construction is the dominant source of the `3.914x` candidate gain. -The scan saves about 0.25 s of 5000-step execution but only about 0.15 s after -its slightly larger cold compile boundary is included. Pair fusion is another -roughly 8.5% end-to-end refinement. The report no longer credits all three -items equally. - -Reproduction and sanitized output: - -- [`profile_factor_ablation.py`](profile_factor_ablation.py) -- [`profiles/factor-ablation.json`](profiles/factor-ablation.json) - -![Task 12 factor-ablation plots](figures/factor-ablation.svg) - -Each panel removes one factor and normalizes runtime to the promoted form. -The scan panel uses cold lower+compile+execute time, matching the end-to-end -evaluation emphasis. Regenerate with -[`plot_factor_ablation.py`](plot_factor_ablation.py). - -## Final-rerun status - -The paired sessions recorded above are the final local-engine benchmarks for -this campaign; no tuning followed them. The formal `GOAL.md` Docker -promotion protocol (one pinned container, six counterbalanced pairs, -`--engine docker`) has not run because this host has no Docker daemon. The -single remaining step for formal promotion is: - -```bash -./bench env build tensorcircuit-py311 -./bench run 12 --solution optimized --compare-to reference \ - --repeat 6 --engine docker --timeout 300 --no-build \ - --output results/task-12-e01-docker-promotion -``` - -on a Docker-capable host, expecting the same qualitative outcome. - -## Limits and next work - -- Local-engine evidence only; the Docker gate is deferred as above. -- One host (4 vCPU Intel Xeon cloud VM); no scaling claim; the fixed - workload prevents scale probes by contract. -- Python 3.12.3 with the exact lock versions (the pinned image ships - Python 3.11); a Docker rerun removes this deviation. -- The 10x stretch target would need a candidate mean near 0.91 s in the e01 - session; the measured campaign-best mean is 2.12 s (fused variant). The - remaining time is roughly 1.15 s of one-time trace/compile, 0.95 s of - optimizer steps, and 0.12 s of target conversion, so a 10x path would have - to attack XLA compile latency itself or amortize compilation across runs, - which the cold-cache measurement rule intentionally forbids. diff --git a/optimized_solutions/challenge-12/research/INSIGHTS.md b/optimized_solutions/challenge-12/research/INSIGHTS.md deleted file mode 100644 index ec1dbb8..0000000 --- a/optimized_solutions/challenge-12/research/INSIGHTS.md +++ /dev/null @@ -1,103 +0,0 @@ -# Task 12 Research Insights - -Task: `task-12` - -Last consolidated: 2026-07-28 - -Evidence ledger: [`LOG.md`](LOG.md) - -## Current best - -Experiment `e01` (`src/solutions/task-12/solution_12.py`, SHA-256 -`1d9a36c2649e938666f680e98e423e966f82c503da64ce7e4e06c5e33344560a`): batched -fixed-order su(4) exponentials plus a whole-training `jax.lax.scan`. Six -eligible local-engine pairs against the immutable reference: paired speedup -mean 3.914x ± 0.015x (95% Student-t CI 3.877x-3.951x), candidate mean -2.320613 s vs reference mean 9.082742 s, 6/6 pairs won. The tracked -pair-fused variant (`variants/solution_12_fused.py`) reaches 4.248x ± 0.020x -(CI 4.196x-4.299x) under the same protocol. Scope: this repository's Task 12 -canonical workload on the campaign host with `--engine local`; the Docker -promotion gate of `GOAL.md` remains closed pending a Docker rerun. - -## Preserved semantics - -- su(4) parameterization of every SU4 gate in the exact - `tc.gates.su4_gate` generator order; gates enter the circuit as unitaries - `expm(-1j * sum_i theta_i G_i)`. -- Seeded float32 initialization (`default_rng(2039)`, scale 0.02), exactly - 5000 sequential Adam updates at learning rate 0.02, pre-update loss and - fidelity recorded per step. -- Direct tensor-network overlap of the evaluator-provided DMRG-MPS bra with - the circuit ket; no target-state preparation circuit, no oracle values. -- complex64 TensorCircuit/JAX semantics; full output contract - (`loss_history`, `fidelity_history`, `final_parameters`, - `final_overlap_phase`). - -## Confirmed bottlenecks - -- ~60% of the reference's end-to-end time is jit trace plus XLA compile of an - 8884-line StableHLO module; the 31 separate `su4` constructions (15 - scalar multiply/stacks and one norm-adaptive Pade-13 `expm` per gate, - all differentiated) dominate the graph - (`profiles/reference-profile.json`, ledger entry "Reference bottleneck - profile"). -- Inside the step, gate construction dominates: batched-gate-build-only scan - steps cost 0.381 ms (adaptive expm) vs 0.123 ms (fixed Pade), while the - whole candidate step is 0.190 ms; the tensor-network contraction is nearly - free because the exact circuit state has bond dimension <= 4 - (`profiles/expm-microbench.json`, `profiles/reference-profile.json`). -- Per-step Python dispatch is only ~5 us, so a scan alone is a single-digit - percentage improvement (external precedent, recorded in `SURVEY.md` - hypothesis 3). - -## What worked - -- One einsum against the stacked 15 su(4) generators plus one batched - fixed 2^5 scaling-and-squaring diagonal Pade(3,3) exponential for all 31 - gates: exactly unitary for anti-Hermitian input (measured unitarity defect - <= 8.3e-6 in complex64), max deviation from a float64 exponential 3.2e-6 - over generator norms up to 33.6 while the training trajectory never - exceeds norm 3.61 (`profiles/equivalence-check.json`); shrinks StableHLO - from 8884 to 954 lines, compile from ~3.4 s to ~0.4 s, and the step from - 0.667 ms to 0.190 ms. -- `jax.lax.scan` over the 5000 Adam updates: compiles the step exactly once - and removes per-step dispatch; essential once the step itself is ~0.2 ms. -- Pair-fusing qubit pairs into 16 four-level `QuditCircuit` sites (tracked - variant): halves the contraction network; worth a further ~8% end to end - after the gate-construction fix (4.248x vs 3.914x). - -## What did not work - -- Scan alone on the unmodified reference objective: ~9% end to end - (external precedent; dispatch is not the bottleneck). Do not resubmit as a - standalone candidate. -- Fusing four qubits per site (dim-16 sites, 256x256 crossing gates): - contraction FLOPs explode; measured ~4x slower than the reference - formulation in the external precursor. Do not repeat unchanged. -- Absorbing the 16 single-site fused gates into the 15 two-site gates: - the absorption einsums cost what the removed network nodes saved. -- `lax.scan` with `unroll=4`: larger compile and slower steps. -- XLA CPU thread flags and core pinning: no gain; the kernels are too small - to parallelize. - -## Open hypotheses - -- Reproduce the six-pair protocol under `--engine docker` on a Docker-capable - host to open the formal `GOAL.md` promotion gate (highest value; no code - change required). -- A hand-scheduled 16-block transfer-matrix contraction inside the scan body - could cut the remaining ~0.06 ms of network kernels per step, but departs - from framework circuit APIs and was deliberately not pursued; revisit only - with maintainer guidance on framework-fidelity boundaries. -- Trimming the residual ~1.15 s of trace+compile (e.g., a lighter contractor - search for the 63-node fused network) is bounded by Amdahl to <25% of the - candidate's remaining runtime. - -## Evidence limits - -- All in-repo runtime evidence uses `--engine local` on one 4 vCPU host; - the Docker single-container protocol has not run here. -- The campaign measured only the canonical fixed workload (32 qubits, 2 - layers, 5000 updates, seed 2039); no scaling claim is made. -- Python 3.12.3 was used with the pinned lock (the Docker image ships - Python 3.11); package versions match the lock exactly. diff --git a/optimized_solutions/challenge-12/research/LOG.md b/optimized_solutions/challenge-12/research/LOG.md deleted file mode 100644 index 9426169..0000000 --- a/optimized_solutions/challenge-12/research/LOG.md +++ /dev/null @@ -1,351 +0,0 @@ -# Task 12 Autoresearch Campaign - -Destination: `research/task-12/LOG.md` - -Task: `task-12` - -Insights: [`INSIGHTS.md`](INSIGHTS.md) - -## Campaign selection and provenance - -Selected task: `task-12` (variational circuit to MPS overlap optimization). - -Live open pull requests on `sxzgroup/ORBIT-Q` inspected on 2026-07-28: -`#2` (ForgeCode agent solver), `#3` (scoring-policy fix), `#4` (Fable 5 -agent-axis run record), `#5` (GPT-5.6 Sol benchmark results). None is an -active Task 12 solution-improvement PR, so Task 12 is eligible for this -campaign. - -Precursor research disclosure: the bottleneck profiling and candidate design -for this campaign were first executed on 2026-07-28 against the -byte-equivalent ORBIT-Q publication reference in the fork -`QingyunQian/ORBIT-Q` (branch `cursor/optimize-challenge-12-f598`, -`optimized_sloutions/challenge-12/`), on the same 4 vCPU cloud VM but with -`tensorcircuit-nightly==1.8.0.dev20260726`. All figures used for claims in -this repository were re-measured here in the pinned lock environment; the -external numbers are context only and are never pooled with in-repo -measurements. See `SURVEY.md` for the porting statement. - -Campaign workspace deviation: this campaign runs in a single working clone on -the campaign host with branch `cursor/task-12-batched-su4-campaign-f598` -(Cursor cloud-agent branch-naming policy) instead of the -`codex/orbitbreakers/task-12/` worktree-per-hypothesis layout. -Exactly one hypothesis edits `src/solutions/task-12/solution_12.py`, so the -one-worktree-per-hypothesis isolation is preserved in substance. - -Engine deviation: the campaign host has no Docker daemon, so every in-repo -measurement uses `./bench ... --engine local` with a virtual environment -installed exactly from `envs/tensorcircuit-py311/requirements.lock` -(Python 3.12.3), the environment `sitecustomize.py` on `PYTHONPATH`, and -`NUMBA_DISABLE_JIT=1`. The `GOAL.md` Gate 3 Docker protocol therefore stays -closed on this host; local-engine paired evidence is recorded below and a -Docker rerun is requested in `IMPLEMENTATION_COMPARISON.md`. - -## Reference baseline (local engine) - -Date: 2026-07-28 - -Command: - -```bash -./bench run 12 --solution reference --repeat 6 --engine local \ - --timeout 300 --output results/task-12-reference-baseline-local -``` - -Reference SHA-256: -`10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38` - -Evaluator SHA-256: -`08940a5fabfd88a957c467edabfbe6faa7b766f38b4d518557e50e94fcf3b277` - -Host fingerprint: -`748423c1790b38ddbdd8eb77499b222a173b313f350e3bc35402ee8889a49dc4` -(4 vCPU Intel Xeon x86_64, 15 GiB RAM, Linux 6.12, Python 3.12.3, JAX 0.10.0, -`tensorcircuit-nightly==1.7.0.dev20260618`) - -Immutable report: `results/task-12-reference-baseline-local/results.json` -(untracked; retained on the campaign host) - -Report SHA-256: -`6d15f64bdf03097f1423fa16e0f434c97b2d1e44dafd5973abec0e08df004975` - -Summary SHA-256: -`600753c2e09b9561afdfd6f79e9c795a7a7d626d4b906bb6149b530a0c2164fe` - -```text -terminal_status: SUCCESS x 6 -valid: 6/6 (Overall: PASS in every cell) -timed_out: 0 -runtime_sec: 9.113186, 9.188893, 9.067477, 9.064269, 9.086606, 9.181242 -mean_runtime_sec: 9.116946 -median_runtime_sec: 9.099896 -sample_stdev_sec: 0.055618 -stderr_sec: 0.022706 -min_sec: 9.064269 -max_sec: 9.188893 -``` - -Decision: `baseline` - -Context only: the shared-container Docker bootstrap of 2026-07-27 measured -the same immutable reference at 11.261 ± 0.972 s under an 8-CPU/9-GiB -container on the maintainer host (`baselines/bootstrap-2026-07-27.md`), and -the ORBIT-Q publication record lists 6.12 s on an unspecified host -(`baselines/historical.json`). These numbers are not pooled with this -campaign's measurements. - -## Reference bottleneck profile - -Date: 2026-07-28 - -Scripts: `research/task-12/profile_reference.py`, -`research/task-12/profile_expm.py` (pinned lock environment, local engine -conditions). Sanitized outputs: -`profiles/reference-profile.json`, `profiles/expm-microbench.json`. - -Findings recorded in `SURVEY.md`: the reference spends ~0.14 s on target -conversion, ~1.7 s on jit trace, ~3.4 s on XLA compile of an 8884-line -StableHLO module, and ~3.3 s on 5000 steps at ~0.67 ms per step. A scan step -containing only the batched 31-gate build plus gradient and Adam costs -0.381 ms with the norm-adaptive `jax.scipy.linalg.expm` versus 0.123 ms with -the candidate's fixed-order diagonal Pade(3,3); the per-gate `su4` -construction chain, not the tensor-network contraction, dominates both the -graph size and the step time. - -## Experiment `e01` - -Branch: `cursor/task-12-batched-su4-campaign-f598` - -Worktree: single working clone on the campaign host (deviation recorded under -"Campaign selection and provenance"). - -## Hypothesis - -Building all 31 SU4 gate matrices per step with one batched einsum against -the stacked su(4) generators and one fixed 2^5 scaling-and-squaring diagonal -Pade(3,3) exponential, applying them through `circuit.any`, and running the -5000 Adam updates inside one `jax.lax.scan` reduces the evaluator-reported -runtime by at least 3x while reproducing the reference trajectory within -complex64 round-off (falsified if any functional gate fails, if the paired -speedup CI includes 1.0, or if the early-step loss trajectory deviates beyond -round-off scale). - -## Parent commit and diff digest - -Latest accepted parent commit: -`611c35b` (`data: release task 12 public workload v2`; campaign base -`690ffbac51715afd0a3e80718eeb6de20f11863a`) - -Hypothesis commit: `ef005888101c98de30fda2df3331f5f8461bf0ab` - -Candidate file: `src/solutions/task-12/solution_12.py` - -Candidate SHA-256: -`1d9a36c2649e938666f680e98e423e966f82c503da64ce7e4e06c5e33344560a` - -Diff SHA-256: -`0badae624ed7ec5e309e1a0684e431e407895dc54f4530d61846a3320ffd309e` - -## Data used - -Public dataset version: `orbitq-workloads-v20260728.2` - -Public manifest SHA-256: -`569a0141c5f0d723f275112d58cc6a69a3af10ad84ab8536c072604ee838cbfd` - -Private evaluation used: `no` - -## Command, seed, and environment - -Benchmark command: - -```bash -./bench run 12 --solution optimized --compare-to reference --repeat 6 \ - --engine local --timeout 300 \ - --output results/task-12-e01-batched-pairs-local -``` - -Public seed or case selector: canonical fixed configuration, seed 2039. - -Reference SHA-256: -`10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38` - -Evaluator SHA-256: -`08940a5fabfd88a957c467edabfbe6faa7b766f38b4d518557e50e94fcf3b277` - -Docker image ID: none (`--engine local`; no Docker daemon on this host) - -Container session ID: none (fresh local evaluator process per cell) - -Pair-order pattern: odd pairs `reference -> candidate`; even pairs -`candidate -> reference` - -TensorCircuit-NG commit/version: `tensorcircuit-nightly==1.7.0.dev20260618` -(+ `envs/tensorcircuit-py311/sitecustomize.py` OMECo shortcut backport) - -JAX/JAXLIB versions: `0.10.0` / `0.10.0` - -## Hardware and five-minute cap - -Host fingerprint: -`748423c1790b38ddbdd8eb77499b222a173b313f350e3bc35402ee8889a49dc4` - -CPU allocation: 4 vCPU Intel Xeon x86_64 (whole host; no container pinning) - -Memory allocation: 15 GiB host memory (no container limit) - -Timeout: `300 seconds` - -Measured region: evaluator-reported `End-to-end solution time` -(`run_solution(config)` only; evaluator-side DMRG excluded by the evaluator) - -## Result: validity, runtime, and improvement - -Immutable report: `results/task-12-e01-batched-pairs-local/results.json` -(untracked; retained on the campaign host) - -Report SHA-256: -`653aaaaabff79a4c9a2bdcdbea4a82b561d8cff0824b83f84df08f5efb2659cd` - -Summary SHA-256: -`725eeecf70b572fca9d7a4e88dbe995f216b87b75173624dedb1a75715c108f7` - -```text -terminal_status: SUCCESS x 12 (6 reference cells, 6 candidate cells) -valid: 12/12 (Overall: PASS in every cell; final fidelities 0.86954-0.87016) -timed_out: 0 -passing_pairs: 6/6 (candidate wins 6/6) -reference_mean_runtime_sec: 9.082742 -reference_runtime_stderr_sec: 0.027423 -reference_median_runtime_sec: 9.086063 -candidate_mean_runtime_sec: 2.320613 -candidate_runtime_stderr_sec: 0.002886 -candidate_median_runtime_sec: 2.320519 -improvement_pct: 74.450306 -improvement_pct_stderr: 0.095146 -speedup: 3.913941 -speedup_stderr: 0.014572 -paired_speedup_ci_low: 3.876545 -paired_speedup_ci_high: 3.951460 -``` - -Pairwise runtimes (reference, candidate, speedup): (9.132, 2.324, 3.9299), -(9.040, 2.317, 3.9023), (9.023, 2.318, 3.8926), (9.163, 2.311, 3.9651), -(9.132, 2.323, 3.9312), (9.006, 2.331, 3.8629). - -Note: the paired benchmark was executed against the exact candidate bytes -recorded in the hypothesis commit; the immutable report's per-row -`source_sha256` values equal the candidate and reference hashes above. - -Trajectory equivalence (`profiles/equivalence-check.json`): the first 10 -recorded losses match the reference bit-for-bit in float32 (max delta 0.0); -max delta 3.98e-05 over 50 steps and 1.27e-03 over 400 steps, the ordinary -round-off scale amplified by optimizer dynamics. The maximum su(4) -generator-norm bound over all 5000 steps is 3.6016 (scaled norm 0.1125 after -2^-5), far inside the fixed-Pade accuracy envelope measured in -`profiles/expm-microbench.json`. - -Decision: `keep` (all validity gates pass; six eligible counterbalanced -local-engine pairs; candidate mean and median lower; 6/6 pairs won; 95% -paired-speedup CI excludes 1.0. The `GOAL.md` Docker promotion gate stays -closed on this host and is deferred to a Docker-capable rerun.) - -## Failure signal and interpretation - -No failures, timeouts, or invalid cells in this session. - -## Next pivot - -Track the pair-fused ququart formulation as a reference-derived variant and -measure it under the same paired protocol; request a Docker-engine rerun for -formal promotion. - -## Append-only corrections - -None. - -## Variant benchmark: pair-fused ququart contraction - -Date: 2026-07-28 - -Variant tracking commit: `3026f94b202bf91d1d5b7c8656e8ab5f23e6a030` - -Variant file: `src/solutions/task-12/variants/solution_12_fused.py` - -Variant SHA-256: -`5edd437829352c573b24ae2f9021ef6046c2cf53788a8b79c8811b8fe0e3103f` - -This is a tracked reference-derived variant benchmark, not a second campaign -candidate; `src/solutions/task-12/solution_12.py` remains the e01 candidate. - -Command: - -```bash -./bench run 12 --solution fused --compare-to reference --repeat 6 \ - --engine local --timeout 300 \ - --output results/task-12-fused-variant-pairs-local -``` - -Environment, host, pair-order pattern, timeout, and measured region: same as -experiment `e01`. - -Immutable report: `results/task-12-fused-variant-pairs-local/results.json` -(untracked; retained on the campaign host) - -Report SHA-256: -`f7bc804a975b299a786b6bc519764a2dd926076e054d82d9aea20094cc0abed8` - -Summary SHA-256: -`4d6a20dd02145ef5717abcb1c9ad643f7e9dbde4a3f59bfcb7c52523fc6c6608` - -```text -terminal_status: SUCCESS x 12 (6 reference cells, 6 variant cells) -valid: 12/12 (Overall: PASS in every cell; final fidelities 0.86904-0.87013) -timed_out: 0 -passing_pairs: 6/6 (variant wins 6/6) -reference_mean_runtime_sec: 9.020622 -reference_runtime_stderr_sec: 0.026975 -reference_median_runtime_sec: 9.033664 -candidate_mean_runtime_sec: 2.123708 -candidate_runtime_stderr_sec: 0.004555 -candidate_median_runtime_sec: 2.121520 -improvement_pct: 76.457192 -improvement_pct_stderr: 0.111281 -speedup: 4.247582 -speedup_stderr: 0.019961 -paired_speedup_ci_low: 4.196459 -paired_speedup_ci_high: 4.299083 -``` - -Pairwise runtimes (reference, variant, speedup): (9.017, 2.119, 4.2557), -(9.082, 2.124, 4.2756), (9.050, 2.111, 4.2870), (8.931, 2.128, 4.1960), -(8.954, 2.143, 4.1786), (9.089, 2.117, 4.2938). - -Interpretation: after e01's gate-construction fix, halving the contraction -network buys a further ~8% end to end (4.248x vs 3.914x); the contraction -was never the dominant cost. The variant stays PASS-equivalent with final -fidelities inside the reference's own run-to-run band. - -Decision: `keep` as a tracked variant (same local-engine eligibility caveat -as e01). - -## Post-merge scan-removal ablation - -Date: 2026-07-29 - -`profile_factor_ablation.py` runs the identical fixed-Pade candidate objective -for all 5000 updates through either one compiled scan or 5000 dispatches of -the same compiled step. Three executions per path on the 6-CPU/7-GiB Docker -backend measured: - -```text -scan mean execution: 0.913101 s -Python-loop mean execution: 1.159565 s -loop / scan: 1.269920x -cold lower+compile+execute: 1.836521 s vs 1.988862 s -``` - -All loss, fidelity, overlap, and parameter arrays were bitwise equal. Combined -with the existing `3.109x` fixed-Pade kernel microbenchmark and the `8.49%` -canonical pair-fusion increment, the report now attributes the dominant gain -to gate construction and treats scan and pair fusion as secondary factors. diff --git a/optimized_solutions/challenge-12/research/SURVEY.md b/optimized_solutions/challenge-12/research/SURVEY.md deleted file mode 100644 index a6ff139..0000000 --- a/optimized_solutions/challenge-12/research/SURVEY.md +++ /dev/null @@ -1,261 +0,0 @@ -# ORBIT-Q Task 12 Runtime Optimization Survey - -**Status: READY** - -Campaign task: `task-12` - -Survey freeze: `2026-07-28T13:12:11Z` - -Reference commit: `690ffbac51715afd0a3e80718eeb6de20f11863a` - -This survey covers only Task 12, as required by the one-task campaign scope in -`GOAL.md`. `READY` means the public knowledge and measurement plan are -complete. It does not attest that the public dataset or repeated-runtime gates -have passed. - -## Provenance of this campaign's prior research - -The bottleneck analysis, candidate design, and cross-validation summarized -below were first executed on 2026-07-28 against the byte-equivalent ORBIT-Q -publication reference (`optimized_sloutions/challenge-12` work in the ORBIT-Q -fork `QingyunQian/ORBIT-Q`, branch `cursor/optimize-challenge-12-f598`), on a -4 vCPU Intel Xeon cloud VM with `tensorcircuit-nightly==1.8.0.dev20260726` -and JAX 0.10.0. The two reference files differ only in suite naming -("Task Suite" vs "Challenge Suite") and documented evaluator timing precision. - -Every profiling figure quoted in this survey was then **re-measured in this -repository's pinned lock environment** on the campaign host; the sanitized -profiler outputs live under `research/task-12/profiles/` with the scripts -beside them. Every runtime-improvement number for this repository comes from -fresh `./bench` paired runs recorded in [`LOG.md`](LOG.md). External-host -numbers are context only and are never pooled with in-repo measurements. - -## Evidence and claim rules - -Repository evidence below names a path, symbol or line, commit, and SHA-256. -Pinned-environment evidence was inspected in a virtual environment installed -exactly from `envs/tensorcircuit-py311/requirements.lock` -(`sha256:cd5ac5cb2102ea7b40bd46dc81320cc59e0ce0671ab88c597f81d82b384a824b`). -External evidence links primary project documentation. - -The bundled immutable human expert is the only reproducible performance -comparator currently available for this exact 32-qubit, 2-layer, 5000-update -overlap workload. The historical ORBIT-Q values in -`baselines/historical.json` (publication 6.12 s) and the shared-container -bootstrap (`baselines/bootstrap-2026-07-27.md`, 11.261 ± 0.972 s on 8 CPUs) -are context only. No cited external paper reports a matched evaluator -runtime, hardware allocation, software version, and output contract. -Therefore this campaign may claim a paired gain *over the bundled human -expert* when the frozen rule passes, but must not claim global state of the -art without a new matched external comparator. - -## Pinned environment and inspected framework paths - -The package versions come from `envs/tensorcircuit-py311/requirements.lock` -(`sha256:cd5ac5cb2102ea7b40bd46dc81320cc59e0ce0671ab88c597f81d82b384a824b`). -The Dockerfile is `envs/tensorcircuit-py311/Dockerfile` -(`sha256:cbcffdfc0f17731bcdf549b94209879043aab8e53ee937be60947a49d2065912`). -The OMECo contractor shortcut backport is -`envs/tensorcircuit-py311/sitecustomize.py` -(`sha256:02800060761f2b15abe9055aded49d2af3877ab93d7b0fae8af94b30bac30120`); -it maps `tc.set_contractor("omeco")` to an `omeco.TreeSA(ntrials=16, -niters=32)` custom contractor with TensorCircuit preprocessing. - -| Component | Pinned version | Inspected source or API | -| --- | --- | --- | -| TensorCircuit-NG | `tensorcircuit-nightly==1.7.0.dev20260618` | `tensorcircuit/gates.py:881`, `su4_gate` builds the generator as `sum(theta[i] * pauli_ops[i])` over 15 stacked Pauli pairs in the fixed order `ix, iy, iz, xi, xx, xy, xz, yi, yx, yy, yz, zi, zx, zy, zz`, then calls `backend.expm(-1j * generator)` per gate (`sha256:1c42762b1164c9bd33a28844ec3e42ab7eea5e853aecc5c4c741510becc5bf68`); `tensorcircuit/quantum.py:1525`, `quimb2qop`, and `quantum.py:889`, `QuVector` (`sha256:550e2cc7d41b4eadfe7ce6dd37f1d295539eb22a580f5e2993a0a2ec3408c1b4`); `tensorcircuit/quditcircuit.py:30,146`, `QuditCircuit` with `any(*indices, unitary=...)` accepting `(d, d)` or `(d^2, d^2)` unitaries (`sha256:bb765d5c33244c95a5b6cd7bfb4e15d7cdb1f6d99f991477f83acaab604fcf57`); `tensorcircuit/cons.py:1136`, `set_contractor` (`sha256:4fb517ad6085328c53ed61c75471579708eb8265fa6c14ec98e4bf1b4fd04365`) | -| JAX / JAXLIB | `0.10.0` | `tensorcircuit/backends/jax_backend.py:438`, `expm` delegates to `jax.scipy.linalg.expm`, the norm-adaptive Pade-13 scaling-and-squaring implementation (`sha256:22bbcf80d5a018884337286f176849ad4e8681b4e1f20efc1f80ba9ec4cc9d69`); `jax.lax.scan`, `jax.value_and_grad`, and `jax.jit` wrapped at `jax_backend.py:712,803,844` | -| OMECo | `0.2.4` | Registered through the sitecustomize `set_contractor("omeco")` shortcut; both the reference and every candidate select it at import time | -| TensorNetwork-NG | `0.5.1` | `QuOperator.eval` contracts the bra/ket node network with the configured contractor | -| Quimb | `1.11.1` | Supplies the evaluator-side DMRG MPS (`config["dmrg_state"]`); solutions only convert it (`quimb2qop` in the reference; pair fusion of `mps.arrays` in the tracked variant) | -| Optax | `0.2.8` | `optax.adam`, first/second-moment state on 465 parameters; the [Optax Adam API](https://optax.readthedocs.io/en/latest/api/generated/optax.adam.html) documents the update rule the expert uses | - -TensorCircuit documents JAX, automatic differentiation, JIT, vectorization, -and contractor selection as supported execution paths in its -[quick start](https://tensorcircuit.readthedocs.io/en/latest/quickstart.html) -and identifies contractor choice and preprocessing in the -[contractor tutorial](https://tensorcircuit.readthedocs.io/en/latest/tutorials/contractors.html). -JAX documents `lax.scan` as the loop primitive that compiles the body once -([jax.lax.scan API](https://docs.jax.dev/en/latest/_autosummary/jax.lax.scan.html)), -and `jax.scipy.linalg.expm` as a Pade-based matrix exponential -([jax.scipy.linalg.expm API](https://docs.jax.dev/en/latest/_autosummary/jax.scipy.linalg.expm.html)). -The fixed-order diagonal Pade approximant used by the candidate follows the -classical scaling-and-squaring analysis (Higham, *The Scaling and Squaring -Method for the Matrix Exponential Revisited*, SIAM J. Matrix Anal. Appl. -26(4), 2005, ); diagonal Pade approximants -of anti-Hermitian arguments are exactly unitary, a standard property of -Cayley-type transforms used in geometric integration (Hairer, Lubich, Wanner, -*Geometric Numerical Integration*, ch. IV). - -## Task 12: variational circuit to DMRG-MPS overlap optimization - -### Immutable expert and output contract - -The immutable expert is `references/task-12/solution_12.py` -(`sha256:10cfd516bc250633f4675653e0d8986002e56f4d5916a9c2972c1085193f5d38`). -The contract and timer are `tasks/task-12/problem.md` -(`sha256:cd795bbbd5fcf2beb0000528e1e169fa09a4f20bdbc829381b2bbf7810c36fec`) -and `tasks/task-12/evaluator/evaluate_12.py` -(`sha256:08940a5fabfd88a957c467edabfbe6faa7b766f38b4d518557e50e94fcf3b277`). - -The evaluator builds a chi-8 DMRG MPS target for a 32-qubit XXZ chain with a -staggered field **before** timing, passes it in as `config["dmrg_state"]`, -and times only `run_solution(config)`. The solution must: - -- prepare the Neel product state `|0101...01>` on 32 qubits; -- apply two brickwork layers of trainable nearest-neighbor SU4 gates (even - bonds then odd bonds; 16 + 15 = 31 gates, 15 su(4) angles each, 465 - parameters), where the expert's SU4 convention is - `tc.gates.su4_gate`: `expm(-1j * sum_i theta_i G_i)` over the fixed - 15-generator Pauli-pair order; -- maximize `F = ||^2` through the direct - tensor-network overlap of the MPS bra with the circuit ket (no - gate-preparation conversion of the target); -- run exactly 5000 Adam updates at learning rate 0.02 from the seeded - `float32` initialization (`numpy.random.default_rng(2039)`, scale 0.02), - recording pre-update loss and fidelity per step; -- return `loss_history (5000,)`, `fidelity_history (5000,)`, - `final_parameters (465,)`, and `final_overlap_phase` as NumPy data, with - final fidelity at least 0.85, improving fidelity/loss, and finite values. - -### Dominant work and measured bottlenecks - -Stage-split profiling of the reference in the pinned lock environment on the -campaign host (`research/task-12/profile_reference.py`, AOT-split via -`jit(...).lower()` / `.compile()`; sanitized output at -`research/task-12/profiles/reference-profile.json`) shows the reference's -end-to-end time is dominated by graph construction and compilation, not by -tensor-network arithmetic: - -- one-time `quimb2qop` conversion: ~0.14 s; -- one-time jit trace (Python tracing of 31 per-gate `su4` constructions plus - the TreeSA contraction-path search): ~1.7 s; -- one-time XLA compile of an 8884-line StableHLO module: ~3.4 s; -- 5000 sequential Adam steps at ~0.67 ms per step: ~3.3 s. - -Inside the step, gate construction dominates: a scan step containing only the -batched gate build costs ~0.44 ms versus ~0.45 ms for a full fused step -(external-host decomposition, reproduced in spirit by -`profiles/expm-microbench.json` in the pinned environment), because each -`tc.gates.su4_gate` call chains 15 scalar multiply/stack operations and one -norm-adaptive Pade-13 `jax.scipy.linalg.expm` (norm estimate, conditionals, -LU solves) per gate, and the whole chain is differentiated. The contraction -itself is nearly free: exactly one brickwork gate crosses each bipartition -cut, so the exact circuit state has bond dimension at most 4 and every -network tensor is tiny. - -### Relevant TensorCircuit-NG primitives and contraction paths - -- `tc.Circuit(n)` with `circuit.any(i, i+1, unitary=...)` applies a - precomputed `(4, 4)` unitary exactly like `circuit.su4` applies its - internally built matrix (`gates.py:881`), so batched gate-matrix - construction preserves gate semantics bit-for-bit up to float ordering. -- `tc.quantum.quimb2qop` wraps the quimb MPS arrays as a `QuOperator` bra; - `(target_bra @ circuit.quvector()).eval()` contracts the joint network with - the globally configured contractor (sitecustomize TreeSA `omeco` shortcut). -- `tc.QuditCircuit(16, dim=4)` supports single- and two-site `(d, d)` / - `(d^2, d^2)` unitaries and `quvector()`, enabling an exact pair-fused - formulation (tracked as the `fused` variant). -- `tc.backend.expm` is a thin alias of `jax.scipy.linalg.expm`; nothing in - the framework requires the norm-adaptive path for fixed-size 4x4 - anti-Hermitian generators. - -### Candidate optimization hypotheses and semantic constraints - -Each hypothesis must preserve: the su(4) generator order and -`expm(-1j * sum theta G)` gate semantics, the seeded float32 initialization, -exactly 5000 sequential Adam updates at learning rate 0.02, pre-update -loss/fidelity recording, the direct MPS-bra x circuit-ket overlap (no target -state preparation circuit, no oracle shortcuts), complex64 TensorCircuit/JAX -semantics, and the full output contract. - -1. **e01 (primary): batched fixed-order su(4) exponentials plus - whole-training scan.** Build all 31 generators with one einsum against the - stacked generator tensor; exponentiate with a fixed 2^5 - scaling-and-squaring diagonal Pade(3,3) core (exactly unitary for - anti-Hermitian input; error below the complex64 noise floor for the - observed parameter range, margin documented in - `profiles/expm-microbench.json`); apply through `circuit.any`; run the - 5000 updates in one `jax.lax.scan`. Expected effect: order-of-magnitude - StableHLO shrinkage, several-fold lower trace/compile time, several-fold - lower per-step time. -2. **Pair-fused ququart contraction (tracked variant).** Fuse qubit pairs - into 16 four-level sites (`QuditCircuit(16, dim=4)`); layer-1 gates become - single-site unitaries with the Neel preparation folded in as a constant - basis permutation; layer-2 gates become two-site `I2 (x) SU4 (x) I2` - unitaries; the target MPS is pair-fused once outside the loop. Halves the - contraction network without changing any contraction result. -3. **Scan alone (rejected by prior evidence).** On the unmodified reference - objective the scan converts ~5 us/step dispatch and nothing else; - external-host measurement saw ~9% end to end. Not pursued separately. -4. **Coarser fusion (rejected by prior evidence).** Fusing 4 qubits per site - (dim-16 crossing gates of shape 256x256) inflates contraction FLOPs and - was measured ~4x slower than the reference formulation externally. Do not - repeat unchanged. - -### External comparison state - -No published external system reports runtime for this exact workload -(32-qubit XXZ chi-8 DMRG target, 465-parameter SU4 brickwork, 5000 Adam -updates, this evaluator timer, this CPU allocation). The strongest available -comparators are the bundled immutable expert (this repository) and the -ORBIT-Q publication runtime record (6.12 s, unspecified publication host, -`baselines/historical.json`). Open evidence gap: no matched-hardware external -baseline exists; global SOTA cannot be claimed. - -## Frozen paired measurement and statistics plan - -This section is frozen before candidate timing results. - -- Workload: public dataset version recorded in - `datasets/public/manifest.json`; Task 12 canonical fixed configuration - only. -- Engine/allocation: this campaign host has no Docker daemon available, so - eligible in-repo measurements use `./bench run 12 ... --engine local` with - a virtual environment installed exactly from - `envs/tensorcircuit-py311/requirements.lock` (Python 3.12.3), the - environment `sitecustomize.py` on `PYTHONPATH`, `NUMBA_DISABLE_JIT=1`, and - a fresh evaluator process per cell on an otherwise idle 4 vCPU host. The - Docker single-container protocol of `GOAL.md` Gate 3 therefore remains - **closed** on this host; the Docker-gated promotion claim is deferred to a - maintainer or CI rerun with `--engine docker`. -- Cells: six matched pairs minimum; a fresh evaluator process per cell. -- Order: odd pairs `reference -> candidate`; even pairs - `candidate -> reference` (the `./bench` counterbalanced default). -- Timeout: hard 300 seconds per evaluator process. -- Cache state: cold process-local Python/JAX caches for every fresh process; - no persistent compilation cache; no candidate-only precomputation outside - `run_solution`. -- Validity: process exit zero, finite positive evaluator runtime, - `Overall: PASS`, full output schema, exactly 5000-step histories. - -For eligible runtimes `R_i` and `C_i`, report means, medians, sample standard -deviations, standard errors, minima, maxima, ratio-of-means improvement -`100*(mean(R)-mean(C))/mean(R)`, pairwise improvements `100*(R_i-C_i)/R_i`, -and pairwise speedups `S_i=R_i/C_i`. - -The predeclared confidence interval is the two-sided 95% Student-t interval -for the arithmetic mean of the pairwise speedups: - -`mean(S) ± t_(0.975,n-1) * sample_stdev(S)/sqrt(n)`. - -With six pairs, the critical value is `t_(0.975,5)=2.5705818366`. Do not -change the method after observing a candidate. - -A candidate is promotable only when every validity gate passes, at least six -pairs are eligible, candidate mean and median are lower, the candidate wins -at least 80% of pairs, and the 95% lower confidence bound for mean paired -speedup exceeds 1.0 -- and, for the repository's formal Docker promotion -gate, when the same protocol is reproduced under `--engine docker` per -`GOAL.md` Gate 3. Until that Docker rerun exists, results below are recorded -as local-engine paired evidence, not a promoted Docker-gated claim. - -## Open evidence gaps - -- No Docker daemon on the campaign host: Gate 3 reference baselines and the - formal promotion protocol cannot run here; local-engine paired evidence is - recorded instead and a Docker rerun is requested in the campaign report. -- No matched external implementation publishes runtime for this exact - configuration; global SOTA remains unestablished. -- Process peak RSS was not measured; XLA memory-traffic estimates were not - collected in the pinned environment. diff --git a/optimized_solutions/challenge-12/research/check_equivalence.py b/optimized_solutions/challenge-12/research/check_equivalence.py deleted file mode 100755 index f97a97f..0000000 --- a/optimized_solutions/challenge-12/research/check_equivalence.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -"""Trajectory-equivalence audit: candidate vs immutable Task 12 reference. - -Runs both training loops from the identical seeded initialization in one -process and reports the per-step loss deviation over the first K steps, plus -the maximum su(4) generator-norm bound reached across all 5000 candidate -steps (the accuracy-margin input for the fixed-order Pade exponential). - -Run from the repository root inside the pinned environment: - - PYTHONPATH=envs/tensorcircuit-py311 NUMBA_DISABLE_JIT=1 \ - python research/task-12/check_equivalence.py - -Writes ``research/task-12/profiles/equivalence-check.json``. -""" - -from __future__ import annotations - -import json -import sys -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -sys.path.insert(0, str(ROOT / "src" / "solutions" / "task-12")) - -import numpy as np -import optax -import tensorcircuit as tc - -K = tc.set_backend("jax") -tc.set_dtype("complex64") -tc.set_contractor("omeco") - -import jax -import jax.numpy as jnp -import solution_12 # campaign candidate - -N = 32 -CHECK_STEPS = 400 - - -def build_dmrg_state(): - import quimb.tensor as qtn - - ham = qtn.SpinHam1D(S=0.5) - for i in range(N - 1): - ham[i, i + 1] += 4.0, "X", "X" - ham[i, i + 1] += 4.0, "Y", "Y" - ham[i, i + 1] += 4.0 * 1.4, "Z", "Z" - for i in range(N): - ham[i] += 2.0 * 0.2 * ((-1) ** i), "Z" - dmrg = qtn.DMRG2(ham.build_mpo(N), bond_dims=[8], cutoffs=1e-8) - dmrg.solve(tol=1e-7, max_sweeps=4, verbosity=0) - dmrg.state.normalize() - return dmrg.state - - -def scan_losses(objective, steps, extra=None): - opt = optax.adam(0.02) - - def body(carry, _): - p, s = carry - (loss, _aux), grads = K.value_and_grad(objective, has_aux=True)(p) - upd, s = opt.update(grads, s, p) - ys = (loss,) if extra is None else (loss, extra(p)) - return (optax.apply_updates(p, upd), s), ys - - rng = np.random.default_rng(2039) - p = jnp.asarray(rng.normal(scale=0.02, size=(465,)).astype(np.float32)) - s = opt.init(p) - (_, _), ys = jax.jit( - lambda p, s: jax.lax.scan(body, (p, s), None, length=steps) - )(p, s) - return tuple(np.asarray(y) for y in ys) - - -def main(): - dmrg_state = build_dmrg_state() - target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() - - def reference_objective(p): - circuit = tc.Circuit(N) - for i in range(1, N, 2): - circuit.x(i) - offset = 0 - for layer in range(2): - for i in range(layer % 2, N - 1, 2): - circuit.su4(i, i + 1, theta=p[offset : offset + 15]) - offset += 15 - overlap = (target_bra @ circuit.quvector()).eval() - fidelity = K.real(K.conj(overlap) * overlap) - return 1.0 - fidelity, (fidelity, overlap) - - gens = jnp.asarray(solution_12._GENERATORS, dtype=jnp.complex64) - - def candidate_objective(p): - gates = solution_12._su4_batch(p.reshape(31, 15), gens) - circuit = tc.Circuit(N) - for i in range(1, N, 2): - circuit.x(i) - k = 0 - for layer in range(2): - for i in range(layer % 2, N - 1, 2): - circuit.any(i, i + 1, unitary=gates[k]) - k += 1 - overlap = (target_bra @ circuit.quvector()).eval() - fidelity = K.real(K.conj(overlap) * overlap) - return 1.0 - fidelity, (fidelity, overlap) - - (ref_losses,) = scan_losses(reference_objective, CHECK_STEPS) - cand_losses, norm_bounds = scan_losses( - candidate_objective, - 5000, - extra=lambda p: jnp.max(jnp.sum(jnp.abs(p.reshape(31, 15)), axis=1)), - ) - delta = np.abs(ref_losses - cand_losses[:CHECK_STEPS]) - report = { - "generated_by": "research/task-12/check_equivalence.py", - "environment_lock": "envs/tensorcircuit-py311/requirements.lock", - "max_abs_loss_delta_first_k_steps": { - "10": float(delta[:10].max()), - "50": float(delta[:50].max()), - "100": float(delta[:100].max()), - "400": float(delta[:400].max()), - }, - "candidate_final_loss_5000": float(cand_losses[-1]), - "max_generator_norm_bound_over_5000_steps": float(norm_bounds.max()), - "pade_scaled_norm_at_max": float(norm_bounds.max() / 32.0), - "note": ( - "Loss histories are compared step-by-step from the identical " - "seeded initialization; a zero delta over the first steps means " - "the candidate reproduces the reference trajectory bit-for-bit " - "in float32 before ordinary round-off noise is amplified by " - "optimizer dynamics." - ), - } - out = Path(__file__).parent / "profiles" / "equivalence-check.json" - out.parent.mkdir(parents=True, exist_ok=True) - out.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") - print(json.dumps(report, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/optimized_solutions/challenge-12/research/plot_factor_ablation.py b/optimized_solutions/challenge-12/research/plot_factor_ablation.py deleted file mode 100644 index af0aadf..0000000 --- a/optimized_solutions/challenge-12/research/plot_factor_ablation.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -"""Render Task 12 factor-ablation panels from tracked measurements.""" - -import json -from pathlib import Path - -import matplotlib -matplotlib.use("Agg") -import matplotlib.pyplot as plt - - -ROOT = Path(__file__).resolve().parent -OUT = ROOT / "figures" / "factor-ablation.svg" - - -def panel(ax, title, ratio, control, ablation): - bars = ax.bar( - [f"Promoted\n{control}", f"Removal\n{ablation}"], - [1, ratio], - color=["#4472C4", "#C44E52"], - ) - ax.axhline(1, color="black", linewidth=0.8) - ax.set_ylabel("Runtime normalized to promoted") - ax.set_title(title) - for bar, value in zip(bars, [1, ratio]): - ax.text( - bar.get_x() + bar.get_width() / 2, - value, - f"{value:.3f}×", - ha="center", - va="bottom", - fontsize=8, - ) - - -def main(): - scan = json.loads((ROOT / "profiles" / "factor-ablation.json").read_text()) - plt.style.use("seaborn-v0_8-whitegrid") - plt.rcParams["svg.hashsalt"] = "task-12-factor-ablation" - fig, axes = plt.subplots(1, 3, figsize=(13.2, 4.2)) - panel( - axes[0], - "Fixed Pade → adaptive expm", - 0.3808 / 0.1225, - "0.1225 ms", - "0.3808 ms", - ) - cold = scan["cold_profile_total_sec"] - panel( - axes[1], - "Scan → Python dispatch", - cold["python_loop"] / cold["scan"], - f'{cold["scan"]:.3f} s cold', - f'{cold["python_loop"]:.3f} s cold', - ) - panel( - axes[2], - "Pair-fused → unfused network", - 2.3206 / 2.1237, - "2.124 s", - "2.321 s", - ) - fig.suptitle( - "Task 12 factor ablations — fixed Pade dominates; scan and pair fusion are secondary", - fontsize=12, - fontweight="bold", - ) - fig.tight_layout() - OUT.parent.mkdir(parents=True, exist_ok=True) - fig.savefig(OUT, bbox_inches="tight", metadata={"Date": None}) - - -if __name__ == "__main__": - main() diff --git a/optimized_solutions/challenge-12/research/profile_expm.py b/optimized_solutions/challenge-12/research/profile_expm.py deleted file mode 100755 index f6e50cb..0000000 --- a/optimized_solutions/challenge-12/research/profile_expm.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python3 -"""Micro-benchmark and accuracy audit of batched 4x4 SU(4) exponentials. - -Compares the generic norm-adaptive ``jax.scipy.linalg.expm`` against the -candidate's fixed-order scaling-and-squaring diagonal Pade(3,3) inside a -value_and_grad + Adam scan step shaped like the Task 12 gate build, and -measures both approximants against a float64 SciPy reference over the -generator-norm range relevant to this protocol. - -Run from the repository root inside the pinned environment: - - PYTHONPATH=envs/tensorcircuit-py311 NUMBA_DISABLE_JIT=1 \ - python research/task-12/profile_expm.py - -Writes ``research/task-12/profiles/expm-microbench.json``. -""" - -from __future__ import annotations - -import json -import sys -import time -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -sys.path.insert(0, str(ROOT / "src" / "solutions" / "task-12")) - -import numpy as np -import optax -import tensorcircuit as tc - -tc.set_backend("jax") -tc.set_dtype("complex64") - -import jax -import jax.numpy as jnp -import solution_12 # campaign candidate; provides _GENERATORS and _su4_batch -from jax.scipy.linalg import expm as jexpm - -GENS = solution_12._GENERATORS -GENS_J = jnp.asarray(GENS, dtype=jnp.complex64) - - -def accuracy_rows(): - import scipy.linalg - - rows = [] - rng = np.random.default_rng(0) - for scale in (0.05, 0.5, 1.0, 2.0): - theta = rng.normal(scale=scale, size=(31, 15)) - h = np.einsum("gi,iab->gab", theta, GENS) - exact = np.stack( - [scipy.linalg.expm(-1j * m.astype(np.complex128)) for m in h] - ) - got_jexpm = np.asarray(jexpm(jnp.asarray(-1j * h, dtype=jnp.complex64))) - got_pade = np.asarray( - solution_12._su4_batch(jnp.asarray(theta, dtype=jnp.float32), GENS_J) - ) - norm_bound = float(np.abs(theta).sum(axis=1).max()) - rows.append( - { - "theta_scale": scale, - "max_generator_norm_bound": round(norm_bound, 3), - "jexpm_max_abs_err": float(np.abs(got_jexpm - exact).max()), - "pade33_s5_max_abs_err": float(np.abs(got_pade - exact).max()), - "pade33_s5_max_unitarity_defect": float( - np.abs( - np.einsum("gab,gcb->gac", got_pade, got_pade.conj()) - - np.eye(4) - ).max() - ), - } - ) - return rows - - -def bench(fn, n=2000): - opt = optax.adam(0.02) - - def loss_fn(p): - u = fn(p.reshape(31, 15)) - return jnp.sum(jnp.abs(u - jnp.eye(4, dtype=u.dtype)) ** 2) - - def body(carry, _): - p, s = carry - loss, g = jax.value_and_grad(loss_fn)(p) - upd, s = opt.update(g, s, p) - return (optax.apply_updates(p, upd), s), loss - - @jax.jit - def train(p, s): - return jax.lax.scan(body, (p, s), None, length=n) - - p = jnp.asarray( - np.random.default_rng(1).normal(scale=0.02, size=(465,)).astype(np.float32) - ) - s = opt.init(p) - out = train(p, s) - jax.block_until_ready(out) - t0 = time.perf_counter() - out = train(p, s) - jax.block_until_ready(out) - return (time.perf_counter() - t0) / n - - -def main(): - def jexpm_gates(thetas): - h = jnp.einsum("gi,iab->gab", thetas.astype(jnp.complex64), GENS_J) - return jexpm(-1j * h) - - report = { - "generated_by": "research/task-12/profile_expm.py", - "environment_lock": "envs/tensorcircuit-py311/requirements.lock", - "accuracy_vs_float64_scipy": accuracy_rows(), - "scan_step_with_grad_and_adam_ms": { - "jax_scipy_expm_pade13_adaptive": round(bench(jexpm_gates) * 1e3, 4), - "fixed_pade33_s5_candidate": round( - bench(lambda t: solution_12._su4_batch(t, GENS_J)) * 1e3, 4 - ), - }, - "note": ( - "Both rows time one scan step containing only the batched 31-gate " - "construction, its gradient, and an Adam update; the candidate " - "kernel is the exact function used by solution_12._su4_batch." - ), - } - out = Path(__file__).parent / "profiles" / "expm-microbench.json" - out.parent.mkdir(parents=True, exist_ok=True) - out.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") - print(json.dumps(report, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/optimized_solutions/challenge-12/research/profile_factor_ablation.py b/optimized_solutions/challenge-12/research/profile_factor_ablation.py deleted file mode 100644 index e7f8337..0000000 --- a/optimized_solutions/challenge-12/research/profile_factor_ablation.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -"""Whole-training scan removal ablation for the merged Task 12 candidate.""" - -from __future__ import annotations - -import importlib.util -import json -import time -from pathlib import Path - -import jax -import jax.numpy as jnp -import numpy as np -import optax -import tensorcircuit as tc - - -ROOT = Path(__file__).resolve().parents[2] -PROFILE_REFERENCE = ROOT / "research/task-12/profile_reference.py" -SOLUTION = ROOT / "src/solutions/task-12/solution_12.py" - - -def load(path: Path, name: str): - spec = importlib.util.spec_from_file_location(name, path) - if spec is None or spec.loader is None: - raise RuntimeError(f"cannot import {path}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def ready(value) -> None: - leaves = jax.tree_util.tree_leaves(value) - if leaves: - jax.block_until_ready(leaves[0]) - - -def lower_compile(fn, *args): - started = time.perf_counter() - lowered = jax.jit(fn).lower(*args) - lower_sec = time.perf_counter() - started - started = time.perf_counter() - compiled = lowered.compile() - compile_sec = time.perf_counter() - started - return compiled, { - "lower_sec": lower_sec, - "compile_sec": compile_sec, - "stablehlo_line_count": lowered.as_text().count("\n"), - } - - -def main() -> None: - profile_reference = load(PROFILE_REFERENCE, "task12_profile_reference") - solution = load(SOLUTION, "task12_candidate") - config = dict(profile_reference.CONFIG) - dmrg_state = profile_reference.build_dmrg_state() - - K = tc.set_backend("jax") - tc.set_dtype("complex64") - tc.set_contractor("omeco") - - n_qubits = config["n_qubits"] - layer_bonds = [ - list(range(layer % 2, n_qubits - 1, 2)) - for layer in range(config["n_layers"]) - ] - n_gates = sum(len(bonds) for bonds in layer_bonds) - target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() - gens = jnp.asarray(solution._GENERATORS, dtype=jnp.complex64) - optimizer = optax.adam(config["learning_rate"]) - - def objective(p): - gates = solution._su4_batch(p.reshape(n_gates, 15), gens) - circuit = tc.Circuit(n_qubits) - for i in range(1, n_qubits, 2): - circuit.x(i) - k = 0 - for bonds in layer_bonds: - for i in bonds: - circuit.any(i, i + 1, unitary=gates[k]) - k += 1 - overlap = (target_bra @ circuit.quvector()).eval() - fidelity = K.real(K.conj(overlap) * overlap) - return 1.0 - fidelity, (fidelity, overlap) - - def body(carry, _): - p, state = carry - (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) - updates, state = optimizer.update(grads, state, p) - return (optax.apply_updates(p, updates), state), (loss,) + aux - - def one_step(carry): - return body(carry, None) - - def scan_train(carry): - return jax.lax.scan( - body, - carry, - None, - length=config["max_steps"], - ) - - rng = np.random.default_rng(config["seed"]) - params = jnp.asarray( - rng.normal( - scale=config["initial_parameter_scale"], - size=(15 * n_gates,), - ).astype(np.float32) - ) - init = (params, optimizer.init(params)) - compiled_scan, scan_compile = lower_compile(scan_train, init) - compiled_step, step_compile = lower_compile(one_step, init) - - scan_times = [] - scan_output = None - for _ in range(3): - started = time.perf_counter() - scan_output = compiled_scan(init) - ready(scan_output) - scan_times.append(time.perf_counter() - started) - - loop_times = [] - loop_history = None - loop_final = None - for _ in range(3): - carry = init - losses, fidelities, overlaps = [], [], [] - started = time.perf_counter() - for _ in range(config["max_steps"]): - carry, values = compiled_step(carry) - loss, fidelity, overlap = values - losses.append(loss) - fidelities.append(fidelity) - overlaps.append(overlap) - ready((carry, losses[-1])) - loop_times.append(time.perf_counter() - started) - loop_final = carry - loop_history = ( - K.stack(losses), - K.stack(fidelities), - K.stack(overlaps), - ) - - scan_final, scan_history = scan_output - history_errors = [ - float(np.max(np.abs(np.asarray(a) - np.asarray(b)))) - for a, b in zip(scan_history, loop_history) - ] - parameter_error = float( - np.max( - np.abs( - np.asarray(scan_final[0]) - - np.asarray(loop_final[0]) - ) - ) - ) - report = { - "generated_by": "research/task-12/profile_factor_ablation.py", - "scope": "scan removal with identical candidate objective", - "steps": config["max_steps"], - "scan_compile": scan_compile, - "step_compile": step_compile, - "scan_execution_sec": scan_times, - "scan_mean_execution_sec": float(np.mean(scan_times)), - "python_loop_execution_sec": loop_times, - "python_loop_mean_execution_sec": float(np.mean(loop_times)), - "history_max_abs_errors": history_errors, - "parameter_max_abs_error": parameter_error, - } - print(json.dumps(report, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/optimized_solutions/challenge-12/research/profile_reference.py b/optimized_solutions/challenge-12/research/profile_reference.py deleted file mode 100755 index 3968fc0..0000000 --- a/optimized_solutions/challenge-12/research/profile_reference.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -"""Stage-split profiling for the Task 12 reference and campaign candidate. - -Splits end-to-end time into: quimb-target conversion, jit trace (including -contraction-path search), XLA compile, and the 5000-step optimizer loop, using -ahead-of-time lowering (``jit(...).lower()`` / ``.compile()``). - -Run from the repository root inside the pinned environment: - - PYTHONPATH=envs/tensorcircuit-py311 NUMBA_DISABLE_JIT=1 \ - python research/task-12/profile_reference.py - -Writes ``research/task-12/profiles/reference-profile.json``. -""" - -from __future__ import annotations - -import importlib -import json -import sys -import time -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -sys.path.insert(0, str(ROOT / "references" / "task-12")) -sys.path.insert(0, str(ROOT / "src" / "solutions" / "task-12")) - -CONFIG = { - "n_qubits": 32, - "zz_anisotropy": 1.4, - "staggered_field": 0.2, - "dmrg_chi": 8, - "dmrg_sweeps": 4, - "dmrg_tolerance": 1e-7, - "n_layers": 2, - "max_steps": 5000, - "learning_rate": 0.02, - "initial_parameter_scale": 0.02, - "seed": 2039, - "fidelity_threshold": 0.85, -} - - -def build_dmrg_state(): - import quimb.tensor as qtn - - n = CONFIG["n_qubits"] - ham = qtn.SpinHam1D(S=0.5) - for i in range(n - 1): - ham[i, i + 1] += 4.0, "X", "X" - ham[i, i + 1] += 4.0, "Y", "Y" - ham[i, i + 1] += 4.0 * CONFIG["zz_anisotropy"], "Z", "Z" - for i in range(n): - ham[i] += 2.0 * CONFIG["staggered_field"] * ((-1) ** i), "Z" - dmrg = qtn.DMRG2( - ham.build_mpo(n), - bond_dims=[CONFIG["dmrg_chi"]], - cutoffs=1e-8, - ) - dmrg.solve(tol=CONFIG["dmrg_tolerance"], max_sweeps=CONFIG["dmrg_sweeps"], verbosity=0) - dmrg.state.normalize() - return dmrg.state - - -def profile_reference(dmrg_state, n_measure=1000): - """Replicates references/task-12/solution_12.py with stage timers.""" - import numpy as np - import optax - import tensorcircuit as tc - - K = tc.set_backend("jax") - tc.set_dtype("complex64") - tc.set_contractor("omeco") - import jax - - n_qubits = CONFIG["n_qubits"] - parameter_count = 0 - for layer in range(CONFIG["n_layers"]): - parameter_count += 15 * len(range(layer % 2, n_qubits - 1, 2)) - rng = np.random.default_rng(CONFIG["seed"]) - params = rng.normal( - scale=CONFIG["initial_parameter_scale"], size=(parameter_count,) - ).astype(np.float32) - params = K.convert_to_tensor(params) - - t0 = time.perf_counter() - target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() - t_convert = time.perf_counter() - t0 - - optimizer = optax.adam(CONFIG["learning_rate"]) - opt_state = optimizer.init(params) - - def objective(p): - circuit = tc.Circuit(n_qubits) - for i in range(1, n_qubits, 2): - circuit.x(i) - offset = 0 - for layer in range(CONFIG["n_layers"]): - for i in range(layer % 2, n_qubits - 1, 2): - circuit.su4(i, i + 1, theta=p[offset : offset + 15]) - offset += 15 - overlap = (target_bra @ circuit.quvector()).eval() - fidelity = K.real(K.conj(overlap) * overlap) - return 1.0 - fidelity, (fidelity, overlap) - - def train_step(p, state): - (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) - updates, state = optimizer.update(grads, state, p) - return optax.apply_updates(p, updates), state, loss, aux - - jitted = jax.jit(train_step) - t0 = time.perf_counter() - lowered = jitted.lower(params, opt_state) - t_trace = time.perf_counter() - t0 - hlo_lines = lowered.as_text().count("\n") - t0 = time.perf_counter() - compiled = lowered.compile() - t_compile = time.perf_counter() - t0 - - p, s = params, opt_state - for _ in range(20): - p, s, loss, _aux = compiled(p, s) - jax.block_until_ready(loss) - t0 = time.perf_counter() - for _ in range(n_measure): - p, s, loss, _aux = compiled(p, s) - jax.block_until_ready(loss) - per_step = (time.perf_counter() - t0) / n_measure - - return { - "solution": "reference (replicated objective)", - "quimb_conversion_sec": round(t_convert, 4), - "jit_trace_sec": round(t_trace, 3), - "xla_compile_sec": round(t_compile, 3), - "stablehlo_line_count": hlo_lines, - "steady_state_step_ms": round(per_step * 1e3, 4), - "loop_5000_estimate_sec": round(per_step * 5000, 3), - "total_estimate_sec": round( - t_convert + t_trace + t_compile + per_step * 5000, 3 - ), - } - - -def profile_candidate(dmrg_state): - """End-to-end candidate timing plus its scan-level stage split.""" - import numpy as np - import optax - import tensorcircuit as tc - - K = tc.set_backend("jax") - tc.set_dtype("complex64") - tc.set_contractor("omeco") - import jax - import jax.numpy as jnp - - solution = importlib.import_module("solution_12") - gens = jnp.asarray(solution._GENERATORS, dtype=jnp.complex64) - target_bra = tc.quantum.quimb2qop(dmrg_state).adjoint() - optimizer = optax.adam(CONFIG["learning_rate"]) - - n_qubits = CONFIG["n_qubits"] - layer_bonds = [ - list(range(layer % 2, n_qubits - 1, 2)) - for layer in range(CONFIG["n_layers"]) - ] - n_gates = sum(len(b) for b in layer_bonds) - - def objective(p): - gates = solution._su4_batch(p.reshape(n_gates, 15), gens) - circuit = tc.Circuit(n_qubits) - for i in range(1, n_qubits, 2): - circuit.x(i) - k = 0 - for bonds in layer_bonds: - for i in bonds: - circuit.any(i, i + 1, unitary=gates[k]) - k += 1 - overlap = (target_bra @ circuit.quvector()).eval() - fidelity = K.real(K.conj(overlap) * overlap) - return 1.0 - fidelity, (fidelity, overlap) - - def body(carry, _): - p, s = carry - (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) - updates, s = optimizer.update(grads, s, p) - return (optax.apply_updates(p, updates), s), (loss,) + aux - - def train(p, s): - return jax.lax.scan(body, (p, s), None, length=CONFIG["max_steps"]) - - rng = np.random.default_rng(CONFIG["seed"]) - params = jnp.asarray( - rng.normal( - scale=CONFIG["initial_parameter_scale"], size=(15 * n_gates,) - ).astype(np.float32) - ) - state = optimizer.init(params) - - jitted = jax.jit(train) - t0 = time.perf_counter() - lowered = jitted.lower(params, state) - t_trace = time.perf_counter() - t0 - hlo_lines = lowered.as_text().count("\n") - t0 = time.perf_counter() - compiled = lowered.compile() - t_compile = time.perf_counter() - t0 - t0 = time.perf_counter() - out = compiled(params, state) - jax.block_until_ready(out) - t_loop = time.perf_counter() - t0 - - return { - "solution": "candidate (e01 objective in a 5000-step scan)", - "jit_trace_sec": round(t_trace, 3), - "xla_compile_sec": round(t_compile, 3), - "stablehlo_line_count": hlo_lines, - "scan_loop_5000_sec": round(t_loop, 3), - "steady_state_step_ms": round(t_loop / CONFIG["max_steps"] * 1e3, 4), - } - - -def main(): - dmrg_state = build_dmrg_state() - report = { - "generated_by": "research/task-12/profile_reference.py", - "environment_lock": "envs/tensorcircuit-py311/requirements.lock", - "reference": profile_reference(dmrg_state), - "candidate": profile_candidate(dmrg_state), - } - out = Path(__file__).parent / "profiles" / "reference-profile.json" - out.parent.mkdir(parents=True, exist_ok=True) - out.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") - print(json.dumps(report, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/optimized_solutions/challenge-12/research/profiles/equivalence-check.json b/optimized_solutions/challenge-12/research/profiles/equivalence-check.json deleted file mode 100644 index b06dd57..0000000 --- a/optimized_solutions/challenge-12/research/profiles/equivalence-check.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "generated_by": "research/task-12/check_equivalence.py", - "environment_lock": "envs/tensorcircuit-py311/requirements.lock", - "max_abs_loss_delta_first_k_steps": { - "10": 0.0, - "50": 3.981590270996094e-05, - "100": 6.973743438720703e-05, - "400": 0.0012745261192321777 - }, - "candidate_final_loss_5000": 0.12984925508499146, - "max_generator_norm_bound_over_5000_steps": 3.601597547531128, - "pade_scaled_norm_at_max": 0.11254992336034775, - "note": "Loss histories are compared step-by-step from the identical seeded initialization; a zero delta over the first steps means the candidate reproduces the reference trajectory bit-for-bit in float32 before ordinary round-off noise is amplified by optimizer dynamics." -} diff --git a/optimized_solutions/challenge-12/research/profiles/expm-microbench.json b/optimized_solutions/challenge-12/research/profiles/expm-microbench.json deleted file mode 100644 index fc2ebad..0000000 --- a/optimized_solutions/challenge-12/research/profiles/expm-microbench.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "generated_by": "research/task-12/profile_expm.py", - "environment_lock": "envs/tensorcircuit-py311/requirements.lock", - "accuracy_vs_float64_scipy": [ - { - "theta_scale": 0.05, - "max_generator_norm_bound": 0.919, - "jexpm_max_abs_err": 1.7127096142602416e-07, - "pade33_s5_max_abs_err": 5.277466592479941e-06, - "pade33_s5_max_unitarity_defect": 1.055002212524414e-05 - }, - { - "theta_scale": 0.5, - "max_generator_norm_bound": 7.892, - "jexpm_max_abs_err": 2.13937326610061e-07, - "pade33_s5_max_abs_err": 4.836887262698418e-06, - "pade33_s5_max_unitarity_defect": 1.0311603546142578e-05 - }, - { - "theta_scale": 1.0, - "max_generator_norm_bound": 18.238, - "jexpm_max_abs_err": 1.0629713454831844e-05, - "pade33_s5_max_abs_err": 3.2686023539371067e-06, - "pade33_s5_max_unitarity_defect": 7.510185241699219e-06 - }, - { - "theta_scale": 2.0, - "max_generator_norm_bound": 33.564, - "jexpm_max_abs_err": 6.245178907907371e-06, - "pade33_s5_max_abs_err": 3.230430040574948e-06, - "pade33_s5_max_unitarity_defect": 8.344650268554688e-06 - } - ], - "scan_step_with_grad_and_adam_ms": { - "jax_scipy_expm_pade13_adaptive": 0.3808, - "fixed_pade33_s5_candidate": 0.1225 - }, - "note": "Both rows time one scan step containing only the batched 31-gate construction, its gradient, and an Adam update; the candidate kernel is the exact function used by solution_12._su4_batch." -} diff --git a/optimized_solutions/challenge-12/research/profiles/factor-ablation.json b/optimized_solutions/challenge-12/research/profiles/factor-ablation.json deleted file mode 100644 index c025f22..0000000 --- a/optimized_solutions/challenge-12/research/profiles/factor-ablation.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "generated_by": "research/task-12/profile_factor_ablation.py", - "scope": "scan removal with identical candidate objective", - "environment": { - "image": "orbitbreakers-expert-benchmarks:tensorcircuit-py311", - "cpus": 6, - "memory": "7g", - "network": "none" - }, - "steps": 5000, - "scan": { - "lower_sec": 0.60646366, - "compile_sec": 0.316957074, - "stablehlo_line_count": 954, - "execution_sec": [ - 0.913055518, - 0.913425434, - 0.912820772 - ], - "mean_execution_sec": 0.913100575 - }, - "python_loop": { - "step_lower_sec": 0.525993696, - "step_compile_sec": 0.303303482, - "stablehlo_line_count": 927, - "execution_sec": [ - 1.166982586, - 1.156237215, - 1.155474601 - ], - "mean_execution_sec": 1.159564801 - }, - "loop_over_scan_execution": 1.2699201302, - "cold_profile_total_sec": { - "scan": 1.836521309, - "python_loop": 1.988861979 - }, - "history_max_abs_errors": [ - 0.0, - 0.0, - 0.0 - ], - "parameter_max_abs_error": 0.0 -} diff --git a/optimized_solutions/challenge-12/research/profiles/reference-profile.json b/optimized_solutions/challenge-12/research/profiles/reference-profile.json deleted file mode 100644 index 7de9d4c..0000000 --- a/optimized_solutions/challenge-12/research/profiles/reference-profile.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "generated_by": "research/task-12/profile_reference.py", - "environment_lock": "envs/tensorcircuit-py311/requirements.lock", - "reference": { - "solution": "reference (replicated objective)", - "quimb_conversion_sec": 0.1379, - "jit_trace_sec": 1.693, - "xla_compile_sec": 3.373, - "stablehlo_line_count": 8884, - "steady_state_step_ms": 0.6668, - "loop_5000_estimate_sec": 3.334, - "total_estimate_sec": 8.538 - }, - "candidate": { - "solution": "candidate (e01 objective in a 5000-step scan)", - "jit_trace_sec": 0.744, - "xla_compile_sec": 0.408, - "stablehlo_line_count": 954, - "scan_loop_5000_sec": 0.952, - "steady_state_step_ms": 0.1904 - } -} diff --git a/optimized_solutions/challenge-12/solution_12_pair_fused.py b/optimized_solutions/challenge-12/solution_12_pair_fused.py deleted file mode 100644 index 02b71e3..0000000 --- a/optimized_solutions/challenge-12/solution_12_pair_fused.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -Task Suite Problem 12: variational circuit to MPS overlap optimization. - -The solution contracts a DMRG-MPS target bra directly with a trainable circuit -ket and differentiates the scalar overlap loss with respect to circuit angles. -""" - -import numpy as np -import optax - -import tensorcircuit as tc - -K = tc.set_backend("jax") -tc.set_dtype("complex64") -tc.set_contractor("omeco") - -import jax -import jax.numpy as jnp - -_PAULI = { - "i": np.eye(2, dtype=complex), - "x": np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex), - "y": np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex), - "z": np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex), -} -# 15 su(4) generators in the exact order used by tc.gates.su4_gate -_GENERATORS = np.stack( - [ - np.einsum("ab,cd->acbd", _PAULI[a], _PAULI[b]).reshape(4, 4) - for a in "ixyz" - for b in "ixyz" - if (a, b) != ("i", "i") - ] -) -# constant single-site basis permutation |b> -> |b xor 1> on the second qubit -# of a fused pair; folds the Neel |01> preparation into the layer-1 gates -_NEEL_PERM = np.eye(4, dtype=complex)[:, [1, 0, 3, 2]] - - -def _su4_batch(thetas, gens): - """(gates, 15) angles -> (gates, 4, 4) SU4 matrices exp(-i sum theta G). - - Fixed 2**5 scaling-and-squaring with a diagonal Pade(3,3) core: exactly - unitary for anti-Hermitian input, static graph, cheap to differentiate. - """ - a = jnp.einsum("gi,iab->gab", thetas.astype(gens.dtype), gens) / 32j - eye = jnp.eye(4, dtype=a.dtype) - a2 = a @ a - odd = a @ (a2 + 60.0 * eye) - even = 12.0 * a2 + 120.0 * eye - r = jnp.linalg.solve(even - odd, even + odd) - for _ in range(5): - r = r @ r - return r - - -def _pair_fused_target_bra(dmrg_state): - """Pair-fuse the quimb DMRG MPS into a 16-site QuVector bra (chi <= 8).""" - mps = dmrg_state.copy() - mps.permute_arrays("lpr") - arrays = [np.asarray(a, dtype=np.complex64) for a in mps.arrays] - arrays[0] = arrays[0].reshape(1, *arrays[0].shape) - arrays[-1] = arrays[-1].reshape(*arrays[-1].shape, 1) - fused = [] - for j in range(len(arrays) // 2): - pair = np.einsum("lpm,mqr->lpqr", arrays[2 * j], arrays[2 * j + 1]) - fused.append(pair.reshape(pair.shape[0], 4, pair.shape[3])) - fused[0] = fused[0][0] - fused[-1] = fused[-1][..., 0] - nodes = [tc.quantum.Node(t) for t in fused] - for left, right in zip(nodes[:-1], nodes[1:]): - left[-1] ^ right[0] - out_edges = [nodes[0][0]] + [node[1] for node in nodes[1:]] - return tc.quantum.QuVector(out_edges).adjoint() - - -def run_solution(config): - n_qubits = config["n_qubits"] - n_sites = n_qubits // 2 - layer_sizes = [ - len(range(layer % 2, n_qubits - 1, 2)) - for layer in range(config["n_layers"]) - ] - n_gates = sum(layer_sizes) - - rng = np.random.default_rng(config["seed"]) - params = rng.normal( - scale=config["initial_parameter_scale"], - size=(15 * n_gates,), - ).astype(np.float32) - params = K.convert_to_tensor(params) - - target_bra = _pair_fused_target_bra(config["dmrg_state"]) - gens = jnp.asarray(_GENERATORS, dtype=jnp.complex64) - neel_perm = jnp.asarray(_NEEL_PERM, dtype=jnp.complex64) - eye2 = jnp.eye(2, dtype=jnp.complex64) - optimizer = optax.adam(config["learning_rate"]) - opt_state = optimizer.init(params) - - def objective(p): - gates = _su4_batch(p.reshape(n_gates, 15), gens) - # layer 1 on fused pairs, with the Neel preparation folded in - single = jnp.einsum("gab,bc->gac", gates[: layer_sizes[0]], neel_perm) - # layer 2 straddles fused sites j, j+1: I2 (x) SU4 (x) I2, regrouped - double = gates[layer_sizes[0] :].reshape(-1, 2, 2, 2, 2) - double = jnp.einsum("xX,gpqPQ,yY->gxpqyXPQY", eye2, double, eye2) - double = double.reshape(-1, 16, 16) - - circuit = tc.QuditCircuit(n_sites, dim=4) - for j in range(n_sites): - circuit.any(j, unitary=single[j]) - for j in range(n_sites - 1): - circuit.any(j, j + 1, unitary=double[j]) - - overlap_value = (target_bra @ circuit.quvector()).eval() - fidelity = K.real(K.conj(overlap_value) * overlap_value) - return 1.0 - fidelity, (fidelity, overlap_value) - - def train_step(carry, _): - p, state = carry - (loss, aux), grads = K.value_and_grad(objective, has_aux=True)(p) - updates, state = optimizer.update(grads, state, p) - return (optax.apply_updates(p, updates), state), (loss,) + aux - - @jax.jit - def train(p, state): - return jax.lax.scan( - train_step, (p, state), None, length=config["max_steps"] - ) - - (params, _), (losses, fidelities, overlaps) = train(params, opt_state) - - return { - "loss_history": K.numpy(losses), - "fidelity_history": K.numpy(fidelities), - "final_parameters": K.numpy(params), - "final_overlap_phase": np.asarray(np.angle(K.numpy(overlaps)[-1])), - }