Repository navigation
Conversation
Add `LossyMeasureAll::measure_batch(&[usize])`, the indices-accepting analogue of the batched Clifford gates. `measure_all` becomes the `0..n_qubits` case; both share a private `measure_one_with_scratch` helper so the all-qubits path keeps its allocation-free loop. Also adds `measure_batch_with_scratch` for samplers, batch-vs-individual tests, and measure_batch cases in the measure-all / measure-scaling benches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The executor's M/MZ arm now calls `measure_batch` for noiseless readout, reusing the measurement scratch across targets like the batched gate paths. Noisy readout keeps the per-qubit `measure_noisy` loop so the interleaved measure/flip RNG draws are unchanged, and MR is untouched. Adds a grouped-vs-separate measurement characterization test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a samply-based profiling setup for measure_batch across three coefficient-count regimes (few ~1, mid ~100, large ~1000): - examples/profile_measure_batch.rs: quick-timing + sustained-loop driver - scripts/profile_measure_batch.sh: records via samply (no sudo on macOS) - scripts/samply_top.py: headless top-functions summary from the saved profile - benches/measure_batch_profiling.md: findings Profiling shows two regimes: a constant ~16us O(n^2) tableau-bound floor (compute_decomposition + update_tableau) dominating low-coefficient states, and the case-a Vec<->HashMap round-trip (~35% at 1024 coefficients) dominating coefficient-heavy ones. No measure_batch code change yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
There was a problem hiding this comment.
Pull request overview
This PR adds a batched Z-basis measurement API to ppvm-tableau (measure_batch) and adopts it in the ppvm-stim executor for noiseless multi-target M/MZ, enabling reuse of measurement scratch across targets (analogous to the batched Clifford-gate work in #116). It also introduces profiling harness scripts and a short write-up capturing current performance findings.
Changes:
- Add
measure_batch/measure_batch_with_scratchinppvm-tableauand refactormeasure_all_with_scratchto share the same per-qubit helper. - Switch noiseless
M/MZinppvm-stimto usemeasure_batchwhile preserving the per-qubit loop for noisy readout to keep RNG draw interleaving unchanged. - Add tests, benches, and profiling tooling/docs for measuring and analyzing
measure_batchperformance.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/samply_top.py | New utility to summarize samply profiles by self/inclusive time. |
| scripts/profile_measure_batch.sh | New profiling driver script for measure_batch across workload regimes. |
| crates/ppvm-tableau/tests/measure_batch.rs | Integration tests asserting measure_batch matches measure/measure_all behavior (including loss). |
| crates/ppvm-tableau/src/measure_all.rs | Introduces measure_batch in the measurement API and factors shared per-qubit logic. |
| crates/ppvm-tableau/examples/profile_measure_batch.rs | Profiling example executable for measure_batch cost regimes. |
| crates/ppvm-tableau/benches/measure-scaling.rs | Adds a benchmark variant measuring measure_batch over all qubits. |
| crates/ppvm-tableau/benches/measure-all.rs | Adds a measure-batch-all benchmark to check for regressions vs measure_all. |
| crates/ppvm-tableau/benches/measure_batch_profiling.md | Documents profiling results and follow-up optimization candidates. |
| crates/ppvm-stim/tests/executor.rs | Adds a seeded equivalence test ensuring grouped M matches separate M instructions. |
| crates/ppvm-stim/src/executor.rs | Uses measure_batch for noiseless M/MZ multi-target execution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,129 @@ | |||
| #!/usr/bin/env python3 | |||
There was a problem hiding this comment.
What does this file do? If it renders something, maybe we can post the render result in this PR just for future reference.
|
Superseded by #142, which carries this work forward onto the |
…upersedes #135) (#142) Supersedes #135. Carries forward David's batched-measurement work (his original commits are preserved, and he's credited as co-author), reworked to land on the `measure_many` API that #134 already established rather than a parallel `measure_batch` name. ### Background: how this relates to #134 and #135 - **#134 (merged)** introduced `LossyMeasure::measure_many` as the canonical batched-measurement name — but only as a **default trait method** (a per-target `measure` loop that allocates a fresh `MeasureScratch` each call, so *no* scratch reuse) — and wired it through to Python. - **#135** added the *optimized* implementation (scratch reused across targets) plus the Stim executor adoption and a profiling harness, but under a second name (`measure_batch` on `LossyMeasureAll`) written before #134 settled the `*_many` convention. This PR folds #135's fast implementation in as the tableau's **override of `LossyMeasure::measure_many`**, so there's a single canonical name and the Python binding + Stim executor both pick up the speedup for free. ### Changes - **`ppvm-tableau/src/measure.rs`** — override `LossyMeasure::measure_many` for `GeneralizedTableau` with the scratch-reusing path (one `MeasureScratch` reused across the batch, amortizing the case-a HashMap / `b_entries` allocations and the cached odd-phase-destabilizer mask). - **`ppvm-tableau/src/measure_all.rs`** — drop `LossyMeasureAll::measure_batch`; rename `measure_batch_with_scratch` → `measure_many_with_scratch`. The shared `measure_one_with_scratch` helper now records `None` for lost qubits, so every batched path matches per-qubit `measure` on the **measurement record**, not just on returned outcomes (also closes a latent gap in `measure_all`). - **`ppvm-stim/src/executor.rs`** — noiseless `M`/`MZ` delegates to `measure_many`; noisy readout and `MR` keep the per-qubit loop so RNG draws are unchanged. - **No Python change needed** — `interface_tableau.rs` already calls `.measure_many(...)`, so the binding now routes through the fast override. - Renamed the tests / benches / profiling harness `*_batch*` → `*_many*`, and added a loss-aware record-equivalence test (`measure_many` vs sequential `measure`, including the measurement record). Profiling findings (`benches/measure_many_profiling.md`) are unchanged from #135: a constant ~16µs O(n²) tableau floor, plus a case-a `Vec`↔`HashMap` round-trip (~35% at ~1000 coefficients). No further optimization applied yet — candidates are written up for follow-up. ### Validation `cargo build --workspace`, `cargo clippy --workspace -- -D warnings`, `cargo test --workspace`, and `uv run --project ppvm-python --group dev pytest` (183 passed) — all green, plus the full prek hook suite. Co-authored-by: David Plankensteiner <dplankensteiner@quera.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: David Plankensteiner <dplankensteiner@quera.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds
measure_batchtoppvm-tableau— measure a chosen set of qubit indices,the analogue of the batched Clifford gates from #116.
measure_allbecomes the0..n_qubitscase (behavior unchanged, no regression), and the noiselessM/MZpath in
ppvm-stimnow uses it, reusing the measurement scratch across targetsthe same way #116 did for gates. Noisy readout and
MRkeep the per-qubit loop,so RNG draws are unchanged.
Also adds a samply profiling harness (
scripts/profile_measure_batch.sh) and a short findings doc. The cost splits into two regimes: a constant~16µs O(n²) tableau floor (
compute_decomposition+update_tableau), and thecase-a
Vec↔HashMapround-trip (~35% at ~1000 coefficients). No optimizationapplied yet — candidates are written up for follow-up.
Note: Rust-only —
measure_allisn't exposed to Python, so no binding changes.FYI, @rafaelha @Roger-luo we may want to consider the Python bindings for this in #134.