Skip to content

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

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

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

Conversation

@Vinny010

@Vinny010 Vinny010 commented Jun 3, 2026

Copy link
Copy Markdown

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 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.03%. Comparing base (fcdb8f4) to head (ef00cbc).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
graphix/flow/exceptions.py 50.00% 2 Missing ⚠️
graphix/flow/core.py 98.80% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #526      +/-   ##
==========================================
+ Coverage   88.85%   89.03%   +0.18%     
==========================================
  Files          49       49              
  Lines        7135     7225      +90     
==========================================
+ Hits         6340     6433      +93     
+ Misses        795      792       -3     

☔ 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 <noreply@anthropic.com>

@thierry-martinez thierry-martinez left a comment

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.

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 <noreply@anthropic.com>
@Vinny010

Vinny010 commented Jun 5, 2026

Copy link
Copy Markdown
Author

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.

@veena-vee989

Copy link
Copy Markdown

Hello - you are currently over the PR limit for unitaryHACK, so your PRs will be disregarded until you align with the rules. Reach out to hack@unitary.foundation to be reinstated.

@thierry-martinez thierry-martinez requested a review from matulni June 9, 2026 08:21

@matulni matulni 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.

Thanks for this nice work! Here are some comments from my first pass. There's some work before merging, but in the mean time I'm ok with assigning you the issue.

For my own curiosity, would you be willing to share the promtps and skills (or even the reasoning log) of the LLM you used to code this ? This is of course not mandatory, but I'm curious to see how the solution was found. Thanks!

