Skip to content

Extract Pauli flow from XZ-corrections (closes #432)#526

Open
Vinny010 wants to merge 3 commits into
TeamGraphix:masterfrom
Vinny010:pauli-flow-extraction
Open

Extract Pauli flow from XZ-corrections (closes #432)#526
Vinny010 wants to merge 3 commits into
TeamGraphix:masterfrom
Vinny010:pauli-flow-extraction

Conversation

@Vinny010
Copy link
Copy Markdown

@Vinny010 Vinny010 commented Jun 3, 2026

Closes #432.

Implements XZCorrections.to_pauli_flow and the convenience method Pattern.extract_pauli_flow (analogous to extract_causal_flow / extract_gflow), reconstructing a Pauli flow directly from the pattern's XZ-corrections (Theorem 4 of Browne et al. 2007) rather than from the underlying open graph — whose Pauli flow is not unique and is not guaranteed to generate the pattern.

How the anachronical corrections were tackled (the crux of the issue)

The hard part, as the issue notes, is that a Pauli flow's correction sets may contain anachronical corrections: corrections targeting X/Y Pauli-measured nodes that lie in the present or past of the corrected node. PauliFlow.to_corrections discards these (the correcting_set & future filter), so they never appear in the pattern and cannot be read off the corrections — they must be reconstructed.

For each measured node i, reconstruction is cast as a linear system over GF(2):

  • Pinned future part. For nodes in the future of i, membership in the correction set p(i) is fixed by the observed X-corrections of i (and must reproduce the Z-corrections via the odd neighbourhood). These become constants of the system.
  • Free anachronical variables. The unknowns are exactly the anachronical candidates — non-future nodes measured along the X or Y axes (permitted in p(i) by P1) — and, where the local proposition allows it, i itself.
  • Equations. The odd-neighbourhood constraints: the Z-corrections on the future nodes, the vanishing of the odd neighbourhood on past non-(Y/Z) nodes (P2), the membership/odd-neighbourhood coupling on past Y nodes (P3), and the local proposition on i (P4–P9, handling the XY/XZ/YZ planes and the X/Y/Z axes).

The system is solved with a small GF(2) Gaussian-elimination helper (_solve_gf2); failure to solve at any node means no Pauli flow is compatible with the corrections. The resulting flow is then validated by PauliFlow.check_well_formed.

Correctness

The decisive check is the round trip: for the reconstructed pf, pf.to_corrections() must reproduce the pattern's X- and Z-corrections exactly (this is what guarantees it generates this pattern). Tests verify well-formedness and the round trip on:

  • the three worked examples of the issue (causal, gflow and Pauli; the reconstructed correction functions match the expected p(0) = {1, 3}, p(1) = {2}, p(2) = {3} etc., including the anachronical node 1),
  • a Pauli-measured open graph, and
  • a randomized family of open graphs that admit a Pauli flow.

There are also unit tests for the GF(2) solver. Passes ruff, mypy --strict, pyright, and pytest locally (new tests plus the existing flow / open-graph / pattern suites).

Developed with LLM assistance, reviewed and tested by me.

Implement `XZCorrections.to_pauli_flow` and the convenience method
`Pattern.extract_pauli_flow`, reconstructing a Pauli flow directly from a
pattern's XZ-corrections (Theorem 4 of Browne et al. 2007) rather than from the
underlying open graph (whose Pauli flow is not unique and need not generate the
pattern).

The difficulty is the anachronical corrections: corrections targeting X/Y
Pauli-measured nodes in the present or past of the corrected node. These are
dropped by `PauliFlow.to_corrections` (the `& future` filter) and so never appear
in the pattern, so they must be reconstructed. For each measured node this is cast
as a GF(2) linear system: the future membership of the correction set is pinned by
the observed X-corrections; the free variables are the anachronical (non-future,
X/Y-measured) candidates and, where allowed, the node itself; and the equations
encode the odd-neighbourhood constraints (Z-corrections on future nodes, P2 on
past non-(Y/Z) nodes, the P3 coupling on past Y nodes, and the local proposition
P4-P9 on the node). The system is solved over GF(2) with `_solve_gf2`.

Tests verify, on the three worked examples of the issue, on a Pauli-measured open
graph, and on a randomized family of open graphs that admit a Pauli flow, that the
reconstructed flow is well formed and that `to_corrections()` reproduces the
pattern's corrections exactly (the decisive round-trip criterion).

Passes ruff, mypy --strict, pyright, and pytest locally.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.08%. Comparing base (fcdb8f4) to head (926197a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #526      +/-   ##
==========================================
+ Coverage   88.85%   89.08%   +0.23%     
==========================================
  Files          49       49              
  Lines        7135     7240     +105     
==========================================
+ Hits         6340     6450     +110     
+ Misses        795      790       -5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Cover the branches where no Pauli flow is compatible with the XZ-corrections: a
measured input node that must correct itself, and an isolated XY-measured node
whose proposition P4 cannot be satisfied (unsolvable GF(2) system). Addresses the
patch-coverage gap reported on the PR.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Copy link
Copy Markdown
Collaborator

@thierry-martinez thierry-martinez left a comment

Choose a reason for hiding this comment

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

Thank you very much for this clean solution. Here are my initial comments on your PR.

Comment thread graphix/flow/core.py Outdated
raise PartialOrderLayerError(PartialOrderLayerErrorReason.FirstLayer, layer_index=0, layer=first_layer)


def _solve_gf2(matrix: list[list[int]], rhs: list[int], n_vars: int) -> list[int] | None:
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.

There is already a GF(2)-linear-system solver implemented in _linalg.solve_f2_linear_system.

Comment thread tests/test_pauli_flow_extraction.py Outdated
Comment on lines +120 to +122
lambda r: Measurement.XY(round(float(r.random()), 3)),
lambda r: Measurement.XZ(round(float(r.random()), 3)),
lambda r: Measurement.YZ(round(float(r.random()), 3)),
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.

Why do you choose to apply round there rather than leave the random float unchanged?

Comment thread tests/test_pauli_flow_extraction.py Outdated
pattern = OpenGraph(
graph=graph, input_nodes=inputs, output_nodes=outputs, measurements=measurements
).to_pattern()
except Exception: # noqa: BLE001, S112 open graph without a flow -> not a valid test case
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 are the relevant exceptions here? We should at least catch OpenGraphError. Are there any others?

Comment thread graphix/flow/core.py Outdated
"""
correction_function = _reconstruct_pauli_correction_function(self)
pf: PauliFlow[_AM_co] = PauliFlow(self.og, correction_function, self.partial_order_layers)
pf.check_well_formed() # Raises a `FlowError` if the reconstructed flow is not well formed.
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.

Is it possible to fail at this point? Can you give an example where the correction_function isn’t well‑formed by construction?

- Reuse `graphix._linalg.solve_f2_linear_system` rather than the ad-hoc
  `_solve_gf2` (which is removed). The per-node augmented matrix `[A | b]` is
  reduced to row echelon form with `MatGF2.gauss_elimination(ncols=n_vars)`,
  inconsistency is detected by scanning for `[0...0 | 1]` rows, and the reduced
  system is then handed to the existing solver.
- Update the `XZCorrections.to_pauli_flow` docstring to point at the existing
  GF(2) solver and to spell out the propositions encoded by the GF(2) system
  (P1 by construction via the X/Y-axis candidate restriction; P2-P9 directly).
- Add a comment on the `pf.check_well_formed()` call: it is a regression guard;
  the algorithm satisfies the propositions by construction, so a failure there
  would indicate a bug rather than malformed input.
- In the randomized round-trip test, narrow `except Exception` to
  `OpenGraphError` (the only documented raise of `OpenGraph.to_pattern` when no
  flow exists) and drop the cosmetic `round` on the random measurement angles.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@Vinny010
Copy link
Copy Markdown
Author

Vinny010 commented Jun 5, 2026

Thanks for the thoughtful review, @thierry-martinez! Pushed 9a73dc7 addressing each point:

  • _linalg.solve_f2_linear_system reuse — good catch. Switched to it. The per-node augmented matrix [A | b] is reduced to row echelon form with MatGF2.gauss_elimination(ncols=n_vars) (which propagates the row operations to the RHS column), inconsistency is detected by scanning for a [0…0 | 1] row, and the reduced system is then handed to the existing solver. _solve_gf2 is removed.
  • round on the random angle — purely cosmetic (kept the test failure messages readable when I was iterating). Dropped.
  • Exception narrowing in the randomized test — narrowed to OpenGraphError, which is the only documented raise condition of OpenGraph.to_pattern when no flow exists.
  • pf.check_well_formed() on line 311 — by construction the GF(2) equations of _reconstruct_pauli_correction_function encode P2–P9 exactly, and restricting the anachronical candidates to X/Y-measured nodes guarantees P1; the general flow properties are satisfied by construction too. So I cannot construct an example where the check fails — it’s effectively a regression guard for the algorithm. Added a comment explaining that. Happy to drop the call entirely if you’d prefer.

Let me know if you’d like any other changes.

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.

Pauli-flow extraction from pattern

2 participants