Skip to content

Conversation

matulni
Copy link
Contributor

@matulni matulni commented Sep 5, 2025

This PR introduces the $O(N^3)$ Pauli flow-finding algorithm presented in [1] which improves the previous version with $O(N^5)$ complexity [2]. Since a Pauli flow in an open graph without Pauli measurements defines a gflow, this algorithm also improves the previous implementation of the gflow-finding algorithm with $O(N^4)$ complexity [3].

The implementation proposed here considers the suggestion in [1] to eliminate the rows that are identically 0 in the order-demand matrix to accelerate the flow-finding routine in open graphs with $n_I \neq n_O$.

Finally, the new algorithm solves the issue discussed in qat-inria#17 , whereby the previous flow-finding functions could return a correction function containing input nodes in the codomain, which is not allowed by the definition of gflow or Pauli flow.

In summary:

  • The core functions of the algorithm are encapsulated in the new module
    graphix.find_pflow, while graphix.gflow and related modules are adapted accordingly to avoid modifying the current API in this PR.
  • The module linalg.py has been updated with more efficient functions for gaussian elimination, back substitution, kernel-finding and right-inverse. New implementation is $\mathbb{F}_2$-specialized and, where appropriate, just-in-time compiled with numba. The dependence on simpyis dropped. Tests are simplified and updated accordingly, and module is now typed.
  • pytest-benchmark is incorporated to allow for easier benchmarking between different commits (courtesy of @thierry-martinez).

Additional comments

  • This PR addresses the same issue as Implement $O(n^3)$ flow finding algorithm #294, but is otherwise unrelated.
  • The module graphix.gflow is currently functioning but rather messy. We leave its restructuration for a future PR to simply the review process.

References

[1] Mitosek and Backens, 2024 (arXiv:2410.23439)
[2] Simmons, 2021 (arXiv:2109.05654)
[3] Backens et al., Quantum 5, 421 (2021)

Copy link

codecov bot commented Sep 5, 2025

Codecov Report

❌ Patch coverage is 85.08287% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.63%. Comparing base (9b75221) to head (167c35f).

Files with missing lines Patch % Lines
graphix/linalg.py 59.82% 47 Missing ⚠️
graphix/find_pflow.py 97.15% 6 Missing ⚠️
graphix/pattern.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #337      +/-   ##
==========================================
+ Coverage   79.59%   79.63%   +0.03%     
==========================================
  Files          40       41       +1     
  Lines        5892     5858      -34     
==========================================
- Hits         4690     4665      -25     
+ Misses       1202     1193       -9     

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Contributor

@EarlMilktea EarlMilktea left a comment

Choose a reason for hiding this comment

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

Give me some time for further reviews.

import numpy.typing as npt


class MatGF2:
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use inheritance if possible, as MatGF2 seems to be exactly the same as the implementation type (that of .data).

for k in range(m):
if mat_data[k, j] == 1 and k != p:
for l in range(n):
mat_data[k, l] ^= mat_data[p, l]
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use numpy 's element-wise operation if possible (though for is also efficient here due to the jit decorator)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, numpy element-wise operations are used everywhere but in the two jit-decorated functions in linalg.

Copy link
Contributor

@EarlMilktea EarlMilktea left a comment

Choose a reason for hiding this comment

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

Still work in progress. Please be patient!

meas_planes = self.get_meas_plane()
flow, l_k = find_gflow(g, vin, vout, meas_planes=meas_planes)
if not flow:
if flow is None or l_k is None: # We check both to avoid typing issues with `get_layers`.
Copy link
Contributor

Choose a reason for hiding this comment

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

MEMO: Let's return None rather than tuple[None, None] for better typing.

Comment on lines +70 to +74
def __getitem__(self, key) -> MatGF2:
"""Allow numpy-style slicing."""
return MatGF2(self.data.__getitem__(key))

def __setitem__(self, key, value: gt.ElementLike | gt.ArrayLike | MatGF2) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's annotate key. I know it's very difficult but still we need it (or improve current design so that we don't have to annotate by ourselves).

iset: AbstractSet[int],
oset: AbstractSet[int],
meas_planes: Mapping[int, Plane],
mode: str = "single", # noqa: ARG001 Compatibility with old API
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's remove it. I believe we need to prioritize simplicity over compatibility in v0 developments.

import galois
import numpy as np
import numpy.typing as npt
import sympy as sp
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's prune sympy from the requirements.

)
pf = _find_pflow(og)
if pf is None:
return None, None # This is to comply with old API. It will be change in the future to `None``
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make it simple.

self.non_outputs_optim = deepcopy(self.non_outputs)


def _get_reduced_adj(ogi: OpenGraphIndex) -> MatGF2:
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be a method (many other occurrences)

self.non_outputs_optim = deepcopy(self.non_outputs)


def _get_reduced_adj(ogi: OpenGraphIndex) -> MatGF2:
Copy link
Contributor

Choose a reason for hiding this comment

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

Please do not use the word get (and also set) for ordinary functions. This is only allowed for getters.

"""
adj_mat = ordering_matrix.data

indegree_map = {}
Copy link
Contributor

Choose a reason for hiding this comment

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

General remark: could you annotate untyped literals, especially when you add something completely new? It greatly improves DX in the future as...

  • we can readily detect type mismatches
  • we are benefited by better intellisense
  • it makes pyright outputs less noisy

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.

2 participants