Comment thread graphix/flow/core.py Outdated
See :meth:`XZCorrections.to_pauli_flow`.
"""
og = xz.og
adjacency: dict[int, set[int]] = {n: set(og.graph.neighbors(n)) for n in og.graph.nodes}

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.

Suggested change
adjacency: dict[int, set[int]] = {n: set(og.graph.neighbors(n)) for n in og.graph.nodes}
adjacency: dict[int, set[int]] = {n: og.neighbors({n}) for n in og.graph.nodes}

Comment thread graphix/flow/core.py
for i in range(lhs.shape[0]):
if not lhs[i].any() and b[i] != 0:
return None
solution = solve_f2_linear_system(lhs, MatGF2(b))

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.

Suggested change
solution = solve_f2_linear_system(lhs, MatGF2(b))
# `solve_f2_linear_system` does not check if a solution exists or if `lhs` is in REF.
solution = solve_f2_linear_system(lhs, MatGF2(b))

Comment thread graphix/flow/core.py Outdated
solution = solve_f2_linear_system(lhs, MatGF2(b))

correction_set = set(fixed_in_p)
correction_set.update(v for v, bit in zip(variables, solution, strict=True) if int(bit))

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.

Suggested change
correction_set.update(v for v, bit in zip(variables, solution, strict=True) if int(bit))
correction_set.update(v for v, bit in zip(variables, solution, strict=True) if bit)

Comment thread graphix/flow/core.py Outdated
Comment on lines +1478 to +1479
candidates = sorted(a for a in nonfuture_others if a in non_inputs and labels.get(a) in {Axis.X, Axis.Y})
variables = [*candidates, node] if self_is_var else list(candidates)

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.

I don't think sorting is necessary, but if it is, please add a test which would fail if candidates are not sorted.

Suggested change
candidates = sorted(a for a in nonfuture_others if a in non_inputs and labels.get(a) in {Axis.X, Axis.Y})
variables = [*candidates, node] if self_is_var else list(candidates)
variables = [a for a in nonfuture_others | {node} if a in non_inputs and labels.get(a) in {Axis.X, Axis.Y}]

With the suggestion above, self_is_var is no longer necessary.

Comment thread graphix/flow/core.py Outdated
Comment on lines +1500 to +1515
# P2: the odd neighbourhood vanishes on non-future, non-(Y/Z) nodes.
for g in nonfuture_others:
lab_g = labels.get(g)
if lab_g is not None and lab_g not in {Axis.Y, Axis.Z}:
matrix.append(row_at(g))
rhs.append(const_at(g))

# P3: a non-future Y-measured node `g` must lie outside the closed odd neighbourhood of the
# correction set, i.e. its membership and odd-neighbourhood membership must coincide.
for g in nonfuture_others:
if labels.get(g) == Axis.Y:
row = row_at(g)
if g in var_index:
row[var_index[g]] ^= 1
matrix.append(row)
rhs.append(const_at(g))

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.

I believe the following is equivalent and more clear:

Suggested change
# P2: the odd neighbourhood vanishes on non-future, non-(Y/Z) nodes.
for g in nonfuture_others:
lab_g = labels.get(g)
if lab_g is not None and lab_g not in {Axis.Y, Axis.Z}:
matrix.append(row_at(g))
rhs.append(const_at(g))
# P3: a non-future Y-measured node `g` must lie outside the closed odd neighbourhood of the
# correction set, i.e. its membership and odd-neighbourhood membership must coincide.
for g in nonfuture_others:
if labels.get(g) == Axis.Y:
row = row_at(g)
if g in var_index:
row[var_index[g]] ^= 1
matrix.append(row)
rhs.append(const_at(g))
for g in nonfuture_others:
lab_g = labels.get(g)
# `nonfuture_others` never contains output nodes
assert lab_g is not None
# P2: the odd neighbourhood vanishes on non-future, non-(Y/Z) nodes.
if lab_g not in {Axis.Y, Axis.Z}:
matrix.append(row_at(g))
rhs.append(const_at(g))
# P3: a non-future Y-measured node `g` must lie outside the closed odd neighbourhood of the
# correction set, i.e. its membership and odd-neighbourhood membership must coincide.
elif lab_g == Axis.Y:
row = row_at(g)
if g in var_index:
row[var_index[g]] ^= 1
matrix.append(row)
rhs.append(const_at(g))

Comment thread graphix/flow/core.py Outdated
Comment on lines +1575 to +1576
# No correction set reconciles the XZ-corrections with the Pauli-flow propositions.
raise FlowError

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.

It may be worth adding a new FlowGenericErrorReason in flow.exceptions.py to pass your comment as an exception message.

Comment thread graphix/flow/core.py Outdated
Comment on lines +317 to +322
# Defensive: by construction the GF(2) equations of `_reconstruct_pauli_correction_function`
# encode propositions P2-P9 exactly, and the anachronical candidates are restricted to X/Y
# axes which guarantees P1; the general flow properties are also satisfied by construction
# (the correction function is defined on the measured nodes and its image is included in
# the non-input nodes). This check is kept as a regression guard.
pf.check_well_formed()

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.

I agree with @thierry-martinez. This seems to be a purely defensive test, I'd be inclined to remove it and eventually add a more comprehensive test suite.

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.

Actually, the current implementation contains a case where this check can fail:Pattern().extract_xzcorrections().to_pauli_flow() raises graphix.flow.exceptions.PartialOrderError: The partial order cannot be empty.
However, this seems to be a bug in check_well_formed (see #531).
I agree with @matulni : these sanity checks should be part of the test-suite and should not be performed systematically in production.

Comment thread tests/test_pauli_flow_extraction.py Outdated
# succeeds) are converted to a pattern, and the reconstructed flow is checked to be
# well formed and to reproduce the pattern's corrections.
tested = 0
for seed in range(400):

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.

Having the seed as an internal parameter makes it difficult to debug. Could you please pass it as pytest.mark.parametrize variable ?

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.

In addition to these tests, could you please mirror the tests test_extract_causal_flow_rnd_circuit, test_extract_gflow_rnd_circuit, test_extract_causal_flow, and test_extract_gflow in test_pattern.py?

Comment thread tests/test_pauli_flow_extraction.py Outdated
assert tested >= 30 # ensure the randomized sweep actually exercised the extraction


def test_to_pauli_flow_raises_when_no_flow_exists() -> None:

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.

Please use pytest.mark.parametrize. I'd appreciate if you could add a few more failure tests.

Comment thread tests/test_pauli_flow_extraction.py Outdated
_assert_round_trip(pattern)


@pytest.mark.filterwarnings("ignore:Open graph with non-inferred Pauli measurements.")

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.

I believe that such warnings should not be triggered here; and if they are, we should address them, because whether Pauli measurements are inferred (or not) affects the Pauli flow.

Comment thread tests/test_pauli_flow_extraction.py Outdated
]


@pytest.mark.filterwarnings("ignore:Open graph with non-inferred Pauli measurements.")

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.

Same comment as above: these warnings should not be triggered, and they are relevant with respect to Pauli flows.

Comment thread graphix/flow/core.py Outdated
Comment on lines +317 to +322
# Defensive: by construction the GF(2) equations of `_reconstruct_pauli_correction_function`
# encode propositions P2-P9 exactly, and the anachronical candidates are restricted to X/Y
# axes which guarantees P1; the general flow properties are also satisfied by construction
# (the correction function is defined on the measured nodes and its image is included in
# the non-input nodes). This check is kept as a regression guard.
pf.check_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.

Actually, the current implementation contains a case where this check can fail:Pattern().extract_xzcorrections().to_pauli_flow() raises graphix.flow.exceptions.PartialOrderError: The partial order cannot be empty.
However, this seems to be a bug in check_well_formed (see #531).
I agree with @matulni : these sanity checks should be part of the test-suite and should not be performed systematically in production.

…check

Responds to @thierry-martinez's review on TeamGraphix#526:

- Remove the two @pytest.mark.filterwarnings("ignore:Open graph with
  non-inferred Pauli measurements.") suppressions. The warning does not
  actually fire for these tests (verified by promoting it to an error), so
  the suppressions were unnecessary and masked a semantically relevant signal.
- Stop running pf.check_well_formed() systematically in production inside
  XZCorrections.to_pauli_flow: the flow is well formed by construction, and
  the sanity check is exercised in the test-suite via is_well_formed().
  This also avoids the production manifestation of the empty-partial-order
  bug (TeamGraphix#531): Pattern().extract_xzcorrections().to_pauli_flow() no longer
  raises PartialOrderError.
- Add test_to_pauli_flow_empty_pattern covering that empty-pattern case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Vinny010

Copy link
Copy Markdown
Author

Thanks, @thierry-martinez — both points addressed in the latest commit.

Non-inferred Pauli warning suppressions. You're right that these shouldn't be silenced. I removed both @pytest.mark.filterwarnings("ignore:Open graph with non-inferred Pauli measurements.") markers. I also checked why they were there: it turns out the warning doesn't actually fire for either test — the open graphs use explicit PauliMeasurement (Measurement.Y/Measurement.Z) and non-Pauli Bloch angles, so there are no "secretly Pauli" Bloch measurements. I verified this by promoting that exact warning to an error (-W "error:Open graph with non-inferred Pauli measurements.:UserWarning") and the whole file still passes. So the suppressions were dead weight masking a semantically relevant signal — gone now.

check_well_formed() in production. Agreed with you and @matulni — removed the pf.check_well_formed() call from XZCorrections.to_pauli_flow. The reconstructed flow is well formed by construction (the GF(2) system encodes P2-P9, the anachronical candidates are X/Y-restricted for P1, and the image/domain conditions hold), and the well-formedness is now exercised only in the test-suite via the existing assert pf.is_well_formed() in the round-trip helper.

This also removes the production manifestation of #531: Pattern().extract_xzcorrections().to_pauli_flow() no longer raises PartialOrderError and instead returns the trivial flow. I added test_to_pauli_flow_empty_pattern to lock that in. (The underlying check_well_formed behaviour on an empty partial order is still the separate concern tracked in #531.)

…trized tests

- core.py: simplify the GF(2) variable selection (drop `self_is_var`/sorting),
  merge the P2/P3 constraint loops into one with an explanatory comment, add a
  general comment describing what `matrix`/`rhs` encode, and tidy the
  `adjacency`/`solve_f2_linear_system`/`correction_set.update` lines.
- exceptions.py: add `FlowGenericErrorReason.NoPauliFlow`; raise the typed
  `FlowGenericError(NoPauliFlow)` instead of a bare `FlowError` when no Pauli
  flow reconciles the XZ-corrections.
- tests: parametrize the randomized round-trip over the seed (one case per
  seed, skipping draws with no edges / no flow) and parametrize the
  no-flow failure cases (asserting the typed reason), adding XZ/YZ-input cases.
- test_pattern.py: mirror the causal/gflow extraction tests for the Pauli flow
  (`test_extract_pauli_flow_rnd_circuit` and `test_extract_pauli_flow`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Vinny010

Copy link
Copy Markdown
Author

Thanks for the careful review, @matulni — all addressed in the latest push.

Reconstruction (core.py).

  • Took your variables suggestion: it now iterates nonfuture_others | {node} with the single filter, which drops both the sorting and self_is_var. (No sorting-dependent test needed since the order no longer matters.)
  • Merged the P2 and P3 loops into the single for g in nonfuture_others form you proposed, with the assert lab_g is not None — confirmed sound: output nodes are layer 0 of the partial order, so they're in every measured node's future and never reach nonfuture_others.
  • Added the general comment explaining that each row is the indicator of which variables lie in a node's neighbourhood, so row · p is its odd-neighbourhood parity, and that rhs is the required parity (0/1) after folding in the fixed part via const_at.
  • Applied the adjacency = {n: og.neighbors({n}) ...}, the solve_f2_linear_system comment, and the if bit tidy-ups.

Typed error. Added FlowGenericErrorReason.NoPauliFlow and the reconstruction now raises FlowGenericError(NoPauliFlow) instead of a bare FlowError, carrying your "no correction set reconciles the XZ-corrections" message.

Defensive check. Removed pf.check_well_formed() from the production to_pauli_flow path (agreeing with you and @thierry-martinez); the well-formedness is asserted in the test-suite instead.

Tests.

  • The randomized round-trip is now @pytest.mark.parametrize("seed", range(400)) — one independently reproducible case per seed, skipping draws with no edges / no flow.
  • The failure test is parametrized and asserts the typed FlowGenericErrorReason.NoPauliFlow, with added XZ- and YZ-input cases alongside the original Z-input and isolated-XY ones.
  • Mirrored the extraction tests in test_pattern.py: test_extract_pauli_flow_rnd_circuit (the random-circuit round trip) and test_extract_pauli_flow over the worked cases. For the worked cases I assert the round trip on every has_gflow case (a gflow always induces a Pauli flow) and skip the non-gflow ones, since a Pauli flow is strictly more general and its absence can't be inferred from has_gflow alone — happy to add a dedicated has_pflow field if you'd prefer the negative cases covered too.

On the LLM question — I did use an AI coding assistant (Claude) and then verified everything myself by running the full test suite, which is how the edge cases (the anachronical corrections, the empty-pattern path) got shaken out. I'd rather keep my specific prompts and workflow to myself, but appreciate the curiosity — and I'm glad to keep iterating on the PR the normal way.

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

4 participants