Skip to content

Add measure_batch and adopt it for STIM measurement - #135

Closed
david-pl wants to merge 4 commits into
mainfrom
david/measure-batch
Closed

david-pl wants to merge 4 commits into
mainfrom
david/measure-batch

Conversation

@david-pl

@david-pl david-pl commented Jun 22, 2026 •

Copy link
Copy Markdown
Collaborator

Adds measure_batch to ppvm-tableau — measure a chosen set of qubit indices,
the analogue of the batched Clifford gates from #116. measure_all becomes the
0..n_qubits case (behavior unchanged, no regression), and the noiseless M/MZ
path in ppvm-stim now uses it, reusing the measurement scratch across targets
the same way #116 did for gates. Noisy readout and MR keep 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 the
case-a Vec↔HashMap round-trip (~35% at ~1000 coefficients). No optimization
applied yet — candidates are written up for follow-up.

Note: Rust-only — measure_all isn't exposed to Python, so no binding changes.

FYI, @rafaelha @Roger-luo we may want to consider the Python bindings for this in #134.

david-pl and others added 3 commits June 22, 2026 10:29
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>
Copilot AI review requested due to automatic review settings June 22, 2026 09:28
@github-actions

github-actions Bot commented Jun 22, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-23 04:44 UTC

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_scratch in ppvm-tableau and refactor measure_all_with_scratch to share the same per-qubit helper.
  • Switch noiseless M/MZ in ppvm-stim to use measure_batch while 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_batch performance.

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.

Comment thread crates/ppvm-tableau/src/measure_all.rs
Comment thread scripts/samply_top.py
Comment thread scripts/profile_measure_batch.sh
Comment thread scripts/samply_top.py
@@ -0,0 +1,129 @@
#!/usr/bin/env python3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this file do? If it renders something, maybe we can post the render result in this PR just for future reference.

@Roger-luo

Copy link
Copy Markdown
Collaborator

Superseded by #142, which carries this work forward onto the measure_many API that #134 established (folding the scratch-reusing implementation in as the tableau's LossyMeasure::measure_many override rather than a parallel measure_batch). Your commits are preserved there and you're credited as co-author. Closing in favor of #142.

@Roger-luo Roger-luo closed this Jun 23, 2026
Roger-luo added a commit that referenced this pull request Jun 23, 2026
…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>
@david-pl
david-pl deleted the david/measure-batch branch July 20, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants