From 664ad2549768e43ea1702cd4f6803737ece0866e Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:24:55 +0900 Subject: [PATCH 01/51] :recycle: Use simple tuple --- python/swiflow/common.py | 30 ++++++++++------------------ python/swiflow/flow.py | 11 +++++++---- python/swiflow/gflow.py | 11 +++++++---- python/swiflow/pflow.py | 19 ++++++++++-------- tests/assets.py | 42 +++++++++++++++++++++++----------------- 5 files changed, 59 insertions(+), 54 deletions(-) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 311edf21..eb391c9a 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -2,9 +2,8 @@ from __future__ import annotations -import dataclasses from collections.abc import Hashable -from typing import Generic, TypeVar +from typing import TypeVar from typing_extensions import ParamSpec @@ -18,24 +17,15 @@ P = TypeVar("P", Plane, PPlane) S = ParamSpec("S") +Flow = dict[V, V] +"""Flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" -@dataclasses.dataclass(frozen=True) -class FlowResult(Generic[V]): - r"""Causal flow of an open graph.""" +GFlow = dict[V, set[V]] +"""Generalized flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" - f: dict[V, V] - """Flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" - layer: dict[V, int] - r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. - """ +PFlow = dict[V, set[V]] +"""Pauli flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" - -@dataclasses.dataclass(frozen=True) -class GFlowResult(Generic[V]): - r"""Generalized flow of an open graph.""" - - f: dict[V, set[V]] - """Generalized flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" - layer: dict[V, int] - r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. - """ +Layer = dict[V, int] +r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. +""" diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 4fb7189c..68df9621 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -11,13 +11,15 @@ from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import flow as flow_bind -from swiflow.common import FlowResult, V +from swiflow.common import Flow, Layer, V if TYPE_CHECKING: from collections.abc import Set as AbstractSet import networkx as nx +FlowResult = tuple[Flow[V], Layer[V]] + def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResult[V] | None: """Compute causal flow. @@ -48,7 +50,7 @@ def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResu f_, layer_ = ret_ f = codec.decode_flow(f_) layer = codec.decode_layer(layer_) - return FlowResult(f, layer) + return f, layer return None @@ -77,6 +79,7 @@ def verify(flow: FlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: Abst g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - f_ = codec.encode_flow(flow.f) - layer_ = codec.encode_layer(flow.layer) + f, layer = flow + f_ = codec.encode_flow(f) + layer_ = codec.encode_layer(layer) codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_) diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 3f36f3ca..c2327478 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -11,7 +11,7 @@ from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import gflow as gflow_bind -from swiflow.common import GFlowResult, Plane, V +from swiflow.common import GFlow, Layer, Plane, V if TYPE_CHECKING: from collections.abc import Mapping @@ -19,6 +19,8 @@ import networkx as nx +GFlowResult = tuple[GFlow[V], Layer[V]] + def find( g: nx.Graph[V], @@ -61,7 +63,7 @@ def find( f_, layer_ = ret_ f = codec.decode_gflow(f_) layer = codec.decode_layer(layer_) - return GFlowResult(f, layer) + return f, layer return None @@ -102,6 +104,7 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) plane_ = codec.encode_dictkey(plane) - f_ = codec.encode_gflow(gflow.f) - layer_ = codec.encode_layer(gflow.layer) + f, layer = gflow + f_ = codec.encode_gflow(f) + layer_ = codec.encode_layer(layer) codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index f6177c40..8bed832d 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -12,7 +12,7 @@ from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import pflow as pflow_bind -from swiflow.common import GFlowResult, PPlane, V +from swiflow.common import Layer, PFlow, PPlane, V if TYPE_CHECKING: from collections.abc import Mapping @@ -20,13 +20,15 @@ import networkx as nx +PFlowResult = tuple[PFlow[V], Layer[V]] + def find( g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], pplane: Mapping[V, PPlane] | None = None, -) -> GFlowResult[V] | None: +) -> PFlowResult[V] | None: r"""Compute Pauli flow. If it returns a Pauli flow, it is guaranteed to be maximally-delayed, i.e., the number of layers is minimized. @@ -45,7 +47,7 @@ def find( Returns ------- - `GFlowResult` or `None` + `PFlowResult` or `None` Return the Pauli flow if any, otherwise `None`. Notes @@ -69,12 +71,12 @@ def find( f_, layer_ = ret_ f = codec.decode_gflow(f_) layer = codec.decode_layer(layer_) - return GFlowResult(f, layer) + return f, layer return None def verify( - pflow: GFlowResult[V], + pflow: PFlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], @@ -84,7 +86,7 @@ def verify( Parameters ---------- - pflow : `GFlowResult` + pflow : `PFlowResult` Pauli flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. @@ -110,6 +112,7 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) pplane_ = codec.encode_dictkey(pplane) - f_ = codec.encode_gflow(pflow.f) - layer_ = codec.encode_layer(pflow.layer) + f, layer = pflow + f_ = codec.encode_gflow(f) + layer_ = codec.encode_layer(layer) codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_) diff --git a/tests/assets.py b/tests/assets.py index 416b33d1..7895c498 100644 --- a/tests/assets.py +++ b/tests/assets.py @@ -3,9 +3,15 @@ from __future__ import annotations import dataclasses +from typing import TYPE_CHECKING import networkx as nx -from swiflow.common import FlowResult, GFlowResult, Plane, PPlane +from swiflow.common import Plane, PPlane + +if TYPE_CHECKING: + from swiflow.flow import FlowResult + from swiflow.gflow import GFlowResult + from swiflow.pflow import PFlowResult @dataclasses.dataclass(frozen=True) @@ -17,7 +23,7 @@ class FlowTestCase: pplane: dict[int, PPlane] | None flow: FlowResult[int] | None gflow: GFlowResult[int] | None - pflow: GFlowResult[int] | None + pflow: PFlowResult[int] | None # MEMO: DO NOT modify while testing @@ -30,9 +36,9 @@ class FlowTestCase: {1, 2}, None, None, - FlowResult({}, {1: 0, 2: 0}), - GFlowResult({}, {1: 0, 2: 0}), - GFlowResult({}, {1: 0, 2: 0}), + ({}, {1: 0, 2: 0}), + ({}, {1: 0, 2: 0}), + ({}, {1: 0, 2: 0}), ) # 1 - 2 - 3 - 4 - 5 @@ -42,9 +48,9 @@ class FlowTestCase: {5}, None, None, - FlowResult({1: 2, 2: 3, 3: 4, 4: 5}, {1: 4, 2: 3, 3: 2, 4: 1, 5: 0}), - GFlowResult({1: {2}, 2: {3}, 3: {4}, 4: {5}}, {1: 4, 2: 3, 3: 2, 4: 1, 5: 0}), - GFlowResult({1: {2}, 2: {3}, 3: {4}, 4: {5}}, {1: 4, 2: 3, 3: 2, 4: 1, 5: 0}), + ({1: 2, 2: 3, 3: 4, 4: 5}, {1: 4, 2: 3, 3: 2, 4: 1, 5: 0}), + ({1: {2}, 2: {3}, 3: {4}, 4: {5}}, {1: 4, 2: 3, 3: 2, 4: 1, 5: 0}), + ({1: {2}, 2: {3}, 3: {4}, 4: {5}}, {1: 4, 2: 3, 3: 2, 4: 1, 5: 0}), ) @@ -57,9 +63,9 @@ class FlowTestCase: {5, 6}, None, None, - FlowResult({3: 5, 4: 6, 1: 3, 2: 4}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0}), - GFlowResult({3: {5}, 4: {6}, 1: {3}, 2: {4}}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0}), - GFlowResult({3: {5}, 4: {6}, 1: {3}, 2: {4}}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0}), + ({3: 5, 4: 6, 1: 3, 2: 4}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0}), + ({3: {5}, 4: {6}, 1: {3}, 2: {4}}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0}), + ({3: {5}, 4: {6}, 1: {3}, 2: {4}}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0}), ) # ______ @@ -80,8 +86,8 @@ class FlowTestCase: None, None, None, - GFlowResult({1: {5, 6}, 2: {4, 5, 6}, 3: {4, 6}}, {1: 1, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0}), - GFlowResult({1: {5, 6}, 2: {4, 5, 6}, 3: {4, 6}}, {1: 1, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0}), + ({1: {5, 6}, 2: {4, 5, 6}, 3: {4, 6}}, {1: 1, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0}), + ({1: {5, 6}, 2: {4, 5, 6}, 3: {4, 6}}, {1: 1, 2: 1, 3: 1, 4: 0, 5: 0, 6: 0}), ) # 0 - 1 @@ -96,8 +102,8 @@ class FlowTestCase: {0: Plane.XY, 1: Plane.XY, 2: Plane.XZ, 3: Plane.YZ}, {0: PPlane.XY, 1: PPlane.XY, 2: PPlane.XZ, 3: PPlane.YZ}, None, - GFlowResult({0: {2}, 1: {5}, 2: {2, 4}, 3: {3}}, {0: 2, 1: 2, 2: 1, 3: 1, 4: 0, 5: 0}), - GFlowResult({0: {2}, 1: {5}, 2: {2, 4}, 3: {3}}, {0: 2, 1: 2, 2: 1, 3: 1, 4: 0, 5: 0}), + ({0: {2}, 1: {5}, 2: {2, 4}, 3: {3}}, {0: 2, 1: 2, 2: 1, 3: 1, 4: 0, 5: 0}), + ({0: {2}, 1: {5}, 2: {2, 4}, 3: {3}}, {0: 2, 1: 2, 2: 1, 3: 1, 4: 0, 5: 0}), ) @@ -130,7 +136,7 @@ class FlowTestCase: {0: PPlane.XY, 1: PPlane.X, 2: PPlane.XY, 3: PPlane.X}, None, None, - GFlowResult({0: {1}, 1: {4}, 2: {3}, 3: {2, 4}}, {0: 1, 1: 1, 2: 0, 3: 1, 4: 0}), + ({0: {1}, 1: {4}, 2: {3}, 3: {2, 4}}, {0: 1, 1: 1, 2: 0, 3: 1, 4: 0}), ) # 1 2 3 @@ -144,7 +150,7 @@ class FlowTestCase: {0: PPlane.Z, 1: PPlane.Z, 2: PPlane.Y, 3: PPlane.Y}, None, None, - GFlowResult({0: {0}, 1: {1}, 2: {2}, 3: {4}}, {0: 1, 1: 0, 2: 0, 3: 1, 4: 0}), + ({0: {0}, 1: {1}, 2: {2}, 3: {4}}, {0: 1, 1: 0, 2: 0, 3: 1, 4: 0}), ) # 0 - 1 -- 3 @@ -160,7 +166,7 @@ class FlowTestCase: {0: PPlane.Z, 1: PPlane.XZ, 2: PPlane.Y}, None, None, - GFlowResult({0: {0, 2, 4}, 1: {1, 2}, 2: {4}}, {0: 1, 1: 1, 2: 1, 3: 0, 4: 0}), + ({0: {0, 2, 4}, 1: {1, 2}, 2: {4}}, {0: 1, 1: 1, 2: 1, 3: 0, 4: 0}), ) CASES: tuple[FlowTestCase, ...] = (CASE0, CASE1, CASE2, CASE3, CASE4, CASE5, CASE6, CASE7, CASE8) From 1916e4db70bbb0453535f2942fdbf442da721703 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:36:13 +0900 Subject: [PATCH 02/51] :sparkles: Add ensure_optimal arg. --- python/swiflow/_impl/flow.pyi | 8 +++++++- python/swiflow/_impl/gflow.pyi | 1 + python/swiflow/_impl/pflow.pyi | 1 + python/swiflow/flow.py | 10 +++++++--- python/swiflow/gflow.py | 8 ++++++-- python/swiflow/pflow.py | 8 ++++++-- src/flow.rs | 18 +++++++++++++----- src/gflow.rs | 15 +++++++++------ src/internal/validate.rs | 2 ++ src/pflow.rs | 21 ++++++++++++--------- 10 files changed, 64 insertions(+), 28 deletions(-) diff --git a/python/swiflow/_impl/flow.pyi b/python/swiflow/_impl/flow.pyi index 1fe37a58..3f06d5cd 100644 --- a/python/swiflow/_impl/flow.pyi +++ b/python/swiflow/_impl/flow.pyi @@ -1,2 +1,8 @@ def find(g: list[set[int]], iset: set[int], oset: set[int]) -> tuple[dict[int, int], list[int]] | None: ... -def verify(flow: tuple[dict[int, int], list[int]], g: list[set[int]], iset: set[int], oset: set[int]) -> None: ... +def verify( + flow: tuple[dict[int, int], list[int]], + g: list[set[int]], + iset: set[int], + oset: set[int], + optimal: bool, +) -> None: ... diff --git a/python/swiflow/_impl/gflow.pyi b/python/swiflow/_impl/gflow.pyi index 2770059c..a2350b6e 100644 --- a/python/swiflow/_impl/gflow.pyi +++ b/python/swiflow/_impl/gflow.pyi @@ -12,4 +12,5 @@ def verify( iset: set[int], oset: set[int], plane: dict[int, Plane], + optimal: bool, ) -> None: ... diff --git a/python/swiflow/_impl/pflow.pyi b/python/swiflow/_impl/pflow.pyi index 8765895b..93fd7a38 100644 --- a/python/swiflow/_impl/pflow.pyi +++ b/python/swiflow/_impl/pflow.pyi @@ -15,4 +15,5 @@ def verify( iset: set[int], oset: set[int], pplane: dict[int, PPlane], + optimal: bool, ) -> None: ... diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 68df9621..6e91d030 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -54,8 +54,10 @@ def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResu return None -def verify(flow: FlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> None: - """Verify maximally-delayed causal flow. +def verify( + flow: FlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], *, ensure_optimal: bool = True +) -> None: + """Verify causal flow. Parameters ---------- @@ -67,6 +69,8 @@ def verify(flow: FlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: Abst Input nodes. oset : `collections.abc.Set` Output nodes. + ensure_optimal : `bool` + Wether the flow should be maximally-delayed. Defaults to `True`. Raises ------ @@ -82,4 +86,4 @@ def verify(flow: FlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: Abst f, layer = flow f_ = codec.encode_flow(f) layer_ = codec.encode_layer(layer) - codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_) + codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_, ensure_optimal) diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index c2327478..eb7c5a0d 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -73,8 +73,10 @@ def verify( iset: AbstractSet[V], oset: AbstractSet[V], plane: Mapping[V, Plane] | None = None, + *, + ensure_optimal: bool = True, ) -> None: - r"""Verify maximally-delayed generalized flow. + r"""Verify generalized flow. Parameters ---------- @@ -89,6 +91,8 @@ def verify( plane : `collections.abc.Mapping` Measurement plane for each node in :math:`V \setminus O`. Defaults to `Plane.XY`. + ensure_optimal : `bool` + Wether the gflow should be maximally-delayed. Defaults to `True`. Raises ------ @@ -107,4 +111,4 @@ def verify( f, layer = gflow f_ = codec.encode_gflow(f) layer_ = codec.encode_layer(layer) - codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_) + codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_, ensure_optimal) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 8bed832d..73ae3e18 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -81,8 +81,10 @@ def verify( iset: AbstractSet[V], oset: AbstractSet[V], pplane: Mapping[V, PPlane] | None = None, + *, + ensure_optimal: bool = True, ) -> None: - r"""Verify maximally-delayed Pauli flow. + r"""Verify Pauli flow. Parameters ---------- @@ -97,6 +99,8 @@ def verify( pplane : `collections.abc.Mapping` Measurement plane or Pauli index for each node in :math:`V \setminus O`. Defaults to `PPlane.XY`. + ensure_optimal : `bool` + Wether the pflow should be maximally-delayed. Defaults to `True`. Raises ------ @@ -115,4 +119,4 @@ def verify( f, layer = pflow f_ = codec.encode_gflow(f) layer_ = codec.encode_layer(layer) - codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_) + codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_, ensure_optimal) diff --git a/src/flow.rs b/src/flow.rs index 8e0d36e1..8a3e9179 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -122,13 +122,21 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { /// - If `flow` is inconsistent with `g`. #[pyfunction] #[allow(clippy::needless_pass_by_value)] -pub fn verify(flow: (Flow, Layer), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { +pub fn verify( + flow: (Flow, Layer), + g: Graph, + iset: Nodes, + oset: Nodes, + optimal: bool, +) -> PyResult<()> { let (f, layer) = flow; let n = g.len(); let vset = (0..n).collect::(); validate::check_domain(f.iter(), &vset, &iset, &oset)?; - validate::check_initial(&layer, &oset, true)?; check_definition(&f, &layer, &g)?; + if optimal { + validate::check_initial(&layer, &oset, true)?; + } Ok(()) } @@ -173,7 +181,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset).unwrap(); + verify((f, layer), g, iset, oset, true).unwrap(); } #[test_log::test] @@ -187,7 +195,7 @@ mod tests { assert_eq!(f[&2], 3); assert_eq!(f[&3], 4); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset).unwrap(); + verify((f, layer), g, iset, oset, true).unwrap(); } #[test_log::test] @@ -201,7 +209,7 @@ mod tests { assert_eq!(f[&2], 4); assert_eq!(f[&3], 5); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset).unwrap(); + verify((f, layer), g, iset, oset, true).unwrap(); } #[test_log::test] diff --git a/src/gflow.rs b/src/gflow.rs index c8cac414..23e92b8e 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -250,6 +250,7 @@ pub fn verify( iset: Nodes, oset: Nodes, planes: Planes, + optimal: bool, ) -> PyResult<()> { let (f, layer) = gflow; let n = g.len(); @@ -258,8 +259,10 @@ pub fn verify( .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; - validate::check_initial(&layer, &oset, true)?; check_definition(&f, &layer, &g, &planes)?; + if optimal { + validate::check_initial(&layer, &oset, true)?; + } Ok(()) } @@ -367,7 +370,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, layer), g, iset, oset, planes, true).unwrap(); } #[test_log::test] @@ -387,7 +390,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, layer), g, iset, oset, planes, true).unwrap(); } #[test_log::test] @@ -407,7 +410,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, layer), g, iset, oset, planes, true).unwrap(); } #[test_log::test] @@ -425,7 +428,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, layer), g, iset, oset, planes, true).unwrap(); } #[test_log::test] @@ -445,7 +448,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, layer), g, iset, oset, planes, true).unwrap(); } #[test_log::test] diff --git a/src/internal/validate.rs b/src/internal/validate.rs index e6c40751..2cbcd719 100644 --- a/src/internal/validate.rs +++ b/src/internal/validate.rs @@ -13,6 +13,8 @@ use crate::common::{ /// Checks if the layer-zero nodes are correctly chosen. /// +/// This check can be skipped unless maximally-delayed flow is required. +/// /// # Arguments /// /// - `layer`: The layer. diff --git a/src/pflow.rs b/src/pflow.rs index b19710fd..05270908 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -438,6 +438,7 @@ pub fn verify( iset: Nodes, oset: Nodes, pplanes: PPlanes, + optimal: bool, ) -> PyResult<()> { let (f, layer) = pflow; let n = g.len(); @@ -446,8 +447,10 @@ pub fn verify( .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; - validate::check_initial(&layer, &oset, false)?; check_definition(&f, &layer, &g, &pplanes)?; + if optimal { + validate::check_initial(&layer, &oset, false)?; + } Ok(()) } @@ -607,7 +610,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -627,7 +630,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -647,7 +650,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -665,7 +668,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -685,7 +688,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -715,7 +718,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([2, 4])); assert_eq!(layer, vec![1, 1, 0, 1, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -737,7 +740,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![1, 0, 0, 1, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } #[test_log::test] @@ -757,6 +760,6 @@ mod tests { assert_eq!(f[&1], Nodes::from([1, 2])); assert_eq!(f[&2], Nodes::from([4])); assert_eq!(layer, vec![1, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, layer), g, iset, oset, pplanes, true).unwrap(); } } From cc866c0354e229f45729b5fbfcf948d6635b8fd4 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:41:47 +0900 Subject: [PATCH 03/51] :wrench: Check examples by mypy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f972372f..71b9b5f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ python-source = "python" [tool.mypy] python_version = "3.9" strict = true -files = ["docs/source/conf.py", "python", "tests"] +files = ["docs/source/conf.py", "examples", "python", "tests"] [tool.ruff] extend-include = ["*.ipynb"] From 074d9b884d7ad1785a3083b457e2ef9283f2248c Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:42:09 +0900 Subject: [PATCH 04/51] :wrench: Update ruff lint --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 71b9b5f5..5c2c3969 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,6 +116,10 @@ required-imports = ["from __future__ import annotations"] [tool.ruff.lint.pydocstyle] convention = "numpy" +[tool.ruff.lint.pylint] +max-positional-args = 4 +max-args = 12 + [tool.ruff.lint.per-file-ignores] "docs/**/*.py" = [ "D1", # undocumented-XXX From 201f10b309c1128f95a5abdb4d7ce4228e56e14b Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:43:01 +0900 Subject: [PATCH 05/51] :children_crossing: Enforce kwargs --- examples/gflow.py | 2 +- examples/pflow.py | 2 +- python/swiflow/flow.py | 7 ++++++- python/swiflow/gflow.py | 3 ++- python/swiflow/pflow.py | 3 ++- tests/test_gflow.py | 6 +++--- tests/test_pflow.py | 8 ++++---- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/examples/gflow.py b/examples/gflow.py index 8d37a388..9ce87f05 100644 --- a/examples/gflow.py +++ b/examples/gflow.py @@ -22,7 +22,7 @@ oset = {4, 5} planes = {0: Plane.XY, 1: Plane.XY, 2: Plane.XZ, 3: Plane.YZ} -result = gflow.find(g, iset, oset, planes) +result = gflow.find(g, iset, oset, plane=planes) # Found assert result is not None diff --git a/examples/pflow.py b/examples/pflow.py index 6dd36758..4fcbe1d8 100644 --- a/examples/pflow.py +++ b/examples/pflow.py @@ -20,7 +20,7 @@ oset = {4} pplanes = {0: PPlane.Z, 1: PPlane.Z, 2: PPlane.Y, 3: PPlane.Y} -result = pflow.find(g, iset, oset, pplanes) +result = pflow.find(g, iset, oset, pplane=pplanes) # Found assert result is not None diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 6e91d030..de375ad3 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -55,7 +55,12 @@ def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResu def verify( - flow: FlowResult[V], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], *, ensure_optimal: bool = True + flow: FlowResult[V], + g: nx.Graph[V], + iset: AbstractSet[V], + oset: AbstractSet[V], + *, + ensure_optimal: bool = True, ) -> None: """Verify causal flow. diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index eb7c5a0d..f4245381 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -26,6 +26,7 @@ def find( g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], + *, plane: Mapping[V, Plane] | None = None, ) -> GFlowResult[V] | None: r"""Compute generalized flow. @@ -72,8 +73,8 @@ def verify( g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], - plane: Mapping[V, Plane] | None = None, *, + plane: Mapping[V, Plane] | None = None, ensure_optimal: bool = True, ) -> None: r"""Verify generalized flow. diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 73ae3e18..8bed247f 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -27,6 +27,7 @@ def find( g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], + *, pplane: Mapping[V, PPlane] | None = None, ) -> PFlowResult[V] | None: r"""Compute Pauli flow. @@ -80,8 +81,8 @@ def verify( g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], - pplane: Mapping[V, PPlane] | None = None, *, + pplane: Mapping[V, PPlane] | None = None, ensure_optimal: bool = True, ) -> None: r"""Verify Pauli flow. diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 56c9194b..4f2e933e 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -10,10 +10,10 @@ @pytest.mark.parametrize("c", CASES) def test_gflow_graphix(c: FlowTestCase) -> None: - result = gflow.find(c.g, c.iset, c.oset, c.plane) + result = gflow.find(c.g, c.iset, c.oset, plane=c.plane) assert result == c.gflow if result is not None: - gflow.verify(result, c.g, c.iset, c.oset, c.plane) + gflow.verify(result, c.g, c.iset, c.oset, plane=c.plane) def test_gflow_redundant() -> None: @@ -22,4 +22,4 @@ def test_gflow_redundant() -> None: oset = {1} planes = {0: Plane.XY, 1: Plane.XY} with pytest.raises(ValueError, match=r".*Excessive measurement planes specified.*"): - gflow.find(g, iset, oset, planes) + gflow.find(g, iset, oset, plane=planes) diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 92ab352f..580a4776 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -11,10 +11,10 @@ @pytest.mark.filterwarnings("ignore:No Pauli measurement found") @pytest.mark.parametrize("c", CASES) def test_pflow_graphix(c: FlowTestCase) -> None: - result = pflow.find(c.g, c.iset, c.oset, c.pplane) + result = pflow.find(c.g, c.iset, c.oset, pplane=c.pplane) assert result == c.pflow if result is not None: - pflow.verify(result, c.g, c.iset, c.oset, c.pplane) + pflow.verify(result, c.g, c.iset, c.oset, pplane=c.pplane) def test_pflow_nopauli() -> None: @@ -23,7 +23,7 @@ def test_pflow_nopauli() -> None: oset = {1} planes = {0: PPlane.XY} with pytest.warns(UserWarning, match=r".*No Pauli measurement found\. Use gflow\.find instead\..*"): - pflow.find(g, iset, oset, planes) + pflow.find(g, iset, oset, pplane=planes) def test_pflow_redundant() -> None: @@ -32,4 +32,4 @@ def test_pflow_redundant() -> None: oset = {1} planes = {0: PPlane.X, 1: PPlane.Y} with pytest.raises(ValueError, match=r".*Excessive measurement planes specified.*"): - pflow.find(g, iset, oset, planes) + pflow.find(g, iset, oset, pplane=planes) From 83c5e696acca67c41bad289cecf2f042208f8cd2 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:49:55 +0900 Subject: [PATCH 06/51] :children_crossing: Add layer range checking --- python/swiflow/_common.py | 7 +++++++ python/swiflow/flow.py | 4 +++- python/swiflow/gflow.py | 4 +++- python/swiflow/pflow.py | 4 +++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index e3dc2a71..bf739c45 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -80,6 +80,13 @@ def check_planelike(vset: AbstractSet[V], oset: AbstractSet[V], plike: Mapping[V raise ValueError(msg) +def check_layer(layer: Mapping[V, int]) -> None: + """Check if layer range is compatible with the current implementation.""" + if min(layer.values(), default=0) != 0: + msg = "Minimum layer must be 0." + raise ValueError(msg) + + class IndexMap(Generic[V]): """Map between `V` and 0-based indices.""" diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index de375ad3..aa38431b 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -83,12 +83,14 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) + f, layer = flow + if ensure_optimal: + _common.check_layer(layer) vset = g.nodes codec = IndexMap(vset) g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - f, layer = flow f_ = codec.encode_flow(f) layer_ = codec.encode_layer(layer) codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_, ensure_optimal) diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index f4245381..d5aa0818 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -101,6 +101,9 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) + f, layer = gflow + if ensure_optimal: + _common.check_layer(layer) vset = g.nodes if plane is None: plane = dict.fromkeys(vset - oset, Plane.XY) @@ -109,7 +112,6 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) plane_ = codec.encode_dictkey(plane) - f, layer = gflow f_ = codec.encode_gflow(f) layer_ = codec.encode_layer(layer) codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_, ensure_optimal) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 8bed247f..38d678c1 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -109,6 +109,9 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) + f, layer = pflow + if ensure_optimal: + _common.check_layer(layer) vset = g.nodes if pplane is None: pplane = dict.fromkeys(vset - oset, PPlane.XY) @@ -117,7 +120,6 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) pplane_ = codec.encode_dictkey(pplane) - f, layer = pflow f_ = codec.encode_gflow(f) layer_ = codec.encode_layer(layer) codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_, ensure_optimal) From 8e366213f81fbe12ef9d82cf88d38e708910b61b Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:53:31 +0900 Subject: [PATCH 07/51] :white_check_mark: Add tests --- tests/test_common.py | 11 +++++++++++ tests/test_flow.py | 5 +++-- tests/test_gflow.py | 5 +++-- tests/test_pflow.py | 5 +++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/test_common.py b/tests/test_common.py index 96475a38..a43ffb8e 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -45,6 +45,17 @@ def test_check_planelike_ng() -> None: _common.check_planelike({"a", "b"}, {"b"}, {}) +def test_check_layer() -> None: + _common.check_layer({"a": 0, "b": 1, "c": 2}) + _common.check_layer({}) + + with pytest.raises(ValueError, match=r".*must be 0.*"): + _common.check_layer({"a": 1, "b": 2}) + + with pytest.raises(ValueError, match=r".*must be 0.*"): + _common.check_layer({"a": -1, "b": 0}) + + @pytest.fixture def fx_indexmap() -> IndexMap[str]: return IndexMap({"a", "b", "c"}) diff --git a/tests/test_flow.py b/tests/test_flow.py index d536c93b..18ce1557 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -7,8 +7,9 @@ @pytest.mark.parametrize("c", CASES) -def test_flow_graphix(c: FlowTestCase) -> None: +@pytest.mark.parametrize("opt", [True, False]) +def test_flow_graphix(c: FlowTestCase, *, opt: bool) -> None: result = flow.find(c.g, c.iset, c.oset) assert result == c.flow if result is not None: - flow.verify(result, c.g, c.iset, c.oset) + flow.verify(result, c.g, c.iset, c.oset, ensure_optimal=opt) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 4f2e933e..216698a9 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -9,11 +9,12 @@ @pytest.mark.parametrize("c", CASES) -def test_gflow_graphix(c: FlowTestCase) -> None: +@pytest.mark.parametrize("opt", [True, False]) +def test_gflow_graphix(c: FlowTestCase, *, opt: bool) -> None: result = gflow.find(c.g, c.iset, c.oset, plane=c.plane) assert result == c.gflow if result is not None: - gflow.verify(result, c.g, c.iset, c.oset, plane=c.plane) + gflow.verify(result, c.g, c.iset, c.oset, plane=c.plane, ensure_optimal=opt) def test_gflow_redundant() -> None: diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 580a4776..776c67c6 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -10,11 +10,12 @@ @pytest.mark.filterwarnings("ignore:No Pauli measurement found") @pytest.mark.parametrize("c", CASES) -def test_pflow_graphix(c: FlowTestCase) -> None: +@pytest.mark.parametrize("opt", [True, False]) +def test_pflow_graphix(c: FlowTestCase, *, opt: bool) -> None: result = pflow.find(c.g, c.iset, c.oset, pplane=c.pplane) assert result == c.pflow if result is not None: - pflow.verify(result, c.g, c.iset, c.oset, pplane=c.pplane) + pflow.verify(result, c.g, c.iset, c.oset, pplane=c.pplane, ensure_optimal=opt) def test_pflow_nopauli() -> None: From bbfbebf893b4e5e46281f909866460d2732b7923 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:06:42 +0900 Subject: [PATCH 08/51] :boom: Change default value --- python/swiflow/flow.py | 4 ++-- python/swiflow/gflow.py | 4 ++-- python/swiflow/pflow.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index aa38431b..168581e3 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -60,7 +60,7 @@ def verify( iset: AbstractSet[V], oset: AbstractSet[V], *, - ensure_optimal: bool = True, + ensure_optimal: bool = False, ) -> None: """Verify causal flow. @@ -75,7 +75,7 @@ def verify( oset : `collections.abc.Set` Output nodes. ensure_optimal : `bool` - Wether the flow should be maximally-delayed. Defaults to `True`. + Wether the flow should be maximally-delayed. Defaults to `False`. Raises ------ diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index d5aa0818..9d6a6eed 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -75,7 +75,7 @@ def verify( oset: AbstractSet[V], *, plane: Mapping[V, Plane] | None = None, - ensure_optimal: bool = True, + ensure_optimal: bool = False, ) -> None: r"""Verify generalized flow. @@ -93,7 +93,7 @@ def verify( Measurement plane for each node in :math:`V \setminus O`. Defaults to `Plane.XY`. ensure_optimal : `bool` - Wether the gflow should be maximally-delayed. Defaults to `True`. + Wether the gflow should be maximally-delayed. Defaults to `False`. Raises ------ diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 38d678c1..d64c3e7a 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -83,7 +83,7 @@ def verify( oset: AbstractSet[V], *, pplane: Mapping[V, PPlane] | None = None, - ensure_optimal: bool = True, + ensure_optimal: bool = False, ) -> None: r"""Verify Pauli flow. @@ -101,7 +101,7 @@ def verify( Measurement plane or Pauli index for each node in :math:`V \setminus O`. Defaults to `PPlane.XY`. ensure_optimal : `bool` - Wether the pflow should be maximally-delayed. Defaults to `True`. + Wether the pflow should be maximally-delayed. Defaults to `False`. Raises ------ From 887304d21475864990e12d5d152100061d91706a Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:53:13 +0900 Subject: [PATCH 09/51] :construction: Add layer inference --- python/swiflow/_common.py | 8 +++++++ python/swiflow/common.py | 47 ++++++++++++++++++++++++++++++++++++++- tests/test_common.py | 36 +++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index bf739c45..0736dd2c 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -87,6 +87,14 @@ def check_layer(layer: Mapping[V, int]) -> None: raise ValueError(msg) +def odd_neighbors(g: nx.Graph[V], kset: AbstractSet[V]) -> set[V]: + """Compute odd neighbors of `kset` in `g`.""" + ret: set[V] = set() + for k in kset: + ret.symmetric_difference_update(g.neighbors(k)) + return ret + + class IndexMap(Generic[V]): """Map between `V` and 0-based indices.""" diff --git a/python/swiflow/common.py b/python/swiflow/common.py index eb391c9a..f77da2af 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -2,11 +2,15 @@ from __future__ import annotations -from collections.abc import Hashable +import itertools +from collections.abc import Hashable, Mapping +from collections.abc import Set as AbstractSet from typing import TypeVar +import networkx as nx from typing_extensions import ParamSpec +from swiflow import _common from swiflow._impl import gflow, pflow Plane = gflow.Plane @@ -29,3 +33,44 @@ Layer = dict[V, int] r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. """ + + +def _infer_layer_impl(gd: nx.DiGraph[V]) -> Mapping[V, int]: + pred = {u: set(gd.predecessors(u)) for u in gd.nodes} + work = {u for u, pu in pred.items() if not pu} + ret: dict[V, int] = {} + for l_now in itertools.count(): + if not work: + break + next_work: set[V] = set() + for u in work: + ret[u] = l_now + for v in gd.successors(u): + ent = pred[v] + ent.discard(u) + if not ent: + next_work.add(v) + work = next_work + if len(ret) != len(gd): + msg = "Failed to determine layer for all nodes." + raise ValueError(msg) + return ret + + +def infer_layer(g: nx.Graph[V], anyflow: Mapping[V, V | AbstractSet[V]]) -> Mapping[V, int]: + """Infer layer from flow/gflow. + + Notes + ----- + This function is based on greedy algorithm. + """ + gd: nx.DiGraph[V] = nx.DiGraph() + for u, fu_ in anyflow.items(): + fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} + fu_odd = _common.odd_neighbors(g, fu) + for v in itertools.chain(fu, fu_odd): + if u == v: + continue + gd.add_edge(u, v) + gd = gd.reverse() + return _infer_layer_impl(gd) diff --git a/tests/test_common.py b/tests/test_common.py index a43ffb8e..9ca5de2a 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -2,12 +2,14 @@ import networkx as nx import pytest -from swiflow import _common +from swiflow import _common, common from swiflow._common import IndexMap from swiflow._impl import FlowValidationMessage from swiflow.common import Plane, PPlane from typing_extensions import Never +from tests.assets import CASE3 + def test_check_graph_ng_g() -> None: with pytest.raises(TypeError): @@ -121,3 +123,35 @@ def dummy_ng(_: int) -> Never: assert fx_indexmap.ecatch(dummy_ok, 1) == 1 with pytest.raises(ValueError, match=r"Zero-layer node a outside output nodes\."): fx_indexmap.ecatch(dummy_ng, 1) + + +def test_odd_neighbors() -> None: + g = CASE3.g + for u in g.nodes: + assert _common.odd_neighbors(g, {u}) == set(g.neighbors(u)) + assert _common.odd_neighbors(g, {1, 4}) == {1, 2, 4, 6} + assert _common.odd_neighbors(g, {2, 5}) == {2, 3, 4, 5, 6} + assert _common.odd_neighbors(g, {3, 6}) == {1, 2, 3, 5, 6} + assert _common.odd_neighbors(g, {1, 2, 3}) == {6} + assert _common.odd_neighbors(g, {4, 5, 6}) == {2} + assert _common.odd_neighbors(g, {1, 2, 3, 4, 5, 6}) == {2, 6} + + +class TestInferLayer: + def test_line(self) -> None: + g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 3)]) + flow = {0: {1}, 1: {2}, 2: {3}} + layer = common.infer_layer(g, flow) + assert layer == {0: 3, 1: 2, 2: 1, 3: 0} + + def test_dag(self) -> None: + g: nx.Graph[int] = nx.Graph([(0, 2), (0, 3), (1, 2), (1, 3)]) + flow = {0: {2, 3}, 1: {2, 3}} + layer = common.infer_layer(g, flow) + assert layer == {0: 1, 1: 1, 2: 0, 3: 0} + + def test_cycle(self) -> None: + g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 0)]) + flow = {0: {1}, 1: {2}, 2: {0}} + with pytest.raises(ValueError, match=r".*determine.*"): + common.infer_layer(g, flow) From aeb4b032c6da764fbf278d236ec7b5d916dc4d99 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:19:50 +0900 Subject: [PATCH 10/51] :children_crossing: Use covariant annotation --- python/swiflow/flow.py | 3 ++- python/swiflow/gflow.py | 2 +- python/swiflow/pflow.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 168581e3..ac674eca 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -14,6 +14,7 @@ from swiflow.common import Flow, Layer, V if TYPE_CHECKING: + from collections.abc import Mapping from collections.abc import Set as AbstractSet import networkx as nx @@ -55,7 +56,7 @@ def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResu def verify( - flow: FlowResult[V], + flow: tuple[Mapping[V, V], Mapping[V, int]], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 9d6a6eed..fdf8cb47 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -69,7 +69,7 @@ def find( def verify( - gflow: GFlowResult[V], + gflow: tuple[Mapping[V, AbstractSet[V]], Mapping[V, int]], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index d64c3e7a..4ecdff03 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -77,7 +77,7 @@ def find( def verify( - pflow: PFlowResult[V], + pflow: tuple[Mapping[V, AbstractSet[V]], Mapping[V, int]], g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V], From 0aa58cddb2340d2ff2e05e574fc978593dcb71fc Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:28:49 +0900 Subject: [PATCH 11/51] :bug: Resolve import loop --- docs/source/swiflow.rst | 2 +- python/swiflow/_common.py | 57 ++++++++++++++++++++++----------------- python/swiflow/common.py | 24 +++++++---------- python/swiflow/flow.py | 18 +++++++------ python/swiflow/gflow.py | 28 ++++++++++--------- python/swiflow/pflow.py | 28 ++++++++++--------- 6 files changed, 83 insertions(+), 74 deletions(-) diff --git a/docs/source/swiflow.rst b/docs/source/swiflow.rst index 1bd3f028..1209175d 100644 --- a/docs/source/swiflow.rst +++ b/docs/source/swiflow.rst @@ -6,7 +6,7 @@ swiflow.common module .. automodule:: swiflow.common :members: - :exclude-members: Plane, PPlane, V, P + :exclude-members: Plane, PPlane .. autoclass:: swiflow.common.Plane diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 0736dd2c..123f4d01 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -2,17 +2,19 @@ from __future__ import annotations -from collections.abc import Callable, Iterable, Mapping +from collections.abc import Callable, Hashable, Iterable, Mapping from collections.abc import Set as AbstractSet -from typing import Generic +from typing import Generic, TypeVar import networkx as nx +from typing_extensions import ParamSpec from swiflow._impl import FlowValidationMessage -from swiflow.common import P, S, T, V +_V = TypeVar("_V", bound=Hashable) -def check_graph(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> None: + +def check_graph(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> None: """Check if `(g, iset, oset)` is a valid open graph for MBQC. Raises @@ -46,7 +48,7 @@ def check_graph(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> N raise ValueError(msg) -def check_planelike(vset: AbstractSet[V], oset: AbstractSet[V], plike: Mapping[V, P]) -> None: +def check_planelike(vset: AbstractSet[_V], oset: AbstractSet[_V], plike: Mapping[_V, _P]) -> None: r"""Check if measurement description is valid. Parameters @@ -80,28 +82,33 @@ def check_planelike(vset: AbstractSet[V], oset: AbstractSet[V], plike: Mapping[V raise ValueError(msg) -def check_layer(layer: Mapping[V, int]) -> None: +def check_layer(layer: Mapping[_V, int]) -> None: """Check if layer range is compatible with the current implementation.""" if min(layer.values(), default=0) != 0: msg = "Minimum layer must be 0." raise ValueError(msg) -def odd_neighbors(g: nx.Graph[V], kset: AbstractSet[V]) -> set[V]: +def odd_neighbors(g: nx.Graph[_V], kset: AbstractSet[_V]) -> set[_V]: """Compute odd neighbors of `kset` in `g`.""" - ret: set[V] = set() + ret: set[_V] = set() for k in kset: ret.symmetric_difference_update(g.neighbors(k)) return ret -class IndexMap(Generic[V]): +_T = TypeVar("_T") +_P = TypeVar("_P") +_S = ParamSpec("_S") + + +class IndexMap(Generic[_V]): """Map between `V` and 0-based indices.""" - __v2i: dict[V, int] - __i2v: list[V] + __v2i: dict[_V, int] + __i2v: list[_V] - def __init__(self, vset: AbstractSet[V]) -> None: + def __init__(self, vset: AbstractSet[_V]) -> None: """Initialize the map from `vset`. Parameters @@ -120,7 +127,7 @@ def __init__(self, vset: AbstractSet[V]) -> None: self.__i2v = list(vset) self.__v2i = {v: i for i, v in enumerate(self.__i2v)} - def encode(self, v: V) -> int: + def encode(self, v: _V) -> int: """Encode `v` to the index. Returns @@ -139,7 +146,7 @@ def encode(self, v: V) -> int: raise ValueError(msg) return ind - def encode_graph(self, g: nx.Graph[V]) -> list[set[int]]: + def encode_graph(self, g: nx.Graph[_V]) -> list[set[int]]: """Encode graph. Returns @@ -148,11 +155,11 @@ def encode_graph(self, g: nx.Graph[V]) -> list[set[int]]: """ return [self.encode_set(g[v].keys()) for v in self.__i2v] - def encode_set(self, vset: AbstractSet[V]) -> set[int]: + def encode_set(self, vset: AbstractSet[_V]) -> set[int]: """Encode set.""" return {self.encode(v) for v in vset} - def encode_dictkey(self, mapping: Mapping[V, P]) -> dict[int, P]: + def encode_dictkey(self, mapping: Mapping[_V, _P]) -> dict[int, _P]: """Encode dict key. Returns @@ -161,7 +168,7 @@ def encode_dictkey(self, mapping: Mapping[V, P]) -> dict[int, P]: """ return {self.encode(k): v for k, v in mapping.items()} - def encode_flow(self, f: Mapping[V, V]) -> dict[int, int]: + def encode_flow(self, f: Mapping[_V, _V]) -> dict[int, int]: """Encode flow. Returns @@ -170,7 +177,7 @@ def encode_flow(self, f: Mapping[V, V]) -> dict[int, int]: """ return {self.encode(i): self.encode(j) for i, j in f.items()} - def encode_gflow(self, f: Mapping[V, AbstractSet[V]]) -> dict[int, set[int]]: + def encode_gflow(self, f: Mapping[_V, AbstractSet[_V]]) -> dict[int, set[int]]: """Encode gflow. Returns @@ -179,7 +186,7 @@ def encode_gflow(self, f: Mapping[V, AbstractSet[V]]) -> dict[int, set[int]]: """ return {self.encode(i): self.encode_set(si) for i, si in f.items()} - def encode_layer(self, layer: Mapping[V, int]) -> list[int]: + def encode_layer(self, layer: Mapping[_V, int]) -> list[int]: """Encode layer. Returns @@ -196,7 +203,7 @@ def encode_layer(self, layer: Mapping[V, int]) -> list[int]: msg = "Layers must be specified for all nodes." raise ValueError(msg) from None - def decode(self, i: int) -> V: + def decode(self, i: int) -> _V: """Decode the index. Returns @@ -215,11 +222,11 @@ def decode(self, i: int) -> V: raise ValueError(msg) from None return v - def decode_set(self, iset: AbstractSet[int]) -> set[V]: + def decode_set(self, iset: AbstractSet[int]) -> set[_V]: """Decode set.""" return {self.decode(i) for i in iset} - def decode_flow(self, f_: Mapping[int, int]) -> dict[V, V]: + def decode_flow(self, f_: Mapping[int, int]) -> dict[_V, _V]: """Decode MBQC flow. Returns @@ -228,7 +235,7 @@ def decode_flow(self, f_: Mapping[int, int]) -> dict[V, V]: """ return {self.decode(i): self.decode(j) for i, j in f_.items()} - def decode_gflow(self, f_: Mapping[int, AbstractSet[int]]) -> dict[V, set[V]]: + def decode_gflow(self, f_: Mapping[int, AbstractSet[int]]) -> dict[_V, set[_V]]: """Decode MBQC gflow. Returns @@ -237,7 +244,7 @@ def decode_gflow(self, f_: Mapping[int, AbstractSet[int]]) -> dict[V, set[V]]: """ return {self.decode(i): self.decode_set(si) for i, si in f_.items()} - def decode_layer(self, layer_: Iterable[int]) -> dict[V, int]: + def decode_layer(self, layer_: Iterable[int]) -> dict[_V, int]: """Decode MBQC layer. Returns @@ -283,7 +290,7 @@ def decode_err(self, err: ValueError) -> ValueError: raise TypeError # pragma: no cover return ValueError(msg) - def ecatch(self, f: Callable[S, T], *args: S.args, **kwargs: S.kwargs) -> T: + def ecatch(self, f: Callable[_S, _T], *args: _S.args, **kwargs: _S.kwargs) -> _T: """Wrap binding call to decode raw error messages.""" try: return f(*args, **kwargs) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index f77da2af..31262700 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -8,7 +8,6 @@ from typing import TypeVar import networkx as nx -from typing_extensions import ParamSpec from swiflow import _common from swiflow._impl import gflow, pflow @@ -16,33 +15,30 @@ Plane = gflow.Plane PPlane = pflow.PPlane -T = TypeVar("T") -V = TypeVar("V", bound=Hashable) -P = TypeVar("P", Plane, PPlane) -S = ParamSpec("S") +_V = TypeVar("_V", bound=Hashable) -Flow = dict[V, V] +Flow = dict[_V, _V] """Flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" -GFlow = dict[V, set[V]] +GFlow = dict[_V, set[_V]] """Generalized flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" -PFlow = dict[V, set[V]] +PFlow = dict[_V, set[_V]] """Pauli flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" -Layer = dict[V, int] +Layer = dict[_V, int] r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. """ -def _infer_layer_impl(gd: nx.DiGraph[V]) -> Mapping[V, int]: +def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: pred = {u: set(gd.predecessors(u)) for u in gd.nodes} work = {u for u, pu in pred.items() if not pu} - ret: dict[V, int] = {} + ret: dict[_V, int] = {} for l_now in itertools.count(): if not work: break - next_work: set[V] = set() + next_work: set[_V] = set() for u in work: ret[u] = l_now for v in gd.successors(u): @@ -57,14 +53,14 @@ def _infer_layer_impl(gd: nx.DiGraph[V]) -> Mapping[V, int]: return ret -def infer_layer(g: nx.Graph[V], anyflow: Mapping[V, V | AbstractSet[V]]) -> Mapping[V, int]: +def infer_layer(g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]]) -> Mapping[_V, int]: """Infer layer from flow/gflow. Notes ----- This function is based on greedy algorithm. """ - gd: nx.DiGraph[V] = nx.DiGraph() + gd: nx.DiGraph[_V] = nx.DiGraph() for u, fu_ in anyflow.items(): fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} fu_odd = _common.odd_neighbors(g, fu) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index ac674eca..9272f36d 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -6,12 +6,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from collections.abc import Hashable +from typing import TYPE_CHECKING, TypeVar from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import flow as flow_bind -from swiflow.common import Flow, Layer, V +from swiflow.common import Flow, Layer if TYPE_CHECKING: from collections.abc import Mapping @@ -19,10 +20,11 @@ import networkx as nx -FlowResult = tuple[Flow[V], Layer[V]] +_V = TypeVar("_V", bound=Hashable) +FlowResult = tuple[Flow[_V], Layer[_V]] -def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResult[V] | None: +def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowResult[_V] | None: """Compute causal flow. If it returns a flow, it is guaranteed to be maximally-delayed, i.e., the number of layers is minimized. @@ -56,10 +58,10 @@ def find(g: nx.Graph[V], iset: AbstractSet[V], oset: AbstractSet[V]) -> FlowResu def verify( - flow: tuple[Mapping[V, V], Mapping[V, int]], - g: nx.Graph[V], - iset: AbstractSet[V], - oset: AbstractSet[V], + flow: tuple[Mapping[_V, _V], Mapping[_V, int]], + g: nx.Graph[_V], + iset: AbstractSet[_V], + oset: AbstractSet[_V], *, ensure_optimal: bool = False, ) -> None: diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index fdf8cb47..6880604a 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -6,12 +6,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from collections.abc import Hashable +from typing import TYPE_CHECKING, TypeVar from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import gflow as gflow_bind -from swiflow.common import GFlow, Layer, Plane, V +from swiflow.common import GFlow, Layer, Plane if TYPE_CHECKING: from collections.abc import Mapping @@ -19,16 +20,17 @@ import networkx as nx -GFlowResult = tuple[GFlow[V], Layer[V]] +_V = TypeVar("_V", bound=Hashable) +GFlowResult = tuple[GFlow[_V], Layer[_V]] def find( - g: nx.Graph[V], - iset: AbstractSet[V], - oset: AbstractSet[V], + g: nx.Graph[_V], + iset: AbstractSet[_V], + oset: AbstractSet[_V], *, - plane: Mapping[V, Plane] | None = None, -) -> GFlowResult[V] | None: + plane: Mapping[_V, Plane] | None = None, +) -> GFlowResult[_V] | None: r"""Compute generalized flow. If it returns a gflow, it is guaranteed to be maximally-delayed, i.e., the number of layers is minimized. @@ -69,12 +71,12 @@ def find( def verify( - gflow: tuple[Mapping[V, AbstractSet[V]], Mapping[V, int]], - g: nx.Graph[V], - iset: AbstractSet[V], - oset: AbstractSet[V], + gflow: tuple[Mapping[_V, AbstractSet[_V]], Mapping[_V, int]], + g: nx.Graph[_V], + iset: AbstractSet[_V], + oset: AbstractSet[_V], *, - plane: Mapping[V, Plane] | None = None, + plane: Mapping[_V, Plane] | None = None, ensure_optimal: bool = False, ) -> None: r"""Verify generalized flow. diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 4ecdff03..c625c721 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -7,12 +7,13 @@ from __future__ import annotations import warnings -from typing import TYPE_CHECKING +from collections.abc import Hashable +from typing import TYPE_CHECKING, TypeVar from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import pflow as pflow_bind -from swiflow.common import Layer, PFlow, PPlane, V +from swiflow.common import Layer, PFlow, PPlane if TYPE_CHECKING: from collections.abc import Mapping @@ -20,16 +21,17 @@ import networkx as nx -PFlowResult = tuple[PFlow[V], Layer[V]] +_V = TypeVar("_V", bound=Hashable) +PFlowResult = tuple[PFlow[_V], Layer[_V]] def find( - g: nx.Graph[V], - iset: AbstractSet[V], - oset: AbstractSet[V], + g: nx.Graph[_V], + iset: AbstractSet[_V], + oset: AbstractSet[_V], *, - pplane: Mapping[V, PPlane] | None = None, -) -> PFlowResult[V] | None: + pplane: Mapping[_V, PPlane] | None = None, +) -> PFlowResult[_V] | None: r"""Compute Pauli flow. If it returns a Pauli flow, it is guaranteed to be maximally-delayed, i.e., the number of layers is minimized. @@ -77,12 +79,12 @@ def find( def verify( - pflow: tuple[Mapping[V, AbstractSet[V]], Mapping[V, int]], - g: nx.Graph[V], - iset: AbstractSet[V], - oset: AbstractSet[V], + pflow: tuple[Mapping[_V, AbstractSet[_V]], Mapping[_V, int]], + g: nx.Graph[_V], + iset: AbstractSet[_V], + oset: AbstractSet[_V], *, - pplane: Mapping[V, PPlane] | None = None, + pplane: Mapping[_V, PPlane] | None = None, ensure_optimal: bool = False, ) -> None: r"""Verify Pauli flow. From 13c8c4e532057c6a92603891470f1b952df50773 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:35:39 +0900 Subject: [PATCH 12/51] :bug: Add nodes --- python/swiflow/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 31262700..84c7748f 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -61,6 +61,7 @@ def infer_layer(g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]]) -> This function is based on greedy algorithm. """ gd: nx.DiGraph[_V] = nx.DiGraph() + gd.add_nodes_from(g.nodes) for u, fu_ in anyflow.items(): fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} fu_odd = _common.odd_neighbors(g, fu) From 45593b0f9bc261d239e051bf16c763a72696ccfc Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:35:52 +0900 Subject: [PATCH 13/51] :test_tube: Add tests --- tests/test_flow.py | 11 ++++++++++- tests/test_gflow.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/test_flow.py b/tests/test_flow.py index 18ce1557..cd5c8bf2 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -1,7 +1,7 @@ from __future__ import annotations import pytest -from swiflow import flow +from swiflow import common, flow from tests.assets import CASES, FlowTestCase @@ -13,3 +13,12 @@ def test_flow_graphix(c: FlowTestCase, *, opt: bool) -> None: assert result == c.flow if result is not None: flow.verify(result, c.g, c.iset, c.oset, ensure_optimal=opt) + + +@pytest.mark.parametrize("c", CASES) +def test_infer_verify(c: FlowTestCase) -> None: + if c.flow is None: + pytest.skip() + f, _ = c.flow + layer = common.infer_layer(c.g, f) + flow.verify((f, layer), c.g, c.iset, c.oset) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 216698a9..56a12f55 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import gflow +from swiflow import common, gflow from swiflow.common import Plane from tests.assets import CASES, FlowTestCase @@ -24,3 +24,12 @@ def test_gflow_redundant() -> None: planes = {0: Plane.XY, 1: Plane.XY} with pytest.raises(ValueError, match=r".*Excessive measurement planes specified.*"): gflow.find(g, iset, oset, plane=planes) + + +@pytest.mark.parametrize("c", CASES) +def test_infer_verify(c: FlowTestCase) -> None: + if c.gflow is None: + pytest.skip() + f, _ = c.gflow + layer = common.infer_layer(c.g, f) + gflow.verify((f, layer), c.g, c.iset, c.oset) From 478cbc48cf2712fb949c57c7aa97aef71fa0a584 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:36:15 +0900 Subject: [PATCH 14/51] :see_no_evil: Ignore lcov output --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d66e958a..be5b69fb 100644 --- a/.gitignore +++ b/.gitignore @@ -187,3 +187,4 @@ cobertura.xml docs/build !docs/build/.nojekyll uv.lock +lcov.info From b1aff8504f9485cc84203db0c03d3dcb42986fa3 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:41:33 +0900 Subject: [PATCH 15/51] :white_check_mark: Fix test --- tests/test_gflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 56a12f55..ffce8456 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -32,4 +32,4 @@ def test_infer_verify(c: FlowTestCase) -> None: pytest.skip() f, _ = c.gflow layer = common.infer_layer(c.g, f) - gflow.verify((f, layer), c.g, c.iset, c.oset) + gflow.verify((f, layer), c.g, c.iset, c.oset, plane=c.plane) From ff9aebe4cc447a4ec9a2fbffb7ff835c36d3021a Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:45:16 +0900 Subject: [PATCH 16/51] :memo: Fix docstrings --- python/swiflow/flow.py | 4 ++-- python/swiflow/gflow.py | 4 ++-- python/swiflow/pflow.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 9272f36d..cfa8871f 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -40,7 +40,7 @@ def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowR Returns ------- - `FlowResult` or `None` + `tuple` of flow/layer or `None` Return the flow if any, otherwise `None`. """ _common.check_graph(g, iset, oset) @@ -69,7 +69,7 @@ def verify( Parameters ---------- - flow : `FlowResult` + flow : `tuple` of flow/layer Flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 6880604a..fd7fd257 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -49,7 +49,7 @@ def find( Returns ------- - `GFlowResult` or `None` + `tuple` of gflow/layer or `None` Return the gflow if any, otherwise `None`. """ _common.check_graph(g, iset, oset) @@ -83,7 +83,7 @@ def verify( Parameters ---------- - gflow : `GFlowResult` + gflow : `tuple` of gflow/layer Generalized flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index c625c721..f4ec6f7f 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -50,7 +50,7 @@ def find( Returns ------- - `PFlowResult` or `None` + `tuple` of Pauli flow/layer or `None` Return the Pauli flow if any, otherwise `None`. Notes @@ -91,7 +91,7 @@ def verify( Parameters ---------- - pflow : `PFlowResult` + pflow : `tuple` of Pauli flow/layer Pauli flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. From 5fac885b783e4a1365c30bb3db9c757676965f15 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:46:02 +0900 Subject: [PATCH 17/51] :memo: Update package docstring --- python/swiflow/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/swiflow/__init__.py b/python/swiflow/__init__.py index 126d71c6..8028f3a7 100644 --- a/python/swiflow/__init__.py +++ b/python/swiflow/__init__.py @@ -1 +1 @@ -"""Initialize the swiflow package.""" +"""swiflow: Rust binding of generalized and pauli flow finding algorithms.""" From 52c5791f29ce56f0f4737c957648c535ebcf03d8 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:48:38 +0900 Subject: [PATCH 18/51] :safety_vest: Require connected graphs --- python/swiflow/_common.py | 3 +++ tests/test_common.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 123f4d01..c9655aeb 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -46,6 +46,9 @@ def check_graph(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) - if not (oset <= vset): msg = "oset must be a subset of the nodes." raise ValueError(msg) + if not nx.is_connected(g): + msg = "Graph is not connected." + raise ValueError(msg) def check_planelike(vset: AbstractSet[_V], oset: AbstractSet[_V], plike: Mapping[_V, _P]) -> None: diff --git a/tests/test_common.py b/tests/test_common.py index 9ca5de2a..e495b4cd 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -27,6 +27,9 @@ def test_check_graph_ng_g() -> None: with pytest.raises(ValueError, match=r"oset must be a subset of the nodes\."): _common.check_graph(nx.Graph([("a", "b")]), set(), {"x"}) + with pytest.raises(ValueError, match=r"Graph is not connected\."): + _common.check_graph(nx.Graph([(1, 2), (3, 4)]), set(), set()) + def test_check_graph_ng_set() -> None: with pytest.raises(TypeError): From 5e923af3423f1d67cff970f285f86ecc643683f3 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:51:19 +0900 Subject: [PATCH 19/51] :white_check_mark: Fix test assets --- examples/flow.py | 2 +- tests/assets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flow.py b/examples/flow.py index 4f8423bf..20c9738c 100644 --- a/examples/flow.py +++ b/examples/flow.py @@ -14,7 +14,7 @@ # 1 - 3 - 5 # | # 2 - 4 - 6 -g = nx.Graph([(1, 3), (2, 4), (3, 5), (4, 6)]) +g = nx.Graph([(1, 3), (2, 4), (3, 5), (4, 6), (3, 4)]) iset = {1, 2} oset = {5, 6} diff --git a/tests/assets.py b/tests/assets.py index 7895c498..9ae4ca45 100644 --- a/tests/assets.py +++ b/tests/assets.py @@ -58,7 +58,7 @@ class FlowTestCase: # | # 2 - 4 - 6 CASE2 = FlowTestCase( - nx.Graph([(1, 3), (2, 4), (3, 5), (4, 6)]), + nx.Graph([(1, 3), (2, 4), (3, 5), (4, 6), (3, 4)]), {1, 2}, {5, 6}, None, From bbbab571629a100fed3e2f34b107d7c1e5187255 Mon Sep 17 00:00:00 2001 From: "S.S." <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:00:56 +0900 Subject: [PATCH 20/51] :pencil2: Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- python/swiflow/flow.py | 2 +- python/swiflow/gflow.py | 2 +- python/swiflow/pflow.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index cfa8871f..10fe6959 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -78,7 +78,7 @@ def verify( oset : `collections.abc.Set` Output nodes. ensure_optimal : `bool` - Wether the flow should be maximally-delayed. Defaults to `False`. + Whether the flow should be maximally-delayed. Defaults to `False`. Raises ------ diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index fd7fd257..cf1c4741 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -95,7 +95,7 @@ def verify( Measurement plane for each node in :math:`V \setminus O`. Defaults to `Plane.XY`. ensure_optimal : `bool` - Wether the gflow should be maximally-delayed. Defaults to `False`. + Whether the gflow should be maximally-delayed. Defaults to `False`. Raises ------ diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index f4ec6f7f..b7fb5352 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -103,7 +103,7 @@ def verify( Measurement plane or Pauli index for each node in :math:`V \setminus O`. Defaults to `PPlane.XY`. ensure_optimal : `bool` - Wether the pflow should be maximally-delayed. Defaults to `False`. + Whether the pflow should be maximally-delayed. Defaults to `False`. Raises ------ From 6a045439dc7c49f849c6e04ba1921153c28ef41c Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:06:58 +0900 Subject: [PATCH 21/51] :memo: Add parameters section --- python/swiflow/common.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 84c7748f..9cc5e716 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -56,6 +56,13 @@ def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: def infer_layer(g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]]) -> Mapping[_V, int]: """Infer layer from flow/gflow. + Parameters + ---------- + g : `networkx.Graph` + Simple graph representing MBQC pattern. + anyflow : `tuple` of flow-like/layer + Flow to verify. Compatible with both flow and generalized flow. + Notes ----- This function is based on greedy algorithm. From ee1ae77dae94651fa910923b4c0332c08fa8f5f7 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 25 Jul 2025 04:06:30 +0900 Subject: [PATCH 22/51] :construction: Add pflow support --- python/swiflow/common.py | 40 ++++++++++++++++++++++++++++++++++++++-- tests/test_pflow.py | 11 ++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 9cc5e716..16626487 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -53,7 +53,40 @@ def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: return ret -def infer_layer(g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]]) -> Mapping[_V, int]: +def _special_edges( + g: nx.Graph[_V], + anyflow: Mapping[_V, _V | AbstractSet[_V]], + pplane: Mapping[_V, PPlane] | None, +) -> set[tuple[_V, _V]]: + """Compute special edges that can bypass partial order constraints in Pauli flow.""" + ret: set[tuple[_V, _V]] = set() + if pplane is None: + return ret + for u, fu_ in anyflow.items(): + fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} + fu_odd = _common.odd_neighbors(g, fu) + for v in itertools.chain(fu, fu_odd): + if u == v: + continue + if (pp := pplane.get(v)) is None: + continue + if pp == PPlane.X and v in fu: + ret.add((u, v)) + continue + if pp == PPlane.Y and v in fu and v in fu_odd: + ret.add((u, v)) + continue + if pp == PPlane.Z and v in fu_odd: + ret.add((u, v)) + continue + return ret + + +def infer_layer( + g: nx.Graph[_V], + anyflow: Mapping[_V, _V | AbstractSet[_V]], + pplane: Mapping[_V, PPlane] | None = None, +) -> Mapping[_V, int]: """Infer layer from flow/gflow. Parameters @@ -62,6 +95,8 @@ def infer_layer(g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]]) -> Simple graph representing MBQC pattern. anyflow : `tuple` of flow-like/layer Flow to verify. Compatible with both flow and generalized flow. + pplane : `collections.abc.Mapping`, optional + Measurement plane or Pauli index. If provided, `anyflow` is treated as Pauli flow. Notes ----- @@ -69,11 +104,12 @@ def infer_layer(g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]]) -> """ gd: nx.DiGraph[_V] = nx.DiGraph() gd.add_nodes_from(g.nodes) + special = _special_edges(g, anyflow, pplane) for u, fu_ in anyflow.items(): fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} fu_odd = _common.odd_neighbors(g, fu) for v in itertools.chain(fu, fu_odd): - if u == v: + if u == v or (u, v) in special: continue gd.add_edge(u, v) gd = gd.reverse() diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 776c67c6..7f9fe329 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import pflow +from swiflow import common, pflow from swiflow.common import PPlane from tests.assets import CASES, FlowTestCase @@ -34,3 +34,12 @@ def test_pflow_redundant() -> None: planes = {0: PPlane.X, 1: PPlane.Y} with pytest.raises(ValueError, match=r".*Excessive measurement planes specified.*"): pflow.find(g, iset, oset, pplane=planes) + + +@pytest.mark.parametrize("c", CASES) +def test_infer_verify(c: FlowTestCase) -> None: + if c.pflow is None: + pytest.skip() + f, _ = c.pflow + layer = common.infer_layer(c.g, f, pplane=c.pplane) + pflow.verify((f, layer), c.g, c.iset, c.oset, pplane=c.pplane) From abe0342fb90be4c361d28e463d41be87844c2b4c Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 25 Jul 2025 04:19:44 +0900 Subject: [PATCH 23/51] :memo: Fix broken any link --- python/swiflow/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 16626487..f2a8c118 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -96,7 +96,7 @@ def infer_layer( anyflow : `tuple` of flow-like/layer Flow to verify. Compatible with both flow and generalized flow. pplane : `collections.abc.Mapping`, optional - Measurement plane or Pauli index. If provided, `anyflow` is treated as Pauli flow. + Measurement plane or Pauli index. If provided, :py:obj:`anyflow` is treated as Pauli flow. Notes ----- From 39687a65a4fe27bf667ed59d7b0e0e9525b23fe3 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Sun, 27 Jul 2025 15:09:54 +0900 Subject: [PATCH 24/51] :fire: Remove connectivity check --- python/swiflow/_common.py | 3 --- tests/test_common.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index c9655aeb..123f4d01 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -46,9 +46,6 @@ def check_graph(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) - if not (oset <= vset): msg = "oset must be a subset of the nodes." raise ValueError(msg) - if not nx.is_connected(g): - msg = "Graph is not connected." - raise ValueError(msg) def check_planelike(vset: AbstractSet[_V], oset: AbstractSet[_V], plike: Mapping[_V, _P]) -> None: diff --git a/tests/test_common.py b/tests/test_common.py index e495b4cd..9ca5de2a 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -27,9 +27,6 @@ def test_check_graph_ng_g() -> None: with pytest.raises(ValueError, match=r"oset must be a subset of the nodes\."): _common.check_graph(nx.Graph([("a", "b")]), set(), {"x"}) - with pytest.raises(ValueError, match=r"Graph is not connected\."): - _common.check_graph(nx.Graph([(1, 2), (3, 4)]), set(), set()) - def test_check_graph_ng_set() -> None: with pytest.raises(TypeError): From bef185385a2a96b8be953c737c96ad47282afcb7 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Sun, 27 Jul 2025 15:24:17 +0900 Subject: [PATCH 25/51] :white_check_mark: Add isolated test case --- tests/assets.py | 42 +++++++++++++++++++++++++++++++++++++++++- tests/test_flow.py | 2 +- tests/test_gflow.py | 2 +- tests/test_pflow.py | 2 +- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/tests/assets.py b/tests/assets.py index 9ae4ca45..1ff3ba73 100644 --- a/tests/assets.py +++ b/tests/assets.py @@ -169,4 +169,44 @@ class FlowTestCase: ({0: {0, 2, 4}, 1: {1, 2}, 2: {4}}, {0: 1, 1: 1, 2: 1, 3: 0, 4: 0}), ) -CASES: tuple[FlowTestCase, ...] = (CASE0, CASE1, CASE2, CASE3, CASE4, CASE5, CASE6, CASE7, CASE8) +# 1 - 2 +# +# 3 - 4 +CASE9 = FlowTestCase( + nx.Graph([(1, 2), (3, 4)]), + {1}, + {2}, + None, + None, + # None exists as 3 - 4 is isolated + None, + None, + None, +) + +# 1 - 2 - 3 +CASE10 = FlowTestCase( + nx.Graph([(1, 2), (2, 3)]), + {1}, + set(), + None, + None, + # None exists as oset is empty + None, + None, + None, +) + +CASES: tuple[FlowTestCase, ...] = ( + CASE0, + CASE1, + CASE2, + CASE3, + CASE4, + CASE5, + CASE6, + CASE7, + CASE8, + CASE9, + CASE10, +) diff --git a/tests/test_flow.py b/tests/test_flow.py index cd5c8bf2..d1eafe76 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -8,7 +8,7 @@ @pytest.mark.parametrize("c", CASES) @pytest.mark.parametrize("opt", [True, False]) -def test_flow_graphix(c: FlowTestCase, *, opt: bool) -> None: +def test_flow(c: FlowTestCase, *, opt: bool) -> None: result = flow.find(c.g, c.iset, c.oset) assert result == c.flow if result is not None: diff --git a/tests/test_gflow.py b/tests/test_gflow.py index ffce8456..020aff2c 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize("c", CASES) @pytest.mark.parametrize("opt", [True, False]) -def test_gflow_graphix(c: FlowTestCase, *, opt: bool) -> None: +def test_gflow(c: FlowTestCase, *, opt: bool) -> None: result = gflow.find(c.g, c.iset, c.oset, plane=c.plane) assert result == c.gflow if result is not None: diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 7f9fe329..7a44b5e0 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -11,7 +11,7 @@ @pytest.mark.filterwarnings("ignore:No Pauli measurement found") @pytest.mark.parametrize("c", CASES) @pytest.mark.parametrize("opt", [True, False]) -def test_pflow_graphix(c: FlowTestCase, *, opt: bool) -> None: +def test_pflow(c: FlowTestCase, *, opt: bool) -> None: result = pflow.find(c.g, c.iset, c.oset, pplane=c.pplane) assert result == c.pflow if result is not None: From f01f05acb362b88c6c2bdfdc8430d718e3513755 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:06:41 +0900 Subject: [PATCH 26/51] :recycle: Hide infer_layer --- python/swiflow/_common.py | 87 ++++++++++++++++++++++++++++++++++++ python/swiflow/common.py | 92 +-------------------------------------- tests/test_common.py | 8 ++-- tests/test_flow.py | 4 +- tests/test_gflow.py | 4 +- tests/test_pflow.py | 4 +- 6 files changed, 98 insertions(+), 101 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 123f4d01..56568f56 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -2,6 +2,7 @@ from __future__ import annotations +import itertools from collections.abc import Callable, Hashable, Iterable, Mapping from collections.abc import Set as AbstractSet from typing import Generic, TypeVar @@ -10,6 +11,7 @@ from typing_extensions import ParamSpec from swiflow._impl import FlowValidationMessage +from swiflow.common import PPlane _V = TypeVar("_V", bound=Hashable) @@ -296,3 +298,88 @@ def ecatch(self, f: Callable[_S, _T], *args: _S.args, **kwargs: _S.kwargs) -> _T return f(*args, **kwargs) except ValueError as e: raise self.decode_err(e) from None + + +def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: + pred = {u: set(gd.predecessors(u)) for u in gd.nodes} + work = {u for u, pu in pred.items() if not pu} + ret: dict[_V, int] = {} + for l_now in itertools.count(): + if not work: + break + next_work: set[_V] = set() + for u in work: + ret[u] = l_now + for v in gd.successors(u): + ent = pred[v] + ent.discard(u) + if not ent: + next_work.add(v) + work = next_work + if len(ret) != len(gd): + msg = "Failed to determine layer for all nodes." + raise ValueError(msg) + return ret + + +def _special_edges( + g: nx.Graph[_V], + anyflow: Mapping[_V, _V | AbstractSet[_V]], + pplane: Mapping[_V, PPlane] | None, +) -> set[tuple[_V, _V]]: + """Compute special edges that can bypass partial order constraints in Pauli flow.""" + ret: set[tuple[_V, _V]] = set() + if pplane is None: + return ret + for u, fu_ in anyflow.items(): + fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} + fu_odd = odd_neighbors(g, fu) + for v in itertools.chain(fu, fu_odd): + if u == v: + continue + if (pp := pplane.get(v)) is None: + continue + if pp == PPlane.X and v in fu: + ret.add((u, v)) + continue + if pp == PPlane.Y and v in fu and v in fu_odd: + ret.add((u, v)) + continue + if pp == PPlane.Z and v in fu_odd: + ret.add((u, v)) + continue + return ret + + +def infer_layer( + g: nx.Graph[_V], + anyflow: Mapping[_V, _V | AbstractSet[_V]], + pplane: Mapping[_V, PPlane] | None = None, +) -> Mapping[_V, int]: + """Infer layer from flow/gflow. + + Parameters + ---------- + g : `networkx.Graph` + Simple graph representing MBQC pattern. + anyflow : `tuple` of flow-like/layer + Flow to verify. Compatible with both flow and generalized flow. + pplane : `collections.abc.Mapping`, optional + Measurement plane or Pauli index. If provided, :py:obj:`anyflow` is treated as Pauli flow. + + Notes + ----- + This function is based on greedy algorithm. + """ + gd: nx.DiGraph[_V] = nx.DiGraph() + gd.add_nodes_from(g.nodes) + special = _special_edges(g, anyflow, pplane) + for u, fu_ in anyflow.items(): + fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} + fu_odd = odd_neighbors(g, fu) + for v in itertools.chain(fu, fu_odd): + if u == v or (u, v) in special: + continue + gd.add_edge(u, v) + gd = gd.reverse() + return _infer_layer_impl(gd) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index f2a8c118..29a64e2b 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -2,14 +2,9 @@ from __future__ import annotations -import itertools -from collections.abc import Hashable, Mapping -from collections.abc import Set as AbstractSet +from collections.abc import Hashable from typing import TypeVar -import networkx as nx - -from swiflow import _common from swiflow._impl import gflow, pflow Plane = gflow.Plane @@ -29,88 +24,3 @@ Layer = dict[_V, int] r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. """ - - -def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: - pred = {u: set(gd.predecessors(u)) for u in gd.nodes} - work = {u for u, pu in pred.items() if not pu} - ret: dict[_V, int] = {} - for l_now in itertools.count(): - if not work: - break - next_work: set[_V] = set() - for u in work: - ret[u] = l_now - for v in gd.successors(u): - ent = pred[v] - ent.discard(u) - if not ent: - next_work.add(v) - work = next_work - if len(ret) != len(gd): - msg = "Failed to determine layer for all nodes." - raise ValueError(msg) - return ret - - -def _special_edges( - g: nx.Graph[_V], - anyflow: Mapping[_V, _V | AbstractSet[_V]], - pplane: Mapping[_V, PPlane] | None, -) -> set[tuple[_V, _V]]: - """Compute special edges that can bypass partial order constraints in Pauli flow.""" - ret: set[tuple[_V, _V]] = set() - if pplane is None: - return ret - for u, fu_ in anyflow.items(): - fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} - fu_odd = _common.odd_neighbors(g, fu) - for v in itertools.chain(fu, fu_odd): - if u == v: - continue - if (pp := pplane.get(v)) is None: - continue - if pp == PPlane.X and v in fu: - ret.add((u, v)) - continue - if pp == PPlane.Y and v in fu and v in fu_odd: - ret.add((u, v)) - continue - if pp == PPlane.Z and v in fu_odd: - ret.add((u, v)) - continue - return ret - - -def infer_layer( - g: nx.Graph[_V], - anyflow: Mapping[_V, _V | AbstractSet[_V]], - pplane: Mapping[_V, PPlane] | None = None, -) -> Mapping[_V, int]: - """Infer layer from flow/gflow. - - Parameters - ---------- - g : `networkx.Graph` - Simple graph representing MBQC pattern. - anyflow : `tuple` of flow-like/layer - Flow to verify. Compatible with both flow and generalized flow. - pplane : `collections.abc.Mapping`, optional - Measurement plane or Pauli index. If provided, :py:obj:`anyflow` is treated as Pauli flow. - - Notes - ----- - This function is based on greedy algorithm. - """ - gd: nx.DiGraph[_V] = nx.DiGraph() - gd.add_nodes_from(g.nodes) - special = _special_edges(g, anyflow, pplane) - for u, fu_ in anyflow.items(): - fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} - fu_odd = _common.odd_neighbors(g, fu) - for v in itertools.chain(fu, fu_odd): - if u == v or (u, v) in special: - continue - gd.add_edge(u, v) - gd = gd.reverse() - return _infer_layer_impl(gd) diff --git a/tests/test_common.py b/tests/test_common.py index 9ca5de2a..c17adbbd 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import _common, common +from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import FlowValidationMessage from swiflow.common import Plane, PPlane @@ -141,17 +141,17 @@ class TestInferLayer: def test_line(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 3)]) flow = {0: {1}, 1: {2}, 2: {3}} - layer = common.infer_layer(g, flow) + layer = _common.infer_layer(g, flow) assert layer == {0: 3, 1: 2, 2: 1, 3: 0} def test_dag(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 2), (0, 3), (1, 2), (1, 3)]) flow = {0: {2, 3}, 1: {2, 3}} - layer = common.infer_layer(g, flow) + layer = _common.infer_layer(g, flow) assert layer == {0: 1, 1: 1, 2: 0, 3: 0} def test_cycle(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 0)]) flow = {0: {1}, 1: {2}, 2: {0}} with pytest.raises(ValueError, match=r".*determine.*"): - common.infer_layer(g, flow) + _common.infer_layer(g, flow) diff --git a/tests/test_flow.py b/tests/test_flow.py index d1eafe76..e377abde 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -1,7 +1,7 @@ from __future__ import annotations import pytest -from swiflow import common, flow +from swiflow import _common, flow from tests.assets import CASES, FlowTestCase @@ -20,5 +20,5 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.flow is None: pytest.skip() f, _ = c.flow - layer = common.infer_layer(c.g, f) + layer = _common.infer_layer(c.g, f) flow.verify((f, layer), c.g, c.iset, c.oset) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 020aff2c..8d59c83f 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import common, gflow +from swiflow import _common, gflow from swiflow.common import Plane from tests.assets import CASES, FlowTestCase @@ -31,5 +31,5 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.gflow is None: pytest.skip() f, _ = c.gflow - layer = common.infer_layer(c.g, f) + layer = _common.infer_layer(c.g, f) gflow.verify((f, layer), c.g, c.iset, c.oset, plane=c.plane) diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 7a44b5e0..b3b925ad 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import common, pflow +from swiflow import _common, pflow from swiflow.common import PPlane from tests.assets import CASES, FlowTestCase @@ -41,5 +41,5 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.pflow is None: pytest.skip() f, _ = c.pflow - layer = common.infer_layer(c.g, f, pplane=c.pplane) + layer = _common.infer_layer(c.g, f, pplane=c.pplane) pflow.verify((f, layer), c.g, c.iset, c.oset, pplane=c.pplane) From 32dacda83f5be5e291db676c1a0440112a4cb55b Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:16:17 +0900 Subject: [PATCH 27/51] :children_crossing: Infer layer automatically if missing --- python/swiflow/flow.py | 14 +++++++++----- python/swiflow/gflow.py | 17 ++++++++++------- python/swiflow/pflow.py | 21 ++++++++++++++------- tests/test_flow.py | 5 ++--- tests/test_gflow.py | 5 ++--- tests/test_pflow.py | 5 ++--- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 10fe6959..af915b57 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -6,7 +6,7 @@ from __future__ import annotations -from collections.abc import Hashable +from collections.abc import Hashable, Mapping from typing import TYPE_CHECKING, TypeVar from swiflow import _common @@ -15,7 +15,6 @@ from swiflow.common import Flow, Layer if TYPE_CHECKING: - from collections.abc import Mapping from collections.abc import Set as AbstractSet import networkx as nx @@ -57,8 +56,12 @@ def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowR return None +_Flow = Mapping[_V, _V] +_Layer = Mapping[_V, int] + + def verify( - flow: tuple[Mapping[_V, _V], Mapping[_V, int]], + flow: tuple[_Flow[_V], _Layer[_V]] | _Flow[_V], g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V], @@ -69,8 +72,9 @@ def verify( Parameters ---------- - flow : `tuple` of flow/layer + flow : flow (required) and layer (optional) Flow to verify. + Layer is automatically computed if omitted. g : `networkx.Graph` Simple graph representing MBQC pattern. iset : `collections.abc.Set` @@ -86,7 +90,7 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = flow + f, layer = flow if isinstance(flow, tuple) else (flow, _common.infer_layer(g, flow)) if ensure_optimal: _common.check_layer(layer) vset = g.nodes diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index cf1c4741..52121a1c 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -6,7 +6,8 @@ from __future__ import annotations -from collections.abc import Hashable +from collections.abc import Hashable, Mapping +from collections.abc import Set as AbstractSet from typing import TYPE_CHECKING, TypeVar from swiflow import _common @@ -15,9 +16,6 @@ from swiflow.common import GFlow, Layer, Plane if TYPE_CHECKING: - from collections.abc import Mapping - from collections.abc import Set as AbstractSet - import networkx as nx _V = TypeVar("_V", bound=Hashable) @@ -70,8 +68,12 @@ def find( return None +_GFlow = Mapping[_V, AbstractSet[_V]] +_Layer = Mapping[_V, int] + + def verify( - gflow: tuple[Mapping[_V, AbstractSet[_V]], Mapping[_V, int]], + gflow: tuple[_GFlow[_V], _Layer[_V]] | _GFlow[_V], g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V], @@ -83,8 +85,9 @@ def verify( Parameters ---------- - gflow : `tuple` of gflow/layer + gflow : gflow (required) and layer (optional) Generalized flow to verify. + Layer is automatically computed if omitted. g : `networkx.Graph` Simple graph representing MBQC pattern. iset : `collections.abc.Set` @@ -103,7 +106,7 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = gflow + f, layer = gflow if isinstance(gflow, tuple) else (gflow, _common.infer_layer(g, gflow)) if ensure_optimal: _common.check_layer(layer) vset = g.nodes diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index b7fb5352..ac2d85e9 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -7,7 +7,8 @@ from __future__ import annotations import warnings -from collections.abc import Hashable +from collections.abc import Hashable, Mapping +from collections.abc import Set as AbstractSet from typing import TYPE_CHECKING, TypeVar from swiflow import _common @@ -16,9 +17,6 @@ from swiflow.common import Layer, PFlow, PPlane if TYPE_CHECKING: - from collections.abc import Mapping - from collections.abc import Set as AbstractSet - import networkx as nx _V = TypeVar("_V", bound=Hashable) @@ -78,8 +76,12 @@ def find( return None +_PFlow = Mapping[_V, AbstractSet[_V]] +_Layer = Mapping[_V, int] + + def verify( - pflow: tuple[Mapping[_V, AbstractSet[_V]], Mapping[_V, int]], + pflow: tuple[_PFlow[_V], _Layer[_V]] | _PFlow[_V], g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V], @@ -91,8 +93,9 @@ def verify( Parameters ---------- - pflow : `tuple` of Pauli flow/layer + pflow : Pauli flow (required) and layer (optional) Pauli flow to verify. + Layer is automatically computed if omitted. g : `networkx.Graph` Simple graph representing MBQC pattern. iset : `collections.abc.Set` @@ -111,7 +114,11 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = pflow + if isinstance(pflow, tuple): + f, layer = pflow + else: + f = pflow + layer = _common.infer_layer(g, pflow, pplane) if ensure_optimal: _common.check_layer(layer) vset = g.nodes diff --git a/tests/test_flow.py b/tests/test_flow.py index e377abde..2e292edd 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -1,7 +1,7 @@ from __future__ import annotations import pytest -from swiflow import _common, flow +from swiflow import flow from tests.assets import CASES, FlowTestCase @@ -20,5 +20,4 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.flow is None: pytest.skip() f, _ = c.flow - layer = _common.infer_layer(c.g, f) - flow.verify((f, layer), c.g, c.iset, c.oset) + flow.verify(f, c.g, c.iset, c.oset) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 8d59c83f..eafceb33 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import _common, gflow +from swiflow import gflow from swiflow.common import Plane from tests.assets import CASES, FlowTestCase @@ -31,5 +31,4 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.gflow is None: pytest.skip() f, _ = c.gflow - layer = _common.infer_layer(c.g, f) - gflow.verify((f, layer), c.g, c.iset, c.oset, plane=c.plane) + gflow.verify(f, c.g, c.iset, c.oset, plane=c.plane) diff --git a/tests/test_pflow.py b/tests/test_pflow.py index b3b925ad..78aeee3d 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import _common, pflow +from swiflow import pflow from swiflow.common import PPlane from tests.assets import CASES, FlowTestCase @@ -41,5 +41,4 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.pflow is None: pytest.skip() f, _ = c.pflow - layer = _common.infer_layer(c.g, f, pplane=c.pplane) - pflow.verify((f, layer), c.g, c.iset, c.oset, pplane=c.pplane) + pflow.verify(f, c.g, c.iset, c.oset, pplane=c.pplane) From 484993941128b3ea65c8afbb88a0e09a840b5fab Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:34:23 +0900 Subject: [PATCH 28/51] :recycle: Refactor --- python/swiflow/_common.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 56568f56..75a41e9b 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -301,6 +301,7 @@ def ecatch(self, f: Callable[_S, _T], *args: _S.args, **kwargs: _S.kwargs) -> _T def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: + """Fix flow layers one by one depending on order constraints.""" pred = {u: set(gd.predecessors(u)) for u in gd.nodes} work = {u for u, pu in pred.items() if not pu} ret: dict[_V, int] = {} @@ -322,6 +323,20 @@ def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: return ret +def _is_special( + pp: PPlane | None, + in_fu: bool, # noqa: FBT001 + in_fu_odd: bool, # noqa: FBT001 +) -> bool: + if pp == PPlane.X: + return in_fu + if pp == PPlane.Y: + return in_fu and in_fu_odd + if pp == PPlane.Z: + return in_fu_odd + return False + + def _special_edges( g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]], @@ -337,17 +352,8 @@ def _special_edges( for v in itertools.chain(fu, fu_odd): if u == v: continue - if (pp := pplane.get(v)) is None: - continue - if pp == PPlane.X and v in fu: - ret.add((u, v)) - continue - if pp == PPlane.Y and v in fu and v in fu_odd: - ret.add((u, v)) - continue - if pp == PPlane.Z and v in fu_odd: + if _is_special(pplane.get(v), v in fu, v in fu_odd): ret.add((u, v)) - continue return ret @@ -356,7 +362,7 @@ def infer_layer( anyflow: Mapping[_V, _V | AbstractSet[_V]], pplane: Mapping[_V, PPlane] | None = None, ) -> Mapping[_V, int]: - """Infer layer from flow/gflow. + """Infer layer from flow/gflow using greedy algorithm. Parameters ---------- @@ -365,11 +371,11 @@ def infer_layer( anyflow : `tuple` of flow-like/layer Flow to verify. Compatible with both flow and generalized flow. pplane : `collections.abc.Mapping`, optional - Measurement plane or Pauli index. If provided, :py:obj:`anyflow` is treated as Pauli flow. + Measurement plane or Pauli index. Notes ----- - This function is based on greedy algorithm. + This function operates in Pauli flow mode only when :py:obj`pplane` is explicitly given. """ gd: nx.DiGraph[_V] = nx.DiGraph() gd.add_nodes_from(g.nodes) From aeb0e8ac3ab3314d73f82be48b091e16541c3303 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:41:59 +0900 Subject: [PATCH 29/51] :truck: Rename infer_layer --- python/swiflow/_common.py | 6 +++--- python/swiflow/flow.py | 2 +- python/swiflow/gflow.py | 2 +- python/swiflow/pflow.py | 2 +- tests/test_common.py | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 75a41e9b..620198ff 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -300,7 +300,7 @@ def ecatch(self, f: Callable[_S, _T], *args: _S.args, **kwargs: _S.kwargs) -> _T raise self.decode_err(e) from None -def _infer_layer_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: +def _infer_layers_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: """Fix flow layers one by one depending on order constraints.""" pred = {u: set(gd.predecessors(u)) for u in gd.nodes} work = {u for u, pu in pred.items() if not pu} @@ -357,7 +357,7 @@ def _special_edges( return ret -def infer_layer( +def infer_layers( g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]], pplane: Mapping[_V, PPlane] | None = None, @@ -388,4 +388,4 @@ def infer_layer( continue gd.add_edge(u, v) gd = gd.reverse() - return _infer_layer_impl(gd) + return _infer_layers_impl(gd) diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index af915b57..dfb6e600 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -90,7 +90,7 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = flow if isinstance(flow, tuple) else (flow, _common.infer_layer(g, flow)) + f, layer = flow if isinstance(flow, tuple) else (flow, _common.infer_layers(g, flow)) if ensure_optimal: _common.check_layer(layer) vset = g.nodes diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 52121a1c..a2dc6f55 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -106,7 +106,7 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = gflow if isinstance(gflow, tuple) else (gflow, _common.infer_layer(g, gflow)) + f, layer = gflow if isinstance(gflow, tuple) else (gflow, _common.infer_layers(g, gflow)) if ensure_optimal: _common.check_layer(layer) vset = g.nodes diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index ac2d85e9..833c3005 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -118,7 +118,7 @@ def verify( f, layer = pflow else: f = pflow - layer = _common.infer_layer(g, pflow, pplane) + layer = _common.infer_layers(g, pflow, pplane) if ensure_optimal: _common.check_layer(layer) vset = g.nodes diff --git a/tests/test_common.py b/tests/test_common.py index c17adbbd..e3eeef79 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -141,17 +141,17 @@ class TestInferLayer: def test_line(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 3)]) flow = {0: {1}, 1: {2}, 2: {3}} - layer = _common.infer_layer(g, flow) + layer = _common.infer_layers(g, flow) assert layer == {0: 3, 1: 2, 2: 1, 3: 0} def test_dag(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 2), (0, 3), (1, 2), (1, 3)]) flow = {0: {2, 3}, 1: {2, 3}} - layer = _common.infer_layer(g, flow) + layer = _common.infer_layers(g, flow) assert layer == {0: 1, 1: 1, 2: 0, 3: 0} def test_cycle(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 0)]) flow = {0: {1}, 1: {2}, 2: {0}} with pytest.raises(ValueError, match=r".*determine.*"): - _common.infer_layer(g, flow) + _common.infer_layers(g, flow) From ee90522f4560c7d0c75277f2d03a8b4b89953b63 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 00:18:15 +0900 Subject: [PATCH 30/51] :zap: Remove intermediate graph allocation --- python/swiflow/_common.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 620198ff..e54d5962 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -3,7 +3,7 @@ from __future__ import annotations import itertools -from collections.abc import Callable, Hashable, Iterable, Mapping +from collections.abc import Callable, Hashable, Iterable, Mapping, MutableSet from collections.abc import Set as AbstractSet from typing import Generic, TypeVar @@ -300,9 +300,13 @@ def ecatch(self, f: Callable[_S, _T], *args: _S.args, **kwargs: _S.kwargs) -> _T raise self.decode_err(e) from None -def _infer_layers_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: - """Fix flow layers one by one depending on order constraints.""" - pred = {u: set(gd.predecessors(u)) for u in gd.nodes} +def _infer_layers_impl(pred: Mapping[_V, MutableSet[_V]], succ: Mapping[_V, AbstractSet[_V]]) -> Mapping[_V, int]: + """Fix flow layers one by one depending on order constraints. + + Notes + ----- + :py:obj:`pred` is mutated in-place. + """ work = {u for u, pu in pred.items() if not pu} ret: dict[_V, int] = {} for l_now in itertools.count(): @@ -311,13 +315,13 @@ def _infer_layers_impl(gd: nx.DiGraph[_V]) -> Mapping[_V, int]: next_work: set[_V] = set() for u in work: ret[u] = l_now - for v in gd.successors(u): + for v in succ[u]: ent = pred[v] ent.discard(u) if not ent: next_work.add(v) work = next_work - if len(ret) != len(gd): + if len(ret) != len(succ): msg = "Failed to determine layer for all nodes." raise ValueError(msg) return ret @@ -377,15 +381,17 @@ def infer_layers( ----- This function operates in Pauli flow mode only when :py:obj`pplane` is explicitly given. """ - gd: nx.DiGraph[_V] = nx.DiGraph() - gd.add_nodes_from(g.nodes) special = _special_edges(g, anyflow, pplane) + pred: dict[_V, set[_V]] = {u: set() for u in g.nodes} + succ: dict[_V, set[_V]] = {u: set() for u in g.nodes} for u, fu_ in anyflow.items(): fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} fu_odd = odd_neighbors(g, fu) for v in itertools.chain(fu, fu_odd): if u == v or (u, v) in special: continue - gd.add_edge(u, v) - gd = gd.reverse() - return _infer_layers_impl(gd) + # Reversed + pred[u].add(v) + succ[v].add(u) + # MEMO: `pred` is invalidated by `_infer_layers_impl` + return _infer_layers_impl(pred, succ) From 55a905f0f701e75f59a7269171230e43586ebfff Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 00:52:49 +0900 Subject: [PATCH 31/51] :fire: Remove ensure_optimal --- python/swiflow/_common.py | 7 ------- python/swiflow/_impl/flow.pyi | 1 - python/swiflow/_impl/gflow.pyi | 1 - python/swiflow/_impl/pflow.pyi | 1 - python/swiflow/flow.py | 8 +------- python/swiflow/gflow.py | 7 +------ python/swiflow/pflow.py | 7 +------ src/flow.rs | 17 ++++------------- src/gflow.rs | 14 +++++--------- src/pflow.rs | 20 ++++++++------------ tests/test_common.py | 11 ----------- tests/test_flow.py | 5 ++--- tests/test_gflow.py | 5 ++--- tests/test_pflow.py | 5 ++--- 14 files changed, 26 insertions(+), 83 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index e54d5962..1c6fc9ae 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -84,13 +84,6 @@ def check_planelike(vset: AbstractSet[_V], oset: AbstractSet[_V], plike: Mapping raise ValueError(msg) -def check_layer(layer: Mapping[_V, int]) -> None: - """Check if layer range is compatible with the current implementation.""" - if min(layer.values(), default=0) != 0: - msg = "Minimum layer must be 0." - raise ValueError(msg) - - def odd_neighbors(g: nx.Graph[_V], kset: AbstractSet[_V]) -> set[_V]: """Compute odd neighbors of `kset` in `g`.""" ret: set[_V] = set() diff --git a/python/swiflow/_impl/flow.pyi b/python/swiflow/_impl/flow.pyi index 3f06d5cd..211ae76d 100644 --- a/python/swiflow/_impl/flow.pyi +++ b/python/swiflow/_impl/flow.pyi @@ -4,5 +4,4 @@ def verify( g: list[set[int]], iset: set[int], oset: set[int], - optimal: bool, ) -> None: ... diff --git a/python/swiflow/_impl/gflow.pyi b/python/swiflow/_impl/gflow.pyi index a2350b6e..2770059c 100644 --- a/python/swiflow/_impl/gflow.pyi +++ b/python/swiflow/_impl/gflow.pyi @@ -12,5 +12,4 @@ def verify( iset: set[int], oset: set[int], plane: dict[int, Plane], - optimal: bool, ) -> None: ... diff --git a/python/swiflow/_impl/pflow.pyi b/python/swiflow/_impl/pflow.pyi index 93fd7a38..8765895b 100644 --- a/python/swiflow/_impl/pflow.pyi +++ b/python/swiflow/_impl/pflow.pyi @@ -15,5 +15,4 @@ def verify( iset: set[int], oset: set[int], pplane: dict[int, PPlane], - optimal: bool, ) -> None: ... diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index dfb6e600..98841809 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -65,8 +65,6 @@ def verify( g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V], - *, - ensure_optimal: bool = False, ) -> None: """Verify causal flow. @@ -81,8 +79,6 @@ def verify( Input nodes. oset : `collections.abc.Set` Output nodes. - ensure_optimal : `bool` - Whether the flow should be maximally-delayed. Defaults to `False`. Raises ------ @@ -91,8 +87,6 @@ def verify( """ _common.check_graph(g, iset, oset) f, layer = flow if isinstance(flow, tuple) else (flow, _common.infer_layers(g, flow)) - if ensure_optimal: - _common.check_layer(layer) vset = g.nodes codec = IndexMap(vset) g_ = codec.encode_graph(g) @@ -100,4 +94,4 @@ def verify( oset_ = codec.encode_set(oset) f_ = codec.encode_flow(f) layer_ = codec.encode_layer(layer) - codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_, ensure_optimal) + codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_) diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index a2dc6f55..480373d3 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -79,7 +79,6 @@ def verify( oset: AbstractSet[_V], *, plane: Mapping[_V, Plane] | None = None, - ensure_optimal: bool = False, ) -> None: r"""Verify generalized flow. @@ -97,8 +96,6 @@ def verify( plane : `collections.abc.Mapping` Measurement plane for each node in :math:`V \setminus O`. Defaults to `Plane.XY`. - ensure_optimal : `bool` - Whether the gflow should be maximally-delayed. Defaults to `False`. Raises ------ @@ -107,8 +104,6 @@ def verify( """ _common.check_graph(g, iset, oset) f, layer = gflow if isinstance(gflow, tuple) else (gflow, _common.infer_layers(g, gflow)) - if ensure_optimal: - _common.check_layer(layer) vset = g.nodes if plane is None: plane = dict.fromkeys(vset - oset, Plane.XY) @@ -119,4 +114,4 @@ def verify( plane_ = codec.encode_dictkey(plane) f_ = codec.encode_gflow(f) layer_ = codec.encode_layer(layer) - codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_, ensure_optimal) + codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 833c3005..61456ee1 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -87,7 +87,6 @@ def verify( oset: AbstractSet[_V], *, pplane: Mapping[_V, PPlane] | None = None, - ensure_optimal: bool = False, ) -> None: r"""Verify Pauli flow. @@ -105,8 +104,6 @@ def verify( pplane : `collections.abc.Mapping` Measurement plane or Pauli index for each node in :math:`V \setminus O`. Defaults to `PPlane.XY`. - ensure_optimal : `bool` - Whether the pflow should be maximally-delayed. Defaults to `False`. Raises ------ @@ -119,8 +116,6 @@ def verify( else: f = pflow layer = _common.infer_layers(g, pflow, pplane) - if ensure_optimal: - _common.check_layer(layer) vset = g.nodes if pplane is None: pplane = dict.fromkeys(vset - oset, PPlane.XY) @@ -131,4 +126,4 @@ def verify( pplane_ = codec.encode_dictkey(pplane) f_ = codec.encode_gflow(f) layer_ = codec.encode_layer(layer) - codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_, ensure_optimal) + codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_) diff --git a/src/flow.rs b/src/flow.rs index 0f900f62..8786f858 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -125,21 +125,12 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { #[pyfunction] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn verify( - flow: (Flow, Layer), - g: Graph, - iset: Nodes, - oset: Nodes, - optimal: bool, -) -> PyResult<()> { +pub fn verify(flow: (Flow, Layer), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { let (f, layer) = flow; let n = g.len(); let vset = (0..n).collect::(); validate::check_domain(f.iter(), &vset, &iset, &oset)?; check_definition(&f, &layer, &g)?; - if optimal { - validate::check_initial(&layer, &oset, true)?; - } Ok(()) } @@ -184,7 +175,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, true).unwrap(); + verify((f, layer), g, iset, oset).unwrap(); } #[test_log::test] @@ -198,7 +189,7 @@ mod tests { assert_eq!(f[&2], 3); assert_eq!(f[&3], 4); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, true).unwrap(); + verify((f, layer), g, iset, oset).unwrap(); } #[test_log::test] @@ -212,7 +203,7 @@ mod tests { assert_eq!(f[&2], 4); assert_eq!(f[&3], 5); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, true).unwrap(); + verify((f, layer), g, iset, oset).unwrap(); } #[test_log::test] diff --git a/src/gflow.rs b/src/gflow.rs index 2f957524..3aa91a69 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -253,7 +253,6 @@ pub fn verify( iset: Nodes, oset: Nodes, planes: Planes, - optimal: bool, ) -> PyResult<()> { let (f, layer) = gflow; let n = g.len(); @@ -263,9 +262,6 @@ pub fn verify( .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; check_definition(&f, &layer, &g, &planes)?; - if optimal { - validate::check_initial(&layer, &oset, true)?; - } Ok(()) } @@ -373,7 +369,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, planes, true).unwrap(); + verify((f, layer), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -393,7 +389,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, planes, true).unwrap(); + verify((f, layer), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -413,7 +409,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, planes, true).unwrap(); + verify((f, layer), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -431,7 +427,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, layer), g, iset, oset, planes, true).unwrap(); + verify((f, layer), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -451,7 +447,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, planes, true).unwrap(); + verify((f, layer), g, iset, oset, planes).unwrap(); } #[test_log::test] diff --git a/src/pflow.rs b/src/pflow.rs index e562357b..e3be2569 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -441,7 +441,6 @@ pub fn verify( iset: Nodes, oset: Nodes, pplanes: PPlanes, - optimal: bool, ) -> PyResult<()> { let (f, layer) = pflow; let n = g.len(); @@ -451,9 +450,6 @@ pub fn verify( .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; check_definition(&f, &layer, &g, &pplanes)?; - if optimal { - validate::check_initial(&layer, &oset, false)?; - } Ok(()) } @@ -613,7 +609,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -633,7 +629,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -653,7 +649,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -671,7 +667,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -691,7 +687,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -721,7 +717,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([2, 4])); assert_eq!(layer, vec![1, 1, 0, 1, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -743,7 +739,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![1, 0, 0, 1, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -763,6 +759,6 @@ mod tests { assert_eq!(f[&1], Nodes::from([1, 2])); assert_eq!(f[&2], Nodes::from([4])); assert_eq!(layer, vec![1, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes, true).unwrap(); + verify((f, layer), g, iset, oset, pplanes).unwrap(); } } diff --git a/tests/test_common.py b/tests/test_common.py index e3eeef79..b6a502cd 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -47,17 +47,6 @@ def test_check_planelike_ng() -> None: _common.check_planelike({"a", "b"}, {"b"}, {}) -def test_check_layer() -> None: - _common.check_layer({"a": 0, "b": 1, "c": 2}) - _common.check_layer({}) - - with pytest.raises(ValueError, match=r".*must be 0.*"): - _common.check_layer({"a": 1, "b": 2}) - - with pytest.raises(ValueError, match=r".*must be 0.*"): - _common.check_layer({"a": -1, "b": 0}) - - @pytest.fixture def fx_indexmap() -> IndexMap[str]: return IndexMap({"a", "b", "c"}) diff --git a/tests/test_flow.py b/tests/test_flow.py index 2e292edd..c17b1432 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -7,12 +7,11 @@ @pytest.mark.parametrize("c", CASES) -@pytest.mark.parametrize("opt", [True, False]) -def test_flow(c: FlowTestCase, *, opt: bool) -> None: +def test_flow(c: FlowTestCase) -> None: result = flow.find(c.g, c.iset, c.oset) assert result == c.flow if result is not None: - flow.verify(result, c.g, c.iset, c.oset, ensure_optimal=opt) + flow.verify(result, c.g, c.iset, c.oset) @pytest.mark.parametrize("c", CASES) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index eafceb33..17e36a95 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -9,12 +9,11 @@ @pytest.mark.parametrize("c", CASES) -@pytest.mark.parametrize("opt", [True, False]) -def test_gflow(c: FlowTestCase, *, opt: bool) -> None: +def test_gflow(c: FlowTestCase) -> None: result = gflow.find(c.g, c.iset, c.oset, plane=c.plane) assert result == c.gflow if result is not None: - gflow.verify(result, c.g, c.iset, c.oset, plane=c.plane, ensure_optimal=opt) + gflow.verify(result, c.g, c.iset, c.oset, plane=c.plane) def test_gflow_redundant() -> None: diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 78aeee3d..29ea9324 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -10,12 +10,11 @@ @pytest.mark.filterwarnings("ignore:No Pauli measurement found") @pytest.mark.parametrize("c", CASES) -@pytest.mark.parametrize("opt", [True, False]) -def test_pflow(c: FlowTestCase, *, opt: bool) -> None: +def test_pflow(c: FlowTestCase) -> None: result = pflow.find(c.g, c.iset, c.oset, pplane=c.pplane) assert result == c.pflow if result is not None: - pflow.verify(result, c.g, c.iset, c.oset, pplane=c.pplane, ensure_optimal=opt) + pflow.verify(result, c.g, c.iset, c.oset, pplane=c.pplane) def test_pflow_nopauli() -> None: From 474208d49e3c71b0412bf134d80060ec2cddd6ba Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 01:32:46 +0900 Subject: [PATCH 32/51] :construction: Split verifier --- python/swiflow/_impl/flow.pyi | 2 +- python/swiflow/_impl/gflow.pyi | 2 +- python/swiflow/_impl/pflow.pyi | 2 +- python/swiflow/flow.py | 16 ++-- python/swiflow/gflow.py | 16 ++-- python/swiflow/pflow.py | 20 ++--- src/flow.rs | 50 +++++++------ src/gflow.rs | 92 ++++++++++++----------- src/pflow.rs | 130 +++++++++++++++++---------------- 9 files changed, 177 insertions(+), 153 deletions(-) diff --git a/python/swiflow/_impl/flow.pyi b/python/swiflow/_impl/flow.pyi index 211ae76d..f219b85c 100644 --- a/python/swiflow/_impl/flow.pyi +++ b/python/swiflow/_impl/flow.pyi @@ -1,6 +1,6 @@ def find(g: list[set[int]], iset: set[int], oset: set[int]) -> tuple[dict[int, int], list[int]] | None: ... def verify( - flow: tuple[dict[int, int], list[int]], + flow: tuple[dict[int, int], list[int] | None], g: list[set[int]], iset: set[int], oset: set[int], diff --git a/python/swiflow/_impl/gflow.pyi b/python/swiflow/_impl/gflow.pyi index 2770059c..c05bb2cb 100644 --- a/python/swiflow/_impl/gflow.pyi +++ b/python/swiflow/_impl/gflow.pyi @@ -7,7 +7,7 @@ def find( g: list[set[int]], iset: set[int], oset: set[int], plane: dict[int, Plane] ) -> tuple[dict[int, set[int]], list[int]] | None: ... def verify( - gflow: tuple[dict[int, set[int]], list[int]], + gflow: tuple[dict[int, set[int]], list[int] | None], g: list[set[int]], iset: set[int], oset: set[int], diff --git a/python/swiflow/_impl/pflow.pyi b/python/swiflow/_impl/pflow.pyi index 8765895b..17eeb207 100644 --- a/python/swiflow/_impl/pflow.pyi +++ b/python/swiflow/_impl/pflow.pyi @@ -10,7 +10,7 @@ def find( g: list[set[int]], iset: set[int], oset: set[int], pplane: dict[int, PPlane] ) -> tuple[dict[int, set[int]], list[int]] | None: ... def verify( - pflow: tuple[dict[int, set[int]], list[int]], + pflow: tuple[dict[int, set[int]], list[int] | None], g: list[set[int]], iset: set[int], oset: set[int], diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 98841809..83fbfd3e 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -60,6 +60,16 @@ def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowR _Layer = Mapping[_V, int] +def _codec_wrap( + codec: IndexMap[_V], + flow: tuple[_Flow[_V], _Layer[_V]] | _Flow[_V], +) -> tuple[dict[int, int], list[int] | None]: + if isinstance(flow, tuple): + f, layer = flow + return codec.encode_flow(f), codec.encode_layer(layer) + return codec.encode_flow(flow), None + + def verify( flow: tuple[_Flow[_V], _Layer[_V]] | _Flow[_V], g: nx.Graph[_V], @@ -72,7 +82,6 @@ def verify( ---------- flow : flow (required) and layer (optional) Flow to verify. - Layer is automatically computed if omitted. g : `networkx.Graph` Simple graph representing MBQC pattern. iset : `collections.abc.Set` @@ -86,12 +95,9 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = flow if isinstance(flow, tuple) else (flow, _common.infer_layers(g, flow)) vset = g.nodes codec = IndexMap(vset) g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - f_ = codec.encode_flow(f) - layer_ = codec.encode_layer(layer) - codec.ecatch(flow_bind.verify, (f_, layer_), g_, iset_, oset_) + codec.ecatch(flow_bind.verify, _codec_wrap(codec, flow), g_, iset_, oset_) diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 480373d3..928f49ad 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -72,6 +72,16 @@ def find( _Layer = Mapping[_V, int] +def _codec_wrap( + codec: IndexMap[_V], + gflow: tuple[_GFlow[_V], _Layer[_V]] | _GFlow[_V], +) -> tuple[dict[int, set[int]], list[int] | None]: + if isinstance(gflow, tuple): + f, layer = gflow + return codec.encode_gflow(f), codec.encode_layer(layer) + return codec.encode_gflow(gflow), None + + def verify( gflow: tuple[_GFlow[_V], _Layer[_V]] | _GFlow[_V], g: nx.Graph[_V], @@ -86,7 +96,6 @@ def verify( ---------- gflow : gflow (required) and layer (optional) Generalized flow to verify. - Layer is automatically computed if omitted. g : `networkx.Graph` Simple graph representing MBQC pattern. iset : `collections.abc.Set` @@ -103,7 +112,6 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - f, layer = gflow if isinstance(gflow, tuple) else (gflow, _common.infer_layers(g, gflow)) vset = g.nodes if plane is None: plane = dict.fromkeys(vset - oset, Plane.XY) @@ -112,6 +120,4 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) plane_ = codec.encode_dictkey(plane) - f_ = codec.encode_gflow(f) - layer_ = codec.encode_layer(layer) - codec.ecatch(gflow_bind.verify, (f_, layer_), g_, iset_, oset_, plane_) + codec.ecatch(gflow_bind.verify, _codec_wrap(codec, gflow), g_, iset_, oset_, plane_) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 61456ee1..0c64f83c 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -80,6 +80,16 @@ def find( _Layer = Mapping[_V, int] +def _codec_wrap( + codec: IndexMap[_V], + pflow: tuple[_PFlow[_V], _Layer[_V]] | _PFlow[_V], +) -> tuple[dict[int, set[int]], list[int] | None]: + if isinstance(pflow, tuple): + f, layer = pflow + return codec.encode_gflow(f), codec.encode_layer(layer) + return codec.encode_gflow(pflow), None + + def verify( pflow: tuple[_PFlow[_V], _Layer[_V]] | _PFlow[_V], g: nx.Graph[_V], @@ -94,7 +104,6 @@ def verify( ---------- pflow : Pauli flow (required) and layer (optional) Pauli flow to verify. - Layer is automatically computed if omitted. g : `networkx.Graph` Simple graph representing MBQC pattern. iset : `collections.abc.Set` @@ -111,11 +120,6 @@ def verify( If the graph is invalid or verification fails. """ _common.check_graph(g, iset, oset) - if isinstance(pflow, tuple): - f, layer = pflow - else: - f = pflow - layer = _common.infer_layers(g, pflow, pplane) vset = g.nodes if pplane is None: pplane = dict.fromkeys(vset - oset, PPlane.XY) @@ -124,6 +128,4 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) pplane_ = codec.encode_dictkey(pplane) - f_ = codec.encode_gflow(f) - layer_ = codec.encode_layer(layer) - codec.ecatch(pflow_bind.verify, (f_, layer_), g_, iset_, oset_, pplane_) + codec.ecatch(pflow_bind.verify, _codec_wrap(codec, pflow), g_, iset_, oset_, pplane_) diff --git a/src/flow.rs b/src/flow.rs index 8786f858..d2df0389 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -14,12 +14,23 @@ use crate::{ type Flow = hashbrown::HashMap; -/// Checks the definition of causal flow. +/// Checks the geometric constraints of flow. /// -/// 1. i -> f(i) -/// 2. j in neighbors(f(i)) => i == j or i -> j -/// 3. i in neighbors(f(i)) -fn check_definition(f: &Flow, layer: &Layer, g: &Graph) -> Result<(), FlowValidationError> { +/// - i in N(f(i)) +fn check_def_geom(f: &Flow, g: &Graph) -> Result<(), FlowValidationError> { + for (&i, &fi) in f { + if !g[i].contains(&fi) { + Err(InconsistentFlowOrder { nodes: (i, fi) })?; + } + } + Ok(()) +} + +/// Checks the layer constraints of flow. +/// +/// - i -> f(i) +/// - j in N(f(i)) => i == j or i -> j +fn check_def_layer(f: &Flow, layer: &Layer, g: &Graph) -> Result<(), FlowValidationError> { for (&i, &fi) in f { if layer[i] <= layer[fi] { Err(InconsistentFlowOrder { nodes: (i, fi) })?; @@ -29,9 +40,6 @@ fn check_definition(f: &Flow, layer: &Layer, g: &Graph) -> Result<(), FlowValida Err(InconsistentFlowOrder { nodes: (i, j) })?; } } - if !(g[fi].contains(&i) && g[i].contains(&fi)) { - Err(InconsistentFlowOrder { nodes: (i, fi) })?; - } } Ok(()) } @@ -107,7 +115,8 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { { validate::check_domain(f.iter(), &vset, &iset, &oset_orig).expect(FATAL_MSG); validate::check_initial(&layer, &oset_orig, true).expect(FATAL_MSG); - check_definition(&f, &layer, &g).expect(FATAL_MSG); + check_def_geom(&f, &g).expect(FATAL_MSG); + check_def_layer(&f, &layer, &g).expect(FATAL_MSG); } Some((f, layer)) } else { @@ -125,12 +134,15 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { #[pyfunction] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn verify(flow: (Flow, Layer), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { +pub fn verify(flow: (Flow, Option), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { let (f, layer) = flow; let n = g.len(); let vset = (0..n).collect::(); validate::check_domain(f.iter(), &vset, &iset, &oset)?; - check_definition(&f, &layer, &g)?; + check_def_geom(&f, &g)?; + if let Some(layer) = layer { + check_def_layer(&f, &layer, &g)?; + } Ok(()) } @@ -145,12 +157,12 @@ mod tests { fn test_check_definition_ng() { // Violate 0 -> f(0) = 1 assert_eq!( - check_definition(&map! { 0: 1 }, &vec![0, 0], &test_utils::graph(&[(0, 1)])), + check_def_layer(&map! { 0: 1 }, &vec![0, 0], &test_utils::graph(&[(0, 1)])), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( - check_definition( + check_def_layer( &map! { 0: 2 }, &vec![1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]) @@ -159,11 +171,7 @@ mod tests { ); // Violate 0 in nb(f(0)) = nb(2) assert_eq!( - check_definition( - &map! { 0: 2 }, - &vec![2, 1, 0], - &test_utils::graph(&[(0, 1), (1, 2)]) - ), + check_def_geom(&map! { 0: 2 }, &test_utils::graph(&[(0, 1), (1, 2)])), Err(InconsistentFlowOrder { nodes: (0, 2) }) ); } @@ -175,7 +183,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset).unwrap(); + verify((f, Some(layer)), g, iset, oset).unwrap(); } #[test_log::test] @@ -189,7 +197,7 @@ mod tests { assert_eq!(f[&2], 3); assert_eq!(f[&3], 4); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset).unwrap(); + verify((f, Some(layer)), g, iset, oset).unwrap(); } #[test_log::test] @@ -203,7 +211,7 @@ mod tests { assert_eq!(f[&2], 4); assert_eq!(f[&3], 5); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset).unwrap(); + verify((f, Some(layer)), g, iset, oset).unwrap(); } #[test_log::test] diff --git a/src/gflow.rs b/src/gflow.rs index 3aa91a69..bb2aa7f8 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -33,19 +33,12 @@ pub enum Plane { type Planes = hashbrown::HashMap; type GFlow = hashbrown::HashMap; -/// Checks the definition of gflow. +/// Checks the geometric constraints of gflow. /// -/// 1. i -> g(i) -/// 2. j in Odd(g(i)) => i == j or i -> j -/// 3. i not in g(i) and in Odd(g(i)) if plane(i) == XY -/// 4. i in g(i) and in Odd(g(i)) if plane(i) == YZ -/// 5. i in g(i) and not in Odd(g(i)) if plane(i) == XZ -fn check_definition( - f: &GFlow, - layer: &Layer, - g: &Graph, - planes: &Planes, -) -> Result<(), FlowValidationError> { +/// - XY: i not in g(i) and in Odd(g(i)) +/// - YZ: i in g(i) and in Odd(g(i)) +/// - XZ: i in g(i) and not in Odd(g(i)) +fn check_def_geom(f: &GFlow, g: &Graph, planes: &Planes) -> Result<(), FlowValidationError> { for &i in itertools::chain(f.keys(), planes.keys()) { if f.contains_key(&i) != planes.contains_key(&i) { Err(InvalidMeasurementSpec { node: i })?; @@ -53,17 +46,7 @@ fn check_definition( } for (&i, fi) in f { let pi = planes[&i]; - for &fij in fi { - if i != fij && layer[i] <= layer[fij] { - Err(InconsistentFlowOrder { nodes: (i, fij) })?; - } - } let odd_fi = utils::odd_neighbors(g, fi); - for &j in &odd_fi { - if i != j && layer[i] <= layer[j] { - Err(InconsistentFlowOrder { nodes: (i, j) })?; - } - } let in_info = (fi.contains(&i), odd_fi.contains(&i)); match pi { Plane::XY if in_info != (false, true) => { @@ -90,6 +73,27 @@ fn check_definition( Ok(()) } +/// Checks the layer constraints of gflow. +/// +/// - i -> g(i) +/// - j in Odd(g(i)) => i == j or i -> j +fn check_def_layer(f: &GFlow, layer: &Layer, g: &Graph) -> Result<(), FlowValidationError> { + for (&i, fi) in f { + for &fij in fi { + if i != fij && layer[i] <= layer[fij] { + Err(InconsistentFlowOrder { nodes: (i, fij) })?; + } + } + let odd_fi = utils::odd_neighbors(g, fi); + for &j in &odd_fi { + if i != j && layer[i] <= layer[j] { + Err(InconsistentFlowOrder { nodes: (i, j) })?; + } + } + } + Ok(()) +} + /// Initializes the working matrix. fn init_work( work: &mut [FixedBitSet], @@ -229,7 +233,8 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); validate::check_initial(&layer, &oset, true).expect(FATAL_MSG); - check_definition(&f, &layer, &g, &planes).expect(FATAL_MSG); + check_def_geom(&f, &g, &planes).expect(FATAL_MSG); + check_def_layer(&f, &layer, &g).expect(FATAL_MSG); } Some((f, layer)) } else { @@ -248,7 +253,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow #[expect(clippy::needless_pass_by_value)] #[inline] pub fn verify( - gflow: (GFlow, Layer), + gflow: (GFlow, Option), g: Graph, iset: Nodes, oset: Nodes, @@ -261,7 +266,10 @@ pub fn verify( .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; - check_definition(&f, &layer, &g, &planes)?; + check_def_geom(&f, &g, &planes)?; + if let Some(layer) = layer { + check_def_layer(&f, &layer, &g)?; + } Ok(()) } @@ -276,9 +284,8 @@ mod tests { fn test_check_definition_ng() { // Missing Plane specification assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! {}, ), @@ -286,32 +293,26 @@ mod tests { ); // Violate 0 -> f(0) = 1 assert_eq!( - check_definition( + check_def_layer( &map! { 0: set!{1} }, &vec![0, 0], &test_utils::graph(&[(0, 1)]), - &map! { 0: Plane::XY }, ), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( - check_definition( + check_def_layer( &map! { 0: set!{2}, 1: set!{2} }, &vec![1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), - &map! { - 0: Plane::XY, - 1: Plane::XY - }, ), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate XY: 0 in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{0} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: Plane::XY }, ), @@ -322,9 +323,8 @@ mod tests { ); // Violate YZ: 0 in Odd(f(0)) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: Plane::YZ }, ), @@ -335,9 +335,8 @@ mod tests { ); // Violate XZ: 0 not in Odd(f(0)) and in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{0} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: Plane::XZ }, ), @@ -348,9 +347,8 @@ mod tests { ); // Violate XZ: 0 in Odd(f(0)) and not in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: Plane::XZ }, ), @@ -369,7 +367,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, Some(layer)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -389,7 +387,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, Some(layer)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -409,7 +407,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, Some(layer)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -427,7 +425,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, Some(layer)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -447,7 +445,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, planes).unwrap(); + verify((f, Some(layer)), g, iset, oset, planes).unwrap(); } #[test_log::test] diff --git a/src/pflow.rs b/src/pflow.rs index e3be2569..00c9a9b5 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -36,13 +36,8 @@ pub enum PPlane { type PPlanes = hashbrown::HashMap; type PFlow = hashbrown::HashMap; -/// Checks the definition of Pauli flow. -fn check_definition( - f: &PFlow, - layer: &Layer, - g: &Graph, - pplanes: &PPlanes, -) -> Result<(), FlowValidationError> { +/// Checks the geometric constraints of pflow. +fn check_def_geom(f: &PFlow, g: &Graph, pplanes: &PPlanes) -> Result<(), FlowValidationError> { for &i in itertools::chain(f.keys(), pplanes.keys()) { if f.contains_key(&i) != pplanes.contains_key(&i) { Err(InvalidMeasurementSpec { node: i })?; @@ -50,33 +45,7 @@ fn check_definition( } for (&i, fi) in f { let pi = pplanes[&i]; - for &fij in fi { - match (i != fij, layer[i] <= layer[fij]) { - (true, true) if !matches!(pplanes.get(&fij), Some(&PPlane::X | &PPlane::Y)) => { - Err(InconsistentFlowOrder { nodes: (i, fij) })?; - } - (false, false) => unreachable!("layer[i] == layer[i]"), - _ => {} - } - } let odd_fi = utils::odd_neighbors(g, fi); - for &j in &odd_fi { - match (i != j, layer[i] <= layer[j]) { - (true, true) if !matches!(pplanes.get(&j), Some(&PPlane::Y | &PPlane::Z)) => { - Err(InconsistentFlowOrder { nodes: (i, j) })?; - } - (false, false) => unreachable!("layer[i] == layer[i]"), - _ => {} - } - } - for &j in fi.symmetric_difference(&odd_fi) { - if pplanes.get(&j) == Some(&PPlane::Y) && i != j && layer[i] <= layer[j] { - Err(InconsistentFlowPPlane { - node: i, - pplane: PPlane::Y, - })?; - } - } let in_info = (fi.contains(&i), odd_fi.contains(&i)); match pi { PPlane::XY if in_info != (false, true) => { @@ -121,6 +90,45 @@ fn check_definition( Ok(()) } +/// Checks the layer constraints of pflow. +fn check_def_layer( + f: &PFlow, + layer: &Layer, + g: &Graph, + pplanes: &PPlanes, +) -> Result<(), FlowValidationError> { + for (&i, fi) in f { + for &fij in fi { + match (i != fij, layer[i] <= layer[fij]) { + (true, true) if !matches!(pplanes.get(&fij), Some(&PPlane::X | &PPlane::Y)) => { + Err(InconsistentFlowOrder { nodes: (i, fij) })?; + } + (false, false) => unreachable!("layer[i] == layer[i]"), + _ => {} + } + } + let odd_fi = utils::odd_neighbors(g, fi); + for &j in &odd_fi { + match (i != j, layer[i] <= layer[j]) { + (true, true) if !matches!(pplanes.get(&j), Some(&PPlane::Y | &PPlane::Z)) => { + Err(InconsistentFlowOrder { nodes: (i, j) })?; + } + (false, false) => unreachable!("layer[i] == layer[i]"), + _ => {} + } + } + for &j in fi.symmetric_difference(&odd_fi) { + if pplanes.get(&j) == Some(&PPlane::Y) && i != j && layer[i] <= layer[j] { + Err(InconsistentFlowPPlane { + node: i, + pplane: PPlane::Y, + })?; + } + } + } + Ok(()) +} + /// Sellects nodes from `src` with `pred`. fn matching_nodes(src: &PPlanes, mut pred: impl FnMut(&PPlane) -> bool) -> Nodes { src.iter() @@ -417,7 +425,8 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); validate::check_initial(&layer, &oset, false).expect(FATAL_MSG); - check_definition(&f, &layer, &g, &pplanes).expect(FATAL_MSG); + check_def_geom(&f, &g, &pplanes).expect(FATAL_MSG); + check_def_layer(&f, &layer, &g, &pplanes).expect(FATAL_MSG); } Some((f, layer)) } else { @@ -436,7 +445,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl #[expect(clippy::needless_pass_by_value)] #[inline] pub fn verify( - pflow: (PFlow, Layer), + pflow: (PFlow, Option), g: Graph, iset: Nodes, oset: Nodes, @@ -449,7 +458,10 @@ pub fn verify( .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; - check_definition(&f, &layer, &g, &pplanes)?; + check_def_geom(&f, &g, &pplanes)?; + if let Some(layer) = layer { + check_def_layer(&f, &layer, &g, &pplanes)?; + } Ok(()) } @@ -464,9 +476,8 @@ mod tests { fn test_check_definition_ng() { // Missing Plane specification assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! {}, ), @@ -474,7 +485,7 @@ mod tests { ); // Violate 0 -> f(0) = 1 assert_eq!( - check_definition( + check_def_layer( &map! { 0: set!{1} }, &vec![0, 0], &test_utils::graph(&[(0, 1)]), @@ -484,7 +495,7 @@ mod tests { ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( - check_definition( + check_def_layer( &map! { 0: set!{2}, 1: set!{2} }, &vec![1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), @@ -497,7 +508,7 @@ mod tests { ); // Violate Y: 0 != 1 and not 0 -> 1 and 1 in f(0) ^ Odd(f(0)) assert_eq!( - check_definition( + check_def_layer( &map! { 0: set!{1}, 1: set!{2} }, &vec![1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), @@ -510,9 +521,8 @@ mod tests { ); // Violate XY: 0 in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{0} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::XY }, ), @@ -523,9 +533,8 @@ mod tests { ); // Violate YZ: 0 in Odd(f(0)) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::YZ }, ), @@ -536,9 +545,8 @@ mod tests { ); // Violate XZ: 0 not in Odd(f(0)) and in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{0} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::XZ }, ), @@ -549,9 +557,8 @@ mod tests { ); // Violate XZ: 0 in Odd(f(0)) and not in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::XZ }, ), @@ -562,9 +569,8 @@ mod tests { ); // Violate X: 0 not in Odd(f(0)) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{0} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::X }, ), @@ -575,9 +581,8 @@ mod tests { ); // Violate Z: 0 not in f(0) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::Z }, ), @@ -588,9 +593,8 @@ mod tests { ); // Violate Y: 0 in f(0) and 0 in Odd(f(0)) assert_eq!( - check_definition( + check_def_geom( &map! { 0: set!{0, 1} }, - &vec![1, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::Y }, ), @@ -609,7 +613,7 @@ mod tests { let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layer, vec![0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -629,7 +633,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -649,7 +653,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -667,7 +671,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -687,7 +691,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -717,7 +721,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([2, 4])); assert_eq!(layer, vec![1, 1, 0, 1, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -739,7 +743,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layer, vec![1, 0, 0, 1, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -759,6 +763,6 @@ mod tests { assert_eq!(f[&1], Nodes::from([1, 2])); assert_eq!(f[&2], Nodes::from([4])); assert_eq!(layer, vec![1, 1, 1, 0, 0]); - verify((f, layer), g, iset, oset, pplanes).unwrap(); + verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); } } From b048b20107553cb697b0f157f5f1b0fe823a96a7 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 01:37:56 +0900 Subject: [PATCH 33/51] :construction: Expose inference again --- python/swiflow/_common.py | 101 +---------------------------------- python/swiflow/common.py | 107 +++++++++++++++++++++++++++++++++++++- tests/test_common.py | 8 +-- tests/test_flow.py | 4 +- tests/test_gflow.py | 4 +- tests/test_pflow.py | 4 +- 6 files changed, 119 insertions(+), 109 deletions(-) diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index 1c6fc9ae..b573a973 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -2,8 +2,7 @@ from __future__ import annotations -import itertools -from collections.abc import Callable, Hashable, Iterable, Mapping, MutableSet +from collections.abc import Callable, Hashable, Iterable, Mapping from collections.abc import Set as AbstractSet from typing import Generic, TypeVar @@ -11,7 +10,6 @@ from typing_extensions import ParamSpec from swiflow._impl import FlowValidationMessage -from swiflow.common import PPlane _V = TypeVar("_V", bound=Hashable) @@ -291,100 +289,3 @@ def ecatch(self, f: Callable[_S, _T], *args: _S.args, **kwargs: _S.kwargs) -> _T return f(*args, **kwargs) except ValueError as e: raise self.decode_err(e) from None - - -def _infer_layers_impl(pred: Mapping[_V, MutableSet[_V]], succ: Mapping[_V, AbstractSet[_V]]) -> Mapping[_V, int]: - """Fix flow layers one by one depending on order constraints. - - Notes - ----- - :py:obj:`pred` is mutated in-place. - """ - work = {u for u, pu in pred.items() if not pu} - ret: dict[_V, int] = {} - for l_now in itertools.count(): - if not work: - break - next_work: set[_V] = set() - for u in work: - ret[u] = l_now - for v in succ[u]: - ent = pred[v] - ent.discard(u) - if not ent: - next_work.add(v) - work = next_work - if len(ret) != len(succ): - msg = "Failed to determine layer for all nodes." - raise ValueError(msg) - return ret - - -def _is_special( - pp: PPlane | None, - in_fu: bool, # noqa: FBT001 - in_fu_odd: bool, # noqa: FBT001 -) -> bool: - if pp == PPlane.X: - return in_fu - if pp == PPlane.Y: - return in_fu and in_fu_odd - if pp == PPlane.Z: - return in_fu_odd - return False - - -def _special_edges( - g: nx.Graph[_V], - anyflow: Mapping[_V, _V | AbstractSet[_V]], - pplane: Mapping[_V, PPlane] | None, -) -> set[tuple[_V, _V]]: - """Compute special edges that can bypass partial order constraints in Pauli flow.""" - ret: set[tuple[_V, _V]] = set() - if pplane is None: - return ret - for u, fu_ in anyflow.items(): - fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} - fu_odd = odd_neighbors(g, fu) - for v in itertools.chain(fu, fu_odd): - if u == v: - continue - if _is_special(pplane.get(v), v in fu, v in fu_odd): - ret.add((u, v)) - return ret - - -def infer_layers( - g: nx.Graph[_V], - anyflow: Mapping[_V, _V | AbstractSet[_V]], - pplane: Mapping[_V, PPlane] | None = None, -) -> Mapping[_V, int]: - """Infer layer from flow/gflow using greedy algorithm. - - Parameters - ---------- - g : `networkx.Graph` - Simple graph representing MBQC pattern. - anyflow : `tuple` of flow-like/layer - Flow to verify. Compatible with both flow and generalized flow. - pplane : `collections.abc.Mapping`, optional - Measurement plane or Pauli index. - - Notes - ----- - This function operates in Pauli flow mode only when :py:obj`pplane` is explicitly given. - """ - special = _special_edges(g, anyflow, pplane) - pred: dict[_V, set[_V]] = {u: set() for u in g.nodes} - succ: dict[_V, set[_V]] = {u: set() for u in g.nodes} - for u, fu_ in anyflow.items(): - fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} - fu_odd = odd_neighbors(g, fu) - for v in itertools.chain(fu, fu_odd): - if u == v or (u, v) in special: - continue - # Reversed - pred[u].add(v) - succ[v].add(u) - # MEMO: `pred` is invalidated by `_infer_layers_impl` - return _infer_layers_impl(pred, succ) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 29a64e2b..96c8313a 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -2,11 +2,17 @@ from __future__ import annotations -from collections.abc import Hashable -from typing import TypeVar +import itertools +from collections.abc import Hashable, Mapping, MutableSet +from collections.abc import Set as AbstractSet +from typing import TYPE_CHECKING, TypeVar +from swiflow import _common from swiflow._impl import gflow, pflow +if TYPE_CHECKING: + import networkx as nx + Plane = gflow.Plane PPlane = pflow.PPlane @@ -24,3 +30,100 @@ Layer = dict[_V, int] r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. """ + + +def _infer_layers_impl(pred: Mapping[_V, MutableSet[_V]], succ: Mapping[_V, AbstractSet[_V]]) -> Mapping[_V, int]: + """Fix flow layers one by one depending on order constraints. + + Notes + ----- + :py:obj:`pred` is mutated in-place. + """ + work = {u for u, pu in pred.items() if not pu} + ret: dict[_V, int] = {} + for l_now in itertools.count(): + if not work: + break + next_work: set[_V] = set() + for u in work: + ret[u] = l_now + for v in succ[u]: + ent = pred[v] + ent.discard(u) + if not ent: + next_work.add(v) + work = next_work + if len(ret) != len(succ): + msg = "Failed to determine layer for all nodes." + raise ValueError(msg) + return ret + + +def _is_special( + pp: PPlane | None, + in_fu: bool, # noqa: FBT001 + in_fu_odd: bool, # noqa: FBT001 +) -> bool: + if pp == PPlane.X: + return in_fu + if pp == PPlane.Y: + return in_fu and in_fu_odd + if pp == PPlane.Z: + return in_fu_odd + return False + + +def _special_edges( + g: nx.Graph[_V], + anyflow: Mapping[_V, _V | AbstractSet[_V]], + pplane: Mapping[_V, PPlane] | None, +) -> set[tuple[_V, _V]]: + """Compute special edges that can bypass partial order constraints in Pauli flow.""" + ret: set[tuple[_V, _V]] = set() + if pplane is None: + return ret + for u, fu_ in anyflow.items(): + fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} + fu_odd = _common.odd_neighbors(g, fu) + for v in itertools.chain(fu, fu_odd): + if u == v: + continue + if _is_special(pplane.get(v), v in fu, v in fu_odd): + ret.add((u, v)) + return ret + + +def infer_layers( + g: nx.Graph[_V], + anyflow: Mapping[_V, _V | AbstractSet[_V]], + pplane: Mapping[_V, PPlane] | None = None, +) -> Mapping[_V, int]: + """Infer layer from flow/gflow using greedy algorithm. + + Parameters + ---------- + g : `networkx.Graph` + Simple graph representing MBQC pattern. + anyflow : `tuple` of flow-like/layer + Flow to verify. Compatible with both flow and generalized flow. + pplane : `collections.abc.Mapping`, optional + Measurement plane or Pauli index. + + Notes + ----- + This function operates in Pauli flow mode only when :py:obj`pplane` is explicitly given. + """ + special = _special_edges(g, anyflow, pplane) + pred: dict[_V, set[_V]] = {u: set() for u in g.nodes} + succ: dict[_V, set[_V]] = {u: set() for u in g.nodes} + for u, fu_ in anyflow.items(): + fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} + fu_odd = _common.odd_neighbors(g, fu) + for v in itertools.chain(fu, fu_odd): + if u == v or (u, v) in special: + continue + # Reversed + pred[u].add(v) + succ[v].add(u) + # MEMO: `pred` is invalidated by `_infer_layers_impl` + return _infer_layers_impl(pred, succ) diff --git a/tests/test_common.py b/tests/test_common.py index b6a502cd..d7ea4069 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import _common +from swiflow import _common, common from swiflow._common import IndexMap from swiflow._impl import FlowValidationMessage from swiflow.common import Plane, PPlane @@ -130,17 +130,17 @@ class TestInferLayer: def test_line(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 3)]) flow = {0: {1}, 1: {2}, 2: {3}} - layer = _common.infer_layers(g, flow) + layer = common.infer_layers(g, flow) assert layer == {0: 3, 1: 2, 2: 1, 3: 0} def test_dag(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 2), (0, 3), (1, 2), (1, 3)]) flow = {0: {2, 3}, 1: {2, 3}} - layer = _common.infer_layers(g, flow) + layer = common.infer_layers(g, flow) assert layer == {0: 1, 1: 1, 2: 0, 3: 0} def test_cycle(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 0)]) flow = {0: {1}, 1: {2}, 2: {0}} with pytest.raises(ValueError, match=r".*determine.*"): - _common.infer_layers(g, flow) + common.infer_layers(g, flow) diff --git a/tests/test_flow.py b/tests/test_flow.py index c17b1432..8ab4b653 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -1,7 +1,7 @@ from __future__ import annotations import pytest -from swiflow import flow +from swiflow import common, flow from tests.assets import CASES, FlowTestCase @@ -20,3 +20,5 @@ def test_infer_verify(c: FlowTestCase) -> None: pytest.skip() f, _ = c.flow flow.verify(f, c.g, c.iset, c.oset) + layer = common.infer_layers(c.g, f) + flow.verify((f, layer), c.g, c.iset, c.oset) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 17e36a95..7af1cc5d 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import gflow +from swiflow import common, gflow from swiflow.common import Plane from tests.assets import CASES, FlowTestCase @@ -31,3 +31,5 @@ def test_infer_verify(c: FlowTestCase) -> None: pytest.skip() f, _ = c.gflow gflow.verify(f, c.g, c.iset, c.oset, plane=c.plane) + layer = common.infer_layers(c.g, f) + gflow.verify((f, layer), c.g, c.iset, c.oset, plane=c.plane) diff --git a/tests/test_pflow.py b/tests/test_pflow.py index 29ea9324..a945302e 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -2,7 +2,7 @@ import networkx as nx import pytest -from swiflow import pflow +from swiflow import common, pflow from swiflow.common import PPlane from tests.assets import CASES, FlowTestCase @@ -41,3 +41,5 @@ def test_infer_verify(c: FlowTestCase) -> None: pytest.skip() f, _ = c.pflow pflow.verify(f, c.g, c.iset, c.oset, pplane=c.pplane) + layer = common.infer_layers(c.g, f, pplane=c.pplane) + pflow.verify((f, layer), c.g, c.iset, c.oset, pplane=c.pplane) From 3abcaf3882bae556e82cbdded9608ac094f58d2b Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 01:49:39 +0900 Subject: [PATCH 34/51] :recycle: Improve types --- src/flow.rs | 4 ++-- src/gflow.rs | 6 +++--- src/internal/test_utils.rs | 2 +- src/internal/utils.rs | 4 ++-- src/internal/validate.rs | 4 ++-- src/pflow.rs | 18 +++++++++--------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/flow.rs b/src/flow.rs index d2df0389..c60086bf 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -17,7 +17,7 @@ type Flow = hashbrown::HashMap; /// Checks the geometric constraints of flow. /// /// - i in N(f(i)) -fn check_def_geom(f: &Flow, g: &Graph) -> Result<(), FlowValidationError> { +fn check_def_geom(f: &Flow, g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, &fi) in f { if !g[i].contains(&fi) { Err(InconsistentFlowOrder { nodes: (i, fi) })?; @@ -30,7 +30,7 @@ fn check_def_geom(f: &Flow, g: &Graph) -> Result<(), FlowValidationError> { /// /// - i -> f(i) /// - j in N(f(i)) => i == j or i -> j -fn check_def_layer(f: &Flow, layer: &Layer, g: &Graph) -> Result<(), FlowValidationError> { +fn check_def_layer(f: &Flow, layer: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, &fi) in f { if layer[i] <= layer[fi] { Err(InconsistentFlowOrder { nodes: (i, fi) })?; diff --git a/src/gflow.rs b/src/gflow.rs index bb2aa7f8..165fe77a 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -38,7 +38,7 @@ type GFlow = hashbrown::HashMap; /// - XY: i not in g(i) and in Odd(g(i)) /// - YZ: i in g(i) and in Odd(g(i)) /// - XZ: i in g(i) and not in Odd(g(i)) -fn check_def_geom(f: &GFlow, g: &Graph, planes: &Planes) -> Result<(), FlowValidationError> { +fn check_def_geom(f: &GFlow, g: &[Nodes], planes: &Planes) -> Result<(), FlowValidationError> { for &i in itertools::chain(f.keys(), planes.keys()) { if f.contains_key(&i) != planes.contains_key(&i) { Err(InvalidMeasurementSpec { node: i })?; @@ -77,7 +77,7 @@ fn check_def_geom(f: &GFlow, g: &Graph, planes: &Planes) -> Result<(), FlowValid /// /// - i -> g(i) /// - j in Odd(g(i)) => i == j or i -> j -fn check_def_layer(f: &GFlow, layer: &Layer, g: &Graph) -> Result<(), FlowValidationError> { +fn check_def_layer(f: &GFlow, layer: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, fi) in f { for &fij in fi { if i != fij && layer[i] <= layer[fij] { @@ -97,7 +97,7 @@ fn check_def_layer(f: &GFlow, layer: &Layer, g: &Graph) -> Result<(), FlowValida /// Initializes the working matrix. fn init_work( work: &mut [FixedBitSet], - g: &Graph, + g: &[Nodes], planes: &Planes, ocset: &OrderedNodes, omiset: &OrderedNodes, diff --git a/src/internal/test_utils.rs b/src/internal/test_utils.rs index 82dfcca5..af880307 100644 --- a/src/internal/test_utils.rs +++ b/src/internal/test_utils.rs @@ -174,7 +174,7 @@ mod tests { /// Checks if the graph is valid. /// /// In production code, this check should be done in the Python layer. - fn check_graph(g: &Graph, iset: &Nodes, oset: &Nodes) { + fn check_graph(g: &[Nodes], iset: &Nodes, oset: &Nodes) { let n = g.len(); assert_ne!(n, 0, "empty graph"); for (u, gu) in g.iter().enumerate() { diff --git a/src/internal/utils.rs b/src/internal/utils.rs index d80faa1b..a748e621 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -8,14 +8,14 @@ use std::collections::BTreeSet; use fixedbitset::FixedBitSet; -use crate::common::{Graph, Nodes, OrderedNodes}; +use crate::common::{Nodes, OrderedNodes}; /// Computes the odd neighbors of the nodes in `kset`. /// /// # Note /// /// - Naive implementation only for post-verification. -pub fn odd_neighbors(g: &Graph, kset: &Nodes) -> Nodes { +pub fn odd_neighbors(g: &[Nodes], kset: &Nodes) -> Nodes { assert!(kset.iter().all(|&ki| ki < g.len()), "kset out of range"); let mut work = kset.clone(); work.extend(kset.iter().flat_map(|&ki| g[ki].iter().copied())); diff --git a/src/internal/validate.rs b/src/internal/validate.rs index 1aab5bef..b1dd2e05 100644 --- a/src/internal/validate.rs +++ b/src/internal/validate.rs @@ -8,7 +8,7 @@ use crate::common::{ FlowValidationError::{ self, ExcessiveNonZeroLayer, ExcessiveZeroLayer, InvalidFlowCodomain, InvalidFlowDomain, }, - Layer, Nodes, + Nodes, }; /// Checks if the layer-zero nodes are correctly chosen. @@ -20,7 +20,7 @@ use crate::common::{ /// - `layer`: The layer. /// - `oset`: The set of output nodes. /// - `iff`: If `true`, `layer[u] == 0` "iff" `u` is in `oset`. Otherwise "if". -pub fn check_initial(layer: &Layer, oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { +pub fn check_initial(layer: &[usize], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { for (u, &lu) in layer.iter().enumerate() { match (oset.contains(&u), lu == 0) { (true, false) => { diff --git a/src/pflow.rs b/src/pflow.rs index 00c9a9b5..d8a140c4 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -37,7 +37,7 @@ type PPlanes = hashbrown::HashMap; type PFlow = hashbrown::HashMap; /// Checks the geometric constraints of pflow. -fn check_def_geom(f: &PFlow, g: &Graph, pplanes: &PPlanes) -> Result<(), FlowValidationError> { +fn check_def_geom(f: &PFlow, g: &[Nodes], pplanes: &PPlanes) -> Result<(), FlowValidationError> { for &i in itertools::chain(f.keys(), pplanes.keys()) { if f.contains_key(&i) != pplanes.contains_key(&i) { Err(InvalidMeasurementSpec { node: i })?; @@ -93,8 +93,8 @@ fn check_def_geom(f: &PFlow, g: &Graph, pplanes: &PPlanes) -> Result<(), FlowVal /// Checks the layer constraints of pflow. fn check_def_layer( f: &PFlow, - layer: &Layer, - g: &Graph, + layer: &[usize], + g: &[Nodes], pplanes: &PPlanes, ) -> Result<(), FlowValidationError> { for (&i, fi) in f { @@ -139,7 +139,7 @@ fn matching_nodes(src: &PPlanes, mut pred: impl FnMut(&PPlane) -> bool) -> Nodes /// Initializes the upper block of working storage. fn init_work_upper_co( work: &mut [FixedBitSet], - g: &Graph, + g: &[Nodes], rowset: &OrderedNodes, colset: &OrderedNodes, ) { @@ -157,7 +157,7 @@ fn init_work_upper_co( /// Initializes the lower block of working storage. fn init_work_lower_co( work: &mut [FixedBitSet], - g: &Graph, + g: &[Nodes], rowset: &OrderedNodes, colset: &OrderedNodes, ) { @@ -185,7 +185,7 @@ const BRANCH_XZ: BranchKind = 2; fn init_work_upper_rhs( work: &mut [FixedBitSet], u: usize, - g: &Graph, + g: &[Nodes], rowset: &OrderedNodes, colset: &OrderedNodes, ) { @@ -215,7 +215,7 @@ fn init_work_upper_rhs( fn init_work_lower_rhs( work: &mut [FixedBitSet], u: usize, - g: &Graph, + g: &[Nodes], rowset: &OrderedNodes, colset: &OrderedNodes, ) { @@ -239,7 +239,7 @@ fn init_work_lower_rhs( fn init_work( work: &mut [FixedBitSet], u: usize, - g: &Graph, + g: &[Nodes], rowset_upper: &OrderedNodes, rowset_lower: &OrderedNodes, colset: &OrderedNodes, @@ -273,7 +273,7 @@ fn decode_solution(u: usize, x: &FixedBitSet, colset: &Orde #[derive(Debug)] struct PFlowContext<'a> { work: &'a mut Vec, - g: &'a Graph, + g: &'a [Nodes], u: usize, rowset_upper: &'a OrderedNodes, rowset_lower: &'a OrderedNodes, From 9c6d19835e15f70324120f2ed5037dd52d8fbca9 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 02:14:34 +0900 Subject: [PATCH 35/51] :children_crossing: Normalize names --- examples/gflow.py | 2 +- examples/pflow.py | 2 +- python/swiflow/_common.py | 16 +++---- python/swiflow/_impl/gflow.pyi | 4 +- python/swiflow/_impl/pflow.pyi | 4 +- python/swiflow/common.py | 22 ++++----- python/swiflow/flow.py | 18 ++++---- python/swiflow/gflow.py | 44 +++++++++--------- python/swiflow/pflow.py | 46 +++++++++---------- src/common.rs | 2 +- src/flow.rs | 48 +++++++++---------- src/gflow.rs | 60 ++++++++++++------------ src/internal/validate.rs | 24 +++++----- src/pflow.rs | 84 +++++++++++++++++----------------- tests/assets.py | 4 +- tests/test_common.py | 10 ++-- tests/test_flow.py | 4 +- tests/test_gflow.py | 12 ++--- tests/test_pflow.py | 14 +++--- 19 files changed, 210 insertions(+), 210 deletions(-) diff --git a/examples/gflow.py b/examples/gflow.py index 9ce87f05..0fe96cb3 100644 --- a/examples/gflow.py +++ b/examples/gflow.py @@ -22,7 +22,7 @@ oset = {4, 5} planes = {0: Plane.XY, 1: Plane.XY, 2: Plane.XZ, 3: Plane.YZ} -result = gflow.find(g, iset, oset, plane=planes) +result = gflow.find(g, iset, oset, planes=planes) # Found assert result is not None diff --git a/examples/pflow.py b/examples/pflow.py index 4fcbe1d8..30bdd3d4 100644 --- a/examples/pflow.py +++ b/examples/pflow.py @@ -20,7 +20,7 @@ oset = {4} pplanes = {0: PPlane.Z, 1: PPlane.Z, 2: PPlane.Y, 3: PPlane.Y} -result = pflow.find(g, iset, oset, pplane=pplanes) +result = pflow.find(g, iset, oset, pplanes=pplanes) # Found assert result is not None diff --git a/python/swiflow/_common.py b/python/swiflow/_common.py index b573a973..1057719e 100644 --- a/python/swiflow/_common.py +++ b/python/swiflow/_common.py @@ -179,19 +179,19 @@ def encode_gflow(self, f: Mapping[_V, AbstractSet[_V]]) -> dict[int, set[int]]: """ return {self.encode(i): self.encode_set(si) for i, si in f.items()} - def encode_layer(self, layer: Mapping[_V, int]) -> list[int]: - """Encode layer. + def encode_layers(self, layers: Mapping[_V, int]) -> list[int]: + """Encode layers. Returns ------- - `layer` values transformed. + `layers` values transformed. Notes ----- `list` is used instead of `dict` here because no missing values are allowed here. """ try: - return [layer[v] for v in self.__i2v] + return [layers[v] for v in self.__i2v] except KeyError: msg = "Layers must be specified for all nodes." raise ValueError(msg) from None @@ -237,18 +237,18 @@ def decode_gflow(self, f_: Mapping[int, AbstractSet[int]]) -> dict[_V, set[_V]]: """ return {self.decode(i): self.decode_set(si) for i, si in f_.items()} - def decode_layer(self, layer_: Iterable[int]) -> dict[_V, int]: - """Decode MBQC layer. + def decode_layers(self, layers_: Iterable[int]) -> dict[_V, int]: + """Decode MBQC layers. Returns ------- - `layer_` transformed. + `layers_` transformed. Notes ----- `list` (generalized as `Iterable`) is used instead of `dict` here because no missing values are allowed here. """ - return {self.decode(i): li for i, li in enumerate(layer_)} + return {self.decode(i): li for i, li in enumerate(layers_)} def decode_err(self, err: ValueError) -> ValueError: """Decode the error message stored in the first ctor argument of ValueError.""" diff --git a/python/swiflow/_impl/gflow.pyi b/python/swiflow/_impl/gflow.pyi index c05bb2cb..b24b460e 100644 --- a/python/swiflow/_impl/gflow.pyi +++ b/python/swiflow/_impl/gflow.pyi @@ -4,12 +4,12 @@ class Plane: XZ: Plane def find( - g: list[set[int]], iset: set[int], oset: set[int], plane: dict[int, Plane] + g: list[set[int]], iset: set[int], oset: set[int], planes: dict[int, Plane] ) -> tuple[dict[int, set[int]], list[int]] | None: ... def verify( gflow: tuple[dict[int, set[int]], list[int] | None], g: list[set[int]], iset: set[int], oset: set[int], - plane: dict[int, Plane], + planes: dict[int, Plane], ) -> None: ... diff --git a/python/swiflow/_impl/pflow.pyi b/python/swiflow/_impl/pflow.pyi index 17eeb207..324d6f30 100644 --- a/python/swiflow/_impl/pflow.pyi +++ b/python/swiflow/_impl/pflow.pyi @@ -7,12 +7,12 @@ class PPlane: Z: PPlane def find( - g: list[set[int]], iset: set[int], oset: set[int], pplane: dict[int, PPlane] + g: list[set[int]], iset: set[int], oset: set[int], pplanes: dict[int, PPlane] ) -> tuple[dict[int, set[int]], list[int]] | None: ... def verify( pflow: tuple[dict[int, set[int]], list[int] | None], g: list[set[int]], iset: set[int], oset: set[int], - pplane: dict[int, PPlane], + pplanes: dict[int, PPlane], ) -> None: ... diff --git a/python/swiflow/common.py b/python/swiflow/common.py index 96c8313a..ba20d1d2 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -27,8 +27,8 @@ PFlow = dict[_V, set[_V]] """Pauli flow map as a dictionary. :math:`f(u)` is stored in :py:obj:`f[u]`.""" -Layer = dict[_V, int] -r"""Layer of each node representing the partial order. :math:`layer(u) > layer(v)` implies :math:`u \prec v`. +Layers = dict[_V, int] +r"""Layer of each node representing the partial order. :math:`layers(u) > layers(v)` implies :math:`u \prec v`. """ @@ -54,7 +54,7 @@ def _infer_layers_impl(pred: Mapping[_V, MutableSet[_V]], succ: Mapping[_V, Abst next_work.add(v) work = next_work if len(ret) != len(succ): - msg = "Failed to determine layer for all nodes." + msg = "Failed to determine layers for all nodes." raise ValueError(msg) return ret @@ -76,11 +76,11 @@ def _is_special( def _special_edges( g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]], - pplane: Mapping[_V, PPlane] | None, + pplanes: Mapping[_V, PPlane] | None, ) -> set[tuple[_V, _V]]: """Compute special edges that can bypass partial order constraints in Pauli flow.""" ret: set[tuple[_V, _V]] = set() - if pplane is None: + if pplanes is None: return ret for u, fu_ in anyflow.items(): fu = fu_ if isinstance(fu_, AbstractSet) else {fu_} @@ -88,7 +88,7 @@ def _special_edges( for v in itertools.chain(fu, fu_odd): if u == v: continue - if _is_special(pplane.get(v), v in fu, v in fu_odd): + if _is_special(pplanes.get(v), v in fu, v in fu_odd): ret.add((u, v)) return ret @@ -96,9 +96,9 @@ def _special_edges( def infer_layers( g: nx.Graph[_V], anyflow: Mapping[_V, _V | AbstractSet[_V]], - pplane: Mapping[_V, PPlane] | None = None, + pplanes: Mapping[_V, PPlane] | None = None, ) -> Mapping[_V, int]: - """Infer layer from flow/gflow using greedy algorithm. + """Infer layers from flow/gflow using greedy algorithm. Parameters ---------- @@ -106,14 +106,14 @@ def infer_layers( Simple graph representing MBQC pattern. anyflow : `tuple` of flow-like/layer Flow to verify. Compatible with both flow and generalized flow. - pplane : `collections.abc.Mapping`, optional + pplanes : `collections.abc.Mapping`, optional Measurement plane or Pauli index. Notes ----- - This function operates in Pauli flow mode only when :py:obj`pplane` is explicitly given. + This function operates in Pauli flow mode only when :py:obj`pplanes` is explicitly given. """ - special = _special_edges(g, anyflow, pplane) + special = _special_edges(g, anyflow, pplanes) pred: dict[_V, set[_V]] = {u: set() for u in g.nodes} succ: dict[_V, set[_V]] = {u: set() for u in g.nodes} for u, fu_ in anyflow.items(): diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index 83fbfd3e..ba5300b6 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -12,7 +12,7 @@ from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import flow as flow_bind -from swiflow.common import Flow, Layer +from swiflow.common import Flow, Layers if TYPE_CHECKING: from collections.abc import Set as AbstractSet @@ -20,7 +20,7 @@ import networkx as nx _V = TypeVar("_V", bound=Hashable) -FlowResult = tuple[Flow[_V], Layer[_V]] +FlowResult = tuple[Flow[_V], Layers[_V]] def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowResult[_V] | None: @@ -39,7 +39,7 @@ def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowR Returns ------- - `tuple` of flow/layer or `None` + `tuple` of flow/layers or `None` Return the flow if any, otherwise `None`. """ _common.check_graph(g, iset, oset) @@ -49,10 +49,10 @@ def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowR iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) if ret_ := flow_bind.find(g_, iset_, oset_): - f_, layer_ = ret_ + f_, layers_ = ret_ f = codec.decode_flow(f_) - layer = codec.decode_layer(layer_) - return f, layer + layers = codec.decode_layers(layers_) + return f, layers return None @@ -65,8 +65,8 @@ def _codec_wrap( flow: tuple[_Flow[_V], _Layer[_V]] | _Flow[_V], ) -> tuple[dict[int, int], list[int] | None]: if isinstance(flow, tuple): - f, layer = flow - return codec.encode_flow(f), codec.encode_layer(layer) + f, layers = flow + return codec.encode_flow(f), codec.encode_layers(layers) return codec.encode_flow(flow), None @@ -80,7 +80,7 @@ def verify( Parameters ---------- - flow : flow (required) and layer (optional) + flow : flow (required) and layers (optional) Flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 928f49ad..6fa1bfdb 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -13,13 +13,13 @@ from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import gflow as gflow_bind -from swiflow.common import GFlow, Layer, Plane +from swiflow.common import GFlow, Layers, Plane if TYPE_CHECKING: import networkx as nx _V = TypeVar("_V", bound=Hashable) -GFlowResult = tuple[GFlow[_V], Layer[_V]] +GFlowResult = tuple[GFlow[_V], Layers[_V]] def find( @@ -27,7 +27,7 @@ def find( iset: AbstractSet[_V], oset: AbstractSet[_V], *, - plane: Mapping[_V, Plane] | None = None, + planes: Mapping[_V, Plane] | None = None, ) -> GFlowResult[_V] | None: r"""Compute generalized flow. @@ -41,30 +41,30 @@ def find( Input nodes. oset : `collections.abc.Set` Output nodes. - plane : `collections.abc.Mapping` + planes : `collections.abc.Mapping` Measurement plane for each node in :math:`V \setminus O`. Defaults to `Plane.XY`. Returns ------- - `tuple` of gflow/layer or `None` + `tuple` of gflow/layers or `None` Return the gflow if any, otherwise `None`. """ _common.check_graph(g, iset, oset) vset = g.nodes - if plane is None: - plane = dict.fromkeys(vset - oset, Plane.XY) - _common.check_planelike(vset, oset, plane) + if planes is None: + planes = dict.fromkeys(vset - oset, Plane.XY) + _common.check_planelike(vset, oset, planes) codec = IndexMap(vset) g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - plane_ = codec.encode_dictkey(plane) - if ret_ := gflow_bind.find(g_, iset_, oset_, plane_): - f_, layer_ = ret_ + planes_ = codec.encode_dictkey(planes) + if ret_ := gflow_bind.find(g_, iset_, oset_, planes_): + f_, layers_ = ret_ f = codec.decode_gflow(f_) - layer = codec.decode_layer(layer_) - return f, layer + layers = codec.decode_layers(layers_) + return f, layers return None @@ -77,8 +77,8 @@ def _codec_wrap( gflow: tuple[_GFlow[_V], _Layer[_V]] | _GFlow[_V], ) -> tuple[dict[int, set[int]], list[int] | None]: if isinstance(gflow, tuple): - f, layer = gflow - return codec.encode_gflow(f), codec.encode_layer(layer) + f, layers = gflow + return codec.encode_gflow(f), codec.encode_layers(layers) return codec.encode_gflow(gflow), None @@ -88,13 +88,13 @@ def verify( iset: AbstractSet[_V], oset: AbstractSet[_V], *, - plane: Mapping[_V, Plane] | None = None, + planes: Mapping[_V, Plane] | None = None, ) -> None: r"""Verify generalized flow. Parameters ---------- - gflow : gflow (required) and layer (optional) + gflow : gflow (required) and layers (optional) Generalized flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. @@ -102,7 +102,7 @@ def verify( Input nodes. oset : `collections.abc.Set` Output nodes. - plane : `collections.abc.Mapping` + planes : `collections.abc.Mapping` Measurement plane for each node in :math:`V \setminus O`. Defaults to `Plane.XY`. @@ -113,11 +113,11 @@ def verify( """ _common.check_graph(g, iset, oset) vset = g.nodes - if plane is None: - plane = dict.fromkeys(vset - oset, Plane.XY) + if planes is None: + planes = dict.fromkeys(vset - oset, Plane.XY) codec = IndexMap(vset) g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - plane_ = codec.encode_dictkey(plane) - codec.ecatch(gflow_bind.verify, _codec_wrap(codec, gflow), g_, iset_, oset_, plane_) + planes_ = codec.encode_dictkey(planes) + codec.ecatch(gflow_bind.verify, _codec_wrap(codec, gflow), g_, iset_, oset_, planes_) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 0c64f83c..fb32f65d 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -14,13 +14,13 @@ from swiflow import _common from swiflow._common import IndexMap from swiflow._impl import pflow as pflow_bind -from swiflow.common import Layer, PFlow, PPlane +from swiflow.common import Layers, PFlow, PPlane if TYPE_CHECKING: import networkx as nx _V = TypeVar("_V", bound=Hashable) -PFlowResult = tuple[PFlow[_V], Layer[_V]] +PFlowResult = tuple[PFlow[_V], Layers[_V]] def find( @@ -28,7 +28,7 @@ def find( iset: AbstractSet[_V], oset: AbstractSet[_V], *, - pplane: Mapping[_V, PPlane] | None = None, + pplanes: Mapping[_V, PPlane] | None = None, ) -> PFlowResult[_V] | None: r"""Compute Pauli flow. @@ -42,13 +42,13 @@ def find( Input nodes. oset : `collections.abc.Set` Output nodes. - pplane : `collections.abc.Mapping` + pplanes : `collections.abc.Mapping` Measurement plane or Pauli index for each node in :math:`V \setminus O`. Defaults to `PPlane.XY`. Returns ------- - `tuple` of Pauli flow/layer or `None` + `tuple` of Pauli flow/layers or `None` Return the Pauli flow if any, otherwise `None`. Notes @@ -57,22 +57,22 @@ def find( """ _common.check_graph(g, iset, oset) vset = g.nodes - if pplane is None: - pplane = dict.fromkeys(vset - oset, PPlane.XY) - _common.check_planelike(vset, oset, pplane) - if all(pp not in {PPlane.X, PPlane.Y, PPlane.Z} for pp in pplane.values()): + if pplanes is None: + pplanes = dict.fromkeys(vset - oset, PPlane.XY) + _common.check_planelike(vset, oset, pplanes) + if all(pp not in {PPlane.X, PPlane.Y, PPlane.Z} for pp in pplanes.values()): msg = "No Pauli measurement found. Use gflow.find instead." warnings.warn(msg, stacklevel=1) codec = IndexMap(vset) g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - pplane_ = codec.encode_dictkey(pplane) - if ret_ := pflow_bind.find(g_, iset_, oset_, pplane_): - f_, layer_ = ret_ + pplanes_ = codec.encode_dictkey(pplanes) + if ret_ := pflow_bind.find(g_, iset_, oset_, pplanes_): + f_, layers_ = ret_ f = codec.decode_gflow(f_) - layer = codec.decode_layer(layer_) - return f, layer + layers = codec.decode_layers(layers_) + return f, layers return None @@ -85,8 +85,8 @@ def _codec_wrap( pflow: tuple[_PFlow[_V], _Layer[_V]] | _PFlow[_V], ) -> tuple[dict[int, set[int]], list[int] | None]: if isinstance(pflow, tuple): - f, layer = pflow - return codec.encode_gflow(f), codec.encode_layer(layer) + f, layers = pflow + return codec.encode_gflow(f), codec.encode_layers(layers) return codec.encode_gflow(pflow), None @@ -96,13 +96,13 @@ def verify( iset: AbstractSet[_V], oset: AbstractSet[_V], *, - pplane: Mapping[_V, PPlane] | None = None, + pplanes: Mapping[_V, PPlane] | None = None, ) -> None: r"""Verify Pauli flow. Parameters ---------- - pflow : Pauli flow (required) and layer (optional) + pflow : Pauli flow (required) and layers (optional) Pauli flow to verify. g : `networkx.Graph` Simple graph representing MBQC pattern. @@ -110,7 +110,7 @@ def verify( Input nodes. oset : `collections.abc.Set` Output nodes. - pplane : `collections.abc.Mapping` + pplanes : `collections.abc.Mapping` Measurement plane or Pauli index for each node in :math:`V \setminus O`. Defaults to `PPlane.XY`. @@ -121,11 +121,11 @@ def verify( """ _common.check_graph(g, iset, oset) vset = g.nodes - if pplane is None: - pplane = dict.fromkeys(vset - oset, PPlane.XY) + if pplanes is None: + pplanes = dict.fromkeys(vset - oset, PPlane.XY) codec = IndexMap(vset) g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - pplane_ = codec.encode_dictkey(pplane) - codec.ecatch(pflow_bind.verify, _codec_wrap(codec, pflow), g_, iset_, oset_, pplane_) + pplanes_ = codec.encode_dictkey(pplanes) + codec.ecatch(pflow_bind.verify, _codec_wrap(codec, pflow), g_, iset_, oset_, pplanes_) diff --git a/src/common.rs b/src/common.rs index 43c84c21..e86d3604 100644 --- a/src/common.rs +++ b/src/common.rs @@ -12,7 +12,7 @@ pub type Nodes = hashbrown::HashSet; /// Simple graph encoded as list of neighbors. pub type Graph = Vec; /// Layer representation of the flow partial order. -pub type Layer = Vec; +pub type Layers = Vec; /// Ordered set of nodes. /// diff --git a/src/flow.rs b/src/flow.rs index c60086bf..a2f0591e 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -7,7 +7,7 @@ use crate::{ common::{ FATAL_MSG, FlowValidationError::{self, InconsistentFlowOrder}, - Graph, Layer, Nodes, + Graph, Layers, Nodes, }, internal::{utils::InPlaceSetDiff, validate}, }; @@ -30,13 +30,13 @@ fn check_def_geom(f: &Flow, g: &[Nodes]) -> Result<(), FlowValidationError> { /// /// - i -> f(i) /// - j in N(f(i)) => i == j or i -> j -fn check_def_layer(f: &Flow, layer: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { +fn check_def_layer(f: &Flow, layers: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, &fi) in f { - if layer[i] <= layer[fi] { + if layers[i] <= layers[fi] { Err(InconsistentFlowOrder { nodes: (i, fi) })?; } for &j in &g[fi] { - if i != j && layer[i] <= layer[j] { + if i != j && layers[i] <= layers[j] { Err(InconsistentFlowOrder { nodes: (i, j) })?; } } @@ -64,7 +64,7 @@ fn check_def_layer(f: &Flow, layer: &[usize], g: &[Nodes]) -> Result<(), FlowVal #[tracing::instrument] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { +pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layers)> { let n = g.len(); let vset = (0..n).collect::(); let mut cset = &oset - &iset; @@ -72,7 +72,7 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { let ocset = &vset - &oset; let oset_orig = oset.clone(); let mut f = Flow::with_capacity(ocset.len()); - let mut layer = vec![0_usize; n]; + let mut layers = vec![0_usize; n]; // check[v] = g[v] & (vset - oset) let mut check = g.iter().map(|x| x & &ocset).collect::>(); let mut oset_work = Nodes::new(); @@ -90,7 +90,7 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { tracing::debug!("f({u}) = {v}"); f.insert(u, v); tracing::debug!("layer({u}) = {l}"); - layer[u] = l; + layers[u] = l; oset_work.insert(u); cset_work.insert(v); } @@ -110,15 +110,15 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { if oset == vset { tracing::debug!("flow found"); tracing::debug!("flow : {f:?}"); - tracing::debug!("layer: {layer:?}"); + tracing::debug!("layers: {layers:?}"); // TODO: Remove this block once stabilized { validate::check_domain(f.iter(), &vset, &iset, &oset_orig).expect(FATAL_MSG); - validate::check_initial(&layer, &oset_orig, true).expect(FATAL_MSG); + validate::check_initial(&layers, &oset_orig, true).expect(FATAL_MSG); check_def_geom(&f, &g).expect(FATAL_MSG); - check_def_layer(&f, &layer, &g).expect(FATAL_MSG); + check_def_layer(&f, &layers, &g).expect(FATAL_MSG); } - Some((f, layer)) + Some((f, layers)) } else { tracing::debug!("flow not found"); None @@ -134,14 +134,14 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layer)> { #[pyfunction] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn verify(flow: (Flow, Option), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { - let (f, layer) = flow; +pub fn verify(flow: (Flow, Option), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { + let (f, layers) = flow; let n = g.len(); let vset = (0..n).collect::(); validate::check_domain(f.iter(), &vset, &iset, &oset)?; check_def_geom(&f, &g)?; - if let Some(layer) = layer { - check_def_layer(&f, &layer, &g)?; + if let Some(layers) = layers { + check_def_layer(&f, &layers, &g)?; } Ok(()) } @@ -180,38 +180,38 @@ mod tests { fn test_find_case0() { let TestCase { g, iset, oset } = test_utils::CASE0.clone(); let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); - assert_eq!(layer, vec![0, 0]); - verify((f, Some(layer)), g, iset, oset).unwrap(); + assert_eq!(layers, vec![0, 0]); + verify((f, Some(layers)), g, iset, oset).unwrap(); } #[test_log::test] fn test_find_case1() { let TestCase { g, iset, oset } = test_utils::CASE1.clone(); let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], 1); assert_eq!(f[&1], 2); assert_eq!(f[&2], 3); assert_eq!(f[&3], 4); - assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, Some(layer)), g, iset, oset).unwrap(); + assert_eq!(layers, vec![4, 3, 2, 1, 0]); + verify((f, Some(layers)), g, iset, oset).unwrap(); } #[test_log::test] fn test_find_case2() { let TestCase { g, iset, oset } = test_utils::CASE2.clone(); let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], 2); assert_eq!(f[&1], 3); assert_eq!(f[&2], 4); assert_eq!(f[&3], 5); - assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layer)), g, iset, oset).unwrap(); + assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); + verify((f, Some(layers)), g, iset, oset).unwrap(); } #[test_log::test] diff --git a/src/gflow.rs b/src/gflow.rs index 165fe77a..0538bb25 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -12,7 +12,7 @@ use crate::{ FlowValidationError::{ self, InconsistentFlowOrder, InconsistentFlowPlane, InvalidMeasurementSpec, }, - Graph, Layer, Nodes, OrderedNodes, + Graph, Layers, Nodes, OrderedNodes, }, internal::{ gf2_linalg::GF2Solver, @@ -77,16 +77,16 @@ fn check_def_geom(f: &GFlow, g: &[Nodes], planes: &Planes) -> Result<(), FlowVal /// /// - i -> g(i) /// - j in Odd(g(i)) => i == j or i -> j -fn check_def_layer(f: &GFlow, layer: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { +fn check_def_layer(f: &GFlow, layers: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, fi) in f { for &fij in fi { - if i != fij && layer[i] <= layer[fij] { + if i != fij && layers[i] <= layers[fij] { Err(InconsistentFlowOrder { nodes: (i, fij) })?; } } let odd_fi = utils::odd_neighbors(g, fi); for &j in &odd_fi { - if i != j && layer[i] <= layer[j] { + if i != j && layers[i] <= layers[j] { Err(InconsistentFlowOrder { nodes: (i, j) })?; } } @@ -156,7 +156,7 @@ fn init_work( #[tracing::instrument] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow, Layer)> { +pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow, Layers)> { let n = g.len(); let vset = (0..n).collect::(); let mut cset = Nodes::new(); @@ -164,7 +164,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow let mut ocset = vset.difference(&oset).copied().collect::(); let mut omiset = oset.difference(&iset).copied().collect::(); let mut f = GFlow::with_capacity(ocset.len()); - let mut layer = vec![0_usize; n]; + let mut layers = vec![0_usize; n]; let mut nrows = ocset.len(); let mut ncols = omiset.len(); let mut neqs = ocset.len(); @@ -214,7 +214,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow tracing::debug!("f({u}) = {fu:?}"); f.insert(u, fu); tracing::debug!("layer({u}) = {l}"); - layer[u] = l; + layers[u] = l; } if cset.is_empty() { break; @@ -225,18 +225,18 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow if ocset.is_empty() { tracing::debug!("gflow found"); tracing::debug!("gflow: {f:?}"); - tracing::debug!("layer: {layer:?}"); + tracing::debug!("layers: {layers:?}"); // TODO: Remove this block once stabilized { let f_flatiter = f .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); - validate::check_initial(&layer, &oset, true).expect(FATAL_MSG); + validate::check_initial(&layers, &oset, true).expect(FATAL_MSG); check_def_geom(&f, &g, &planes).expect(FATAL_MSG); - check_def_layer(&f, &layer, &g).expect(FATAL_MSG); + check_def_layer(&f, &layers, &g).expect(FATAL_MSG); } - Some((f, layer)) + Some((f, layers)) } else { tracing::debug!("gflow not found"); None @@ -253,13 +253,13 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow #[expect(clippy::needless_pass_by_value)] #[inline] pub fn verify( - gflow: (GFlow, Option), + gflow: (GFlow, Option), g: Graph, iset: Nodes, oset: Nodes, planes: Planes, ) -> PyResult<()> { - let (f, layer) = gflow; + let (f, layers) = gflow; let n = g.len(); let vset = (0..n).collect::(); let f_flatiter = f @@ -267,8 +267,8 @@ pub fn verify( .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; check_def_geom(&f, &g, &planes)?; - if let Some(layer) = layer { - check_def_layer(&f, &layer, &g)?; + if let Some(layers) = layers { + check_def_layer(&f, &layers, &g)?; } Ok(()) } @@ -364,10 +364,10 @@ mod tests { let TestCase { g, iset, oset } = test_utils::CASE0.clone(); let planes = map! {}; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); - assert_eq!(layer, vec![0, 0]); - verify((f, Some(layer)), g, iset, oset, planes).unwrap(); + assert_eq!(layers, vec![0, 0]); + verify((f, Some(layers)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -380,14 +380,14 @@ mod tests { 3: Plane::XY }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([1])); assert_eq!(f[&1], Nodes::from([2])); assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); - assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, Some(layer)), g, iset, oset, planes).unwrap(); + assert_eq!(layers, vec![4, 3, 2, 1, 0]); + verify((f, Some(layers)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -400,14 +400,14 @@ mod tests { 3: Plane::XY }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([2])); assert_eq!(f[&1], Nodes::from([3])); assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); - assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layer)), g, iset, oset, planes).unwrap(); + assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); + verify((f, Some(layers)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -419,13 +419,13 @@ mod tests { 2: Plane::XY }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([4, 5])); assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); - assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, Some(layer)), g, iset, oset, planes).unwrap(); + assert_eq!(layers, vec![1, 1, 1, 0, 0, 0]); + verify((f, Some(layers)), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -438,14 +438,14 @@ mod tests { 3: Plane::YZ }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([2])); assert_eq!(f[&1], Nodes::from([5])); assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); - assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layer)), g, iset, oset, planes).unwrap(); + assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); + verify((f, Some(layers)), g, iset, oset, planes).unwrap(); } #[test_log::test] diff --git a/src/internal/validate.rs b/src/internal/validate.rs index b1dd2e05..510230c2 100644 --- a/src/internal/validate.rs +++ b/src/internal/validate.rs @@ -17,11 +17,11 @@ use crate::common::{ /// /// # Arguments /// -/// - `layer`: The layer. +/// - `layers`: The layer. /// - `oset`: The set of output nodes. -/// - `iff`: If `true`, `layer[u] == 0` "iff" `u` is in `oset`. Otherwise "if". -pub fn check_initial(layer: &[usize], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { - for (u, &lu) in layer.iter().enumerate() { +/// - `iff`: If `true`, `layers[u] == 0` "iff" `u` is in `oset`. Otherwise "if". +pub fn check_initial(layers: &[usize], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { + for (u, &lu) in layers.iter().enumerate() { match (oset.contains(&u), lu == 0) { (true, false) => { Err(ExcessiveNonZeroLayer { node: u, layer: lu })?; @@ -77,30 +77,30 @@ mod tests { #[test] fn test_check_initial() { - let layer = vec![0, 0, 0, 1, 1, 1]; + let layers = vec![0, 0, 0, 1, 1, 1]; let oset = Nodes::from([0, 1]); - check_initial(&layer, &oset, false).unwrap(); + check_initial(&layers, &oset, false).unwrap(); } #[test] fn test_check_initial_ng() { - let layer = vec![0, 0, 0, 1, 1, 1]; + let layers = vec![0, 0, 0, 1, 1, 1]; let oset = Nodes::from([0, 1, 2, 3]); - assert!(check_initial(&layer, &oset, false).is_err()); + assert!(check_initial(&layers, &oset, false).is_err()); } #[test] fn test_check_initial_iff() { - let layer = vec![0, 0, 0, 1, 1, 1]; + let layers = vec![0, 0, 0, 1, 1, 1]; let oset = Nodes::from([0, 1, 2]); - check_initial(&layer, &oset, true).unwrap(); + check_initial(&layers, &oset, true).unwrap(); } #[test] fn test_check_initial_iff_ng() { - let layer = vec![0, 0, 0, 1, 1, 1]; + let layers = vec![0, 0, 0, 1, 1, 1]; let oset = Nodes::from([0, 1]); - assert!(check_initial(&layer, &oset, true).is_err()); + assert!(check_initial(&layers, &oset, true).is_err()); } #[test] diff --git a/src/pflow.rs b/src/pflow.rs index d8a140c4..fb622171 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -12,7 +12,7 @@ use crate::{ FlowValidationError::{ self, InconsistentFlowOrder, InconsistentFlowPPlane, InvalidMeasurementSpec, }, - Graph, Layer, Nodes, OrderedNodes, + Graph, Layers, Nodes, OrderedNodes, }, internal::{ gf2_linalg::GF2Solver, @@ -93,32 +93,32 @@ fn check_def_geom(f: &PFlow, g: &[Nodes], pplanes: &PPlanes) -> Result<(), FlowV /// Checks the layer constraints of pflow. fn check_def_layer( f: &PFlow, - layer: &[usize], + layers: &[usize], g: &[Nodes], pplanes: &PPlanes, ) -> Result<(), FlowValidationError> { for (&i, fi) in f { for &fij in fi { - match (i != fij, layer[i] <= layer[fij]) { + match (i != fij, layers[i] <= layers[fij]) { (true, true) if !matches!(pplanes.get(&fij), Some(&PPlane::X | &PPlane::Y)) => { Err(InconsistentFlowOrder { nodes: (i, fij) })?; } - (false, false) => unreachable!("layer[i] == layer[i]"), + (false, false) => unreachable!("layers[i] == layers[i]"), _ => {} } } let odd_fi = utils::odd_neighbors(g, fi); for &j in &odd_fi { - match (i != j, layer[i] <= layer[j]) { + match (i != j, layers[i] <= layers[j]) { (true, true) if !matches!(pplanes.get(&j), Some(&PPlane::Y | &PPlane::Z)) => { Err(InconsistentFlowOrder { nodes: (i, j) })?; } - (false, false) => unreachable!("layer[i] == layer[i]"), + (false, false) => unreachable!("layers[i] == layers[i]"), _ => {} } } for &j in fi.symmetric_difference(&odd_fi) { - if pplanes.get(&j) == Some(&PPlane::Y) && i != j && layer[i] <= layer[j] { + if pplanes.get(&j) == Some(&PPlane::Y) && i != j && layers[i] <= layers[j] { Err(InconsistentFlowPPlane { node: i, pplane: PPlane::Y, @@ -333,7 +333,7 @@ fn find_impl(ctx: &mut PFlowContext<'_>) -> bool { #[tracing::instrument] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFlow, Layer)> { +pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFlow, Layers)> { let yset = matching_nodes(&pplanes, |pp| matches!(pp, PPlane::Y)); let xyset = matching_nodes(&pplanes, |pp| matches!(pp, PPlane::X | PPlane::Y)); let yzset = matching_nodes(&pplanes, |pp| matches!(pp, PPlane::Y | PPlane::Z)); @@ -345,7 +345,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl let mut rowset_lower = yset.iter().copied().collect::(); let mut colset = xyset.difference(&iset).copied().collect::(); let mut f = PFlow::with_capacity(ocset.len()); - let mut layer = vec![0_usize; n]; + let mut layers = vec![0_usize; n]; let mut work = vec![FixedBitSet::new(); rowset_upper.len() + rowset_lower.len()]; for l in 0_usize.. { tracing::debug!("=====layer {l}====="); @@ -396,7 +396,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl if done { tracing::debug!("f({}) = {:?}", u, &f[&u]); tracing::debug!("layer({u}) = {l}"); - layer[u] = l; + layers[u] = l; cset.insert(u); } else { tracing::debug!("solution not found: {u} (all branches)"); @@ -417,18 +417,18 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl if ocset.is_empty() { tracing::debug!("pflow found"); tracing::debug!("pflow: {f:?}"); - tracing::debug!("layer: {layer:?}"); + tracing::debug!("layers: {layers:?}"); // TODO: Remove this block once stabilized { let f_flatiter = f .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); - validate::check_initial(&layer, &oset, false).expect(FATAL_MSG); + validate::check_initial(&layers, &oset, false).expect(FATAL_MSG); check_def_geom(&f, &g, &pplanes).expect(FATAL_MSG); - check_def_layer(&f, &layer, &g, &pplanes).expect(FATAL_MSG); + check_def_layer(&f, &layers, &g, &pplanes).expect(FATAL_MSG); } - Some((f, layer)) + Some((f, layers)) } else { tracing::debug!("pflow not found"); None @@ -445,13 +445,13 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl #[expect(clippy::needless_pass_by_value)] #[inline] pub fn verify( - pflow: (PFlow, Option), + pflow: (PFlow, Option), g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes, ) -> PyResult<()> { - let (f, layer) = pflow; + let (f, layers) = pflow; let n = g.len(); let vset = (0..n).collect::(); let f_flatiter = f @@ -459,8 +459,8 @@ pub fn verify( .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); validate::check_domain(f_flatiter, &vset, &iset, &oset)?; check_def_geom(&f, &g, &pplanes)?; - if let Some(layer) = layer { - check_def_layer(&f, &layer, &g, &pplanes)?; + if let Some(layers) = layers { + check_def_layer(&f, &layers, &g, &pplanes)?; } Ok(()) } @@ -610,10 +610,10 @@ mod tests { let TestCase { g, iset, oset } = test_utils::CASE0.clone(); let pplanes = map! {}; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); - assert_eq!(layer, vec![0, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![0, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -626,14 +626,14 @@ mod tests { 3: PPlane::XY }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([1])); assert_eq!(f[&1], Nodes::from([2])); assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); - assert_eq!(layer, vec![4, 3, 2, 1, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![4, 3, 2, 1, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -646,14 +646,14 @@ mod tests { 3: PPlane::XY }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([2])); assert_eq!(f[&1], Nodes::from([3])); assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); - assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -665,13 +665,13 @@ mod tests { 2: PPlane::XY }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([4, 5])); assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); - assert_eq!(layer, vec![1, 1, 1, 0, 0, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![1, 1, 1, 0, 0, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -684,14 +684,14 @@ mod tests { 3: PPlane::YZ }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([2])); assert_eq!(f[&1], Nodes::from([5])); assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); - assert_eq!(layer, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -714,14 +714,14 @@ mod tests { 3: PPlane::X }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(f[&0], Nodes::from([1])); assert_eq!(f[&1], Nodes::from([4])); assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([2, 4])); - assert_eq!(layer, vec![1, 1, 0, 1, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![1, 1, 0, 1, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -734,7 +734,7 @@ mod tests { 3: PPlane::Y }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); // Graphix // assert_eq!(f[&0], Nodes::from([0, 1])); @@ -742,8 +742,8 @@ mod tests { assert_eq!(f[&1], Nodes::from([1])); assert_eq!(f[&2], Nodes::from([2])); assert_eq!(f[&3], Nodes::from([4])); - assert_eq!(layer, vec![1, 0, 0, 1, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![1, 0, 0, 1, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -755,14 +755,14 @@ mod tests { 2: PPlane::Y }; let flen = g.len() - oset.len(); - let (f, layer) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); + let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); // Graphix // assert_eq!(f[&0], Nodes::from([0, 3, 4])); assert_eq!(f[&0], Nodes::from([0, 2, 4])); assert_eq!(f[&1], Nodes::from([1, 2])); assert_eq!(f[&2], Nodes::from([4])); - assert_eq!(layer, vec![1, 1, 1, 0, 0]); - verify((f, Some(layer)), g, iset, oset, pplanes).unwrap(); + assert_eq!(layers, vec![1, 1, 1, 0, 0]); + verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); } } diff --git a/tests/assets.py b/tests/assets.py index 1ff3ba73..66808297 100644 --- a/tests/assets.py +++ b/tests/assets.py @@ -19,8 +19,8 @@ class FlowTestCase: g: nx.Graph[int] iset: set[int] oset: set[int] - plane: dict[int, Plane] | None - pplane: dict[int, PPlane] | None + planes: dict[int, Plane] | None + pplanes: dict[int, PPlane] | None flow: FlowResult[int] | None gflow: GFlowResult[int] | None pflow: PFlowResult[int] | None diff --git a/tests/test_common.py b/tests/test_common.py index d7ea4069..e8d02263 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -100,7 +100,7 @@ def test_decode_err(self, fx_indexmap: IndexMap[str], emsg: ValueError) -> None: def test_encode_layer_missing(self, fx_indexmap: IndexMap[str]) -> None: with pytest.raises(ValueError, match=r"Layers must be specified for all nodes\."): - fx_indexmap.encode_layer({"a": 0, "b": 1}) + fx_indexmap.encode_layers({"a": 0, "b": 1}) def test_ecatch(self, fx_indexmap: IndexMap[str]) -> None: def dummy_ok(x: int) -> int: @@ -130,14 +130,14 @@ class TestInferLayer: def test_line(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 3)]) flow = {0: {1}, 1: {2}, 2: {3}} - layer = common.infer_layers(g, flow) - assert layer == {0: 3, 1: 2, 2: 1, 3: 0} + layers = common.infer_layers(g, flow) + assert layers == {0: 3, 1: 2, 2: 1, 3: 0} def test_dag(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 2), (0, 3), (1, 2), (1, 3)]) flow = {0: {2, 3}, 1: {2, 3}} - layer = common.infer_layers(g, flow) - assert layer == {0: 1, 1: 1, 2: 0, 3: 0} + layers = common.infer_layers(g, flow) + assert layers == {0: 1, 1: 1, 2: 0, 3: 0} def test_cycle(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 0)]) diff --git a/tests/test_flow.py b/tests/test_flow.py index 8ab4b653..3c6cbb74 100644 --- a/tests/test_flow.py +++ b/tests/test_flow.py @@ -20,5 +20,5 @@ def test_infer_verify(c: FlowTestCase) -> None: pytest.skip() f, _ = c.flow flow.verify(f, c.g, c.iset, c.oset) - layer = common.infer_layers(c.g, f) - flow.verify((f, layer), c.g, c.iset, c.oset) + layers = common.infer_layers(c.g, f) + flow.verify((f, layers), c.g, c.iset, c.oset) diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 7af1cc5d..5da414e4 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -10,10 +10,10 @@ @pytest.mark.parametrize("c", CASES) def test_gflow(c: FlowTestCase) -> None: - result = gflow.find(c.g, c.iset, c.oset, plane=c.plane) + result = gflow.find(c.g, c.iset, c.oset, planes=c.planes) assert result == c.gflow if result is not None: - gflow.verify(result, c.g, c.iset, c.oset, plane=c.plane) + gflow.verify(result, c.g, c.iset, c.oset, planes=c.planes) def test_gflow_redundant() -> None: @@ -22,7 +22,7 @@ def test_gflow_redundant() -> None: oset = {1} planes = {0: Plane.XY, 1: Plane.XY} with pytest.raises(ValueError, match=r".*Excessive measurement planes specified.*"): - gflow.find(g, iset, oset, plane=planes) + gflow.find(g, iset, oset, planes=planes) @pytest.mark.parametrize("c", CASES) @@ -30,6 +30,6 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.gflow is None: pytest.skip() f, _ = c.gflow - gflow.verify(f, c.g, c.iset, c.oset, plane=c.plane) - layer = common.infer_layers(c.g, f) - gflow.verify((f, layer), c.g, c.iset, c.oset, plane=c.plane) + gflow.verify(f, c.g, c.iset, c.oset, planes=c.planes) + layers = common.infer_layers(c.g, f) + gflow.verify((f, layers), c.g, c.iset, c.oset, planes=c.planes) diff --git a/tests/test_pflow.py b/tests/test_pflow.py index a945302e..53a9e2ef 100644 --- a/tests/test_pflow.py +++ b/tests/test_pflow.py @@ -11,10 +11,10 @@ @pytest.mark.filterwarnings("ignore:No Pauli measurement found") @pytest.mark.parametrize("c", CASES) def test_pflow(c: FlowTestCase) -> None: - result = pflow.find(c.g, c.iset, c.oset, pplane=c.pplane) + result = pflow.find(c.g, c.iset, c.oset, pplanes=c.pplanes) assert result == c.pflow if result is not None: - pflow.verify(result, c.g, c.iset, c.oset, pplane=c.pplane) + pflow.verify(result, c.g, c.iset, c.oset, pplanes=c.pplanes) def test_pflow_nopauli() -> None: @@ -23,7 +23,7 @@ def test_pflow_nopauli() -> None: oset = {1} planes = {0: PPlane.XY} with pytest.warns(UserWarning, match=r".*No Pauli measurement found\. Use gflow\.find instead\..*"): - pflow.find(g, iset, oset, pplane=planes) + pflow.find(g, iset, oset, pplanes=planes) def test_pflow_redundant() -> None: @@ -32,7 +32,7 @@ def test_pflow_redundant() -> None: oset = {1} planes = {0: PPlane.X, 1: PPlane.Y} with pytest.raises(ValueError, match=r".*Excessive measurement planes specified.*"): - pflow.find(g, iset, oset, pplane=planes) + pflow.find(g, iset, oset, pplanes=planes) @pytest.mark.parametrize("c", CASES) @@ -40,6 +40,6 @@ def test_infer_verify(c: FlowTestCase) -> None: if c.pflow is None: pytest.skip() f, _ = c.pflow - pflow.verify(f, c.g, c.iset, c.oset, pplane=c.pplane) - layer = common.infer_layers(c.g, f, pplane=c.pplane) - pflow.verify((f, layer), c.g, c.iset, c.oset, pplane=c.pplane) + pflow.verify(f, c.g, c.iset, c.oset, pplanes=c.pplanes) + layers = common.infer_layers(c.g, f, pplanes=c.pplanes) + pflow.verify((f, layers), c.g, c.iset, c.oset, pplanes=c.pplanes) From 43c1bcebbd461a4d25277d8bdff2ee024c1022ec Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 31 Jul 2025 02:16:34 +0900 Subject: [PATCH 36/51] :rotating_light: Apply clippy hints --- src/flow.rs | 4 ++-- src/gflow.rs | 8 ++------ src/pflow.rs | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/flow.rs b/src/flow.rs index a2f0591e..a3168e87 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -157,14 +157,14 @@ mod tests { fn test_check_definition_ng() { // Violate 0 -> f(0) = 1 assert_eq!( - check_def_layer(&map! { 0: 1 }, &vec![0, 0], &test_utils::graph(&[(0, 1)])), + check_def_layer(&map! { 0: 1 }, &[0, 0], &test_utils::graph(&[(0, 1)])), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( check_def_layer( &map! { 0: 2 }, - &vec![1, 1, 0], + &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]) ), Err(InconsistentFlowOrder { nodes: (0, 1) }) diff --git a/src/gflow.rs b/src/gflow.rs index 0538bb25..f4ce225d 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -293,18 +293,14 @@ mod tests { ); // Violate 0 -> f(0) = 1 assert_eq!( - check_def_layer( - &map! { 0: set!{1} }, - &vec![0, 0], - &test_utils::graph(&[(0, 1)]), - ), + check_def_layer(&map! { 0: set!{1} }, &[0, 0], &test_utils::graph(&[(0, 1)]),), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( check_def_layer( &map! { 0: set!{2}, 1: set!{2} }, - &vec![1, 1, 0], + &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), ), Err(InconsistentFlowOrder { nodes: (0, 1) }) diff --git a/src/pflow.rs b/src/pflow.rs index fb622171..d51dcd40 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -487,7 +487,7 @@ mod tests { assert_eq!( check_def_layer( &map! { 0: set!{1} }, - &vec![0, 0], + &[0, 0], &test_utils::graph(&[(0, 1)]), &map! { 0: PPlane::XY }, ), @@ -497,7 +497,7 @@ mod tests { assert_eq!( check_def_layer( &map! { 0: set!{2}, 1: set!{2} }, - &vec![1, 1, 0], + &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), &map! { 0: PPlane::XY, @@ -510,7 +510,7 @@ mod tests { assert_eq!( check_def_layer( &map! { 0: set!{1}, 1: set!{2} }, - &vec![1, 1, 0], + &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), &map! { 0: PPlane::XY, 1: PPlane::Y }, ), From b42c8505d51fcdf8a590abca30c795e37de65a14 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:41:44 +0900 Subject: [PATCH 37/51] :bug: Perform strict check --- src/internal/validate.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/internal/validate.rs b/src/internal/validate.rs index 510230c2..38b2c18a 100644 --- a/src/internal/validate.rs +++ b/src/internal/validate.rs @@ -43,10 +43,6 @@ pub fn check_initial(layers: &[usize], oset: &Nodes, iff: bool) -> Result<(), Fl /// - `vset`: All nodes. /// - `iset`: Input nodes. /// - `oset`: Output nodes. -/// -/// # Note -/// -/// It is allowed for `f[i]` to contain `i`, even if `i` is in `iset`. pub fn check_domain<'a, 'b>( f_flatiter: impl Iterator, vset: &Nodes, @@ -58,7 +54,7 @@ pub fn check_domain<'a, 'b>( let mut dom = Nodes::new(); for (&i, &fi) in f_flatiter { dom.insert(i); - if i != fi && !icset.contains(&fi) { + if !icset.contains(&fi) { Err(InvalidFlowCodomain { node: i })?; } } From 830fafe8a31e4c584c5cd400d22893d1f8d279cb Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 14:08:20 +0900 Subject: [PATCH 38/51] :safety_vest: Capture ScopedXXX explicitly --- src/pflow.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pflow.rs b/src/pflow.rs index d51dcd40..9bcee3fe 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -275,9 +275,9 @@ struct PFlowContext<'a> { work: &'a mut Vec, g: &'a [Nodes], u: usize, - rowset_upper: &'a OrderedNodes, - rowset_lower: &'a OrderedNodes, - colset: &'a OrderedNodes, + rowset_upper: &'a ScopedInclude<'a>, + rowset_lower: &'a ScopedExclude<'a>, + colset: &'a ScopedExclude<'a>, x: &'a mut FixedBitSet, f: &'a mut PFlow, } From 620c24df50003848bada16c92691770feebfaef0 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:11:04 +0900 Subject: [PATCH 39/51] :white_check_mark: Fix test --- src/internal/validate.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/internal/validate.rs b/src/internal/validate.rs index 38b2c18a..5ea20791 100644 --- a/src/internal/validate.rs +++ b/src/internal/validate.rs @@ -111,8 +111,7 @@ mod tests { #[test] fn test_check_domain_gflow() { let f = hashbrown::HashMap::::from([ - // OK: 0 in f(0) - (0, Nodes::from([0, 1])), + (0, Nodes::from([1, 2])), (1, Nodes::from([2])), ]); let vset = Nodes::from([0, 1, 2]); From f3a867b52f9feb2e2cd927efa78497ea72950239 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:27:16 +0900 Subject: [PATCH 40/51] :recycle: Use alias --- src/common.rs | 28 ++++++++++++++++------------ src/flow.rs | 6 +++--- src/gflow.rs | 8 ++++---- src/internal/test_utils.rs | 4 ++-- src/internal/utils.rs | 10 +++++----- src/internal/validate.rs | 20 +++++++++----------- src/pflow.rs | 18 +++++++++--------- 7 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/common.rs b/src/common.rs index e86d3604..2da8e3aa 100644 --- a/src/common.rs +++ b/src/common.rs @@ -7,19 +7,23 @@ use thiserror::Error; use crate::{gflow::Plane, pflow::PPlane}; -/// Set of nodes indexed by 0-based integers. -pub type Nodes = hashbrown::HashSet; +/// Node index. +pub type Node = usize; +/// Layer index. +pub type Layer = usize; +/// Set of nodes. +pub type Nodes = hashbrown::HashSet; /// Simple graph encoded as list of neighbors. pub type Graph = Vec; /// Layer representation of the flow partial order. -pub type Layers = Vec; +pub type Layers = Vec; /// Ordered set of nodes. /// /// # Note /// /// Used only when iteration order matters. -pub(crate) type OrderedNodes = BTreeSet; +pub(crate) type OrderedNodes = BTreeSet; /// Error type for flow validation. /// @@ -29,21 +33,21 @@ pub(crate) type OrderedNodes = BTreeSet; pub enum FlowValidationError { // Keep in sync with Python-side error messages #[error("layer-{layer} node {node} inside output nodes")] - ExcessiveNonZeroLayer { node: usize, layer: usize }, + ExcessiveNonZeroLayer { node: Node, layer: Layer }, #[error("zero-layer node {node} outside output nodes")] - ExcessiveZeroLayer { node: usize }, + ExcessiveZeroLayer { node: Node }, #[error("f({node}) has invalid codomain")] - InvalidFlowCodomain { node: usize }, + InvalidFlowCodomain { node: Node }, #[error("f({node}) has invalid domain")] - InvalidFlowDomain { node: usize }, + InvalidFlowDomain { node: Node }, #[error("node {node} has invalid measurement specification")] - InvalidMeasurementSpec { node: usize }, + InvalidMeasurementSpec { node: Node }, #[error("flow-order inconsistency on nodes ({}, {})",.nodes.0, .nodes.1)] - InconsistentFlowOrder { nodes: (usize, usize) }, + InconsistentFlowOrder { nodes: (Node, Node) }, #[error("broken {plane:?} measurement on node {node}")] - InconsistentFlowPlane { node: usize, plane: Plane }, + InconsistentFlowPlane { node: Node, plane: Plane }, #[error("broken {pplane:?} measurement on node {node}")] - InconsistentFlowPPlane { node: usize, pplane: PPlane }, + InconsistentFlowPPlane { node: Node, pplane: PPlane }, } impl From for PyErr { diff --git a/src/flow.rs b/src/flow.rs index a3168e87..e8efb610 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -7,12 +7,12 @@ use crate::{ common::{ FATAL_MSG, FlowValidationError::{self, InconsistentFlowOrder}, - Graph, Layers, Nodes, + Graph, Layer, Layers, Node, Nodes, }, internal::{utils::InPlaceSetDiff, validate}, }; -type Flow = hashbrown::HashMap; +type Flow = hashbrown::HashMap; /// Checks the geometric constraints of flow. /// @@ -30,7 +30,7 @@ fn check_def_geom(f: &Flow, g: &[Nodes]) -> Result<(), FlowValidationError> { /// /// - i -> f(i) /// - j in N(f(i)) => i == j or i -> j -fn check_def_layer(f: &Flow, layers: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { +fn check_def_layer(f: &Flow, layers: &[Layer], g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, &fi) in f { if layers[i] <= layers[fi] { Err(InconsistentFlowOrder { nodes: (i, fi) })?; diff --git a/src/gflow.rs b/src/gflow.rs index f4ce225d..42e2818d 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -12,7 +12,7 @@ use crate::{ FlowValidationError::{ self, InconsistentFlowOrder, InconsistentFlowPlane, InvalidMeasurementSpec, }, - Graph, Layers, Nodes, OrderedNodes, + Graph, Layer, Layers, Node, Nodes, OrderedNodes, }, internal::{ gf2_linalg::GF2Solver, @@ -30,8 +30,8 @@ pub enum Plane { XZ, } -type Planes = hashbrown::HashMap; -type GFlow = hashbrown::HashMap; +type Planes = hashbrown::HashMap; +type GFlow = hashbrown::HashMap; /// Checks the geometric constraints of gflow. /// @@ -77,7 +77,7 @@ fn check_def_geom(f: &GFlow, g: &[Nodes], planes: &Planes) -> Result<(), FlowVal /// /// - i -> g(i) /// - j in Odd(g(i)) => i == j or i -> j -fn check_def_layer(f: &GFlow, layers: &[usize], g: &[Nodes]) -> Result<(), FlowValidationError> { +fn check_def_layer(f: &GFlow, layers: &[Layer], g: &[Nodes]) -> Result<(), FlowValidationError> { for (&i, fi) in f { for &fij in fi { if i != fij && layers[i] <= layers[fij] { diff --git a/src/internal/test_utils.rs b/src/internal/test_utils.rs index af880307..8310d8da 100644 --- a/src/internal/test_utils.rs +++ b/src/internal/test_utils.rs @@ -2,7 +2,7 @@ use std::sync::LazyLock; -use crate::common::{Graph, Nodes}; +use crate::common::{Graph, Node, Nodes}; pub mod exports { pub use hashbrown::{HashMap, HashSet}; @@ -24,7 +24,7 @@ macro_rules! set { } /// Creates a undirected graph from edges. -pub fn graph(edges: &[(usize, usize); N]) -> Graph { +pub fn graph(edges: &[(Node, Node); N]) -> Graph { let n = edges .iter() .map(|&(u, v)| u.max(v) + 1) diff --git a/src/internal/utils.rs b/src/internal/utils.rs index a748e621..ed64e1ce 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -8,7 +8,7 @@ use std::collections::BTreeSet; use fixedbitset::FixedBitSet; -use crate::common::{Nodes, OrderedNodes}; +use crate::common::{Node, Nodes, OrderedNodes}; /// Computes the odd neighbors of the nodes in `kset`. /// @@ -78,11 +78,11 @@ pub fn indexmap>(set: &OrderedNodes) -> T { /// Inserts `u` on construction and reverts on drop. pub struct ScopedInclude<'a> { target: &'a mut OrderedNodes, - u: Option, + u: Option, } impl<'a> ScopedInclude<'a> { - pub fn new(target: &'a mut OrderedNodes, u: usize) -> Self { + pub fn new(target: &'a mut OrderedNodes, u: Node) -> Self { let u = if target.insert(u) { Some(u) } else { None }; Self { target, u } } @@ -116,11 +116,11 @@ impl Drop for ScopedInclude<'_> { /// Removes `u` on construction and reverts on drop. pub struct ScopedExclude<'a> { target: &'a mut OrderedNodes, - u: Option, + u: Option, } impl<'a> ScopedExclude<'a> { - pub fn new(target: &'a mut OrderedNodes, u: usize) -> Self { + pub fn new(target: &'a mut OrderedNodes, u: Node) -> Self { let u = if target.remove(&u) { Some(u) } else { None }; Self { target, u } } diff --git a/src/internal/validate.rs b/src/internal/validate.rs index 5ea20791..8556d6c4 100644 --- a/src/internal/validate.rs +++ b/src/internal/validate.rs @@ -8,7 +8,7 @@ use crate::common::{ FlowValidationError::{ self, ExcessiveNonZeroLayer, ExcessiveZeroLayer, InvalidFlowCodomain, InvalidFlowDomain, }, - Nodes, + Layer, Node, Nodes, }; /// Checks if the layer-zero nodes are correctly chosen. @@ -20,7 +20,7 @@ use crate::common::{ /// - `layers`: The layer. /// - `oset`: The set of output nodes. /// - `iff`: If `true`, `layers[u] == 0` "iff" `u` is in `oset`. Otherwise "if". -pub fn check_initial(layers: &[usize], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { +pub fn check_initial(layers: &[Layer], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { for (u, &lu) in layers.iter().enumerate() { match (oset.contains(&u), lu == 0) { (true, false) => { @@ -39,12 +39,12 @@ pub fn check_initial(layers: &[usize], oset: &Nodes, iff: bool) -> Result<(), Fl /// /// # Arguments /// -/// - `f_flatiter`: Flow, gflow, or pflow as `impl Iterator`. +/// - `f_flatiter`: Flow, gflow, or pflow as `impl Iterator`. /// - `vset`: All nodes. /// - `iset`: Input nodes. /// - `oset`: Output nodes. pub fn check_domain<'a, 'b>( - f_flatiter: impl Iterator, + f_flatiter: impl Iterator, vset: &Nodes, iset: &Nodes, oset: &Nodes, @@ -101,7 +101,7 @@ mod tests { #[test] fn test_check_domain_flow() { - let f = hashbrown::HashMap::::from([(0, 1), (1, 2)]); + let f = hashbrown::HashMap::::from([(0, 1), (1, 2)]); let vset = Nodes::from([0, 1, 2]); let iset = Nodes::from([0]); let oset = Nodes::from([2]); @@ -110,7 +110,7 @@ mod tests { #[test] fn test_check_domain_gflow() { - let f = hashbrown::HashMap::::from([ + let f = hashbrown::HashMap::::from([ (0, Nodes::from([1, 2])), (1, Nodes::from([2])), ]); @@ -125,7 +125,7 @@ mod tests { #[test] fn test_check_domain_ng_iset() { - let f = hashbrown::HashMap::::from([ + let f = hashbrown::HashMap::::from([ (0, Nodes::from([0, 1])), (2, Nodes::from([2])), ]); @@ -140,10 +140,8 @@ mod tests { #[test] fn test_check_domain_ng_oset() { - let f = hashbrown::HashMap::::from([ - (0, Nodes::from([1])), - (1, Nodes::from([0])), - ]); + let f = + hashbrown::HashMap::::from([(0, Nodes::from([1])), (1, Nodes::from([0]))]); let vset = Nodes::from([0, 1, 2]); let iset = Nodes::from([0]); let oset = Nodes::from([2]); diff --git a/src/pflow.rs b/src/pflow.rs index 9bcee3fe..08303c28 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -12,7 +12,7 @@ use crate::{ FlowValidationError::{ self, InconsistentFlowOrder, InconsistentFlowPPlane, InvalidMeasurementSpec, }, - Graph, Layers, Nodes, OrderedNodes, + Graph, Layer, Layers, Node, Nodes, OrderedNodes, }, internal::{ gf2_linalg::GF2Solver, @@ -33,8 +33,8 @@ pub enum PPlane { Z, } -type PPlanes = hashbrown::HashMap; -type PFlow = hashbrown::HashMap; +type PPlanes = hashbrown::HashMap; +type PFlow = hashbrown::HashMap; /// Checks the geometric constraints of pflow. fn check_def_geom(f: &PFlow, g: &[Nodes], pplanes: &PPlanes) -> Result<(), FlowValidationError> { @@ -93,7 +93,7 @@ fn check_def_geom(f: &PFlow, g: &[Nodes], pplanes: &PPlanes) -> Result<(), FlowV /// Checks the layer constraints of pflow. fn check_def_layer( f: &PFlow, - layers: &[usize], + layers: &[Layer], g: &[Nodes], pplanes: &PPlanes, ) -> Result<(), FlowValidationError> { @@ -184,7 +184,7 @@ const BRANCH_XZ: BranchKind = 2; /// Initializes the right-hand side of working storage for the upper block. fn init_work_upper_rhs( work: &mut [FixedBitSet], - u: usize, + u: Node, g: &[Nodes], rowset: &OrderedNodes, colset: &OrderedNodes, @@ -214,7 +214,7 @@ fn init_work_upper_rhs( /// Initializes the right-hand side of working storage for the lower block. fn init_work_lower_rhs( work: &mut [FixedBitSet], - u: usize, + u: Node, g: &[Nodes], rowset: &OrderedNodes, colset: &OrderedNodes, @@ -238,7 +238,7 @@ fn init_work_lower_rhs( /// Initializes working storage for the given branch kind. fn init_work( work: &mut [FixedBitSet], - u: usize, + u: Node, g: &[Nodes], rowset_upper: &OrderedNodes, rowset_lower: &OrderedNodes, @@ -255,7 +255,7 @@ fn init_work( } /// Decodes the solution returned by `GF2Solver`. -fn decode_solution(u: usize, x: &FixedBitSet, colset: &OrderedNodes) -> Nodes { +fn decode_solution(u: Node, x: &FixedBitSet, colset: &OrderedNodes) -> Nodes { const { assert!(K == BRANCH_XY || K == BRANCH_YZ || K == BRANCH_XZ); }; @@ -274,7 +274,7 @@ fn decode_solution(u: usize, x: &FixedBitSet, colset: &Orde struct PFlowContext<'a> { work: &'a mut Vec, g: &'a [Nodes], - u: usize, + u: Node, rowset_upper: &'a ScopedInclude<'a>, rowset_lower: &'a ScopedExclude<'a>, colset: &'a ScopedExclude<'a>, From 275185b5ed092c0ba82fc899c582a0bc9a52adbc Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:33:35 +0900 Subject: [PATCH 41/51] :recycle: Use stl --- src/gflow.rs | 2 +- src/pflow.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gflow.rs b/src/gflow.rs index 42e2818d..f0a08ec4 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -39,7 +39,7 @@ type GFlow = hashbrown::HashMap; /// - YZ: i in g(i) and in Odd(g(i)) /// - XZ: i in g(i) and not in Odd(g(i)) fn check_def_geom(f: &GFlow, g: &[Nodes], planes: &Planes) -> Result<(), FlowValidationError> { - for &i in itertools::chain(f.keys(), planes.keys()) { + for &i in Iterator::chain(f.keys(), planes.keys()) { if f.contains_key(&i) != planes.contains_key(&i) { Err(InvalidMeasurementSpec { node: i })?; } diff --git a/src/pflow.rs b/src/pflow.rs index 08303c28..8baa56bd 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -38,7 +38,7 @@ type PFlow = hashbrown::HashMap; /// Checks the geometric constraints of pflow. fn check_def_geom(f: &PFlow, g: &[Nodes], pplanes: &PPlanes) -> Result<(), FlowValidationError> { - for &i in itertools::chain(f.keys(), pplanes.keys()) { + for &i in Iterator::chain(f.keys(), pplanes.keys()) { if f.contains_key(&i) != pplanes.contains_key(&i) { Err(InvalidMeasurementSpec { node: i })?; } From 53def3c3160027253cd4eddebaa696b64b783151 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:55:42 +0900 Subject: [PATCH 42/51] :recycle: Remove validate.rs --- src/common.rs | 144 +++++++++++++++++++++++++++++++++++- src/flow.rs | 10 +-- src/gflow.rs | 9 +-- src/internal.rs | 1 - src/internal/validate.rs | 153 --------------------------------------- src/lib.rs | 2 +- src/pflow.rs | 9 +-- 7 files changed, 157 insertions(+), 171 deletions(-) delete mode 100644 src/internal/validate.rs diff --git a/src/common.rs b/src/common.rs index 2da8e3aa..8af1717c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -5,7 +5,13 @@ use std::collections::BTreeSet; use pyo3::{exceptions::PyValueError, prelude::*}; use thiserror::Error; -use crate::{gflow::Plane, pflow::PPlane}; +use crate::{ + common::FlowValidationError::{ + ExcessiveNonZeroLayer, ExcessiveZeroLayer, InvalidFlowCodomain, InvalidFlowDomain, + }, + gflow::Plane, + pflow::PPlane, +}; /// Node index. pub type Node = usize; @@ -66,12 +72,148 @@ pub const FATAL_MSG: &str = "\ Please report to the developers via GitHub: https://github.com/TeamGraphix/swiflow/issues/new"; +/// Checks if the layer-zero nodes are correctly chosen. +/// +/// This check can be skipped unless maximally-delayed flow is required. +/// +/// # Arguments +/// +/// - `layers`: The layer. +/// - `oset`: The set of output nodes. +/// - `iff`: If `true`, `layers[u] == 0` "iff" `u` is in `oset`. Otherwise "if". +pub fn check_initial(layers: &[Layer], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { + for (u, &lu) in layers.iter().enumerate() { + match (oset.contains(&u), lu == 0) { + (true, false) => { + Err(ExcessiveNonZeroLayer { node: u, layer: lu })?; + } + (false, true) if iff => { + Err(ExcessiveZeroLayer { node: u })?; + } + _ => {} + } + } + Ok(()) +} + +/// Checks if the domain of `f` is in `vset - oset` and the codomain is in `vset - iset`. +/// +/// # Arguments +/// +/// - `f_flatiter`: Flow, gflow, or pflow as `impl Iterator`. +/// - `vset`: All nodes. +/// - `iset`: Input nodes. +/// - `oset`: Output nodes. +pub fn check_domain<'a, 'b>( + f_flatiter: impl Iterator, + vset: &Nodes, + iset: &Nodes, + oset: &Nodes, +) -> Result<(), FlowValidationError> { + let icset = vset - iset; + let ocset = vset - oset; + let mut dom = Nodes::new(); + for (&i, &fi) in f_flatiter { + dom.insert(i); + if !icset.contains(&fi) { + Err(InvalidFlowCodomain { node: i })?; + } + } + if let Some(&i) = dom.symmetric_difference(&ocset).next() { + Err(InvalidFlowDomain { node: i })?; + } + Ok(()) +} + #[cfg(test)] mod tests { + use core::iter; + use super::*; + use crate::common::Nodes; #[test] fn test_err_from() { let _ = PyErr::from(FlowValidationError::ExcessiveNonZeroLayer { node: 1, layer: 2 }); } + + #[test] + fn test_check_initial() { + let layers = vec![0, 0, 0, 1, 1, 1]; + let oset = Nodes::from([0, 1]); + check_initial(&layers, &oset, false).unwrap(); + } + + #[test] + fn test_check_initial_ng() { + let layers = vec![0, 0, 0, 1, 1, 1]; + let oset = Nodes::from([0, 1, 2, 3]); + assert!(check_initial(&layers, &oset, false).is_err()); + } + + #[test] + fn test_check_initial_iff() { + let layers = vec![0, 0, 0, 1, 1, 1]; + let oset = Nodes::from([0, 1, 2]); + check_initial(&layers, &oset, true).unwrap(); + } + + #[test] + fn test_check_initial_iff_ng() { + let layers = vec![0, 0, 0, 1, 1, 1]; + let oset = Nodes::from([0, 1]); + assert!(check_initial(&layers, &oset, true).is_err()); + } + + #[test] + fn test_check_domain_flow() { + let f = hashbrown::HashMap::::from([(0, 1), (1, 2)]); + let vset = Nodes::from([0, 1, 2]); + let iset = Nodes::from([0]); + let oset = Nodes::from([2]); + check_domain(f.iter(), &vset, &iset, &oset).unwrap(); + } + + #[test] + fn test_check_domain_gflow() { + let f = hashbrown::HashMap::::from([ + (0, Nodes::from([1, 2])), + (1, Nodes::from([2])), + ]); + let vset = Nodes::from([0, 1, 2]); + let iset = Nodes::from([0]); + let oset = Nodes::from([2]); + let f_flatiter = f + .iter() + .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); + check_domain(f_flatiter, &vset, &iset, &oset).unwrap(); + } + + #[test] + fn test_check_domain_ng_iset() { + let f = hashbrown::HashMap::::from([ + (0, Nodes::from([0, 1])), + (2, Nodes::from([2])), + ]); + let vset = Nodes::from([0, 1, 2]); + let iset = Nodes::from([0]); + let oset = Nodes::from([2]); + let f_flatiter = f + .iter() + .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); + assert!(check_domain(f_flatiter, &vset, &iset, &oset).is_err()); + } + + #[test] + fn test_check_domain_ng_oset() { + let f = + hashbrown::HashMap::::from([(0, Nodes::from([1])), (1, Nodes::from([0]))]); + let vset = Nodes::from([0, 1, 2]); + let iset = Nodes::from([0]); + let oset = Nodes::from([2]); + let f_flatiter = f + .iter() + .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); + assert!(check_domain(f_flatiter, &vset, &iset, &oset).is_err()); + } } diff --git a/src/flow.rs b/src/flow.rs index e8efb610..9d961be9 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -5,11 +5,11 @@ use pyo3::prelude::*; use crate::{ common::{ - FATAL_MSG, + self, FATAL_MSG, FlowValidationError::{self, InconsistentFlowOrder}, Graph, Layer, Layers, Node, Nodes, }, - internal::{utils::InPlaceSetDiff, validate}, + internal::utils::InPlaceSetDiff, }; type Flow = hashbrown::HashMap; @@ -113,8 +113,8 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layers)> { tracing::debug!("layers: {layers:?}"); // TODO: Remove this block once stabilized { - validate::check_domain(f.iter(), &vset, &iset, &oset_orig).expect(FATAL_MSG); - validate::check_initial(&layers, &oset_orig, true).expect(FATAL_MSG); + common::check_domain(f.iter(), &vset, &iset, &oset_orig).expect(FATAL_MSG); + common::check_initial(&layers, &oset_orig, true).expect(FATAL_MSG); check_def_geom(&f, &g).expect(FATAL_MSG); check_def_layer(&f, &layers, &g).expect(FATAL_MSG); } @@ -138,7 +138,7 @@ pub fn verify(flow: (Flow, Option), g: Graph, iset: Nodes, oset: Nodes) let (f, layers) = flow; let n = g.len(); let vset = (0..n).collect::(); - validate::check_domain(f.iter(), &vset, &iset, &oset)?; + common::check_domain(f.iter(), &vset, &iset, &oset)?; check_def_geom(&f, &g)?; if let Some(layers) = layers { check_def_layer(&f, &layers, &g)?; diff --git a/src/gflow.rs b/src/gflow.rs index f0a08ec4..6744f003 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; use crate::{ common::{ - FATAL_MSG, + self, FATAL_MSG, FlowValidationError::{ self, InconsistentFlowOrder, InconsistentFlowPlane, InvalidMeasurementSpec, }, @@ -17,7 +17,6 @@ use crate::{ internal::{ gf2_linalg::GF2Solver, utils::{self, InPlaceSetDiff}, - validate, }, }; @@ -231,8 +230,8 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow let f_flatiter = f .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - validate::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); - validate::check_initial(&layers, &oset, true).expect(FATAL_MSG); + common::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); + common::check_initial(&layers, &oset, true).expect(FATAL_MSG); check_def_geom(&f, &g, &planes).expect(FATAL_MSG); check_def_layer(&f, &layers, &g).expect(FATAL_MSG); } @@ -265,7 +264,7 @@ pub fn verify( let f_flatiter = f .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - validate::check_domain(f_flatiter, &vset, &iset, &oset)?; + common::check_domain(f_flatiter, &vset, &iset, &oset)?; check_def_geom(&f, &g, &planes)?; if let Some(layers) = layers { check_def_layer(&f, &layers, &g)?; diff --git a/src/internal.rs b/src/internal.rs index 1df283f0..151dde55 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -4,4 +4,3 @@ pub mod test_utils; pub mod gf2_linalg; pub mod utils; -pub mod validate; diff --git a/src/internal/validate.rs b/src/internal/validate.rs deleted file mode 100644 index 8556d6c4..00000000 --- a/src/internal/validate.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Rust-side input validations. -//! -//! # Note -//! -//! - Internal module for testing. - -use crate::common::{ - FlowValidationError::{ - self, ExcessiveNonZeroLayer, ExcessiveZeroLayer, InvalidFlowCodomain, InvalidFlowDomain, - }, - Layer, Node, Nodes, -}; - -/// Checks if the layer-zero nodes are correctly chosen. -/// -/// This check can be skipped unless maximally-delayed flow is required. -/// -/// # Arguments -/// -/// - `layers`: The layer. -/// - `oset`: The set of output nodes. -/// - `iff`: If `true`, `layers[u] == 0` "iff" `u` is in `oset`. Otherwise "if". -pub fn check_initial(layers: &[Layer], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { - for (u, &lu) in layers.iter().enumerate() { - match (oset.contains(&u), lu == 0) { - (true, false) => { - Err(ExcessiveNonZeroLayer { node: u, layer: lu })?; - } - (false, true) if iff => { - Err(ExcessiveZeroLayer { node: u })?; - } - _ => {} - } - } - Ok(()) -} - -/// Checks if the domain of `f` is in `vset - oset` and the codomain is in `vset - iset`. -/// -/// # Arguments -/// -/// - `f_flatiter`: Flow, gflow, or pflow as `impl Iterator`. -/// - `vset`: All nodes. -/// - `iset`: Input nodes. -/// - `oset`: Output nodes. -pub fn check_domain<'a, 'b>( - f_flatiter: impl Iterator, - vset: &Nodes, - iset: &Nodes, - oset: &Nodes, -) -> Result<(), FlowValidationError> { - let icset = vset - iset; - let ocset = vset - oset; - let mut dom = Nodes::new(); - for (&i, &fi) in f_flatiter { - dom.insert(i); - if !icset.contains(&fi) { - Err(InvalidFlowCodomain { node: i })?; - } - } - if let Some(&i) = dom.symmetric_difference(&ocset).next() { - Err(InvalidFlowDomain { node: i })?; - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use core::iter; - - use super::*; - use crate::common::Nodes; - - #[test] - fn test_check_initial() { - let layers = vec![0, 0, 0, 1, 1, 1]; - let oset = Nodes::from([0, 1]); - check_initial(&layers, &oset, false).unwrap(); - } - - #[test] - fn test_check_initial_ng() { - let layers = vec![0, 0, 0, 1, 1, 1]; - let oset = Nodes::from([0, 1, 2, 3]); - assert!(check_initial(&layers, &oset, false).is_err()); - } - - #[test] - fn test_check_initial_iff() { - let layers = vec![0, 0, 0, 1, 1, 1]; - let oset = Nodes::from([0, 1, 2]); - check_initial(&layers, &oset, true).unwrap(); - } - - #[test] - fn test_check_initial_iff_ng() { - let layers = vec![0, 0, 0, 1, 1, 1]; - let oset = Nodes::from([0, 1]); - assert!(check_initial(&layers, &oset, true).is_err()); - } - - #[test] - fn test_check_domain_flow() { - let f = hashbrown::HashMap::::from([(0, 1), (1, 2)]); - let vset = Nodes::from([0, 1, 2]); - let iset = Nodes::from([0]); - let oset = Nodes::from([2]); - check_domain(f.iter(), &vset, &iset, &oset).unwrap(); - } - - #[test] - fn test_check_domain_gflow() { - let f = hashbrown::HashMap::::from([ - (0, Nodes::from([1, 2])), - (1, Nodes::from([2])), - ]); - let vset = Nodes::from([0, 1, 2]); - let iset = Nodes::from([0]); - let oset = Nodes::from([2]); - let f_flatiter = f - .iter() - .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - check_domain(f_flatiter, &vset, &iset, &oset).unwrap(); - } - - #[test] - fn test_check_domain_ng_iset() { - let f = hashbrown::HashMap::::from([ - (0, Nodes::from([0, 1])), - (2, Nodes::from([2])), - ]); - let vset = Nodes::from([0, 1, 2]); - let iset = Nodes::from([0]); - let oset = Nodes::from([2]); - let f_flatiter = f - .iter() - .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - assert!(check_domain(f_flatiter, &vset, &iset, &oset).is_err()); - } - - #[test] - fn test_check_domain_ng_oset() { - let f = - hashbrown::HashMap::::from([(0, Nodes::from([1])), (1, Nodes::from([0]))]); - let vset = Nodes::from([0, 1, 2]); - let iset = Nodes::from([0]); - let oset = Nodes::from([2]); - let f_flatiter = f - .iter() - .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - assert!(check_domain(f_flatiter, &vset, &iset, &oset).is_err()); - } -} diff --git a/src/lib.rs b/src/lib.rs index 44f43580..eefdb877 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ #[macro_use] mod internal; -pub mod common; +mod common; pub mod flow; pub mod gflow; pub mod pflow; diff --git a/src/pflow.rs b/src/pflow.rs index 8baa56bd..cb53cfaa 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; use crate::{ common::{ - FATAL_MSG, + self, FATAL_MSG, FlowValidationError::{ self, InconsistentFlowOrder, InconsistentFlowPPlane, InvalidMeasurementSpec, }, @@ -17,7 +17,6 @@ use crate::{ internal::{ gf2_linalg::GF2Solver, utils::{self, InPlaceSetDiff, ScopedExclude, ScopedInclude}, - validate, }, }; @@ -423,8 +422,8 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl let f_flatiter = f .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - validate::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); - validate::check_initial(&layers, &oset, false).expect(FATAL_MSG); + common::check_domain(f_flatiter, &vset, &iset, &oset).expect(FATAL_MSG); + common::check_initial(&layers, &oset, false).expect(FATAL_MSG); check_def_geom(&f, &g, &pplanes).expect(FATAL_MSG); check_def_layer(&f, &layers, &g, &pplanes).expect(FATAL_MSG); } @@ -457,7 +456,7 @@ pub fn verify( let f_flatiter = f .iter() .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); - validate::check_domain(f_flatiter, &vset, &iset, &oset)?; + common::check_domain(f_flatiter, &vset, &iset, &oset)?; check_def_geom(&f, &g, &pplanes)?; if let Some(layers) = layers { check_def_layer(&f, &layers, &g, &pplanes)?; From 8c8b348940d014f9e0faf43d9f53f2b3ef0b7d62 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:02:08 +0900 Subject: [PATCH 43/51] :recycle: Remove custom Debug --- src/internal/gf2_linalg.rs | 39 ++------------------------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/src/internal/gf2_linalg.rs b/src/internal/gf2_linalg.rs index 8589e140..7e7724a6 100644 --- a/src/internal/gf2_linalg.rs +++ b/src/internal/gf2_linalg.rs @@ -1,16 +1,12 @@ //! GF(2) linear solver for gflow algorithm. -use core::{ - fmt::{self, Debug, Formatter}, - ops::DerefMut, -}; -use std::collections::BTreeMap; +use core::{fmt::Debug, ops::DerefMut}; use fixedbitset::FixedBitSet; use itertools::Itertools; /// Solver for GF(2) linear equations. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug)] pub struct GF2Solver { /// Number of rows in the coefficient matrix. rows: usize, @@ -248,37 +244,6 @@ impl> GF2Solver { } } -impl> Debug for GF2Solver { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut ret = f.debug_struct("GF2Solver"); - ret.field("rows", &self.rows) - .field("cols", &self.cols) - .field("neqs", &self.neqs) - .field("rank", &self.rank) - .field("perm", &self.perm); - let mut work = BTreeMap::new(); - for (r, row) in self.work.iter().enumerate() { - let mut s = String::with_capacity(self.cols); - for c in 0..self.cols { - s.push(if row[c] { '1' } else { '0' }); - } - work.insert(r, s); - } - ret.field("co", &work); - let mut work = BTreeMap::new(); - for (r, row) in self.work.iter().enumerate() { - let mut s = String::with_capacity(self.neqs); - for ieq in 0..self.neqs { - let c = self.cols + ieq; - s.push(if row[c] { '1' } else { '0' }); - } - work.insert(r, s); - } - ret.field("rhs", &work); - ret.finish() - } -} - #[cfg(test)] mod tests { use rand::{self, Rng}; From 41b42f19d41f21125fcb4cb0629e6e8541add27e Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:07:37 +0900 Subject: [PATCH 44/51] :heavy_minus_sign: Remove hashbrown --- Cargo.toml | 3 +-- src/common.rs | 20 +++++++------------- src/flow.rs | 5 +++-- src/gflow.rs | 10 +++++----- src/internal/test_utils.rs | 14 +++++++------- src/internal/utils.rs | 8 ++++---- src/pflow.rs | 14 +++++++------- 7 files changed, 34 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 63fd698c..3644dc38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,8 @@ crate-type = ["cdylib"] [dependencies] fixedbitset = "0.5" -hashbrown = "0.15" itertools = "0.14" -pyo3 = { version = "0.25", features = ["abi3-py39", "hashbrown"] } +pyo3 = { version = "0.25", features = ["abi3-py39"] } thiserror = "2" tracing = { version = "0.1", features = ["release_max_level_off"] } diff --git a/src/common.rs b/src/common.rs index 8af1717c..9d35dcae 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,6 +1,6 @@ //! Common functionalities. -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashSet}; use pyo3::{exceptions::PyValueError, prelude::*}; use thiserror::Error; @@ -18,7 +18,7 @@ pub type Node = usize; /// Layer index. pub type Layer = usize; /// Set of nodes. -pub type Nodes = hashbrown::HashSet; +pub type Nodes = HashSet; /// Simple graph encoded as list of neighbors. pub type Graph = Vec; /// Layer representation of the flow partial order. @@ -128,6 +128,7 @@ pub fn check_domain<'a, 'b>( #[cfg(test)] mod tests { use core::iter; + use std::collections::HashMap; use super::*; use crate::common::Nodes; @@ -167,7 +168,7 @@ mod tests { #[test] fn test_check_domain_flow() { - let f = hashbrown::HashMap::::from([(0, 1), (1, 2)]); + let f = HashMap::::from([(0, 1), (1, 2)]); let vset = Nodes::from([0, 1, 2]); let iset = Nodes::from([0]); let oset = Nodes::from([2]); @@ -176,10 +177,7 @@ mod tests { #[test] fn test_check_domain_gflow() { - let f = hashbrown::HashMap::::from([ - (0, Nodes::from([1, 2])), - (1, Nodes::from([2])), - ]); + let f = HashMap::::from([(0, Nodes::from([1, 2])), (1, Nodes::from([2]))]); let vset = Nodes::from([0, 1, 2]); let iset = Nodes::from([0]); let oset = Nodes::from([2]); @@ -191,10 +189,7 @@ mod tests { #[test] fn test_check_domain_ng_iset() { - let f = hashbrown::HashMap::::from([ - (0, Nodes::from([0, 1])), - (2, Nodes::from([2])), - ]); + let f = HashMap::::from([(0, Nodes::from([0, 1])), (2, Nodes::from([2]))]); let vset = Nodes::from([0, 1, 2]); let iset = Nodes::from([0]); let oset = Nodes::from([2]); @@ -206,8 +201,7 @@ mod tests { #[test] fn test_check_domain_ng_oset() { - let f = - hashbrown::HashMap::::from([(0, Nodes::from([1])), (1, Nodes::from([0]))]); + let f = HashMap::::from([(0, Nodes::from([1])), (1, Nodes::from([0]))]); let vset = Nodes::from([0, 1, 2]); let iset = Nodes::from([0]); let oset = Nodes::from([2]); diff --git a/src/flow.rs b/src/flow.rs index 9d961be9..5e899dfb 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -1,6 +1,7 @@ //! Maximally-delayed causal flow algorithm. -use hashbrown; +use std::collections::HashMap; + use pyo3::prelude::*; use crate::{ @@ -12,7 +13,7 @@ use crate::{ internal::utils::InPlaceSetDiff, }; -type Flow = hashbrown::HashMap; +type Flow = HashMap; /// Checks the geometric constraints of flow. /// diff --git a/src/gflow.rs b/src/gflow.rs index 6744f003..001b83ab 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -1,9 +1,9 @@ //! Maximally-delayed generalized flow algorithm. use core::iter; +use std::collections::HashMap; use fixedbitset::FixedBitSet; -use hashbrown; use pyo3::prelude::*; use crate::{ @@ -29,8 +29,8 @@ pub enum Plane { XZ, } -type Planes = hashbrown::HashMap; -type GFlow = hashbrown::HashMap; +type Planes = HashMap; +type GFlow = HashMap; /// Checks the geometric constraints of gflow. /// @@ -103,8 +103,8 @@ fn init_work( ) { let ncols = omiset.len(); // Set-to-index maps - let oc2i = utils::indexmap::>(ocset); - let omi2i = utils::indexmap::>(omiset); + let oc2i = utils::indexmap::>(ocset); + let omi2i = utils::indexmap::>(omiset); // Encode node as one-hot vector for (i, &u) in ocset.iter().enumerate() { let gu = &g[u]; diff --git a/src/internal/test_utils.rs b/src/internal/test_utils.rs index 8310d8da..54ea47ee 100644 --- a/src/internal/test_utils.rs +++ b/src/internal/test_utils.rs @@ -5,7 +5,7 @@ use std::sync::LazyLock; use crate::common::{Graph, Node, Nodes}; pub mod exports { - pub use hashbrown::{HashMap, HashSet}; + pub use std::collections::{HashMap, HashSet}; } macro_rules! map { @@ -179,17 +179,17 @@ mod tests { assert_ne!(n, 0, "empty graph"); for (u, gu) in g.iter().enumerate() { assert!(!gu.contains(&u), "self-loop detected: {u}"); - gu.iter().for_each(|&v| { + for &v in gu { assert!(v < n, "node index out of range: {v}"); assert!(g[v].contains(&u), "g must be undirected: {u} -> {v}"); - }); + } } - iset.iter().for_each(|&u| { + for &u in iset { assert!((0..n).contains(&u), "unknown node in iset: {u}"); - }); - oset.iter().for_each(|&u| { + } + for &u in oset { assert!((0..n).contains(&u), "unknown node in oset: {u}"); - }); + } } #[apply(template_tests)] diff --git a/src/internal/utils.rs b/src/internal/utils.rs index ed64e1ce..9243decb 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -4,7 +4,7 @@ use core::{ hash::Hash, ops::{Deref, DerefMut}, }; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashSet}; use fixedbitset::FixedBitSet; @@ -39,7 +39,7 @@ pub trait InPlaceSetDiff { U: Deref; } -impl InPlaceSetDiff for hashbrown::HashSet +impl InPlaceSetDiff for HashSet where T: Eq + Hash, { @@ -193,9 +193,9 @@ mod tests { #[test] fn test_difference_with_hashset() { - let mut set = hashbrown::HashSet::from([1, 2, 3]); + let mut set = HashSet::from([1, 2, 3]); set.difference_with(&[2, 3, 4]); - assert_eq!(set, hashbrown::HashSet::from([1])); + assert_eq!(set, HashSet::from([1])); } #[test] diff --git a/src/pflow.rs b/src/pflow.rs index cb53cfaa..b65c1047 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -1,9 +1,9 @@ //! Maximally-delayed Pauli flow algorithm. use core::iter; +use std::collections::HashMap; use fixedbitset::FixedBitSet; -use hashbrown; use pyo3::prelude::*; use crate::{ @@ -32,8 +32,8 @@ pub enum PPlane { Z, } -type PPlanes = hashbrown::HashMap; -type PFlow = hashbrown::HashMap; +type PPlanes = HashMap; +type PFlow = HashMap; /// Checks the geometric constraints of pflow. fn check_def_geom(f: &PFlow, g: &[Nodes], pplanes: &PPlanes) -> Result<(), FlowValidationError> { @@ -142,7 +142,7 @@ fn init_work_upper_co( rowset: &OrderedNodes, colset: &OrderedNodes, ) { - let colset2i = utils::indexmap::>(colset); + let colset2i = utils::indexmap::>(colset); for (r, &v) in rowset.iter().enumerate() { let gv = &g[v]; for &w in gv { @@ -160,7 +160,7 @@ fn init_work_lower_co( rowset: &OrderedNodes, colset: &OrderedNodes, ) { - let colset2i = utils::indexmap::>(colset); + let colset2i = utils::indexmap::>(colset); for (r, &v) in rowset.iter().enumerate() { // need to introduce self-loops if let Some(&c) = colset2i.get(&v) { @@ -192,7 +192,7 @@ fn init_work_upper_rhs( assert!(K == BRANCH_XY || K == BRANCH_YZ || K == BRANCH_XZ); }; debug_assert!(rowset.contains(&u)); - let rowset2i = utils::indexmap::>(rowset); + let rowset2i = utils::indexmap::>(rowset); let c = colset.len(); let gu = &g[u]; if K != BRANCH_YZ { @@ -221,7 +221,7 @@ fn init_work_lower_rhs( const { assert!(K == BRANCH_XY || K == BRANCH_YZ || K == BRANCH_XZ); }; - let rowset2i = utils::indexmap::>(rowset); + let rowset2i = utils::indexmap::>(rowset); let c = colset.len(); let gu = &g[u]; if K == BRANCH_XY { From eb208d25cec9851e77085aacbd4f7ad959c17f89 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:16:05 +0900 Subject: [PATCH 45/51] :heavy_plus_sign: Use maplit --- Cargo.toml | 1 + src/flow.rs | 7 ++- src/gflow.rs | 103 ++++++++++++++++--------------- src/internal.rs | 1 - src/internal/test_utils.rs | 19 ------ src/lib.rs | 4 +- src/pflow.rs | 123 +++++++++++++++++++------------------ 7 files changed, 122 insertions(+), 136 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3644dc38..f4726cfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ thiserror = "2" tracing = { version = "0.1", features = ["release_max_level_off"] } [dev-dependencies] +maplit = "1" rand = "0.9" rstest = "0.25" rstest_reuse = "0.7" diff --git a/src/flow.rs b/src/flow.rs index 5e899dfb..49e4abfe 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -149,6 +149,7 @@ pub fn verify(flow: (Flow, Option), g: Graph, iset: Nodes, oset: Nodes) #[cfg(test)] mod tests { + use maplit::hashmap; use test_log; use super::*; @@ -158,13 +159,13 @@ mod tests { fn test_check_definition_ng() { // Violate 0 -> f(0) = 1 assert_eq!( - check_def_layer(&map! { 0: 1 }, &[0, 0], &test_utils::graph(&[(0, 1)])), + check_def_layer(&hashmap! { 0 => 1 }, &[0, 0], &test_utils::graph(&[(0, 1)])), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( check_def_layer( - &map! { 0: 2 }, + &hashmap! { 0 => 2 }, &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]) ), @@ -172,7 +173,7 @@ mod tests { ); // Violate 0 in nb(f(0)) = nb(2) assert_eq!( - check_def_geom(&map! { 0: 2 }, &test_utils::graph(&[(0, 1), (1, 2)])), + check_def_geom(&hashmap! { 0 => 2 }, &test_utils::graph(&[(0, 1), (1, 2)])), Err(InconsistentFlowOrder { nodes: (0, 2) }) ); } diff --git a/src/gflow.rs b/src/gflow.rs index 001b83ab..ccd874dd 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -274,6 +274,7 @@ pub fn verify( #[cfg(test)] mod tests { + use maplit::{hashmap, hashset}; use test_log; use super::*; @@ -284,21 +285,25 @@ mod tests { // Missing Plane specification assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! {}, + &hashmap! {}, ), Err(InvalidMeasurementSpec { node: 0 }) ); // Violate 0 -> f(0) = 1 assert_eq!( - check_def_layer(&map! { 0: set!{1} }, &[0, 0], &test_utils::graph(&[(0, 1)]),), + check_def_layer( + &hashmap! { 0 => hashset!{1} }, + &[0, 0], + &test_utils::graph(&[(0, 1)]), + ), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( check_def_layer( - &map! { 0: set!{2}, 1: set!{2} }, + &hashmap! { 0 => hashset!{2}, 1 => hashset!{2} }, &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), ), @@ -307,9 +312,9 @@ mod tests { // Violate XY: 0 in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{0} }, + &hashmap! { 0 => hashset!{0} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: Plane::XY }, + &hashmap! { 0 => Plane::XY }, ), Err(InconsistentFlowPlane { node: 0, @@ -319,9 +324,9 @@ mod tests { // Violate YZ: 0 in Odd(f(0)) assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: Plane::YZ }, + &hashmap! { 0 => Plane::YZ }, ), Err(InconsistentFlowPlane { node: 0, @@ -331,9 +336,9 @@ mod tests { // Violate XZ: 0 not in Odd(f(0)) and in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{0} }, + &hashmap! { 0 => hashset!{0} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: Plane::XZ }, + &hashmap! { 0 => Plane::XZ }, ), Err(InconsistentFlowPlane { node: 0, @@ -343,9 +348,9 @@ mod tests { // Violate XZ: 0 in Odd(f(0)) and not in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: Plane::XZ }, + &hashmap! { 0 => Plane::XZ }, ), Err(InconsistentFlowPlane { node: 0, @@ -357,7 +362,7 @@ mod tests { #[test_log::test] fn test_find_case0() { let TestCase { g, iset, oset } = test_utils::CASE0.clone(); - let planes = map! {}; + let planes = hashmap! {}; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); @@ -368,11 +373,11 @@ mod tests { #[test_log::test] fn test_find_case1() { let TestCase { g, iset, oset } = test_utils::CASE1.clone(); - let planes = map! { - 0: Plane::XY, - 1: Plane::XY, - 2: Plane::XY, - 3: Plane::XY + let planes = hashmap! { + 0 => Plane::XY, + 1 => Plane::XY, + 2 => Plane::XY, + 3 => Plane::XY }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); @@ -388,11 +393,11 @@ mod tests { #[test_log::test] fn test_find_case2() { let TestCase { g, iset, oset } = test_utils::CASE2.clone(); - let planes = map! { - 0: Plane::XY, - 1: Plane::XY, - 2: Plane::XY, - 3: Plane::XY + let planes = hashmap! { + 0 => Plane::XY, + 1 => Plane::XY, + 2 => Plane::XY, + 3 => Plane::XY }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); @@ -408,10 +413,10 @@ mod tests { #[test_log::test] fn test_find_case3() { let TestCase { g, iset, oset } = test_utils::CASE3.clone(); - let planes = map! { - 0: Plane::XY, - 1: Plane::XY, - 2: Plane::XY + let planes = hashmap! { + 0 => Plane::XY, + 1 => Plane::XY, + 2 => Plane::XY }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); @@ -426,11 +431,11 @@ mod tests { #[test_log::test] fn test_find_case4() { let TestCase { g, iset, oset } = test_utils::CASE4.clone(); - let planes = map! { - 0: Plane::XY, - 1: Plane::XY, - 2: Plane::XZ, - 3: Plane::YZ + let planes = hashmap! { + 0 => Plane::XY, + 1 => Plane::XY, + 2 => Plane::XZ, + 3 => Plane::YZ }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); @@ -446,9 +451,9 @@ mod tests { #[test_log::test] fn test_find_case5() { let TestCase { g, iset, oset } = test_utils::CASE5.clone(); - let planes = map! { - 0: Plane::XY, - 1: Plane::XY + let planes = hashmap! { + 0 => Plane::XY, + 1 => Plane::XY }; assert!(find(g, iset, oset, planes).is_none()); } @@ -456,11 +461,11 @@ mod tests { #[test_log::test] fn test_find_case6() { let TestCase { g, iset, oset } = test_utils::CASE6.clone(); - let planes = map! { - 0: Plane::XY, - 1: Plane::XY, - 2: Plane::XY, - 3: Plane::XY + let planes = hashmap! { + 0 => Plane::XY, + 1 => Plane::XY, + 2 => Plane::XY, + 3 => Plane::XY }; assert!(find(g, iset, oset, planes).is_none()); } @@ -468,11 +473,11 @@ mod tests { #[test_log::test] fn test_find_case7() { let TestCase { g, iset, oset } = test_utils::CASE7.clone(); - let planes = map! { - 0: Plane::YZ, - 1: Plane::XZ, - 2: Plane::XY, - 3: Plane::YZ + let planes = hashmap! { + 0 => Plane::YZ, + 1 => Plane::XZ, + 2 => Plane::XY, + 3 => Plane::YZ }; assert!(find(g, iset, oset, planes).is_none()); } @@ -480,10 +485,10 @@ mod tests { #[test_log::test] fn test_find_case8() { let TestCase { g, iset, oset } = test_utils::CASE8.clone(); - let planes = map! { - 0: Plane::YZ, - 1: Plane::XZ, - 2: Plane::XY + let planes = hashmap! { + 0 => Plane::YZ, + 1 => Plane::XZ, + 2 => Plane::XY }; assert!(find(g, iset, oset, planes).is_none()); } diff --git a/src/internal.rs b/src/internal.rs index 151dde55..46f4aef4 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -1,5 +1,4 @@ #[cfg(test)] -#[macro_use] pub mod test_utils; pub mod gf2_linalg; diff --git a/src/internal/test_utils.rs b/src/internal/test_utils.rs index 54ea47ee..8862e0eb 100644 --- a/src/internal/test_utils.rs +++ b/src/internal/test_utils.rs @@ -4,25 +4,6 @@ use std::sync::LazyLock; use crate::common::{Graph, Node, Nodes}; -pub mod exports { - pub use std::collections::{HashMap, HashSet}; -} - -macro_rules! map { - ($($u:literal: $v:expr),*) => { - // Dirty .expect to handle i32 -> usize conversion - $crate::internal::test_utils::exports::HashMap::from_iter([$(($u, ($v).try_into().expect("dynamic coersion"))),*].into_iter()) - }; - ($($u:literal: $v:expr),*,) => {map! { $($u: $v),* }}; -} - -macro_rules! set { - ($($u:literal),*) => { - $crate::internal::test_utils::exports::HashSet::from_iter([$($u),*].into_iter()) - }; - ($($u:literal),*,) => {set! { $($u),* }}; -} - /// Creates a undirected graph from edges. pub fn graph(edges: &[(Node, Node); N]) -> Graph { let n = edges diff --git a/src/lib.rs b/src/lib.rs index eefdb877..5cc678b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,10 @@ rust_2024_compatibility )] -#[macro_use] -mod internal; - mod common; pub mod flow; pub mod gflow; +mod internal; pub mod pflow; use common::FlowValidationError; diff --git a/src/pflow.rs b/src/pflow.rs index b65c1047..fd521a63 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -466,6 +466,7 @@ pub fn verify( #[cfg(test)] mod tests { + use maplit::{hashmap, hashset}; use test_log; use super::*; @@ -476,31 +477,31 @@ mod tests { // Missing Plane specification assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! {}, + &hashmap! {}, ), Err(InvalidMeasurementSpec { node: 0 }) ); // Violate 0 -> f(0) = 1 assert_eq!( check_def_layer( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &[0, 0], &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::XY }, + &hashmap! { 0 => PPlane::XY }, ), Err(InconsistentFlowOrder { nodes: (0, 1) }) ); // Violate 1 in nb(f(0)) = nb(2) => 0 == 1 or 0 -> 1 assert_eq!( check_def_layer( - &map! { 0: set!{2}, 1: set!{2} }, + &hashmap! { 0 => hashset!{2}, 1 => hashset!{2} }, &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), - &map! { - 0: PPlane::XY, - 1: PPlane::XY + &hashmap! { + 0 => PPlane::XY, + 1 => PPlane::XY }, ), Err(InconsistentFlowOrder { nodes: (0, 1) }) @@ -508,10 +509,10 @@ mod tests { // Violate Y: 0 != 1 and not 0 -> 1 and 1 in f(0) ^ Odd(f(0)) assert_eq!( check_def_layer( - &map! { 0: set!{1}, 1: set!{2} }, + &hashmap! { 0 => hashset!{1}, 1 => hashset!{2} }, &[1, 1, 0], &test_utils::graph(&[(0, 1), (1, 2)]), - &map! { 0: PPlane::XY, 1: PPlane::Y }, + &hashmap! { 0 => PPlane::XY, 1 => PPlane::Y }, ), Err(InconsistentFlowPPlane { node: 0, @@ -521,9 +522,9 @@ mod tests { // Violate XY: 0 in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{0} }, + &hashmap! { 0 => hashset!{0} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::XY }, + &hashmap! { 0 => PPlane::XY }, ), Err(InconsistentFlowPPlane { node: 0, @@ -533,9 +534,9 @@ mod tests { // Violate YZ: 0 in Odd(f(0)) assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::YZ }, + &hashmap! { 0 => PPlane::YZ }, ), Err(InconsistentFlowPPlane { node: 0, @@ -545,9 +546,9 @@ mod tests { // Violate XZ: 0 not in Odd(f(0)) and in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{0} }, + &hashmap! { 0 => hashset!{0} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::XZ }, + &hashmap! { 0 => PPlane::XZ }, ), Err(InconsistentFlowPPlane { node: 0, @@ -557,9 +558,9 @@ mod tests { // Violate XZ: 0 in Odd(f(0)) and not in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::XZ }, + &hashmap! { 0 => PPlane::XZ }, ), Err(InconsistentFlowPPlane { node: 0, @@ -569,9 +570,9 @@ mod tests { // Violate X: 0 not in Odd(f(0)) assert_eq!( check_def_geom( - &map! { 0: set!{0} }, + &hashmap! { 0 => hashset!{0} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::X }, + &hashmap! { 0 => PPlane::X }, ), Err(InconsistentFlowPPlane { node: 0, @@ -581,9 +582,9 @@ mod tests { // Violate Z: 0 not in f(0) assert_eq!( check_def_geom( - &map! { 0: set!{1} }, + &hashmap! { 0 => hashset!{1} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::Z }, + &hashmap! { 0 => PPlane::Z }, ), Err(InconsistentFlowPPlane { node: 0, @@ -593,9 +594,9 @@ mod tests { // Violate Y: 0 in f(0) and 0 in Odd(f(0)) assert_eq!( check_def_geom( - &map! { 0: set!{0, 1} }, + &hashmap! { 0 => hashset!{0, 1} }, &test_utils::graph(&[(0, 1)]), - &map! { 0: PPlane::Y }, + &hashmap! { 0 => PPlane::Y }, ), Err(InconsistentFlowPPlane { node: 0, @@ -607,7 +608,7 @@ mod tests { #[test_log::test] fn test_find_case0() { let TestCase { g, iset, oset } = test_utils::CASE0.clone(); - let pplanes = map! {}; + let pplanes = hashmap! {}; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); @@ -618,11 +619,11 @@ mod tests { #[test_log::test] fn test_find_case1() { let TestCase { g, iset, oset } = test_utils::CASE1.clone(); - let pplanes = map! { - 0: PPlane::XY, - 1: PPlane::XY, - 2: PPlane::XY, - 3: PPlane::XY + let pplanes = hashmap! { + 0 => PPlane::XY, + 1 => PPlane::XY, + 2 => PPlane::XY, + 3 => PPlane::XY }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); @@ -638,11 +639,11 @@ mod tests { #[test_log::test] fn test_find_case2() { let TestCase { g, iset, oset } = test_utils::CASE2.clone(); - let pplanes = map! { - 0: PPlane::XY, - 1: PPlane::XY, - 2: PPlane::XY, - 3: PPlane::XY + let pplanes = hashmap! { + 0 => PPlane::XY, + 1 => PPlane::XY, + 2 => PPlane::XY, + 3 => PPlane::XY }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); @@ -658,10 +659,10 @@ mod tests { #[test_log::test] fn test_find_case3() { let TestCase { g, iset, oset } = test_utils::CASE3.clone(); - let pplanes = map! { - 0: PPlane::XY, - 1: PPlane::XY, - 2: PPlane::XY + let pplanes = hashmap! { + 0 => PPlane::XY, + 1 => PPlane::XY, + 2 => PPlane::XY }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); @@ -676,11 +677,11 @@ mod tests { #[test_log::test] fn test_find_case4() { let TestCase { g, iset, oset } = test_utils::CASE4.clone(); - let pplanes = map! { - 0: PPlane::XY, - 1: PPlane::XY, - 2: PPlane::XZ, - 3: PPlane::YZ + let pplanes = hashmap! { + 0 => PPlane::XY, + 1 => PPlane::XY, + 2 => PPlane::XZ, + 3 => PPlane::YZ }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); @@ -696,9 +697,9 @@ mod tests { #[test_log::test] fn test_find_case5() { let TestCase { g, iset, oset } = test_utils::CASE5.clone(); - let pplanes = map! { - 0: PPlane::XY, - 1: PPlane::XY + let pplanes = hashmap! { + 0 => PPlane::XY, + 1 => PPlane::XY }; assert!(find(g, iset, oset, pplanes).is_none()); } @@ -706,11 +707,11 @@ mod tests { #[test_log::test] fn test_find_case6() { let TestCase { g, iset, oset } = test_utils::CASE6.clone(); - let pplanes = map! { - 0: PPlane::XY, - 1: PPlane::X, - 2: PPlane::XY, - 3: PPlane::X + let pplanes = hashmap! { + 0 => PPlane::XY, + 1 => PPlane::X, + 2 => PPlane::XY, + 3 => PPlane::X }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); @@ -726,11 +727,11 @@ mod tests { #[test_log::test] fn test_find_case7() { let TestCase { g, iset, oset } = test_utils::CASE7.clone(); - let pplanes = map! { - 0: PPlane::Z, - 1: PPlane::Z, - 2: PPlane::Y, - 3: PPlane::Y + let pplanes = hashmap! { + 0 => PPlane::Z, + 1 => PPlane::Z, + 2 => PPlane::Y, + 3 => PPlane::Y }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); @@ -748,10 +749,10 @@ mod tests { #[test_log::test] fn test_find_case8() { let TestCase { g, iset, oset } = test_utils::CASE8.clone(); - let pplanes = map! { - 0: PPlane::Z, - 1: PPlane::XZ, - 2: PPlane::Y + let pplanes = hashmap! { + 0 => PPlane::Z, + 1 => PPlane::XZ, + 2 => PPlane::Y }; let flen = g.len() - oset.len(); let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); From a26a97ca78ec28f352d2bc9426add98b3abe2cd6 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:32:29 +0900 Subject: [PATCH 46/51] :zap: Compute odd_neighbors in-place --- src/internal/utils.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 9243decb..a7a0dcdc 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -11,15 +11,18 @@ use fixedbitset::FixedBitSet; use crate::common::{Node, Nodes, OrderedNodes}; /// Computes the odd neighbors of the nodes in `kset`. -/// -/// # Note -/// -/// - Naive implementation only for post-verification. pub fn odd_neighbors(g: &[Nodes], kset: &Nodes) -> Nodes { assert!(kset.iter().all(|&ki| ki < g.len()), "kset out of range"); - let mut work = kset.clone(); - work.extend(kset.iter().flat_map(|&ki| g[ki].iter().copied())); - work.retain(|&u| kset.intersection(&g[u]).count() % 2 == 1); + let mut work = Nodes::default(); + for &k in kset { + for &u in &g[k] { + if work.contains(&u) { + work.remove(&u); + } else { + work.insert(u); + } + } + } work } From d897bc72bb7f7c49b75e15fc6dabedaa78973ee1 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:43:06 +0900 Subject: [PATCH 47/51] :recycle: Use pub(crate) --- src/common.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common.rs b/src/common.rs index 9d35dcae..9e329f9e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -64,7 +64,7 @@ impl From for PyErr { } // TODO: Remove once stabilized -pub const FATAL_MSG: &str = "\ +pub(crate) const FATAL_MSG: &str = "\ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! POST VERIFICATION FAILED ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -81,7 +81,11 @@ https://github.com/TeamGraphix/swiflow/issues/new"; /// - `layers`: The layer. /// - `oset`: The set of output nodes. /// - `iff`: If `true`, `layers[u] == 0` "iff" `u` is in `oset`. Otherwise "if". -pub fn check_initial(layers: &[Layer], oset: &Nodes, iff: bool) -> Result<(), FlowValidationError> { +pub(crate) fn check_initial( + layers: &[Layer], + oset: &Nodes, + iff: bool, +) -> Result<(), FlowValidationError> { for (u, &lu) in layers.iter().enumerate() { match (oset.contains(&u), lu == 0) { (true, false) => { @@ -104,7 +108,7 @@ pub fn check_initial(layers: &[Layer], oset: &Nodes, iff: bool) -> Result<(), Fl /// - `vset`: All nodes. /// - `iset`: Input nodes. /// - `oset`: Output nodes. -pub fn check_domain<'a, 'b>( +pub(crate) fn check_domain<'a, 'b>( f_flatiter: impl Iterator, vset: &Nodes, iset: &Nodes, From 145b5bf35acd89c8fe9ce7f8b187f2f9a8a174bd Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:44:11 +0900 Subject: [PATCH 48/51] :construction: Call infer_layers for validation --- python/swiflow/_impl/flow.pyi | 2 +- python/swiflow/_impl/gflow.pyi | 2 +- python/swiflow/_impl/pflow.pyi | 2 +- python/swiflow/flow.py | 21 +++++++++------------ python/swiflow/gflow.py | 21 +++++++++------------ python/swiflow/pflow.py | 11 +++++++++-- src/flow.rs | 12 +++++------- src/gflow.rs | 16 +++++++--------- src/pflow.rs | 22 ++++++++++------------ 9 files changed, 52 insertions(+), 57 deletions(-) diff --git a/python/swiflow/_impl/flow.pyi b/python/swiflow/_impl/flow.pyi index f219b85c..211ae76d 100644 --- a/python/swiflow/_impl/flow.pyi +++ b/python/swiflow/_impl/flow.pyi @@ -1,6 +1,6 @@ def find(g: list[set[int]], iset: set[int], oset: set[int]) -> tuple[dict[int, int], list[int]] | None: ... def verify( - flow: tuple[dict[int, int], list[int] | None], + flow: tuple[dict[int, int], list[int]], g: list[set[int]], iset: set[int], oset: set[int], diff --git a/python/swiflow/_impl/gflow.pyi b/python/swiflow/_impl/gflow.pyi index b24b460e..bd5d88cf 100644 --- a/python/swiflow/_impl/gflow.pyi +++ b/python/swiflow/_impl/gflow.pyi @@ -7,7 +7,7 @@ def find( g: list[set[int]], iset: set[int], oset: set[int], planes: dict[int, Plane] ) -> tuple[dict[int, set[int]], list[int]] | None: ... def verify( - gflow: tuple[dict[int, set[int]], list[int] | None], + gflow: tuple[dict[int, set[int]], list[int]], g: list[set[int]], iset: set[int], oset: set[int], diff --git a/python/swiflow/_impl/pflow.pyi b/python/swiflow/_impl/pflow.pyi index 324d6f30..0f6b6c30 100644 --- a/python/swiflow/_impl/pflow.pyi +++ b/python/swiflow/_impl/pflow.pyi @@ -10,7 +10,7 @@ def find( g: list[set[int]], iset: set[int], oset: set[int], pplanes: dict[int, PPlane] ) -> tuple[dict[int, set[int]], list[int]] | None: ... def verify( - pflow: tuple[dict[int, set[int]], list[int] | None], + pflow: tuple[dict[int, set[int]], list[int]], g: list[set[int]], iset: set[int], oset: set[int], diff --git a/python/swiflow/flow.py b/python/swiflow/flow.py index ba5300b6..16058235 100644 --- a/python/swiflow/flow.py +++ b/python/swiflow/flow.py @@ -9,7 +9,7 @@ from collections.abc import Hashable, Mapping from typing import TYPE_CHECKING, TypeVar -from swiflow import _common +from swiflow import _common, common from swiflow._common import IndexMap from swiflow._impl import flow as flow_bind from swiflow.common import Flow, Layers @@ -60,16 +60,6 @@ def find(g: nx.Graph[_V], iset: AbstractSet[_V], oset: AbstractSet[_V]) -> FlowR _Layer = Mapping[_V, int] -def _codec_wrap( - codec: IndexMap[_V], - flow: tuple[_Flow[_V], _Layer[_V]] | _Flow[_V], -) -> tuple[dict[int, int], list[int] | None]: - if isinstance(flow, tuple): - f, layers = flow - return codec.encode_flow(f), codec.encode_layers(layers) - return codec.encode_flow(flow), None - - def verify( flow: tuple[_Flow[_V], _Layer[_V]] | _Flow[_V], g: nx.Graph[_V], @@ -100,4 +90,11 @@ def verify( g_ = codec.encode_graph(g) iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) - codec.ecatch(flow_bind.verify, _codec_wrap(codec, flow), g_, iset_, oset_) + if isinstance(flow, tuple): + f, layers = flow + common.infer_layers(g, f) + else: + f = flow + layers = common.infer_layers(g, f) + f_ = (codec.encode_flow(f), codec.encode_layers(layers)) + codec.ecatch(flow_bind.verify, f_, g_, iset_, oset_) diff --git a/python/swiflow/gflow.py b/python/swiflow/gflow.py index 6fa1bfdb..7b7ec418 100644 --- a/python/swiflow/gflow.py +++ b/python/swiflow/gflow.py @@ -10,7 +10,7 @@ from collections.abc import Set as AbstractSet from typing import TYPE_CHECKING, TypeVar -from swiflow import _common +from swiflow import _common, common from swiflow._common import IndexMap from swiflow._impl import gflow as gflow_bind from swiflow.common import GFlow, Layers, Plane @@ -72,16 +72,6 @@ def find( _Layer = Mapping[_V, int] -def _codec_wrap( - codec: IndexMap[_V], - gflow: tuple[_GFlow[_V], _Layer[_V]] | _GFlow[_V], -) -> tuple[dict[int, set[int]], list[int] | None]: - if isinstance(gflow, tuple): - f, layers = gflow - return codec.encode_gflow(f), codec.encode_layers(layers) - return codec.encode_gflow(gflow), None - - def verify( gflow: tuple[_GFlow[_V], _Layer[_V]] | _GFlow[_V], g: nx.Graph[_V], @@ -120,4 +110,11 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) planes_ = codec.encode_dictkey(planes) - codec.ecatch(gflow_bind.verify, _codec_wrap(codec, gflow), g_, iset_, oset_, planes_) + if isinstance(gflow, tuple): + f, layers = gflow + common.infer_layers(g, f) + else: + f = gflow + layers = common.infer_layers(g, f) + f_ = (codec.encode_gflow(f), codec.encode_layers(layers)) + codec.ecatch(gflow_bind.verify, f_, g_, iset_, oset_, planes_) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index fb32f65d..13f92f4a 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -11,7 +11,7 @@ from collections.abc import Set as AbstractSet from typing import TYPE_CHECKING, TypeVar -from swiflow import _common +from swiflow import _common, common from swiflow._common import IndexMap from swiflow._impl import pflow as pflow_bind from swiflow.common import Layers, PFlow, PPlane @@ -128,4 +128,11 @@ def verify( iset_ = codec.encode_set(iset) oset_ = codec.encode_set(oset) pplanes_ = codec.encode_dictkey(pplanes) - codec.ecatch(pflow_bind.verify, _codec_wrap(codec, pflow), g_, iset_, oset_, pplanes_) + if isinstance(pflow, tuple): + f, layers = pflow + common.infer_layers(g, f, pplanes) + else: + f = pflow + layers = common.infer_layers(g, f, pplanes) + f_ = (codec.encode_gflow(f), codec.encode_layers(layers)) + codec.ecatch(pflow_bind.verify, f_, g_, iset_, oset_, pplanes_) diff --git a/src/flow.rs b/src/flow.rs index 49e4abfe..f29f19ff 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -135,15 +135,13 @@ pub fn find(g: Graph, iset: Nodes, mut oset: Nodes) -> Option<(Flow, Layers)> { #[pyfunction] #[expect(clippy::needless_pass_by_value)] #[inline] -pub fn verify(flow: (Flow, Option), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { +pub fn verify(flow: (Flow, Layers), g: Graph, iset: Nodes, oset: Nodes) -> PyResult<()> { let (f, layers) = flow; let n = g.len(); let vset = (0..n).collect::(); common::check_domain(f.iter(), &vset, &iset, &oset)?; check_def_geom(&f, &g)?; - if let Some(layers) = layers { - check_def_layer(&f, &layers, &g)?; - } + check_def_layer(&f, &layers, &g)?; Ok(()) } @@ -185,7 +183,7 @@ mod tests { let (f, layers) = find(g.clone(), iset.clone(), oset.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layers, vec![0, 0]); - verify((f, Some(layers)), g, iset, oset).unwrap(); + verify((f, layers), g, iset, oset).unwrap(); } #[test_log::test] @@ -199,7 +197,7 @@ mod tests { assert_eq!(f[&2], 3); assert_eq!(f[&3], 4); assert_eq!(layers, vec![4, 3, 2, 1, 0]); - verify((f, Some(layers)), g, iset, oset).unwrap(); + verify((f, layers), g, iset, oset).unwrap(); } #[test_log::test] @@ -213,7 +211,7 @@ mod tests { assert_eq!(f[&2], 4); assert_eq!(f[&3], 5); assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layers)), g, iset, oset).unwrap(); + verify((f, layers), g, iset, oset).unwrap(); } #[test_log::test] diff --git a/src/gflow.rs b/src/gflow.rs index ccd874dd..aa05a101 100644 --- a/src/gflow.rs +++ b/src/gflow.rs @@ -252,7 +252,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, planes: Planes) -> Option<(GFlow #[expect(clippy::needless_pass_by_value)] #[inline] pub fn verify( - gflow: (GFlow, Option), + gflow: (GFlow, Layers), g: Graph, iset: Nodes, oset: Nodes, @@ -266,9 +266,7 @@ pub fn verify( .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); common::check_domain(f_flatiter, &vset, &iset, &oset)?; check_def_geom(&f, &g, &planes)?; - if let Some(layers) = layers { - check_def_layer(&f, &layers, &g)?; - } + check_def_layer(&f, &layers, &g)?; Ok(()) } @@ -367,7 +365,7 @@ mod tests { let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), planes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layers, vec![0, 0]); - verify((f, Some(layers)), g, iset, oset, planes).unwrap(); + verify((f, layers), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -387,7 +385,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layers, vec![4, 3, 2, 1, 0]); - verify((f, Some(layers)), g, iset, oset, planes).unwrap(); + verify((f, layers), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -407,7 +405,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layers)), g, iset, oset, planes).unwrap(); + verify((f, layers), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -425,7 +423,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layers, vec![1, 1, 1, 0, 0, 0]); - verify((f, Some(layers)), g, iset, oset, planes).unwrap(); + verify((f, layers), g, iset, oset, planes).unwrap(); } #[test_log::test] @@ -445,7 +443,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layers)), g, iset, oset, planes).unwrap(); + verify((f, layers), g, iset, oset, planes).unwrap(); } #[test_log::test] diff --git a/src/pflow.rs b/src/pflow.rs index fd521a63..59ed727b 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -444,7 +444,7 @@ pub fn find(g: Graph, iset: Nodes, oset: Nodes, pplanes: PPlanes) -> Option<(PFl #[expect(clippy::needless_pass_by_value)] #[inline] pub fn verify( - pflow: (PFlow, Option), + pflow: (PFlow, Layers), g: Graph, iset: Nodes, oset: Nodes, @@ -458,9 +458,7 @@ pub fn verify( .flat_map(|(i, fi)| Iterator::zip(iter::repeat(i), fi.iter())); common::check_domain(f_flatiter, &vset, &iset, &oset)?; check_def_geom(&f, &g, &pplanes)?; - if let Some(layers) = layers { - check_def_layer(&f, &layers, &g, &pplanes)?; - } + check_def_layer(&f, &layers, &g, &pplanes)?; Ok(()) } @@ -613,7 +611,7 @@ mod tests { let (f, layers) = find(g.clone(), iset.clone(), oset.clone(), pplanes.clone()).unwrap(); assert_eq!(f.len(), flen); assert_eq!(layers, vec![0, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -633,7 +631,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layers, vec![4, 3, 2, 1, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -653,7 +651,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([4])); assert_eq!(f[&3], Nodes::from([5])); assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -671,7 +669,7 @@ mod tests { assert_eq!(f[&1], Nodes::from([3, 4, 5])); assert_eq!(f[&2], Nodes::from([3, 5])); assert_eq!(layers, vec![1, 1, 1, 0, 0, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -691,7 +689,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2, 4])); assert_eq!(f[&3], Nodes::from([3])); assert_eq!(layers, vec![2, 2, 1, 1, 0, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -721,7 +719,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([3])); assert_eq!(f[&3], Nodes::from([2, 4])); assert_eq!(layers, vec![1, 1, 0, 1, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -743,7 +741,7 @@ mod tests { assert_eq!(f[&2], Nodes::from([2])); assert_eq!(f[&3], Nodes::from([4])); assert_eq!(layers, vec![1, 0, 0, 1, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } #[test_log::test] @@ -763,6 +761,6 @@ mod tests { assert_eq!(f[&1], Nodes::from([1, 2])); assert_eq!(f[&2], Nodes::from([4])); assert_eq!(layers, vec![1, 1, 1, 0, 0]); - verify((f, Some(layers)), g, iset, oset, pplanes).unwrap(); + verify((f, layers), g, iset, oset, pplanes).unwrap(); } } From 12d8e47336c8acf0a0ac9f00d47d893a877db784 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:46:48 +0900 Subject: [PATCH 49/51] :children_crossing: Change error message --- python/swiflow/common.py | 2 +- tests/test_common.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/swiflow/common.py b/python/swiflow/common.py index ba20d1d2..5dcb6706 100644 --- a/python/swiflow/common.py +++ b/python/swiflow/common.py @@ -54,7 +54,7 @@ def _infer_layers_impl(pred: Mapping[_V, MutableSet[_V]], succ: Mapping[_V, Abst next_work.add(v) work = next_work if len(ret) != len(succ): - msg = "Failed to determine layers for all nodes." + msg = "Cannot satisfy all the partial order constraints." raise ValueError(msg) return ret diff --git a/tests/test_common.py b/tests/test_common.py index e8d02263..2b7023e7 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -142,5 +142,5 @@ def test_dag(self) -> None: def test_cycle(self) -> None: g: nx.Graph[int] = nx.Graph([(0, 1), (1, 2), (2, 0)]) flow = {0: {1}, 1: {2}, 2: {0}} - with pytest.raises(ValueError, match=r".*determine.*"): + with pytest.raises(ValueError, match=r".*constraints.*"): common.infer_layers(g, flow) From 1254cd72b76c65dfcbf214f0f910b3aaa14499c0 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Thu, 28 Aug 2025 21:07:44 +0900 Subject: [PATCH 50/51] :coffin: Remove unused code --- python/swiflow/pflow.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/python/swiflow/pflow.py b/python/swiflow/pflow.py index 13f92f4a..24e3976b 100644 --- a/python/swiflow/pflow.py +++ b/python/swiflow/pflow.py @@ -80,16 +80,6 @@ def find( _Layer = Mapping[_V, int] -def _codec_wrap( - codec: IndexMap[_V], - pflow: tuple[_PFlow[_V], _Layer[_V]] | _PFlow[_V], -) -> tuple[dict[int, set[int]], list[int] | None]: - if isinstance(pflow, tuple): - f, layers = pflow - return codec.encode_gflow(f), codec.encode_layers(layers) - return codec.encode_gflow(pflow), None - - def verify( pflow: tuple[_PFlow[_V], _Layer[_V]] | _PFlow[_V], g: nx.Graph[_V], From 2be0f29de6feb5ea3006aa76dc60ba12773538c4 Mon Sep 17 00:00:00 2001 From: SS <66886825+EarlMilktea@users.noreply.github.com> Date: Fri, 29 Aug 2025 21:42:34 +0900 Subject: [PATCH 51/51] :bulb: Note on codimain violation --- src/pflow.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pflow.rs b/src/pflow.rs index 59ed727b..2390b501 100644 --- a/src/pflow.rs +++ b/src/pflow.rs @@ -264,6 +264,7 @@ fn decode_solution(u: Node, x: &FixedBitSet, colset: &Order .filter_map(|(i, &v)| if x[i] { Some(v) } else { None }) .collect::(); if K != BRANCH_XY { + // MEMO: Violating `f(u) in V\I` ? fu.insert(u); } fu