Skip to content

Make PauliString efficient via a symplectic bit representation (#47) [unitaryHACK]#67

Open
mk0dz wants to merge 1 commit into
tqec:mainfrom
mk0dz:perf/47-symplectic-paulistring
Open

Make PauliString efficient via a symplectic bit representation (#47) [unitaryHACK]#67
mk0dz wants to merge 1 commit into
tqec:mainfrom
mk0dz:perf/47-symplectic-paulistring

Conversation

@mk0dz

@mk0dz mk0dz commented Jun 15, 2026

Copy link
Copy Markdown

/claim #47

The ask

PauliString is dict-based (dict[int, "X"/"Y"/"Z"]); the hot methods
(anticommutes, collapse_by, __mul__, __hash__, to_int) do per-qubit
dictionary lookups, dominating annotate_detectors_automatically on a rotated
surface code. Reimplement it (stim/numpy/int bit-array) to reduce benchmark runtime.

What I changed

1. pauli.py — symplectic (x, z) bit-integer representation

Each Pauli string is two non-negative Python ints _x, _z; bit q encodes the
X / Z component at qubit q ((0,0)=I, (1,0)=X, (0,1)=Z, (1,1)=Y). The hot
operations become single big-integer bitwise ops:

method before (dict) after (symplectic)
__mul__ loop over union of qubits (_x ^ _x', _z ^ _z')
anticommutes loop over shared qubits ((_x & _z') ^ (_z & _x')).bit_count() & 1
collapse_by per-op dict deletions per-op &= ~(op._x | op._z)
__hash__/__eq__ tuple of items (_x, _z)

The anticommutation identity was verified per-Pauli (X·Z, X·Y, Y·Z = 1; I·*, equal
= 0) and is exact because GF(2) parity is linear. Full public API preserved
(.qubits, .qubit, [q], to_int, to_stim_pauli_string, from_stim_pauli_string,
contains, overlaps, after, …). from_stim_pauli_string uses stim's
to_numpy(). Added __slots__ and immutable __copy__/__deepcopy__ (return
self) — safe because the object is now frozen — which removes object churn from
the deepcopy of flow structures in match.py.

2. boundary.py — cache after_collapse (the next bottleneck)

Profiling showed BoundaryStabilizer.after_collapse (which runs collapse_by)
was recomputed on every access, and it's accessed several times per stabilizer.
A BoundaryStabilizer is immutable (its stabilizer + frozenset of collapsing
operations are fixed at construction), so the property is now @cached_property.
This was the single biggest win (the issue invites finding the "next bottleneck").

Results

version ms / annotate speedup
baseline (dict) 96.8 1.00×
+ symplectic PauliString 63.7 1.52×
+ cached after_collapse 41.0 2.36×

All checks green: pytest 115 passed, mypy clean (19 files), ruff clean.

Remaining hotspots (noted for the PR as future work): PauliString.to_int in the
GF(2) cover search, and the deepcopy of FragmentFlows in match.py.

Files changed (PR contents)

 src/tqecd/pauli.py      (symplectic rewrite, full API preserved)
 src/tqecd/boundary.py   (cached_property on after_collapse)

(bench_pauli.py is kept out of the PR; the issue provides its own benchmark.)

Store each Pauli string as two Python ints (x, z bit vectors) instead of a
dict, turning __mul__, anticommutes, collapse_by and hashing into single
big-integer bitwise ops. Preserve the full public API. Make PauliString
immutable (slots + identity copy) to avoid deepcopy churn. Separately, cache
BoundaryStabilizer.after_collapse (an immutable property recomputing the hot
collapse_by on every access) - the largest single win.

Benchmark (d=5 rotated surface code, 100 rounds): annotate_detectors_
automatically 96.8 ms -> 41.0 ms (2.36x). pytest/mypy/ruff all green.
@nelimee nelimee added the unitaryHACK26 Issues that are part of the Unitary Hack 2026 label Jun 16, 2026
@nelimee

nelimee commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Hey, #63 exists and has already been through several rounds of review. Because the hackathon is close to an end, we might not have the time to iterate on this PR. I'll try to provide you feedback on it anyway, but know that there is a high chance that #63 is merged instead, because I am reasonably certain it is correct after 2 rounds of review. In that case, my review will likely focus on the differences between this PR and #63 to find where the potential additional performance is gained in this PR.

@mk0dz

mk0dz commented Jun 16, 2026

Copy link
Copy Markdown
Author

Thanks @nelimee, that's completely fair — #63 being correct after two review
rounds should take priority, and I'm not asking you to hold that up. I'll just
flag where the extra performance in this PR comes from, in case it's useful to
fold in regardless of which PR lands.

There are two largely independent changes here:

  1. Symplectic PauliString — each string is two Int bit-vectors (x, z),
    so *, anticommutes, collapse_by and hashing become single big-integer
    bitwise ops instead of per-qubit dict work. (__copy__/__deepcopy__ return
    self since it's now immutable, which also removes the deepcopy churn in
    match.py.)

  2. BoundaryStabilizer.after_collapse is memoised (@cached_property in
    boundary.py). It was being recomputed on every access — and it's hit several
    times per stabilizer — while a BoundaryStabilizer is immutable, so the result
    is safe to cache. This is the single biggest win, and it's orthogonal to the
    PauliString change, so it should stack on top of Optimize PauliString and detector flow performance #63.

Benchmark (annotate_detectors_automatically, d=5 rotated surface code, 100 rounds,
4000 detectors), measured incrementally:

version ms / annotate speedup
baseline (devel) 96.8 1.00×
+ symplectic PauliString 63.7 1.52×
+ cached after_collapse 41.0 2.36×

pytest is green (115 passed), mypy and ruff clean.

If #63 merges, I'm happy to rebase this on top of it and open a smaller follow-up
with just the after_collapse caching (and any symplectic delta), so you can see
exactly where the additional ~1.5× is — whichever is least work for you.

@nelimee

nelimee commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Thanks for understanding! I'll let you know when I can focus on this PR. Expect around the end of this week, or maybe next week, so don't worry if you do not hear back in the next few days.

@nelimee

nelimee commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Hey, #63 will be merged, but I keep this PR open for the moment because we might re-use some of the optimisations here. Just wanted to let you know about #63.

@mk0dz

mk0dz commented Jun 16, 2026

Copy link
Copy Markdown
Author

Hey, #63 will be merged, but I keep this PR open for the moment because we might re-use some of the optimisations here. Just wanted to let you know about #63.

you find my implementation worthy enough to use in your core codebase , thats actually a win for me , otherwise #63 is a clear winner because @Danop404 actually worked hard on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

unitaryHACK26 Issues that are part of the Unitary Hack 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants