Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
ba29748
wip
matulni Oct 7, 2025
55ce45a
wip
matulni Oct 7, 2025
8b50154
wip
matulni Oct 8, 2025
e4a21bb
Up PartialOrder constructors
matulni Oct 9, 2025
0c36fbc
wip
matulni Oct 10, 2025
16d1376
wip
matulni Oct 14, 2025
5b72191
wip
matulni Oct 14, 2025
5dcc95d
wip
matulni Oct 14, 2025
65f0817
wip
matulni Oct 15, 2025
f2653f1
wip
matulni Oct 16, 2025
b92f76b
Introduce measurement abc
matulni Oct 17, 2025
0cb2c2e
wip
matulni Oct 21, 2025
69a1295
wip
matulni Oct 23, 2025
bf8607a
wip
matulni Oct 23, 2025
ac0826a
wip
matulni Oct 23, 2025
6a7e84a
to corrections working
matulni Oct 27, 2025
5deab85
Adapt algebraic open graph tests
matulni Oct 27, 2025
7f98a86
Change convention in XZcorrections to domain:nodes
matulni Oct 27, 2025
af7bd17
wip corrections
matulni Oct 27, 2025
fea19a4
Add partial order in XZCorrections and methods
matulni Oct 28, 2025
a883791
corrections to pattern working
matulni Oct 28, 2025
e25ba9b
fix mypy
matulni Oct 28, 2025
65f1143
Add test suite OG
matulni Oct 29, 2025
a727f04
Add docs og
matulni Oct 29, 2025
50687b4
Up docs _find_cflow
matulni Oct 30, 2025
943782a
Up docs _find_gpflow
matulni Oct 30, 2025
4c90bbe
Add docs flow.core
matulni Oct 30, 2025
62f1972
Add docs measurements
matulni Oct 30, 2025
8dc221b
Add tests
matulni Oct 30, 2025
a0fd3d3
PR part 1
matulni Oct 30, 2025
47a313f
Merge branch 'master' into flow_refactor
matulni Oct 30, 2025
5263da0
Mod layer in XZCorrections
matulni Oct 30, 2025
51a9313
Merge branch 'master' into flow_refactor
matulni Nov 3, 2025
7ae925d
Remove unnecessary files
matulni Nov 3, 2025
ddf1b35
Fix circular imports
matulni Nov 3, 2025
98757b2
Fix compatibility with Python <= 3.11
matulni Nov 3, 2025
d85cb3b
Replace NamedTuple by dataclass in CorrectionMatrix
matulni Nov 3, 2025
8aae0fe
Remove from docs
matulni Nov 4, 2025
dd2de79
Fix bugs doc
matulni Nov 4, 2025
264a0af
Replace set by frozenset in flow attributes
matulni Nov 5, 2025
dab0ee8
Apply suggestions from Thierry's review
matulni Nov 12, 2025
6e7d36b
Add Thierry suggestions
matulni Nov 12, 2025
3f394a5
Fix bugs in XZCorrections.partial_order_layers
matulni Nov 12, 2025
b3a82c2
Replace set comprehension by frozenset.union + unpacking
matulni Nov 12, 2025
0fe3113
Fix bug in XZCorrections partial order
matulni Nov 13, 2025
a7c6778
Change OpenGraph.from_pattern by Pattern.extract_opengraph
matulni Nov 13, 2025
97d54f7
Fix CI
matulni Nov 14, 2025
3b4a2b8
Fix docs and add OpenGraphException
matulni Nov 14, 2025
235ab71
Revert graph extraction. Keep :func:`Pattern.extract_opengraph` only.
matulni Nov 17, 2025
e663726
Merge branch 'master' into flow_refactor
matulni Nov 17, 2025
e4dd856
Improve opengraph extraction from pattern
matulni Nov 17, 2025
daa3348
Merge branch 'master' into flow_refactor
matulni Nov 18, 2025
05bf591
Remove test_visualization.py dependence on test_generator and remove …
matulni Nov 18, 2025
0614633
Replace by exceptions in finding flow methods
matulni Nov 18, 2025
c4111fb
Add Thierry's comments
matulni Nov 18, 2025
0c21256
Add find_flow functions which return None
matulni Nov 18, 2025
339ba1c
Registry pattern for flow test cases
thierry-martinez Nov 18, 2025
04d97f8
Add find_flow methods to OpenGraph
matulni Nov 18, 2025
e6a69e5
Rename from_correction_matrix as try_from_correction_matrix since it …
matulni Nov 20, 2025
13c4e05
Merge branch 'master' into flow_refactor
matulni Nov 21, 2025
9adb729
Up CHANGELOG.md
matulni Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### Added

