Repository navigation
Lossy-Pauli-word-2 crate - #261
Merged
Merged
Conversation
As this crate can be quite complicated, this is just PR 1 of incremental
merges to support lossy-pauli-word-2. This patch does the following:
- Initialize the lossy-pauli-word-2 crate
- adds forgotten LossySite enum in ppvm-traits-2/src/loss.rs
- implements LossyPauliWord, which has:
- two hash cashes, for the xz structure and the loss structure
- implements Word, PauliBits, LossSite, and other traits
Part of the effort to refactoring
#204
The other half of pr #250 for implementing the `ppvm-lossy-pauli-word-2` crate. This PR should be merged only **AFTER** #250 has been merged in. This PR introduces the following: - hash.rs with functionality for hashing the xz hash and loss hash separately - LossyPauliKeyColumn that implements traits of interests, such as KeyColumn, KeyColumnMut, PauliColumn, and LossColumn - SymplecticColumns and PhaseTrack implementation for LossyPauliWord in Clifford.rs Part of the effort to refactoring #204
…generator (#252) ## Summary This makes `exp(dt·M)·b` in `ppvm-lindblad` bit-identical for every rayon thread count. It does so by replacing the column-chunked `CscOp::dot` with a row-major (CSR) cache and a row-parallel matvec. I'm framing it as a determinism and scaling fix, not a speed-up. **Problem.** `CscOp::dot` split the columns into `rayon::current_num_threads()` chunks, accumulated each chunk into a dense length-n partial vector, and summed the partials on one core. The summation order, and hence the round-off, depended on the thread count. A rank-capped evolution amplifies that round-off to the size of the truncation error. On the 82-qubit XX ladder (open two-leg ladder, centre-rung spin density, A = 3B, `drop_tol = 0`), changing `RAYON_NUM_THREADS` moved the MSD by up to 1.6e-2 by Jt = 2. A stored run could therefore only be reproduced at the same core count. The design also allocated threads × n scalars per matvec and did an O(threads × n) serial reduction. **Change.** - The action pass is still column-wise. Each column block stages its entries in exactly-sized buckets, one per row range (16 B/entry). - A second parallel pass counting-sorts every range into CSR (12 B/entry), dropping the buckets as it goes. The cache is never held in both layouts at once. - The range size is the next power of two ≥ max(64, ⌈n/256⌉) rows, so it depends on n only. - `dot` computes each output entry on one thread, over its row in ascending column order. - Together with a canonical term order in `compute_action_terms` (sorted by word hash), every sum in the cache build, the matvec and the leakage merge runs in an order fixed by the basis alone. Results differ from `main` only at round-off level (≤ 3.6e-15 in the ladder MSD by Jt = 2). - No partial vectors, no serial reduction. Nothing else in the step depends on the thread count, for these reasons: - quspin's `apply` runs sequentially (`parallel_hint = false`). - The leakage merge runs in fixed chunk order. - The norm estimates are maxima. **Tests.** - `pc_step_is_bit_identical_across_thread_counts`: a rank-capped XXZ chain with Z dephasing (n = 14, cap 1200, admit 3600, 12 steps), compared at 1/2/3/8 threads. It fails on `main` at 2 threads. - `pc_step_orbit_rep_is_bit_identical_across_thread_counts`: the same check on the orbit-rep path (ring with collective decay, cap binding). - `action_term_order_is_independent_of_scratch_capacity`: `compute_action_terms` must return the same order from a fresh and from a pre-grown scratch map. It fails without the sort. All `ppvm-lindblad` tests and the Python lindblad tests pass, and `cargo fmt` and `clippy --all-targets` are clean. ## Measurements Apple M4 (4P + 6E cores), 82-qubit XX ladder, T = 2, δ = 0.025. **Revisions covered.** The baseline is `main` at `1d78a44b`. The determinism, cost and breakdown numbers below were measured on the first revision, `a73875f6`. For the current revision, `a5271fcf` (canonical term order and review fixes), I re-verified bit-identity across 1/2/4/8/10 threads at B = 2^12–2^14 and peak RSS at 2^16/2^18. Its wall times are still to follow: the machine was shared with other jobs while I measured. **Determinism**, B = 2^12–2^16, 1/2/4/8(6)/10 threads: - This branch: the MSD and the full density profile are bit-identical across all thread counts. `a73875f6` was also identical to `main` at 1 thread; `a5271fcf` differs from it by ≤ 3.6e-15 in MSD. - `main`: max|ΔMSD| vs its own 1-thread run is up to 1.6e-2 (B = 2^13), 5.0e-3 (2^14) and 1.9e-3 (2^16). **Cost**, 6 threads, two interleaved repetitions: | B | build | wall [s] | peak RSS [MiB] | |---|---|---|---| | 2^16 | main | 9.65 / 10.14 | 336 / 332 | | 2^16 | this PR | 9.69 / 10.05 | 316 / 303 | | 2^18 | main | 60.6 / 65.8 | 674 / 673 | | 2^18 | this PR | 58.1 / 63.4 | 711 / 710 | Expm breakdown at 2^18 / 6 threads (n = 786k, nnz = 4.4M): | | main | this PR | |---|---|---| | time per matvec | 5.94 ms | 2.35 ms (2.5× faster) | | cache build, whole run | 25.3 s | 27.3 s (+8%) | | matvecs, whole run | 10.5 s | 4.2 s | At 2^16 the time per matvec (ms) scales with threads as follows: | threads | 1 | 2 | 4 | 6 | 10 | |---|---|---|---|---|---| | main | 1.12 | 0.76 | 0.68 | 0.85 | 1.05 | | this PR | 1.01 | 0.68 | 0.43 | 0.46 | 0.42 | The old matvec degrades above 4 threads; this one keeps scaling. **Costs, stated honestly:** - The whole-step wall time changes within run-to-run noise (about 8%), because the old matvec was only 13–17% of the wall at 2^18. - The cache build is 5–8% slower. - Peak RSS is 6–9% lower at 2^16 but 5.5% higher at 2^18 / 6 threads. This most likely comes from bucket staging that the allocator does not fully reuse. - The memory trade should reverse at high thread counts, because the old partials scale with the thread count: about 1.6 GB per matvec at B = 2^21 and 32 threads, vs about 0.6 GB of staging here. This is not measured. ## Deliberately not included **Canonical (|c|, word) tie-breaking in `cap_basis` / `cap_map_to_room`.** - It is not needed for thread independence: once the matvec is deterministic, row order and hash iteration order are deterministic too. - Ties do decide the cut on the ladder, so the rule moves the MSD by about 2e-3 to 4e-2. That is the size of the old thread spread, and there is no accuracy argument for either rule. **Leakage merge.** In local profiles, the leakage merge and room cap are the next serial bottleneck, about 60% of the wall at 10 threads. A separate branch addresses that. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: David Plankensteiner <david-pl@users.noreply.github.com>
Implement `IsOne` in the Squin simulator, with integration coverage for zero, one, loss, and conditional correction.
`two_qubit_pauli_error_probabilities` accidentally contained the key `"IT"` instead of `"IY"`. This means that any `IY` probability passed in was silently dropped and treated as 0.0, in every backend that inherits `NoiseMixin`. The wrong key came from the `typos` pre-commit hook, which used to rewrite `"IY"` as `"IT"`. That hook was removed and the other occurrences were fixed in a61ed93, but this copy came back with #34, whose branch was created before that fix. The current prek hooks don't run a spell-checker, so no exception is needed. The existing test only used `"ZZ"` and `"XX"`, which is why this went unnoticed. I added a regression test that gives each of the 15 non-identity two-qubit Paulis a distinct probability and checks that each one ends up in the right position. It fails on the old code and passes with the fix. `prek run --all-files` and the full Python test suite pass locally. Co-authored-by: David Plankensteiner <david-pl@users.noreply.github.com>
Add Squin CCZ execution using existing T/T-adjoint and CNOT gates. Require Bloqade Circuit 0.15.0, which introduced its CCZ statement. --------- Co-authored-by: David Plankensteiner <david-pl@users.noreply.github.com>
## Summary `ppvm-python/src/ppvm/paulisum.py` imports `Self` from `typing`, which only exists on Python ≥ 3.11. Since `ppvm/__init__` imports that module, `import ppvm` fails on 3.10 even though `requires-python = ">=3.10"`. This has been the case since #38, unnoticed because CI doesn't test 3.10. It was flagged by Copilot on #237, whose release workflow runs the test suite on 3.10 and would block the PyPI publish. - Import `Self` from `typing_extensions` instead. - Declare `typing-extensions>=4.0` as a direct dependency. `squin_interpreter/_interp.py` already imported it but only got it transitively (via `kirin-toolchain` / `pydantic`). ## Testing - Python 3.12: 355 passed, 1 skipped. - Python 3.10: 353 passed, 1 skipped. `test/benchmarks/test_msd.py` fails to import locally because scipy 1.15's macOS wheel (the newest available for 3.10) doesn't load on the dev machine's macOS. This is unrelated to ppvm and should pass on CI runners. - `vermin -t=3.10-` over `src/` reports no remaining 3.11+ features. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
|
david-pl
reviewed
Oct 23, 2026
david-pl
left a comment
Collaborator
There was a problem hiding this comment.
This looks good overall. Unfortunately, there appear to be some issues with caching in the current main branch that are at least partially carried over here.
We should wait for #267 to be merged, rebase this branch and also apply the same fixes to the hashing in this PR. Apologies for the delay.
david-pl
approved these changes
Oct 27, 2026
| let bit = zbits[i]; | ||
| zbits.set(i, !bit); | ||
| } | ||
| Self::from_planes(xbits, zbits, self.lbits, self.nqubits) |
Collaborator
There was a problem hiding this comment.
Reconstructing with from_planes always throws away the caches. Would it make more sense to replace this (and similar calls) with a clone and only rehash when needed?
david-pl
approved these changes
Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Combination of merges from #250 and #253
This PR is to have one final look over the crate prior to merge.