- #358: Refactor of flow tools - Part I
- New module `graphix.flow.core` which introduces classes `PauliFlow`, `GFlow`, `CausalFlow` and `XZCorrections` allowing a finer analysis of MBQC flows. This module subsumes `graphix.generator` which has been removed and part of `graphix.gflow` which will be removed in the future.
- New module `graphix.flow._find_cflow` with the existing causal-flow finding algorithm.
- New module `graphix.flow._find_gpflow` with the existing g- and Pauli-flow finding algorithm introduced in #337.
- New abstract types `graphix.fundamentals.AbstractMeasurement` and `graphix.fundamentals.AbstractPlanarMeasurement` which serve as an umbrella of the existing types `graphix.measurements.Measurement`, `graphix.fundamentals.Plane` and `graphix.fundamentals.Axis`.
- New method `graphix.pattern.Pattern.extract_opengraph` which subsumes the static method `graphix.opengraph.OpenGraph.from_pattern`.
- New methods of `graphix.opengraph.OpenGraph` which allow to extract a causal, g- or Pauli flow.
### Fixed

### Changed
- #358: Refactor of flow tools - Part I
- API for the `graphix.opengraph.OpenGraph` class:
- `OpenGraphs` are parametrically typed so that they can be defined on planes and axes mappings in addition to measurements mappings.
- Attribute names are now `graph`, `input_nodes`, `output_nodes` and `measurements`.


## [0.3.3] - 2025-10-23

Expand Down
12 changes: 12 additions & 0 deletions docs/source/data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ This module defines standard data structure for Pauli operators.

.. autoclass:: Pauli

:mod:`graphix.measurements` module
++++++++++++++++++++++++++++++++++

This module defines data structures for single-qubit measurements in MBQC.

.. automodule:: graphix.measurements

.. currentmodule:: graphix.measurements

.. autoclass:: Measurement
:members:

:mod:`graphix.instruction` module
+++++++++++++++++++++++++++++++++

Expand Down
9 changes: 0 additions & 9 deletions docs/source/generator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,3 @@ Pattern Generation
.. autoclass:: TranspileResult

.. autoclass:: SimulateResult

:mod:`graphix.generator` module
+++++++++++++++++++++++++++++++

.. automodule:: graphix.generator

.. currentmodule:: graphix.generator

.. autofunction:: graphix.generator.generate_from_graph
2 changes: 0 additions & 2 deletions docs/source/open_graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ This module defines classes for defining MBQC patterns as Open Graphs.
.. currentmodule:: graphix.opengraph

.. autoclass:: OpenGraph

.. autoclass:: Measurement
3 changes: 1 addition & 2 deletions graphix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

from __future__ import annotations

from graphix.generator import generate_from_graph
from graphix.graphsim import GraphState
from graphix.pattern import Pattern
from graphix.sim.statevec import Statevec
from graphix.transpiler import Circuit

__all__ = ["Circuit", "GraphState", "Pattern", "Statevec", "generate_from_graph"]
__all__ = ["Circuit", "GraphState", "Pattern", "Statevec"]
25 changes: 13 additions & 12 deletions graphix/find_pflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
if TYPE_CHECKING:
from collections.abc import Set as AbstractSet

from graphix.measurements import Measurement
from graphix.opengraph import OpenGraph


Expand All @@ -44,14 +45,14 @@ class OpenGraphIndex:
At initialization, `non_outputs_optim` is a copy of `non_outputs`. The nodes corresponding to zero-rows of the order-demand matrix are removed for calculating the P matrix more efficiently in the `:func: _find_pflow_general` routine.
"""

def __init__(self, og: OpenGraph) -> None:
def __init__(self, og: OpenGraph[Measurement]) -> None:
self.og = og
nodes = set(og.inside.nodes)
nodes = set(og.graph.nodes)

# Nodes don't need to be sorted. We do it for debugging purposes, so we can check the matrices in intermediate steps of the algorithm.

nodes_non_input = sorted(nodes - set(og.inputs))
nodes_non_output = sorted(nodes - set(og.outputs))
nodes_non_input = sorted(nodes - set(og.input_nodes))
nodes_non_output = sorted(nodes - set(og.output_nodes))

self.non_inputs = NodeIndex()
self.non_inputs.extend(nodes_non_input)
Expand Down Expand Up @@ -84,7 +85,7 @@ def _compute_reduced_adj(ogi: OpenGraphIndex) -> MatGF2:
See Definition 3.3 in Mitosek and Backens, 2024 (arXiv:2410.23439).
"""
graph = ogi.og.inside
graph = ogi.og.graph
row_tags = ogi.non_outputs
col_tags = ogi.non_inputs

Expand Down Expand Up @@ -119,7 +120,7 @@ def _compute_pflow_matrices(ogi: OpenGraphIndex) -> tuple[MatGF2, MatGF2]:
flow_demand_matrix = _compute_reduced_adj(ogi)
order_demand_matrix = flow_demand_matrix.copy()

inputs_set = set(ogi.og.inputs)
inputs_set = set(ogi.og.input_nodes)
meas = ogi.og.measurements

row_tags = ogi.non_outputs
Expand Down Expand Up @@ -212,7 +213,7 @@ def _compute_p_matrix(ogi: OpenGraphIndex, nb_matrix: MatGF2) -> MatGF2 | None:
See Theorem 4.4, steps 8 - 12 in Mitosek and Backens, 2024 (arXiv:2410.23439).
"""
n_no = len(ogi.non_outputs) # number of columns of P matrix.
n_oi_diff = len(ogi.og.outputs) - len(ogi.og.inputs) # number of rows of P matrix.
n_oi_diff = len(ogi.og.output_nodes) - len(ogi.og.input_nodes) # number of rows of P matrix.
n_no_optim = len(ogi.non_outputs_optim) # number of rows and columns of the third block of the K_{LS} matrix.

# Steps 8, 9 and 10
Expand Down Expand Up @@ -414,7 +415,7 @@ def _find_pflow_general(ogi: OpenGraphIndex) -> tuple[MatGF2, MatGF2] | None:
See Theorem 4.4 and Algorithm 3 in Mitosek and Backens, 2024 (arXiv:2410.23439).
"""
n_no = len(ogi.non_outputs)
n_oi_diff = len(ogi.og.outputs) - len(ogi.og.inputs)
n_oi_diff = len(ogi.og.output_nodes) - len(ogi.og.input_nodes)

# Steps 1 and 2
flow_demand_matrix, order_demand_matrix = _compute_pflow_matrices(ogi)
Expand Down Expand Up @@ -552,7 +553,7 @@ def _cnc_matrices2pflow(
if (topo_gen := _compute_topological_generations(ordering_matrix)) is None:
return None # The NC matrix is not a DAG, therefore there's no flow.

l_k = dict.fromkeys(ogi.og.outputs, 0) # Output nodes are always in layer 0.
l_k = dict.fromkeys(ogi.og.output_nodes, 0) # Output nodes are always in layer 0.

# If m >_c n, with >_c the flow order for two nodes m, n, then layer(n) > layer(m).
# Therefore, we iterate the topological sort of the graph in _reverse_ order to obtain the order of measurements.
Expand All @@ -570,7 +571,7 @@ def _cnc_matrices2pflow(
return pf, l_k


def find_pflow(og: OpenGraph) -> tuple[dict[int, set[int]], dict[int, int]] | None:
def find_pflow(og: OpenGraph[Measurement]) -> tuple[dict[int, set[int]], dict[int, int]] | None:
"""Return a Pauli flow of the input open graph if it exists.
Parameters
Expand All @@ -592,8 +593,8 @@ def find_pflow(og: OpenGraph) -> tuple[dict[int, set[int]], dict[int, int]] | No
-----
See Theorems 3.1, 4.2 and 4.4, and Algorithms 2 and 3 in Mitosek and Backens, 2024 (arXiv:2410.23439).
"""
ni = len(og.inputs)
no = len(og.outputs)
ni = len(og.input_nodes)
no = len(og.output_nodes)

if ni > no:
return None
Expand Down
1 change: 1 addition & 0 deletions graphix/flow/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Flow classes and flow-finding algorithms."""
120 changes: 120 additions & 0 deletions graphix/flow/_find_cflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Causal flow finding algorithm.
This module implements Algorithm 1 from Ref. [1]. For a given labelled open graph (G, I, O, meas_plane), this algorithm finds a causal flow [2] in polynomial time with the number of nodes, :math:`O(N^2)`.
References
----------
[1] Mhalla and Perdrix, (2008), Finding Optimal Flows Efficiently, doi.org/10.1007/978-3-540-70575-8_70
[2] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212)
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from graphix.flow.core import CausalFlow
from graphix.fundamentals import Plane

if TYPE_CHECKING:
from collections.abc import Set as AbstractSet

from graphix.opengraph import OpenGraph, _PM_co


def find_cflow(og: OpenGraph[_PM_co]) -> CausalFlow[_PM_co] | None:
"""Return the causal flow of the input open graph if it exists.
Parameters
----------
og : OpenGraph[_PM_co]
Open graph whose causal flow is calculated.
Returns
-------
CausalFlow[_PM_co] | None
A causal flow object if the open graph has causal flow, `None` otherwise.
Notes
-----
- See Definition 2, Theorem 1 and Algorithm 1 in Ref. [1].
- The open graph instance must be of parametric type `Measurement` or `Plane` since the causal flow is only defined on open graphs with :math:`XY` measurements.
References
----------
[1] Mhalla and Perdrix, (2008), Finding Optimal Flows Efficiently, doi.org/10.1007/978-3-540-70575-8_70
"""
for measurement in og.measurements.values():
if measurement.to_plane() in {Plane.XZ, Plane.YZ}:
return None

corrected_nodes = set(og.output_nodes)
corrector_candidates = corrected_nodes - set(og.input_nodes)
non_input_nodes = og.graph.nodes - set(og.input_nodes)

cf: dict[int, frozenset[int]] = {}
# Output nodes are always in layer 0. If the open graph has flow, it must have outputs, so we never end up with an empty set at `layers[0]`.
layers: list[frozenset[int]] = [
frozenset(corrected_nodes)
] # A copy is necessary because `corrected_nodes` is mutable and changes during the algorithm.

return _flow_aux(og, non_input_nodes, corrected_nodes, corrector_candidates, cf, layers)


def _flow_aux(
og: OpenGraph[_PM_co],
non_input_nodes: AbstractSet[int],
corrected_nodes: AbstractSet[int],
corrector_candidates: AbstractSet[int],
cf: dict[int, frozenset[int]],
layers: list[frozenset[int]],
) -> CausalFlow[_PM_co] | None:
"""Find one layer of the causal flow.
Parameters
----------
og : OpenGraph[_PM_co]
Open graph whose causal flow is calculated.
non_input_nodes : AbstractSet[int]
Non-input nodes of the input open graph. This parameter remains constant throughout the execution of the algorithm and can be derived from `og` at any time. It is passed as an argument to avoid unnecessary recalculations.
corrected_nodes : AbstractSet[int]
Nodes which have already been corrected.
corrector_candidates : AbstractSet[int]
Nodes which could correct a node at the time of calling the function. This set can never contain input nodes, uncorrected nodes or nodes which already correct another node.
cf : dict[int, frozenset[int]]
Causal flow correction function. `cf[i]` is the one-qubit set correcting the measurement of qubit `i`.
layers : list[frozenset[int]]
Partial order between corrected qubits in a layer form. The set `layers[i]` comprises the nodes in layer `i`. Nodes in layer `i` are "larger" in the partial order than nodes in layer `i+1`.
Returns
-------
CausalFlow[_PM_co] | None
A causal flow object if the open graph has causal flow, `None` otherwise.
"""
corrected_nodes_new: set[int] = set()
corrector_nodes_new: set[int] = set()
curr_layer: set[int] = set()

non_corrected_nodes = og.graph.nodes - corrected_nodes

if corrected_nodes == set(og.graph.nodes):
return CausalFlow(og, cf, tuple(layers))

for p in corrector_candidates:
non_corrected_neighbors = og.neighbors({p}) & non_corrected_nodes
if len(non_corrected_neighbors) == 1:
(q,) = non_corrected_neighbors
cf[q] = frozenset({p})
curr_layer.add(q)
corrected_nodes_new |= {q}
corrector_nodes_new |= {p}

layers.append(frozenset(curr_layer))

if len(corrected_nodes_new) == 0:
return None

corrected_nodes |= corrected_nodes_new
corrector_candidates = (corrector_candidates - corrector_nodes_new) | (corrected_nodes_new & non_input_nodes)

return _flow_aux(og, non_input_nodes, corrected_nodes, corrector_candidates, cf, layers)
Loading