From abb9fa4fd5c08264cefdf0b945027ac3ece9acde Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 3 Feb 2025 20:28:34 +0900 Subject: [PATCH 01/72] :white_check_mark: Add tests for convert_to_phase_gadget --- tests/test_zxgraphstate.py | 107 +++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 4ec0caa88..db3e599f1 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -983,5 +983,112 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: assert clifford_nodes == [] +@pytest.mark.parametrize( + ("measurements", "exp_measurements", "exp_edges"), + [ + # no pair of adjacent nodes with YZ measurements + # and no node with XZ measurement + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, + ), + # a pair of adjacent nodes with YZ measurements + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 0.22 * np.pi), + (3, Plane.YZ, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 1.44 * np.pi), + (5, Plane.XY, 1.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + {(1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (4, 6), (5, 6)}, + ), + # a node with XZ measurement + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XZ, 0.44 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 1.72 * np.pi), + (3, Plane.XY, 1.83 * np.pi), + (4, Plane.XY, 1.94 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + {(1, 2), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, + ), + # a pair of adjacent nodes with YZ measurements and a node with XZ measurement + ( + [ + (1, Plane.XZ, 0.11 * np.pi), + (2, Plane.YZ, 0.22 * np.pi), + (3, Plane.YZ, 0.33 * np.pi), + (4, Plane.XZ, 0.44 * np.pi), + (5, Plane.XZ, 0.55 * np.pi), + (6, Plane.XZ, 0.66 * np.pi), + ], + [ + (1, Plane.XY, 0.61 * np.pi), + (2, Plane.XY, 1.22 * np.pi), + (3, Plane.XY, 1.83 * np.pi), + (4, Plane.XY, 1.56 * np.pi), + (5, Plane.XY, 1.45 * np.pi), + (6, Plane.YZ, 0.66 * np.pi), + ], + {(1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (2, 5), (2, 6), (3, 6), (4, 5)}, + ), + ], +) +def test_convert_to_phase_gadget( + zx_graph: ZXGraphState, + measurements: list[tuple[int, Plane, float]], + exp_measurements: list[tuple[int, Plane, float]], + exp_edges: set[tuple[int, int]], +) -> None: + _initialize_graph( + zx_graph, + nodes=range(1, 7), + edges=[(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)], + ) + _apply_measurements(zx_graph, measurements) + zx_graph.convert_to_phase_gadget() + _test( + zx_graph, + exp_nodes={1, 2, 3, 4, 5, 6}, + exp_edges=exp_edges, + exp_measurements=exp_measurements, + ) + + if __name__ == "__main__": pytest.main() From 5ef6d904c31a26c6f60fc196115ea032f9e25e9d Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 3 Feb 2025 20:29:17 +0900 Subject: [PATCH 02/72] :sparkles: Add convert_to_phase_gadget to ZXGraphState --- graphix_zx/zxgraphstate.py | 47 +++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index e67724f19..8ea6b061d 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -30,7 +30,7 @@ class ZXGraphState(GraphState): set of output nodes physical_nodes : set[int] set of physical nodes - physical_edges : dict[int, set[int]] + physical_edges : set[tuple[int]] physical edges meas_bases : dict[int, MeasBasis] q_indices : dict[int, int] @@ -509,3 +509,48 @@ def remove_cliffords(self, atol: float = 1e-9) -> None: ] for action_func, check_func in steps: self._remove_cliffords(action_func, check_func, atol) + + def _extract_yz_adjacent_pair(self) -> tuple[int, int] | None: + """Call inside convert_to_phase_gadget. + + Find a pair of adjacent nodes that are both measured in the YZ-plane. + + Returns + ------- + tuple[int, int] | None + A pair of adjacent nodes that are both measured in the YZ-plane, or None if no such pair exists. + """ + yz_nodes = {node for node, basis in self.meas_bases.items() if basis.plane == Plane.YZ} + for u in yz_nodes: + for v in self.get_neighbors(u): + if v in yz_nodes: + return (min(u, v), max(u, v)) + return None + + def _extract_xz_node(self) -> int | None: + """Call inside convert_to_phase_gadget. + + Find a node that is measured in the XZ-plane. + + Returns + ------- + int | None + A node that is measured in the XZ-plane, or None if no such node exists. + """ + for node, basis in self.meas_bases.items(): + if basis.plane == Plane.XZ: + return node + return None + + def convert_to_phase_gadget(self) -> None: + """Convert a ZX-diagram with gflow in MBQC+LC form into its phase-gadget form while preserving gflow.""" + while True: + if pair := self._extract_yz_adjacent_pair(): + self.pivot(*pair) + del pair + continue + if u := self._extract_xz_node(): + self.local_complement(u) + del u + continue + break From ecd81a0c38f86f0c0f6f89c1317ec92d94dbdbae Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 5 Feb 2025 20:23:29 +0900 Subject: [PATCH 03/72] :art: Add diagramatic representation for each tests in convert_to_phase_gadget --- tests/test_zxgraphstate.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index db3e599f1..2e478d322 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1008,6 +1008,11 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, ), # a pair of adjacent nodes with YZ measurements + # 4(XY) 4(XY) 4 4 4 + # / \ | | | | + # 1(XY) - 2(YZ) - 3(YZ) - 6(XY) -> 1(XY) - 3(XY) - 2(XY) - 6(XY) - 1 + # \ / | | | | + # 5(XY) 5(XY) 5 5 5 ( [ (1, Plane.XY, 0.11 * np.pi), @@ -1027,7 +1032,13 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: ], {(1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (4, 6), (5, 6)}, ), - # a node with XZ measurement + # no pair of adjacent nodes with YZ measurements + # but a node with XZ measurement + # 4(XZ) 4(XY) + # / \ / \ + # 1(XY) - 2(XY) - 3(XY) - 6(XY) -> 1(XY) - 2(XY) 3(XY) - 6(XY) + # \ / \ / + # 5(XY) 5(XY) ( [ (1, Plane.XY, 0.11 * np.pi), @@ -1047,7 +1058,13 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: ], {(1, 2), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, ), - # a pair of adjacent nodes with YZ measurements and a node with XZ measurement + # a pair of adjacent nodes with YZ measurements + # and a node with XZ measurement + # 4(XZ) 6(YZ) - 3(XY) + # / \ | x | + # 1(XZ) - 2(YZ) - 3(YZ) - 6(XZ) -> 1(XY) 2(XY) + # \ / | x | + # 5(XZ) 5(XY) - 4(XY) ( [ (1, Plane.XZ, 0.11 * np.pi), From d15f954a06f0394a170637630c75a99987c343a9 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 5 Feb 2025 21:33:44 +0900 Subject: [PATCH 04/72] :white_check_mark: Add tests for merge_yz_to_xy --- tests/test_zxgraphstate.py | 81 +++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 2e478d322..31494890e 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -25,7 +25,7 @@ def zx_graph() -> ZXGraphState: def _initialize_graph( zx_graph: ZXGraphState, nodes: range, - edges: list[tuple[int, int]], + edges: set[tuple[int, int]], inputs: tuple[int, ...] = (), outputs: tuple[int, ...] = (), ) -> None: @@ -125,7 +125,7 @@ def test_local_complement_with_no_edge(zx_graph: ZXGraphState) -> None: def test_local_complement_on_output_node(zx_graph: ZXGraphState) -> None: """Test local complement on an output node.""" - _initialize_graph(zx_graph, range(1, 4), [(1, 2), (2, 3)], outputs=(2,)) + _initialize_graph(zx_graph, range(1, 4), {(1, 2), (2, 3)}, outputs=(2,)) measurements = [ (1, Plane.XY, 1.1 * np.pi), (3, Plane.YZ, 1.3 * np.pi), @@ -470,13 +470,13 @@ def graph_1(zx_graph: ZXGraphState) -> None: # 4---1---2 4 2 # | -> # 3 3 - _initialize_graph(zx_graph, nodes=range(1, 5), edges=[(1, 2), (1, 3), (1, 4)]) + _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) def graph_2(zx_graph: ZXGraphState) -> None: # _needs_lc # 1---2---3 -> 1---3 - _initialize_graph(zx_graph, nodes=range(1, 4), edges=[(1, 2), (2, 3)]) + _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}) def graph_3(zx_graph: ZXGraphState) -> None: @@ -487,7 +487,7 @@ def graph_3(zx_graph: ZXGraphState) -> None: # \ / \ | / # 5(I) 5(I) _initialize_graph( - zx_graph, nodes=range(1, 7), edges=[(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)], inputs=(1, 4, 5) + zx_graph, nodes=range(1, 7), edges={(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, inputs=(1, 4, 5) ) @@ -499,7 +499,7 @@ def graph_4(zx_graph: ZXGraphState) -> None: _initialize_graph( zx_graph, nodes=range(1, 6), - edges=[(1, 2), (2, 3), (2, 4), (3, 4), (4, 5)], + edges={(1, 2), (2, 3), (2, 4), (3, 4), (4, 5)}, inputs=(1,), outputs=(2, 3, 5), ) @@ -856,7 +856,7 @@ def test_remove_clifford_pivot2_with_xz_1p5_pi(zx_graph: ZXGraphState) -> None: def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: - _initialize_graph(zx_graph, nodes=range(1, 4), edges=[(1, 2), (2, 3)], inputs=(1, 3)) + _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}, inputs=(1, 3)) measurements = [ (1, Plane.XY, 0.5 * np.pi), (2, Plane.XY, np.pi), @@ -869,7 +869,7 @@ def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: def test_remove_cliffords(zx_graph: ZXGraphState) -> None: """Test removing multiple Clifford vertices.""" - _initialize_graph(zx_graph, nodes=range(1, 5), edges=[(1, 2), (1, 3), (1, 4)]) + _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) measurements = [ (1, Plane.XY, 0.5 * np.pi), (2, Plane.XY, 0.5 * np.pi), @@ -1095,7 +1095,7 @@ def test_convert_to_phase_gadget( _initialize_graph( zx_graph, nodes=range(1, 7), - edges=[(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)], + edges={(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, ) _apply_measurements(zx_graph, measurements) zx_graph.convert_to_phase_gadget() @@ -1107,5 +1107,68 @@ def test_convert_to_phase_gadget( ) +@pytest.mark.parametrize( + ("initial_edges", "measurements", "exp_measurements", "exp_edges"), + [ + # 4(XY) 4(XY) + # | -> | + # 1(YZ) - 2(XY) - 3(XY) 2(XY) - 3(XY) + ( + {(1, 2), (2, 3), (2, 4)}, + [ + (1, Plane.YZ, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + ], + [ + (2, Plane.XY, 0.33 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + ], + {(2, 3), (2, 4)}, + ), + # 4(YZ) 4(YZ) + # | \ -> | \ + # 1(YZ) - 2(XY) - 3(XY) 2(XY) - 3(XY) + ( + {(1, 2), (2, 3), (2, 4), (3, 4)}, + [ + (1, Plane.YZ, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + ], + [ + (2, Plane.XY, 0.33 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + ], + {(2, 3), (2, 4), (3, 4)}, + ), + ], +) +def test_merge_yz_to_xy( + zx_graph: ZXGraphState, + initial_edges: list[tuple[int, Plane, float]], + measurements: list[tuple[int, Plane, float]], + exp_measurements: list[tuple[int, Plane, float]], + exp_edges: set[tuple[int, int]], +) -> None: + _initialize_graph( + zx_graph, + nodes=range(1, 5), + edges=initial_edges, + ) + _apply_measurements(zx_graph, measurements) + zx_graph.merge_yz_to_xy() + _test( + zx_graph, + exp_nodes={2, 3, 4}, + exp_edges=exp_edges, + exp_measurements=exp_measurements, + ) + + if __name__ == "__main__": pytest.main() From 912cc8a65fc975e4c360332d99176fe1ea802e09 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 5 Feb 2025 21:33:55 +0900 Subject: [PATCH 05/72] :art: Add merge_yz_to_xy --- graphix_zx/zxgraphstate.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 8ea6b061d..48fb44354 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -554,3 +554,21 @@ def convert_to_phase_gadget(self) -> None: del u continue break + + def merge_yz_to_xy(self) -> None: + """Merge YZ-measured nodes that have only one neighbor with an XY-measured node. + + If a node u is measured in the YZ-plane and u has only one neighbor v with a XY-measurement, + then the node u can be merged into the node v. + """ + target_candidates = { + u for u, basis in self.meas_bases.items() if (basis.plane == Plane.YZ and len(self.get_neighbors(u)) == 1) + } + target_nodes = { + u for u in target_candidates if self.meas_bases[next(iter(self.get_neighbors(u)))].plane == Plane.XY + } + for u in target_nodes: + v = self.get_neighbors(u).pop() + new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) + self.set_meas_basis(v, PlannerMeasBasis(Plane.XY, new_angle)) + self.remove_physical_node(u) From eb6f70e6e1876539f8f147f366accf0eb705cdab Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 6 Feb 2025 20:47:20 +0900 Subject: [PATCH 06/72] :art: Improve tests' readability --- tests/test_zxgraphstate.py | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 31494890e..aca91de07 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1092,19 +1092,11 @@ def test_convert_to_phase_gadget( exp_measurements: list[tuple[int, Plane, float]], exp_edges: set[tuple[int, int]], ) -> None: - _initialize_graph( - zx_graph, - nodes=range(1, 7), - edges={(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, - ) + initial_edges = {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)} + _initialize_graph(zx_graph, nodes=range(1, 7), edges=initial_edges) _apply_measurements(zx_graph, measurements) zx_graph.convert_to_phase_gadget() - _test( - zx_graph, - exp_nodes={1, 2, 3, 4, 5, 6}, - exp_edges=exp_edges, - exp_measurements=exp_measurements, - ) + _test(zx_graph, exp_nodes={1, 2, 3, 4, 5, 6}, exp_edges=exp_edges, exp_measurements=exp_measurements) @pytest.mark.parametrize( @@ -1150,24 +1142,15 @@ def test_convert_to_phase_gadget( ) def test_merge_yz_to_xy( zx_graph: ZXGraphState, - initial_edges: list[tuple[int, Plane, float]], + initial_edges: set[tuple[int, int]], measurements: list[tuple[int, Plane, float]], exp_measurements: list[tuple[int, Plane, float]], exp_edges: set[tuple[int, int]], ) -> None: - _initialize_graph( - zx_graph, - nodes=range(1, 5), - edges=initial_edges, - ) + _initialize_graph(zx_graph, nodes=range(1, 5), edges=initial_edges) _apply_measurements(zx_graph, measurements) zx_graph.merge_yz_to_xy() - _test( - zx_graph, - exp_nodes={2, 3, 4}, - exp_edges=exp_edges, - exp_measurements=exp_measurements, - ) + _test(zx_graph, exp_nodes={2, 3, 4}, exp_edges=exp_edges, exp_measurements=exp_measurements) if __name__ == "__main__": From 260577a12a536ccceffc2e7bc0052b795c101bb7 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 6 Feb 2025 20:57:17 +0900 Subject: [PATCH 07/72] :white_check_mark: Add tests for merge_yz_nodes --- tests/test_zxgraphstate.py | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index aca91de07..a3cae6981 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1153,5 +1153,96 @@ def test_merge_yz_to_xy( _test(zx_graph, exp_nodes={2, 3, 4}, exp_edges=exp_edges, exp_measurements=exp_measurements) +@pytest.mark.parametrize( + ("initial_edges", "measurements", "exp_zxgraph"), + [ + # 4(YZ) 4(YZ) + # / \ / \ + # 1(XY) - 2(XY) - 3(XY) -> 1(XY) - 2(XY) - 3(XY) + # \ / + # 5(YZ) + ( + {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + (5, Plane.YZ, 0.55 * np.pi), + ], + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.99 * np.pi), + ], + {(1, 2), (1, 4), (2, 3), (3, 4)}, + {1, 2, 3, 4}, + ), + ), + # 4(YZ) + # / \ + # 1(XY) - 2(YZ) - 3(XY) -> 1(XY) - 2(YZ) - 3(XY) + # \ / + # 5(YZ) + ( + {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + (5, Plane.YZ, 0.55 * np.pi), + ], + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 1.21 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + ], + {(1, 2), (2, 3)}, + {1, 2, 3}, + ), + ), + # 4(YZ) + # / \ + # 1(XY) - 2(YZ) - 3(XY) - 1(XY) -> 1(XY) - 2(YZ) - 3(XY) - 1(XY) + # \ / + # 5(YZ) + ( + {(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + (5, Plane.YZ, 0.55 * np.pi), + ], + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 1.21 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + ], + {(1, 2), (1, 3), (2, 3)}, + {1, 2, 3}, + ), + ), + ], +) +def test_merge_yz_nodes( + zx_graph: ZXGraphState, + initial_edges: set[tuple[int, int]], + measurements: list[tuple[int, Plane, float]], + exp_zxgraph: tuple[list[tuple[int, Plane, float]], set[tuple[int, int]], set[int]], +) -> None: + _initialize_graph(zx_graph, nodes=range(1, 6), edges=initial_edges) + _apply_measurements(zx_graph, measurements) + zx_graph.merge_yz_nodes() + exp_measurements, exp_edges, exp_nodes = exp_zxgraph + _test(zx_graph, exp_nodes, exp_edges, exp_measurements) + + if __name__ == "__main__": pytest.main() From 0777029415f9464b572f3fdb89898d69f340db3b Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 6 Feb 2025 20:57:36 +0900 Subject: [PATCH 08/72] :art: Add merge_yz_nodes --- graphix_zx/zxgraphstate.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 48fb44354..8a14a5df0 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -572,3 +572,23 @@ def merge_yz_to_xy(self) -> None: new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) self.set_meas_basis(v, PlannerMeasBasis(Plane.XY, new_angle)) self.remove_physical_node(u) + + def merge_yz_nodes(self) -> None: + """Merge isolated YZ-measured nodes into a single node. + + If u, v nodes are measured in the YZ-plane and u, v have the same neighbors, + then u, v can be merged into a single node. + """ + while True: + yz_nodes = {u for u, basis in self.meas_bases.items() if basis.plane == Plane.YZ} + least_nodes = 2 + if len(yz_nodes) < least_nodes: + break + u = yz_nodes.pop() + for v in yz_nodes: + if u > v or self.get_neighbors(u) != self.get_neighbors(v): + continue + + new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) + self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) + self.remove_physical_node(v) From 735b32dc3f55752868f637d781e89d7bea1260ad Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 19:26:03 +0900 Subject: [PATCH 09/72] :recycle: Refactor test codes --- tests/test_zxgraphstate.py | 561 +++++++++++++++++-------------------- 1 file changed, 257 insertions(+), 304 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index a3cae6981..5aebeb2d7 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -519,261 +519,226 @@ def _test_remove_clifford( _test(zx_graph, exp_nodes, exp_edges, exp_measurements) -def test_remove_clifford_removable_with_xz_0(zx_graph: ZXGraphState) -> None: - """Test removing a removable Clifford vertex with measurement plane XZ and angle 0.""" +@pytest.mark.parametrize( + ("measurements", "exp_measurements"), + [ + # XZ plane with angle 0 + ( + [ + (1, Plane.XZ, 0), + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ], + [ + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ], + ), + # XZ plane with angle pi + ( + [ + (1, Plane.XZ, np.pi), + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ], + [ + (2, Plane.XY, 1.1 * np.pi), + (3, Plane.XZ, 1.8 * np.pi), + (4, Plane.YZ, 1.7 * np.pi), + ], + ), + # YZ plane with angle 0 + ( + [ + (1, Plane.YZ, 0), + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ], + [ + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ], + ), + # YZ plane with angle pi + ( + [ + (1, Plane.YZ, np.pi), + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ], + [ + (2, Plane.XY, 1.1 * np.pi), + (3, Plane.XZ, 1.8 * np.pi), + (4, Plane.YZ, 1.7 * np.pi), + ], + ), + ], +) +def test_remove_clifford( + zx_graph: ZXGraphState, + measurements: list[tuple[int, Plane, float]], + exp_measurements: list[tuple[int, Plane, float]], +) -> None: + """Test removing a removable Clifford vertex.""" graph_1(zx_graph) - measurements = [ - (1, Plane.XZ, 0), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] - exp_measurements = [ - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] _test_remove_clifford( zx_graph, node=1, measurements=measurements, exp_graph=({2, 3, 4}, set()), exp_measurements=exp_measurements ) -def test_remove_clifford_removable_with_xz_pi(zx_graph: ZXGraphState) -> None: - """Test removing a removable Clifford vertex with measurement plane XZ and angle pi.""" - graph_1(zx_graph) - measurements = [ - (1, Plane.XZ, np.pi), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] - exp_measurements = [ - (2, Plane.XY, 1.1 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=1, - measurements=measurements, - exp_graph=({2, 3, 4}, set()), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_removable_with_yz_0(zx_graph: ZXGraphState) -> None: - """Test removing a removable Clifford vertex with measurement plane YZ and angle 0.""" - graph_1(zx_graph) - measurements = [ - (1, Plane.YZ, 0), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] - exp_measurements = [ - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=1, - measurements=measurements, - exp_graph=({2, 3, 4}, set()), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_removable_with_yz_pi(zx_graph: ZXGraphState) -> None: - """Test removing a removable Clifford vertex with measurement plane YZ and angle pi.""" - graph_1(zx_graph) - measurements = [ - (1, Plane.YZ, np.pi), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] - exp_measurements = [ - (2, Plane.XY, 1.1 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=1, - measurements=measurements, - exp_graph=({2, 3, 4}, set()), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_lc_with_xy_0p5_pi(zx_graph: ZXGraphState) -> None: - graph_2(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, 0.5 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 1.6 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - ] - _test_remove_clifford( - zx_graph, node=2, measurements=measurements, exp_graph=({1, 3}, {(1, 3)}), exp_measurements=exp_measurements - ) - - -def test_remove_clifford_lc_with_xy_1p5_pi(zx_graph: ZXGraphState) -> None: +@pytest.mark.parametrize( + ("measurements", "exp_measurements"), + [ + # XY plane with angle 0.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XY, 0.5 * np.pi), + (3, Plane.YZ, 0.2 * np.pi), + ], + [ + (1, Plane.XY, 1.6 * np.pi), + (3, Plane.XZ, 1.8 * np.pi), + ], + ), + # XY plane with angle 1.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XY, 1.5 * np.pi), + (3, Plane.YZ, 0.2 * np.pi), + ], + [ + (1, Plane.XY, 0.6 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + ], + ), + # YZ plane with angle 0.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.YZ, 0.5 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + ], + [ + (1, Plane.XY, 0.6 * np.pi), + (3, Plane.YZ, 1.8 * np.pi), + ], + ), + # YZ plane with angle 1.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.YZ, 1.5 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + ], + [ + (1, Plane.XY, 1.6 * np.pi), + (3, Plane.YZ, 0.2 * np.pi), + ], + ), + ], +) +def test_remove_clifford_lc( + zx_graph: ZXGraphState, + measurements: list[tuple[int, Plane, float]], + exp_measurements: list[tuple[int, Plane, float]], +) -> None: graph_2(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, 1.5 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.6 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ] _test_remove_clifford( zx_graph, node=2, measurements=measurements, exp_graph=({1, 3}, {(1, 3)}), exp_measurements=exp_measurements ) -def test_remove_clifford_lc_with_yz_0p5_pi(zx_graph: ZXGraphState) -> None: - graph_2(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.YZ, 0.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.6 * np.pi), - (3, Plane.YZ, 1.8 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=2, - measurements=measurements, - exp_graph=({1, 3}, {(1, 3)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_lc_with_yz_1p5_pi(zx_graph: ZXGraphState) -> None: - graph_2(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.YZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 1.6 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=2, - measurements=measurements, - exp_graph=({1, 3}, {(1, 3)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot1_with_xy_0(zx_graph: ZXGraphState) -> None: - graph_3(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, 0), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.3 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - (5, Plane.XY, 1.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=2, - measurements=measurements, - exp_graph=({1, 3, 4, 5, 6}, {(1, 3), (1, 4), (1, 5), (1, 6), (3, 4), (3, 5), (4, 6), (5, 6)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot1_with_xy_pi(zx_graph: ZXGraphState) -> None: - graph_3(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 1.7 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 1.5 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=2, - measurements=measurements, - exp_graph=({1, 3, 4, 5, 6}, {(1, 3), (1, 4), (1, 5), (1, 6), (3, 4), (3, 5), (4, 6), (5, 6)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot1_with_xz_0p5_pi(zx_graph: ZXGraphState) -> None: - graph_3(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 0.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.3 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - (5, Plane.XY, 1.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=2, - measurements=measurements, - exp_graph=({1, 3, 4, 5, 6}, {(1, 3), (1, 4), (1, 5), (1, 6), (3, 4), (3, 5), (4, 6), (5, 6)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot1_with_xz_1p5_pi(zx_graph: ZXGraphState) -> None: +@pytest.mark.parametrize( + ("measurements", "exp_measurements"), + [ + # XY plane with angle 0 + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XY, 0), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ], + [ + (1, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.3 * np.pi), + (4, Plane.YZ, 1.7 * np.pi), + (5, Plane.XY, 1.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ], + ), + # XY plane with angle pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XY, np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ], + [ + (1, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 1.7 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 1.5 * np.pi), + ], + ), + # XZ plane with angle 0.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XZ, 0.5 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ], + [ + (1, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.3 * np.pi), + (4, Plane.YZ, 1.7 * np.pi), + (5, Plane.XY, 1.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ], + ), + # XZ plane with angle 1.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XZ, 1.5 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ], + [ + (1, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 1.7 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 1.5 * np.pi), + ], + ), + ], +) +def test_remove_clifford_pivot1( + zx_graph: ZXGraphState, + measurements: list[tuple[int, Plane, float]], + exp_measurements: list[tuple[int, Plane, float]], +) -> None: graph_3(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 1.7 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 1.5 * np.pi), - ] _test_remove_clifford( zx_graph, node=2, @@ -783,69 +748,57 @@ def test_remove_clifford_pivot1_with_xz_1p5_pi(zx_graph: ZXGraphState) -> None: ) -def test_remove_clifford_pivot2_with_xy_0(zx_graph: ZXGraphState) -> None: - graph_4(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XY, 0), - ] - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=4, - measurements=measurements, - exp_graph=({1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot2_with_xy_pi(zx_graph: ZXGraphState) -> None: - graph_4(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XY, np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 1.1 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=4, - measurements=measurements, - exp_graph=({1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot2_with_xz_0p5_pi(zx_graph: ZXGraphState) -> None: - graph_4(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XZ, 0.5 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - ] - _test_remove_clifford( - zx_graph, - node=4, - measurements=measurements, - exp_graph=({1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}), - exp_measurements=exp_measurements, - ) - - -def test_remove_clifford_pivot2_with_xz_1p5_pi(zx_graph: ZXGraphState) -> None: +@pytest.mark.parametrize( + ("measurements", "exp_measurements"), + [ + # XY plane with angle 0 + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (4, Plane.XY, 0), + ], + [ + (1, Plane.XY, 0.1 * np.pi), + ], + ), + # XY plane with angle pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (4, Plane.XY, np.pi), + ], + [ + (1, Plane.XY, 1.1 * np.pi), + ], + ), + # XZ plane with angle 0.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (4, Plane.XZ, 0.5 * np.pi), + ], + [ + (1, Plane.XY, 0.1 * np.pi), + ], + ), + # XZ plane with angle 1.5 * pi + ( + [ + (1, Plane.XY, 0.1 * np.pi), + (4, Plane.XZ, 1.5 * np.pi), + ], + [ + (1, Plane.XY, 1.1 * np.pi), + ], + ), + ], +) +def test_remove_clifford_pivot2( + zx_graph: ZXGraphState, + measurements: list[tuple[int, Plane, float]], + exp_measurements: list[tuple[int, Plane, float]], +) -> None: graph_4(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XZ, 1.5 * np.pi), - ] - exp_measurements = [ - (1, Plane.XY, 1.1 * np.pi), - ] _test_remove_clifford( zx_graph, node=4, From 85a79bb70b4fd9da23eba5d910d5523f0927c378 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 20:54:50 +0900 Subject: [PATCH 10/72] :white_check_mark: Add tests for prune_non_clifford --- tests/test_zxgraphstate.py | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 5aebeb2d7..4af493f0b 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1197,5 +1197,80 @@ def test_merge_yz_nodes( _test(zx_graph, exp_nodes, exp_edges, exp_measurements) +@pytest.mark.parametrize( + ("initial_zxgraph", "measurements", "exp_zxgraph"), + [ + # test for a phase gadget: apply merge_yz_to_xy then remove_cliffords + ( + (range(1, 5), {(1, 2), (2, 3), (2, 4)}), + [ + (1, Plane.YZ, 0.1 * np.pi), + (2, Plane.XY, 0.4 * np.pi), + (3, Plane.XY, 0.3 * np.pi), + (4, Plane.XY, 0.4 * np.pi), + ], + ( + [ + (3, Plane.XY, 1.8 * np.pi), + (4, Plane.XY, 1.9 * np.pi), + ], + {(3, 4)}, + {3, 4}, + ), + ), + # apply convert_to_phase_gadget, merge_yz_to_xy, then remove_cliffords + ( + (range(1, 5), {(1, 2), (2, 3), (2, 4)}), + [ + (1, Plane.YZ, 0.1 * np.pi), + (2, Plane.XY, 0.9 * np.pi), + (3, Plane.XZ, 0.8 * np.pi), + (4, Plane.XY, 0.4 * np.pi), + ], + ( + [ + (3, Plane.XY, 1.8 * np.pi), + (4, Plane.XY, 1.9 * np.pi), + ], + {(3, 4)}, + {3, 4}, + ), + ), + # apply remove_cliffords, convert_to_phase_gadget, merge_yz_to_xy, then remove_cliffords + ( + (range(1, 7), {(1, 2), (2, 3), (2, 4), (3, 6), (4, 5)}), + [ + (1, Plane.YZ, 0.1 * np.pi), + (2, Plane.XY, 0.9 * np.pi), + (3, Plane.YZ, 1.2 * np.pi), + (4, Plane.XY, 1.4 * np.pi), + (5, Plane.YZ, 1.0 * np.pi), + (6, Plane.XY, 0.5 * np.pi), + ], + ( + [ + (3, Plane.XY, 1.8 * np.pi), + (4, Plane.XY, 1.9 * np.pi), + ], + {(3, 4)}, + {3, 4}, + ), + ), + ], +) +def test_prune_non_clifford( + zx_graph: ZXGraphState, + initial_zxgraph: tuple[range, set[tuple[int, int]]], + measurements: list[tuple[int, Plane, float]], + exp_zxgraph: tuple[list[tuple[int, Plane, float]], set[tuple[int, int]], set[int]], +) -> None: + nodes, edges = initial_zxgraph + _initialize_graph(zx_graph, nodes, edges) + exp_measurements, exp_edges, exp_nodes = exp_zxgraph + _apply_measurements(zx_graph, measurements) + zx_graph.prune_non_clifford() + _test(zx_graph, exp_nodes, exp_edges, exp_measurements) + + if __name__ == "__main__": pytest.main() From d8d07e60a30ab9055c7cf0c222c918120d4d1a39 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 20:55:30 +0900 Subject: [PATCH 11/72] :sparkles: Add prune_non_clifford to ZXGraphState --- graphix_zx/zxgraphstate.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 8a14a5df0..25e40909a 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -592,3 +592,29 @@ def merge_yz_nodes(self) -> None: new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) self.remove_physical_node(v) + + def prune_non_clifford(self, atol: float = 1e-9) -> None: + """Prune non-Clifford vertices from the graph state. + + Repeat the following steps until there are no non-Clifford vertices: + 1. remove_cliffords + 2. convert_to_phase_gadget + 3. merge_yz_to_xy + 4. merge_yz_nodes + 5. if there are some removable Clifford vertices, back to step 1. + + Parameters + ---------- + atol : float, optional + absolute tolerance, by default 1e-9 + """ + while True: + self.remove_cliffords(atol) + self.convert_to_phase_gadget() + self.merge_yz_to_xy() + self.merge_yz_nodes() + if not any( + is_clifford_angle(self.meas_bases[node].angle, atol) and self.is_removable_clifford(node, atol) + for node in self.physical_nodes - self.input_nodes - self.output_nodes + ): + break From 31fc41d193c83d10f996672beb77fbdba257d1bc Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 20:56:05 +0900 Subject: [PATCH 12/72] :art: Apply ruff check --- graphix_zx/simulator.py | 4 ---- tests/test_graphstate.py | 4 ++-- tests/test_zxgraphstate.py | 14 +++++++------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/graphix_zx/simulator.py b/graphix_zx/simulator.py index f14b3cde3..6a96ca08b 100644 --- a/graphix_zx/simulator.py +++ b/graphix_zx/simulator.py @@ -346,8 +346,6 @@ def _apply_x(self, cmd: X) -> None: result ^= self.__results[node] if result: self.__state.evolve(np.asarray([[0, 1], [1, 0]]), [node_id]) - else: - pass def _apply_z(self, cmd: Z) -> None: node_id = self.__node_indices.index(cmd.node) @@ -357,8 +355,6 @@ def _apply_z(self, cmd: Z) -> None: result ^= self.__results[node] if result: self.__state.evolve(np.asarray([[1, 0], [0, -1]]), [node_id]) - else: - pass def _apply_c(self, cmd: C) -> None: clifford = C.local_clifford.get_matrix() diff --git a/tests/test_graphstate.py b/tests/test_graphstate.py index 32318cf1d..8c7abb876 100644 --- a/tests/test_graphstate.py +++ b/tests/test_graphstate.py @@ -173,14 +173,14 @@ def test_set_output_raises_1(graph: GraphState) -> None: graph.set_output(1) graph.add_physical_node(1) graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)) - with pytest.raises(ValueError, match="Cannot set output node with measurement basis."): + with pytest.raises(ValueError, match=r"Cannot set output node with measurement basis."): graph.set_output(1) def test_set_output_raises_2(graph: GraphState) -> None: graph.add_physical_node(1) graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)) - with pytest.raises(ValueError, match="Cannot set output node with measurement basis."): + with pytest.raises(ValueError, match=r"Cannot set output node with measurement basis."): graph.set_output(1) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 4af493f0b..a6f67a626 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -98,7 +98,7 @@ def test_local_complement_fails_with_input_node(zx_graph: ZXGraphState) -> None: """Test local complement fails with input node.""" zx_graph.add_physical_node(1) zx_graph.set_input(1) - with pytest.raises(ValueError, match="Cannot apply local complement to input node."): + with pytest.raises(ValueError, match=r"Cannot apply local complement to input node."): zx_graph.local_complement(1) @@ -816,7 +816,7 @@ def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: (3, Plane.XY, 0.5 * np.pi), ] _apply_measurements(zx_graph, measurements) - with pytest.raises(ValueError, match="This Clifford vertex is unremovable."): + with pytest.raises(ValueError, match=r"This Clifford vertex is unremovable."): zx_graph.remove_clifford(2) @@ -961,11 +961,11 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, ), # a pair of adjacent nodes with YZ measurements - # 4(XY) 4(XY) 4 4 4 - # / \ | | | | - # 1(XY) - 2(YZ) - 3(YZ) - 6(XY) -> 1(XY) - 3(XY) - 2(XY) - 6(XY) - 1 - # \ / | | | | - # 5(XY) 5(XY) 5 5 5 + # 4(XY) 4(XY) 4 4 4 + # / \ | | | | + # 1(XY) - 2(YZ) - 3(YZ) - 6(XY) -> 1(XY) - 3(XY) - 2(XY) - 6(XY) - 1(XY) + # \ / | | | | + # 5(XY) 5(XY) 5 5 5 ( [ (1, Plane.XY, 0.11 * np.pi), From 6d196b56fc0ffa1148e0680ebd5fa78a5e968b6b Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 21:46:58 +0900 Subject: [PATCH 13/72] :pencil2: Fix typo --- graphix_zx/zxgraphstate.py | 2 +- tests/test_zxgraphstate.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 25e40909a..b394d03fe 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -593,7 +593,7 @@ def merge_yz_nodes(self) -> None: self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) self.remove_physical_node(v) - def prune_non_clifford(self, atol: float = 1e-9) -> None: + def prune_non_cliffords(self, atol: float = 1e-9) -> None: """Prune non-Clifford vertices from the graph state. Repeat the following steps until there are no non-Clifford vertices: diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index a6f67a626..495321c54 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1258,7 +1258,7 @@ def test_merge_yz_nodes( ), ], ) -def test_prune_non_clifford( +def test_prune_non_cliffords( zx_graph: ZXGraphState, initial_zxgraph: tuple[range, set[tuple[int, int]]], measurements: list[tuple[int, Plane, float]], @@ -1268,7 +1268,7 @@ def test_prune_non_clifford( _initialize_graph(zx_graph, nodes, edges) exp_measurements, exp_edges, exp_nodes = exp_zxgraph _apply_measurements(zx_graph, measurements) - zx_graph.prune_non_clifford() + zx_graph.prune_non_cliffords() _test(zx_graph, exp_nodes, exp_edges, exp_measurements) From 7e7a73dc13115952c5f40d407e4ae8bd97fac41e Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 23:08:24 +0900 Subject: [PATCH 14/72] :bug: Fix merge_yz_to_xy & merge_yz_nodes --- graphix_zx/zxgraphstate.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index b394d03fe..0fda4cbfb 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -562,10 +562,14 @@ def merge_yz_to_xy(self) -> None: then the node u can be merged into the node v. """ target_candidates = { - u for u, basis in self.meas_bases.items() if (basis.plane == Plane.YZ and len(self.get_neighbors(u)) == 1) + u + for u, basis in self.meas_bases.items() + if (basis.plane == Plane.YZ and len(self.get_neighbors(u)) == 1 and (u not in self.output_nodes)) } target_nodes = { - u for u in target_candidates if self.meas_bases[next(iter(self.get_neighbors(u)))].plane == Plane.XY + u + for u in target_candidates + if any(self.meas_bases[v].plane == Plane.XY for v in self.get_neighbors(u) - self.output_nodes) } for u in target_nodes: v = self.get_neighbors(u).pop() @@ -579,19 +583,27 @@ def merge_yz_nodes(self) -> None: If u, v nodes are measured in the YZ-plane and u, v have the same neighbors, then u, v can be merged into a single node. """ + min_yz_nodes = 2 while True: yz_nodes = {u for u, basis in self.meas_bases.items() if basis.plane == Plane.YZ} - least_nodes = 2 - if len(yz_nodes) < least_nodes: + if len(yz_nodes) < min_yz_nodes: + break + merged = False + for u in sorted(yz_nodes): + for v in sorted(yz_nodes - {u}): + if self.get_neighbors(u) != self.get_neighbors(v): + continue + + new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) + self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) + self.remove_physical_node(v) + + merged = True + break + if merged: + break + if not merged: break - u = yz_nodes.pop() - for v in yz_nodes: - if u > v or self.get_neighbors(u) != self.get_neighbors(v): - continue - - new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) - self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) - self.remove_physical_node(v) def prune_non_cliffords(self, atol: float = 1e-9) -> None: """Prune non-Clifford vertices from the graph state. From fdf0aa4377fc29a5f16eac472dc2770271de352e Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 23:08:54 +0900 Subject: [PATCH 15/72] :sparkles: Add get_random_gflow_circ --- graphix_zx/random_objects.py | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index b5a368c50..7e9d44984 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -10,6 +10,7 @@ import numpy as np +from graphix_zx.circuit import MBQCCircuit from graphix_zx.common import default_meas_basis from graphix_zx.graphstate import GraphState @@ -82,3 +83,50 @@ def get_random_flow_graph( num_nodes += 1 return graph, flow + + +def get_random_gflow_circ( + width: int, + depth: int, + rng: np.random.Generator | None = None, + edge_p: float = 0.5, + angle_list: list | None = None, +) -> MBQCCircuit: + """Generate a random MBQC circuit which has gflow. + + Parameters + ---------- + width : int + circuit width + depth : int + circuit depth + rng : np.random.Generator, optional + random number generator, by default np.random.default_rng() + edge_p : float, optional + probability of adding CZ gate, by default 0.5 + angle_list : list, optional + list of angles, by default [0, np.pi / 3, 2 * np.pi / 3, np.pi] + + Returns + ------- + MBQCCircuit + generated MBQC circuit + """ + if rng is None: + rng = np.random.default_rng() + if angle_list is None: + angle_list = [0, np.pi / 3, 2 * np.pi / 3, np.pi] + circ = MBQCCircuit(width) + for d in range(depth): + for j in range(width): + circ.j(j, rng.choice(angle_list)) + if d < depth - 1: + for j in range(width): + if rng.random() < edge_p: + circ.cz(j, (j + 1) % width) + num = rng.integers(0, width) + if num > 0: + target = set(rng.choice(list(range(width)), num)) + circ.phase_gadget(target, rng.choice(angle_list)) + + return circ From 9a7c3a75c1c9048308d8d6fbffc7d189c63c6d4b Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 15 Feb 2025 23:09:32 +0900 Subject: [PATCH 16/72] :sparkles: Add an example for Clifford/non-Clifford removal --- .../measurement_pattern_simplification.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/measurement_pattern_simplification.py diff --git a/examples/measurement_pattern_simplification.py b/examples/measurement_pattern_simplification.py new file mode 100644 index 000000000..5f5dc32b6 --- /dev/null +++ b/examples/measurement_pattern_simplification.py @@ -0,0 +1,42 @@ +"""Basic example of simplifying a measurement pattern via a ZX-diagram simplification. + +By using the `prune_non_cliffords` method, +we can remove certain Clifford nodes and non-Clifford nodes from the ZX-diagram, +which can simplify the resulting measurement pattern. +""" + +# %% +from copy import deepcopy + +import numpy as np + +from graphix_zx.circuit import circuit2graph +from graphix_zx.random_objects import get_random_gflow_circ +from graphix_zx.visualizer import visualize +from graphix_zx.zxgraphstate import ZXGraphState + +# %% +circ = get_random_gflow_circ(4, 4, angle_list=[0, np.pi / 3, 2 * np.pi / 3, np.pi]) +graph, flow = circuit2graph(circ) +zx_graph = ZXGraphState() +zx_graph.append(graph) + +visualize(zx_graph) +print("node | plane | angle (/pi)") +for node in zx_graph.input_nodes: + print(f"{node} (input)", zx_graph.meas_bases[node].plane, zx_graph.meas_bases[node].angle / np.pi) +for node in zx_graph.physical_nodes - zx_graph.input_nodes - zx_graph.output_nodes: + print(node, zx_graph.meas_bases[node].plane, zx_graph.meas_bases[node].angle / np.pi) + +# %% +zx_graph_smp = deepcopy(zx_graph) +zx_graph_smp.prune_non_cliffords() + +visualize(zx_graph_smp) +print("node | plane | angle (/pi)") +for node in zx_graph.input_nodes: + print(f"{node} (input)", zx_graph.meas_bases[node].plane, zx_graph.meas_bases[node].angle / np.pi) +for node in zx_graph_smp.physical_nodes - zx_graph.input_nodes - zx_graph_smp.output_nodes: + print(node, zx_graph_smp.meas_bases[node].plane, zx_graph_smp.meas_bases[node].angle / np.pi) + +# %% From d8520fe713a613dc7f029a2578b8850870a5f4a7 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 24 Feb 2025 21:12:53 +0900 Subject: [PATCH 17/72] :bug: Fix _angle_check --- graphix_zx/euler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix_zx/euler.py b/graphix_zx/euler.py index ecab91f7d..75309f30f 100644 --- a/graphix_zx/euler.py +++ b/graphix_zx/euler.py @@ -243,7 +243,7 @@ def _angle_check(cls, alpha: float, beta: float, gamma: float, atol: float = 1e- ValueError if any of the angles is not a Clifford angle """ - if not any(is_clifford_angle(angle, atol=atol) for angle in [alpha, beta, gamma]): + if not all(is_clifford_angle(angle, atol=atol) for angle in [alpha, beta, gamma]): msg = "The angles must be multiples of pi/2" raise ValueError(msg) From eab57879f4994551dd19dd856f8df5c187a70fd6 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 24 Feb 2025 21:15:31 +0900 Subject: [PATCH 18/72] :bug: Fix test_local_complement in test_euler.py --- tests/test_euler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_euler.py b/tests/test_euler.py index 09e832f6e..6a7bf44f0 100644 --- a/tests/test_euler.py +++ b/tests/test_euler.py @@ -173,15 +173,15 @@ def test_lc_basis_update( def test_local_complement_target_update(plane: Plane, rng: np.random.Generator) -> None: lc = LocalClifford(0, np.pi / 2, 0) measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { - Plane.XY: (Plane.XZ, lambda angle: angle + np.pi / 2), - Plane.XZ: (Plane.XY, lambda angle: np.pi / 2 - angle), - Plane.YZ: (Plane.YZ, lambda angle: angle + np.pi / 2), + Plane.XY: (Plane.XZ, lambda angle: -angle + np.pi / 2), + Plane.XZ: (Plane.XY, lambda angle: angle - np.pi / 2), + Plane.YZ: (Plane.YZ, lambda angle: angle - np.pi / 2), } angle = rng.random() * 2 * np.pi meas_basis = PlannerMeasBasis(plane, angle) - result_basis = update_lc_basis(lc.conjugate(), meas_basis) + result_basis = update_lc_basis(lc, meas_basis) ref_plane, ref_angle_func = measurement_action[plane] ref_angle = ref_angle_func(angle) @@ -193,15 +193,15 @@ def test_local_complement_target_update(plane: Plane, rng: np.random.Generator) def test_local_complement_neighbors(plane: Plane, rng: np.random.Generator) -> None: lc = LocalClifford(-np.pi / 2, 0, 0) measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { - Plane.XY: (Plane.XY, lambda angle: angle + np.pi / 2), - Plane.XZ: (Plane.YZ, lambda angle: angle), - Plane.YZ: (Plane.XZ, lambda angle: -1 * angle), + Plane.XY: (Plane.XY, lambda angle: angle - np.pi / 2), + Plane.XZ: (Plane.YZ, lambda angle: -1 * angle), + Plane.YZ: (Plane.XZ, lambda angle: angle), } angle = rng.random() * 2 * np.pi meas_basis = PlannerMeasBasis(plane, angle) - result_basis = update_lc_basis(lc.conjugate(), meas_basis) + result_basis = update_lc_basis(lc, meas_basis) ref_plane, ref_angle_func = measurement_action[plane] ref_angle = ref_angle_func(angle) From 0c25e6093a49739d1332aebcd8a844e15edbef46 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 24 Feb 2025 21:30:20 +0900 Subject: [PATCH 19/72] :bug: Fix update rule --- tests/test_euler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_euler.py b/tests/test_euler.py index 6a7bf44f0..1407ab946 100644 --- a/tests/test_euler.py +++ b/tests/test_euler.py @@ -173,15 +173,15 @@ def test_lc_basis_update( def test_local_complement_target_update(plane: Plane, rng: np.random.Generator) -> None: lc = LocalClifford(0, np.pi / 2, 0) measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { - Plane.XY: (Plane.XZ, lambda angle: -angle + np.pi / 2), - Plane.XZ: (Plane.XY, lambda angle: angle - np.pi / 2), - Plane.YZ: (Plane.YZ, lambda angle: angle - np.pi / 2), + Plane.XY: (Plane.XZ, lambda angle: angle + np.pi / 2), + Plane.XZ: (Plane.XY, lambda angle: -angle + np.pi / 2), + Plane.YZ: (Plane.YZ, lambda angle: angle + np.pi / 2), } angle = rng.random() * 2 * np.pi meas_basis = PlannerMeasBasis(plane, angle) - result_basis = update_lc_basis(lc, meas_basis) + result_basis = update_lc_basis(lc.conjugate(), meas_basis) ref_plane, ref_angle_func = measurement_action[plane] ref_angle = ref_angle_func(angle) @@ -193,15 +193,15 @@ def test_local_complement_target_update(plane: Plane, rng: np.random.Generator) def test_local_complement_neighbors(plane: Plane, rng: np.random.Generator) -> None: lc = LocalClifford(-np.pi / 2, 0, 0) measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { - Plane.XY: (Plane.XY, lambda angle: angle - np.pi / 2), - Plane.XZ: (Plane.YZ, lambda angle: -1 * angle), - Plane.YZ: (Plane.XZ, lambda angle: angle), + Plane.XY: (Plane.XY, lambda angle: angle + np.pi / 2), + Plane.XZ: (Plane.YZ, lambda angle: angle), + Plane.YZ: (Plane.XZ, lambda angle: -angle), } angle = rng.random() * 2 * np.pi meas_basis = PlannerMeasBasis(plane, angle) - result_basis = update_lc_basis(lc, meas_basis) + result_basis = update_lc_basis(lc.conjugate(), meas_basis) ref_plane, ref_angle_func = measurement_action[plane] ref_angle = ref_angle_func(angle) From 39cb744f20c8f90e2544cc4ec4292fd99319dfc9 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Fri, 28 Feb 2025 18:47:47 +0900 Subject: [PATCH 20/72] :bug: Add property method --- graphix_zx/graphstate.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/graphix_zx/graphstate.py b/graphix_zx/graphstate.py index c48d54686..188cbc9ab 100644 --- a/graphix_zx/graphstate.py +++ b/graphix_zx/graphstate.py @@ -400,6 +400,28 @@ def local_cliffords(self) -> dict[int, LocalClifford]: """ return self.__local_cliffords + @property + def inner2nodes(self) -> dict[int, int]: + """Return inner index to node index mapping. + + Returns + ------- + dict[int, int] + inner index to node index mapping. + """ + return self.__inner2nodes + + @property + def nodes2inner(self) -> dict[int, int]: + """Return node index to inner index mapping. + + Returns + ------- + dict[int, int] + node index to inner index mapping. + """ + return self.__nodes2inner + def check_meas_basis(self) -> None: """Check if the measurement basis is set for all physical nodes except output nodes. From 6cb9263d7f1c6428cac03d0848604ef140bdabe0 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Fri, 28 Feb 2025 18:57:51 +0900 Subject: [PATCH 21/72] :art: Apply ruff --- tests/test_euler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_euler.py b/tests/test_euler.py index 1407ab946..5268e49b6 100644 --- a/tests/test_euler.py +++ b/tests/test_euler.py @@ -1,3 +1,4 @@ +import operator from typing import TYPE_CHECKING import numpy as np @@ -195,7 +196,7 @@ def test_local_complement_neighbors(plane: Plane, rng: np.random.Generator) -> N measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XY, lambda angle: angle + np.pi / 2), Plane.XZ: (Plane.YZ, lambda angle: angle), - Plane.YZ: (Plane.XZ, lambda angle: -angle), + Plane.YZ: (Plane.XZ, operator.neg), } angle = rng.random() * 2 * np.pi From 0e7357ac65953b1b4a8f40a10140bd46b4232fb5 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Fri, 28 Feb 2025 18:58:30 +0900 Subject: [PATCH 22/72] :white_check_mark: Add pivot tests for euler.py --- tests/test_euler.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/test_euler.py b/tests/test_euler.py index 5268e49b6..bd2eb740a 100644 --- a/tests/test_euler.py +++ b/tests/test_euler.py @@ -208,3 +208,43 @@ def test_local_complement_neighbors(plane: Plane, rng: np.random.Generator) -> N assert result_basis.plane == ref_plane assert _is_close_angle(result_basis.angle, ref_angle) + + +@pytest.mark.parametrize("plane", [Plane.XY, Plane.YZ, Plane.XZ]) +def test_pivot_target_update(plane: Plane, rng: np.random.Generator) -> None: + lc = LocalClifford(np.pi / 2, np.pi / 2, np.pi / 2) + measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { + Plane.XY: (Plane.YZ, operator.neg), + Plane.XZ: (Plane.XZ, lambda angle: -angle + np.pi / 2), + Plane.YZ: (Plane.XY, operator.neg), + } + + angle = rng.random() * 2 * np.pi + + meas_basis = PlannerMeasBasis(plane, angle) + result_basis = update_lc_basis(lc.conjugate(), meas_basis) + ref_plane, ref_angle_func = measurement_action[plane] + ref_angle = ref_angle_func(angle) + + assert result_basis.plane == ref_plane + assert _is_close_angle(result_basis.angle, ref_angle) + + +@pytest.mark.parametrize("plane", [Plane.XY, Plane.YZ, Plane.XZ]) +def test_pivot_neighbors(plane: Plane, rng: np.random.Generator) -> None: + lc = LocalClifford(np.pi, 0, 0) + measurement_action: dict[Plane, tuple[Plane, Callable[[float], float]]] = { + Plane.XY: (Plane.XY, lambda angle: angle + np.pi), + Plane.XZ: (Plane.XZ, operator.neg), + Plane.YZ: (Plane.YZ, operator.neg), + } + + angle = rng.random() * 2 * np.pi + + meas_basis = PlannerMeasBasis(plane, angle) + result_basis = update_lc_basis(lc.conjugate(), meas_basis) + ref_plane, ref_angle_func = measurement_action[plane] + ref_angle = ref_angle_func(angle) + + assert result_basis.plane == ref_plane + assert _is_close_angle(result_basis.angle, ref_angle) From 6e29aa470a893f8d1b2ba09fcf863c883a886403 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Tue, 22 Apr 2025 23:52:11 +0900 Subject: [PATCH 23/72] :sparkles: Add measurement_actions in test_zxgraphstate --- tests/test_zxgraphstate.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 495321c54..29ebb449c 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1,15 +1,36 @@ from __future__ import annotations from copy import deepcopy +import itertools +import operator +from typing import TYPE_CHECKING import numpy as np import pytest from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import is_clifford_angle +from graphix_zx.euler import is_clifford_angle, _is_close_angle, LocalClifford, update_lc_basis from graphix_zx.random_objects import get_random_flow_graph from graphix_zx.zxgraphstate import ZXGraphState +if TYPE_CHECKING: + from typing import Callable + + Func = Callable[[float], float] + MeasurementAction = dict[Plane, tuple[Plane, Func]] + Measurements = list[tuple[int, Plane, float]] + +measurement_action_lc_target: MeasurementAction = { + Plane.XY: (Plane.XZ, lambda angle: angle + np.pi / 2), + Plane.XZ: (Plane.XY, lambda angle: -angle + np.pi / 2), + Plane.YZ: (Plane.YZ, lambda angle: angle + np.pi / 2), +} +measurement_action_lc_neighbors: MeasurementAction = { + Plane.XY: (Plane.XY, lambda angle: angle + np.pi / 2), + Plane.XZ: (Plane.YZ, lambda angle: angle), + Plane.YZ: (Plane.XZ, operator.neg), +} + @pytest.fixture def zx_graph() -> ZXGraphState: From 4dc91c65846349446e7b366f245df10598758903 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Tue, 22 Apr 2025 23:53:38 +0900 Subject: [PATCH 24/72] :sparkles: Add plane_combinations in test_zxgraphstate --- tests/test_zxgraphstate.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 29ebb449c..3f8544802 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -32,6 +32,10 @@ } +def plane_combinations(n: int) -> list[tuple[Plane, ...]]: + return list(itertools.product(Plane, repeat=n)) + + @pytest.fixture def zx_graph() -> ZXGraphState: """Generate an empty ZXGraphState object. From d806447cea77095d4bc0b22dc52ec4f798bc58b0 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Tue, 22 Apr 2025 23:54:06 +0900 Subject: [PATCH 25/72] :sparkles: Add rng in test_zxgraphstate --- tests/test_zxgraphstate.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 3f8544802..8c871c63e 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -36,6 +36,11 @@ def plane_combinations(n: int) -> list[tuple[Plane, ...]]: return list(itertools.product(Plane, repeat=n)) +@pytest.fixture +def rng() -> np.random.Generator: + return np.random.default_rng() + + @pytest.fixture def zx_graph() -> ZXGraphState: """Generate an empty ZXGraphState object. From 16e2c10ce659e15ac7cf93c1778617640818f6c9 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Tue, 22 Apr 2025 23:57:48 +0900 Subject: [PATCH 26/72] :sparkles: Add doc in plane_combinations --- tests/test_zxgraphstate.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 8c871c63e..85b6333fb 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -33,6 +33,17 @@ def plane_combinations(n: int) -> list[tuple[Plane, ...]]: + """Generate all combinations of planes of length n. + + Parameters + ---------- + n : int + The length of the combinations. n > 1. + + Returns + ------- + list[tuple[Plane, ...]]: A list of tuples containing all combinations of planes of length n. + """ return list(itertools.product(Plane, repeat=n)) From 3e41c7ed5eec5f7912838982af607684789b9e49 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:00:26 +0900 Subject: [PATCH 27/72] :art: Change typehints --- tests/test_zxgraphstate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 85b6333fb..d4d96e307 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -96,7 +96,7 @@ def _initialize_graph( zx_graph.set_output(i) -def _apply_measurements(zx_graph: ZXGraphState, measurements: list[tuple[int, Plane, float]]) -> None: +def _apply_measurements(zx_graph: ZXGraphState, measurements: Measurements) -> None: for node_id, plane, angle in measurements: if node_id in zx_graph.output_nodes: continue @@ -107,7 +107,7 @@ def _test( zx_graph: ZXGraphState, exp_nodes: set[int], exp_edges: set[tuple[int, int]], - exp_measurements: list[tuple[int, Plane, float]], + exp_measurements: Measurements, ) -> None: assert zx_graph.physical_nodes == exp_nodes assert zx_graph.physical_edges == exp_edges From 790fc5a6b496cc7533d63d7dd7d9c5179ac95a72 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:00:59 +0900 Subject: [PATCH 28/72] :bug: Fix bug in _test --- tests/test_zxgraphstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index d4d96e307..dbbb16490 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -113,7 +113,7 @@ def _test( assert zx_graph.physical_edges == exp_edges for node_id, plane, angle in exp_measurements: assert zx_graph.meas_bases[node_id].plane == plane - assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) + assert _is_close_angle(zx_graph.meas_bases[node_id].angle, angle) def test_local_complement_fails_if_nonexistent_node(zx_graph: ZXGraphState) -> None: From abf22bd1e50fa6b3ba0f83410f3a7bdb782b2d3e Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:05:24 +0900 Subject: [PATCH 29/72] :art: Fix test_local_complement_with_no_edge --- tests/test_zxgraphstate.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index dbbb16490..283695b50 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -143,25 +143,18 @@ def test_local_complement_fails_with_input_node(zx_graph: ZXGraphState) -> None: zx_graph.local_complement(1) -def test_local_complement_with_no_edge(zx_graph: ZXGraphState) -> None: - """Test local complement with a graph with no edge.""" +@pytest.mark.parametrize("plane", [Plane.XY, Plane.XZ, Plane.YZ]) +def test_local_complement_with_no_edge(zx_graph: ZXGraphState, plane: Plane, rng: np.random.Generator) -> None: + angle = rng.random() * 2 * np.pi + ref_plane, ref_angle_func = measurement_action_lc_target[plane] + ref_angle = ref_angle_func(angle) zx_graph.add_physical_node(1) - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 1.1 * np.pi)) - zx_graph.local_complement(1) - assert zx_graph.physical_edges == set() - assert zx_graph.meas_bases[1].plane == Plane.XZ - assert np.isclose(zx_graph.meas_bases[1].angle, 1.4 * np.pi) + zx_graph.set_meas_basis(1, PlannerMeasBasis(plane, angle)) - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XZ, 1.1 * np.pi)) zx_graph.local_complement(1) - # this might be a bug in mypy, as it's useful comparison - assert zx_graph.meas_bases[1].plane == Plane.XY # type: ignore[comparison-overlap] - assert np.isclose(zx_graph.meas_bases[1].angle, 0.6 * np.pi) - - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.YZ, 1.1 * np.pi)) - zx_graph.local_complement(1) - assert zx_graph.meas_bases[1].plane == Plane.YZ - assert np.isclose(zx_graph.meas_bases[1].angle, 1.6 * np.pi) + assert zx_graph.physical_edges == set() + assert zx_graph.meas_bases[1].plane == ref_plane + assert _is_close_angle(zx_graph.meas_bases[1].angle, ref_angle) def test_local_complement_on_output_node(zx_graph: ZXGraphState) -> None: From 03b514c649af084858c5f6dae00aaa5c1c826fd9 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:07:26 +0900 Subject: [PATCH 30/72] :art: Fix test_local_complement_on_output_node --- tests/test_zxgraphstate.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 283695b50..ed18f3d4e 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -157,21 +157,26 @@ def test_local_complement_with_no_edge(zx_graph: ZXGraphState, plane: Plane, rng assert _is_close_angle(zx_graph.meas_bases[1].angle, ref_angle) -def test_local_complement_on_output_node(zx_graph: ZXGraphState) -> None: +@pytest.mark.parametrize("plane1, plane3", plane_combinations(2)) +def test_local_complement_on_output_node( + zx_graph: ZXGraphState, plane1: Plane, plane3: Plane, rng: np.random.Generator +) -> None: """Test local complement on an output node.""" _initialize_graph(zx_graph, range(1, 4), {(1, 2), (2, 3)}, outputs=(2,)) - measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), - ] + angle1 = rng.random() * 2 * np.pi + angle3 = rng.random() * 2 * np.pi + measurements = [(1, plane1, angle1), (3, plane3, angle3)] _apply_measurements(zx_graph, measurements) zx_graph.local_complement(2) + ref_plane1, ref_angle_func1 = measurement_action_lc_neighbors[plane1] + ref_plane3, ref_angle_func3 = measurement_action_lc_neighbors[plane3] exp_measurements = [ - (1, Plane.XY, 0.6 * np.pi), - (3, Plane.XZ, 0.7 * np.pi), + (1, ref_plane1, ref_angle_func1(measurements[0][2])), + (3, ref_plane3, ref_angle_func3(measurements[1][2])), ] _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (1, 3), (2, 3)}, exp_measurements=exp_measurements) + assert zx_graph.meas_bases.get(2) is None def test_local_complement_with_two_nodes_graph(zx_graph: ZXGraphState) -> None: From 0ab74be9089d15994a4da6693dbe63889d50b0cd Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:16:48 +0900 Subject: [PATCH 31/72] :art: Fix test_local_complement_with_two_nodes_graph --- tests/test_zxgraphstate.py | 78 ++++++-------------------------------- 1 file changed, 12 insertions(+), 66 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index ed18f3d4e..cf946744d 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -179,78 +179,24 @@ def test_local_complement_on_output_node( assert zx_graph.meas_bases.get(2) is None -def test_local_complement_with_two_nodes_graph(zx_graph: ZXGraphState) -> None: +@pytest.mark.parametrize("plane1, plane2", plane_combinations(2)) +def test_local_complement_with_two_nodes_graph( + zx_graph: ZXGraphState, plane1: Plane, plane2: Plane, rng: np.random.Generator +) -> None: """Test local complement with a graph with two nodes.""" zx_graph.add_physical_node(1) zx_graph.add_physical_node(2) zx_graph.add_physical_edge(1, 2) - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XZ, 1.1 * np.pi)) - zx_graph.set_meas_basis(2, PlannerMeasBasis(Plane.XZ, 1.2 * np.pi)) - original_edges = zx_graph.physical_edges.copy() + angle1 = rng.random() * 2 * np.pi + angle2 = rng.random() * 2 * np.pi + zx_graph.set_meas_basis(1, PlannerMeasBasis(plane1, angle1)) + zx_graph.set_meas_basis(2, PlannerMeasBasis(plane2, angle2)) zx_graph.local_complement(1) - assert zx_graph.physical_edges == original_edges - for node_id, plane, angle in [(1, Plane.XY, 0.6 * np.pi), (2, Plane.YZ, 1.2 * np.pi)]: - assert zx_graph.meas_bases[node_id].plane == plane - assert is_clifford_angle(zx_graph.meas_bases[node_id].angle, angle) - - -def test_local_complement_with_minimal_graph(zx_graph: ZXGraphState) -> None: - """Test local complement with a minimal graph.""" - zx_graph.add_physical_node(1) - zx_graph.add_physical_node(2) - zx_graph.add_physical_node(3) - zx_graph.add_physical_edge(1, 2) - zx_graph.add_physical_edge(2, 3) - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 1.1 * np.pi)) - zx_graph.set_meas_basis(2, PlannerMeasBasis(Plane.XZ, 1.2 * np.pi)) - zx_graph.set_meas_basis(3, PlannerMeasBasis(Plane.YZ, 1.3 * np.pi)) - original_edges = zx_graph.physical_edges.copy() - zx_graph.local_complement(2) - assert zx_graph.physical_edges == {(1, 2), (2, 3), (1, 3)} - exp_measurements = [ - (1, Plane.XY, 0.6 * np.pi), - (2, Plane.XY, 0.7 * np.pi), - (3, Plane.XZ, 0.7 * np.pi), - ] - for node_id, plane, angle in exp_measurements: - assert zx_graph.meas_bases[node_id].plane == plane - assert is_clifford_angle(zx_graph.meas_bases[node_id].angle, angle) - - zx_graph.local_complement(2) - assert zx_graph.physical_edges == original_edges - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 1.8 * np.pi), - (3, Plane.YZ, 0.7 * np.pi), - ] - for node_id, plane, angle in exp_measurements: - assert zx_graph.meas_bases[node_id].plane == plane - assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) - - -def test_local_complement_4_times(zx_graph: ZXGraphState) -> None: - """Test local complement is applied 4 times and the graph goes back to the original shape.""" - zx_graph.add_physical_node(1) - zx_graph.add_physical_node(2) - zx_graph.add_physical_node(3) - zx_graph.add_physical_edge(1, 2) - zx_graph.add_physical_edge(2, 3) - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 1.1 * np.pi)) - zx_graph.set_meas_basis(2, PlannerMeasBasis(Plane.XZ, 1.2 * np.pi)) - zx_graph.set_meas_basis(3, PlannerMeasBasis(Plane.YZ, 1.3 * np.pi)) - original_edges = zx_graph.physical_edges.copy() - for _ in range(4): - zx_graph.local_complement(2) - assert zx_graph.physical_edges == original_edges - exp_measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (2, Plane.XZ, 1.2 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), - ] - for node_id, plane, angle in exp_measurements: - assert zx_graph.meas_bases[node_id].plane == plane - assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) + ref_plane1, ref_angle_func1 = measurement_action_lc_target[plane1] + ref_plane2, ref_angle_func2 = measurement_action_lc_neighbors[plane2] + exp_measurements = [(1, ref_plane1, ref_angle_func1(angle1)), (2, ref_plane2, ref_angle_func2(angle2))] + _test(zx_graph, exp_nodes={1, 2}, exp_edges={(1, 2)}, exp_measurements=exp_measurements) def test_local_complement_with_h_shaped_graph(zx_graph: ZXGraphState) -> None: """Test local complement with an H-shaped graph.""" From 215adbddf2bdb2d72379eba5ea246da2a3fe8f4c Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:41:37 +0900 Subject: [PATCH 32/72] :art: Fix test_local_complement_with_minimal_graph --- tests/test_zxgraphstate.py | 736 ++----------------------------------- 1 file changed, 26 insertions(+), 710 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index cf946744d..10cffd15a 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -198,725 +198,41 @@ def test_local_complement_with_two_nodes_graph( exp_measurements = [(1, ref_plane1, ref_angle_func1(angle1)), (2, ref_plane2, ref_angle_func2(angle2))] _test(zx_graph, exp_nodes={1, 2}, exp_edges={(1, 2)}, exp_measurements=exp_measurements) -def test_local_complement_with_h_shaped_graph(zx_graph: ZXGraphState) -> None: - """Test local complement with an H-shaped graph.""" - for i in range(1, 7): - zx_graph.add_physical_node(i) - - zx_graph.set_input(1) - zx_graph.set_input(4) - - for i, j in [(1, 2), (2, 3), (2, 5), (4, 5), (5, 6)]: - zx_graph.add_physical_edge(i, j) - measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (2, Plane.XZ, 1.2 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.XZ, 1.5 * np.pi), - (6, Plane.YZ, 1.6 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - - original_edges = zx_graph.physical_edges.copy() - zx_graph.local_complement(2) - assert zx_graph.physical_edges == {(1, 2), (1, 3), (1, 5), (2, 3), (2, 5), (3, 5), (4, 5), (5, 6)} - exp_measurements = [ - (1, Plane.XY, 0.6 * np.pi), - (2, Plane.XY, 0.7 * np.pi), - (3, Plane.XZ, 0.7 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.YZ, 1.5 * np.pi), - (6, Plane.YZ, 1.6 * np.pi), - ] - for node_id, plane, angle in exp_measurements: - assert zx_graph.meas_bases[node_id].plane == plane - assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) - - zx_graph.local_complement(2) - assert zx_graph.physical_edges == original_edges - exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 1.8 * np.pi), - (3, Plane.YZ, 0.7 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.XZ, 0.5 * np.pi), - (6, Plane.YZ, 1.6 * np.pi), - ] - for node_id, plane, angle in exp_measurements: - assert zx_graph.meas_bases[node_id].plane == plane - assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) - - -def test_pivot_fails_with_nonexistent_nodes(zx_graph: ZXGraphState) -> None: - """Test pivot fails with nonexistent nodes.""" - with pytest.raises(ValueError, match="Node does not exist node=1"): - zx_graph.pivot(1, 2) - zx_graph.add_physical_node(1) - with pytest.raises(ValueError, match="Node does not exist node=2"): - zx_graph.pivot(1, 2) - - -def test_pivot_fails_with_input_node(zx_graph: ZXGraphState) -> None: - """Test pivot fails with input node.""" - zx_graph.add_physical_node(1) - zx_graph.add_physical_node(2) - zx_graph.set_input(1) - with pytest.raises(ValueError, match="Cannot apply pivot to input node"): - zx_graph.pivot(1, 2) - - -def test_pivot_with_obvious_graph(zx_graph: ZXGraphState) -> None: - """Test pivot with an obvious graph.""" - # 1---2---3 +@pytest.mark.parametrize("planes", plane_combinations(3)) +def test_local_complement_with_minimal_graph( + zx_graph: ZXGraphState, planes: tuple[Plane, Plane, Plane], rng: np.random.Generator +) -> None: + """Test local complement with a minimal graph.""" for i in range(1, 4): zx_graph.add_physical_node(i) - for i, j in [(1, 2), (2, 3)]: zx_graph.add_physical_edge(i, j) - - measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (2, Plane.XZ, 1.2 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - - original_zx_graph = deepcopy(zx_graph) - zx_graph.pivot(2, 3) - original_zx_graph.local_complement(2) - original_zx_graph.local_complement(3) - original_zx_graph.local_complement(2) - assert zx_graph.physical_edges == original_zx_graph.physical_edges - original_planes = [original_zx_graph.meas_bases[i].plane for i in range(1, 4)] - planes = [zx_graph.meas_bases[i].plane for i in range(1, 4)] - assert planes == original_planes - - -def test_pivot_with_minimal_graph(zx_graph: ZXGraphState) -> None: - """Test pivot with a minimal graph.""" - # 1---2---3---5 - # \ / - # 4 - for i in range(1, 6): - zx_graph.add_physical_node(i) - - for i, j in [(1, 2), (2, 3), (2, 4), (3, 4), (3, 5)]: - zx_graph.add_physical_edge(i, j) - - measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (2, Plane.XZ, 1.2 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.XZ, 1.5 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - - original_edges = zx_graph.physical_edges.copy() - expected_edges = {(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (4, 5)} - zx_graph.pivot(2, 3) - assert zx_graph.physical_edges == expected_edges - zx_graph.pivot(2, 3) - assert zx_graph.physical_edges == original_edges - zx_graph.pivot(3, 2) - assert zx_graph.physical_edges == expected_edges - zx_graph.pivot(3, 2) - assert zx_graph.physical_edges == original_edges - - -def test_pivot_with_h_shaped_graph(zx_graph: ZXGraphState) -> None: - """Test pivot with an H-shaped graph.""" - # 3 6 - # | | - # 2---5 - # | | - # 1 4 - for i in range(1, 7): - zx_graph.add_physical_node(i) - for i, j in [(1, 2), (2, 3), (2, 5), (4, 5), (5, 6)]: - zx_graph.add_physical_edge(i, j) - - measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (2, Plane.XZ, 1.2 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.XZ, 1.5 * np.pi), - (6, Plane.YZ, 1.6 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - - original_edges = zx_graph.physical_edges.copy() - expected_edges = {(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)} - zx_graph.pivot(2, 5) - assert zx_graph.physical_edges == expected_edges - zx_graph.pivot(2, 5) - assert zx_graph.physical_edges == original_edges - zx_graph.pivot(5, 2) - assert zx_graph.physical_edges == expected_edges - zx_graph.pivot(5, 2) - assert zx_graph.physical_edges == original_edges - - -def test_pivot_with_8_nodes_graph(zx_graph: ZXGraphState) -> None: - """Test pivot with a graph with 8 nodes.""" - # 1 4 7 - # \ / \ / - # 3 - 6 - # / \ / \ - # 2 5 8 - for i in range(1, 9): - zx_graph.add_physical_node(i) - - for i, j in [(1, 3), (2, 3), (3, 4), (3, 5), (3, 6), (4, 6), (5, 6), (6, 7), (6, 8)]: - zx_graph.add_physical_edge(i, j) - - measurements = [ - (1, Plane.XY, 1.1), - (2, Plane.XZ, 1.2), - (3, Plane.YZ, 1.3), - (4, Plane.XY, 1.4), - (5, Plane.XZ, 1.5), - (6, Plane.YZ, 1.6), - (7, Plane.XY, 1.7), - (8, Plane.XZ, 1.8), - ] - _apply_measurements(zx_graph, measurements) - - original_edges = zx_graph.physical_edges.copy() - expected_edges = { - (1, 4), - (1, 5), - (1, 6), - (1, 7), - (1, 8), - (2, 4), - (2, 5), - (2, 6), - (2, 7), - (2, 8), - (3, 4), - (3, 5), - (3, 6), - (3, 7), - (3, 8), - (4, 6), - (4, 7), - (4, 8), - (5, 6), - (5, 7), - (5, 8), - } - zx_graph.pivot(3, 6) - assert zx_graph.physical_edges == expected_edges - zx_graph.pivot(3, 6) - assert zx_graph.physical_edges == original_edges - zx_graph.pivot(6, 3) - assert zx_graph.physical_edges == expected_edges - zx_graph.pivot(6, 3) - assert zx_graph.physical_edges == original_edges - - -def test_remove_clifford_fails_if_nonexistent_node(zx_graph: ZXGraphState) -> None: - """Test remove_clifford raises an error if the node does not exist.""" - with pytest.raises(ValueError, match="Node does not exist node=1"): - zx_graph.remove_clifford(1) - - -def test_remove_clifford_fails_with_input_node(zx_graph: ZXGraphState) -> None: - zx_graph.add_physical_node(1) - zx_graph.set_input(1) - with pytest.raises(ValueError, match="Clifford vertex removal not allowed for input node"): - zx_graph.remove_clifford(1) - - -def test_remove_clifford_fails_with_invalid_plane(zx_graph: ZXGraphState) -> None: - """Test remove_clifford fails if the measurement plane is invalid.""" - zx_graph.add_physical_node(1) - zx_graph.set_meas_basis( - 1, - PlannerMeasBasis("test_plane", 0.5 * np.pi), # type: ignore[reportArgumentType, arg-type, unused-ignore] - ) - with pytest.raises(ValueError, match="This node is not a Clifford vertex"): - zx_graph.remove_clifford(1) - - -def test_remove_clifford_fails_for_non_clifford_vertex(zx_graph: ZXGraphState) -> None: - zx_graph.add_physical_node(1) - zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)) - with pytest.raises(ValueError, match="This node is not a Clifford vertex"): - zx_graph.remove_clifford(1) - - -def graph_1(zx_graph: ZXGraphState) -> None: - # _needs_nop - # 4---1---2 4 2 - # | -> - # 3 3 - _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) - - -def graph_2(zx_graph: ZXGraphState) -> None: - # _needs_lc - # 1---2---3 -> 1---3 - _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}) - - -def graph_3(zx_graph: ZXGraphState) -> None: - # _needs_pivot_1 on (2, 3) - # 4(I) 4(I) - # / \ / | \ - # 1(I) - 2 - 3 - 6 -> 1(I) - 3 6 - 1(I) - # \ / \ | / - # 5(I) 5(I) - _initialize_graph( - zx_graph, nodes=range(1, 7), edges={(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, inputs=(1, 4, 5) - ) - - -def graph_4(zx_graph: ZXGraphState) -> None: - # _needs_pivot_2 on (2, 4) - # 1(I) 3(O) 5(O) 1(I)-3(O)-5(O)-1(I) - # \ / \ / -> \ / - # 2(O)- 4 2(O) - _initialize_graph( - zx_graph, - nodes=range(1, 6), - edges={(1, 2), (2, 3), (2, 4), (3, 4), (4, 5)}, - inputs=(1,), - outputs=(2, 3, 5), - ) - - -def _test_remove_clifford( - zx_graph: ZXGraphState, - node: int, - measurements: list[tuple[int, Plane, float]], - exp_graph: tuple[set[int], set[tuple[int, int]]], - exp_measurements: list[tuple[int, Plane, float]], -) -> None: - _apply_measurements(zx_graph, measurements) - zx_graph.remove_clifford(node) - exp_nodes = exp_graph[0] - exp_edges = exp_graph[1] - _test(zx_graph, exp_nodes, exp_edges, exp_measurements) - - -@pytest.mark.parametrize( - ("measurements", "exp_measurements"), - [ - # XZ plane with angle 0 - ( - [ - (1, Plane.XZ, 0), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ], - [ - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ], - ), - # XZ plane with angle pi - ( - [ - (1, Plane.XZ, np.pi), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ], - [ - (2, Plane.XY, 1.1 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - ], - ), - # YZ plane with angle 0 - ( - [ - (1, Plane.YZ, 0), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ], - [ - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ], - ), - # YZ plane with angle pi - ( - [ - (1, Plane.YZ, np.pi), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ], - [ - (2, Plane.XY, 1.1 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - ], - ), - ], -) -def test_remove_clifford( - zx_graph: ZXGraphState, - measurements: list[tuple[int, Plane, float]], - exp_measurements: list[tuple[int, Plane, float]], -) -> None: - """Test removing a removable Clifford vertex.""" - graph_1(zx_graph) - _test_remove_clifford( - zx_graph, node=1, measurements=measurements, exp_graph=({2, 3, 4}, set()), exp_measurements=exp_measurements - ) - - -@pytest.mark.parametrize( - ("measurements", "exp_measurements"), - [ - # XY plane with angle 0.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, 0.5 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), - ], - [ - (1, Plane.XY, 1.6 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - ], - ), - # XY plane with angle 1.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, 1.5 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), - ], - [ - (1, Plane.XY, 0.6 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ], - ), - # YZ plane with angle 0.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.YZ, 0.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ], - [ - (1, Plane.XY, 0.6 * np.pi), - (3, Plane.YZ, 1.8 * np.pi), - ], - ), - # YZ plane with angle 1.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.YZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ], - [ - (1, Plane.XY, 1.6 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), - ], - ), - ], -) -def test_remove_clifford_lc( - zx_graph: ZXGraphState, - measurements: list[tuple[int, Plane, float]], - exp_measurements: list[tuple[int, Plane, float]], -) -> None: - graph_2(zx_graph) - _test_remove_clifford( - zx_graph, node=2, measurements=measurements, exp_graph=({1, 3}, {(1, 3)}), exp_measurements=exp_measurements - ) - - -@pytest.mark.parametrize( - ("measurements", "exp_measurements"), - [ - # XY plane with angle 0 - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, 0), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ], - [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.3 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - (5, Plane.XY, 1.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ], - ), - # XY plane with angle pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XY, np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ], - [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 1.7 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 1.5 * np.pi), - ], - ), - # XZ plane with angle 0.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 0.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ], - [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.3 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - (5, Plane.XY, 1.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ], - ), - # XZ plane with angle 1.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ], - [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 1.7 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 1.5 * np.pi), - ], - ), - ], -) -def test_remove_clifford_pivot1( - zx_graph: ZXGraphState, - measurements: list[tuple[int, Plane, float]], - exp_measurements: list[tuple[int, Plane, float]], -) -> None: - graph_3(zx_graph) - _test_remove_clifford( - zx_graph, - node=2, - measurements=measurements, - exp_graph=({1, 3, 4, 5, 6}, {(1, 3), (1, 4), (1, 5), (1, 6), (3, 4), (3, 5), (4, 6), (5, 6)}), - exp_measurements=exp_measurements, - ) - - -@pytest.mark.parametrize( - ("measurements", "exp_measurements"), - [ - # XY plane with angle 0 - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XY, 0), - ], - [ - (1, Plane.XY, 0.1 * np.pi), - ], - ), - # XY plane with angle pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XY, np.pi), - ], - [ - (1, Plane.XY, 1.1 * np.pi), - ], - ), - # XZ plane with angle 0.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XZ, 0.5 * np.pi), - ], - [ - (1, Plane.XY, 0.1 * np.pi), - ], - ), - # XZ plane with angle 1.5 * pi - ( - [ - (1, Plane.XY, 0.1 * np.pi), - (4, Plane.XZ, 1.5 * np.pi), - ], - [ - (1, Plane.XY, 1.1 * np.pi), - ], - ), - ], -) -def test_remove_clifford_pivot2( - zx_graph: ZXGraphState, - measurements: list[tuple[int, Plane, float]], - exp_measurements: list[tuple[int, Plane, float]], -) -> None: - graph_4(zx_graph) - _test_remove_clifford( - zx_graph, - node=4, - measurements=measurements, - exp_graph=({1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}), - exp_measurements=exp_measurements, - ) - - -def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: - _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}, inputs=(1, 3)) - measurements = [ - (1, Plane.XY, 0.5 * np.pi), - (2, Plane.XY, np.pi), - (3, Plane.XY, 0.5 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - with pytest.raises(ValueError, match=r"This Clifford vertex is unremovable."): - zx_graph.remove_clifford(2) - - -def test_remove_cliffords(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices.""" - _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) - measurements = [ - (1, Plane.XY, 0.5 * np.pi), - (2, Plane.XY, 0.5 * np.pi), - (3, Plane.XY, 0.5 * np.pi), - (4, Plane.XY, 0.5 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - zx_graph.remove_cliffords() - _test(zx_graph, {3}, set(), []) - - -def test_remove_cliffords_graph1(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices.""" - graph_1(zx_graph) - measurements = [ - (1, Plane.YZ, np.pi), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - ] - exp_measurements = [ - (2, Plane.XY, 1.1 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - zx_graph.remove_cliffords() - _test(zx_graph, {2, 3, 4}, set(), exp_measurements=exp_measurements) - - -def test_remove_cliffords_graph2(zx_graph: ZXGraphState) -> None: - graph_2(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.YZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - ] + angles = [rng.random() * 2 * np.pi for _ in range(3)] + for i in range(1, 4): + zx_graph.set_meas_basis(i, PlannerMeasBasis(planes[i - 1], angles[i - 1])) + zx_graph.local_complement(2) + ref_plane1, ref_angle_func1 = measurement_action_lc_neighbors[planes[0]] + ref_plane2, ref_angle_func2 = measurement_action_lc_target[planes[1]] + ref_plane3, ref_angle_func3 = measurement_action_lc_neighbors[planes[2]] + ref_angle1 = ref_angle_func1(angles[0]) + ref_angle2 = ref_angle_func2(angles[1]) + ref_angle3 = ref_angle_func3(angles[2]) exp_measurements = [ - (1, Plane.XY, 1.6 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), + (1, ref_plane1, ref_angle1), + (2, ref_plane2, ref_angle2), + (3, ref_plane3, ref_angle3), ] - _apply_measurements(zx_graph, measurements) - zx_graph.remove_cliffords() - _test(zx_graph, {1, 3}, {(1, 3)}, exp_measurements=exp_measurements) - + _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3), (1, 3)}, exp_measurements=exp_measurements) -def test_remove_cliffords_graph3(zx_graph: ZXGraphState) -> None: - graph_3(zx_graph) - measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), - ] + zx_graph.local_complement(2) + ref_plane1, ref_angle_func1 = measurement_action_lc_neighbors[ref_plane1] + ref_plane2, ref_angle_func2 = measurement_action_lc_target[ref_plane2] + ref_plane3, ref_angle_func3 = measurement_action_lc_neighbors[ref_plane3] exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 1.7 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 1.5 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - zx_graph.remove_cliffords() - _test( - zx_graph, - {1, 3, 4, 5, 6}, - {(1, 3), (1, 4), (1, 5), (1, 6), (3, 4), (3, 5), (4, 6), (5, 6)}, - exp_measurements=exp_measurements, - ) - - -def test_remove_cliffords_graph4(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices.""" - graph_4(zx_graph) - measurements = [ - (1, Plane.XY, np.pi), - (4, Plane.XZ, 0.5 * np.pi), - ] - _apply_measurements(zx_graph, measurements) - zx_graph.remove_cliffords() - _test(zx_graph, {1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}, [(1, Plane.XY, np.pi)]) - - -def test_random_graph(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices from a random graph.""" - random_graph, _ = get_random_flow_graph(5, 5) - zx_graph.append(random_graph) - - for i in zx_graph.physical_nodes - zx_graph.output_nodes: - rng = np.random.default_rng(seed=0) - rnd = rng.random() - if 0 <= rnd < 0.33: - pass - elif 0.33 <= rnd < 0.66: - angle = zx_graph.meas_bases[i].angle - zx_graph.set_meas_basis(i, PlannerMeasBasis(Plane.XZ, angle)) - else: - angle = zx_graph.meas_bases[i].angle - zx_graph.set_meas_basis(i, PlannerMeasBasis(Plane.YZ, angle)) - - zx_graph.remove_cliffords() - atol = 1e-9 - nodes = zx_graph.physical_nodes - zx_graph.input_nodes - zx_graph.output_nodes - clifford_nodes = [ - node - for node in nodes - if is_clifford_angle(zx_graph.meas_bases[node].angle, atol) and zx_graph.is_removable_clifford(node, atol) + (1, ref_plane1, ref_angle_func1(ref_angle1)), + (2, ref_plane2, ref_angle_func2(ref_angle2)), + (3, ref_plane3, ref_angle_func3(ref_angle3)), ] assert clifford_nodes == [] From f9e41c3605e9b20f5e14e458afc0ca398c8c3e40 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 00:48:02 +0900 Subject: [PATCH 33/72] :art: Fix test_local_complement_4_times --- tests/test_zxgraphstate.py | 364 +++---------------------------------- 1 file changed, 28 insertions(+), 336 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 10cffd15a..255bf634f 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -234,343 +234,35 @@ def test_local_complement_with_minimal_graph( (2, ref_plane2, ref_angle_func2(ref_angle2)), (3, ref_plane3, ref_angle_func3(ref_angle3)), ] - assert clifford_nodes == [] - - -@pytest.mark.parametrize( - ("measurements", "exp_measurements", "exp_edges"), - [ - # no pair of adjacent nodes with YZ measurements - # and no node with XZ measurement - ( - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), - ], - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), - ], - {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, - ), - # a pair of adjacent nodes with YZ measurements - # 4(XY) 4(XY) 4 4 4 - # / \ | | | | - # 1(XY) - 2(YZ) - 3(YZ) - 6(XY) -> 1(XY) - 3(XY) - 2(XY) - 6(XY) - 1(XY) - # \ / | | | | - # 5(XY) 5(XY) 5 5 5 - ( - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 0.22 * np.pi), - (3, Plane.YZ, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), - ], - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 1.44 * np.pi), - (5, Plane.XY, 1.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), - ], - {(1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (4, 6), (5, 6)}, - ), - # no pair of adjacent nodes with YZ measurements - # but a node with XZ measurement - # 4(XZ) 4(XY) - # / \ / \ - # 1(XY) - 2(XY) - 3(XY) - 6(XY) -> 1(XY) - 2(XY) 3(XY) - 6(XY) - # \ / \ / - # 5(XY) 5(XY) - ( - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XZ, 0.44 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), - ], - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 1.72 * np.pi), - (3, Plane.XY, 1.83 * np.pi), - (4, Plane.XY, 1.94 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), - ], - {(1, 2), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, - ), - # a pair of adjacent nodes with YZ measurements - # and a node with XZ measurement - # 4(XZ) 6(YZ) - 3(XY) - # / \ | x | - # 1(XZ) - 2(YZ) - 3(YZ) - 6(XZ) -> 1(XY) 2(XY) - # \ / | x | - # 5(XZ) 5(XY) - 4(XY) - ( - [ - (1, Plane.XZ, 0.11 * np.pi), - (2, Plane.YZ, 0.22 * np.pi), - (3, Plane.YZ, 0.33 * np.pi), - (4, Plane.XZ, 0.44 * np.pi), - (5, Plane.XZ, 0.55 * np.pi), - (6, Plane.XZ, 0.66 * np.pi), - ], - [ - (1, Plane.XY, 0.61 * np.pi), - (2, Plane.XY, 1.22 * np.pi), - (3, Plane.XY, 1.83 * np.pi), - (4, Plane.XY, 1.56 * np.pi), - (5, Plane.XY, 1.45 * np.pi), - (6, Plane.YZ, 0.66 * np.pi), - ], - {(1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (2, 5), (2, 6), (3, 6), (4, 5)}, - ), - ], -) -def test_convert_to_phase_gadget( - zx_graph: ZXGraphState, - measurements: list[tuple[int, Plane, float]], - exp_measurements: list[tuple[int, Plane, float]], - exp_edges: set[tuple[int, int]], -) -> None: - initial_edges = {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)} - _initialize_graph(zx_graph, nodes=range(1, 7), edges=initial_edges) - _apply_measurements(zx_graph, measurements) - zx_graph.convert_to_phase_gadget() - _test(zx_graph, exp_nodes={1, 2, 3, 4, 5, 6}, exp_edges=exp_edges, exp_measurements=exp_measurements) - - -@pytest.mark.parametrize( - ("initial_edges", "measurements", "exp_measurements", "exp_edges"), - [ - # 4(XY) 4(XY) - # | -> | - # 1(YZ) - 2(XY) - 3(XY) 2(XY) - 3(XY) - ( - {(1, 2), (2, 3), (2, 4)}, - [ - (1, Plane.YZ, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - ], - [ - (2, Plane.XY, 0.33 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - ], - {(2, 3), (2, 4)}, - ), - # 4(YZ) 4(YZ) - # | \ -> | \ - # 1(YZ) - 2(XY) - 3(XY) 2(XY) - 3(XY) - ( - {(1, 2), (2, 3), (2, 4), (3, 4)}, - [ - (1, Plane.YZ, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - ], - [ - (2, Plane.XY, 0.33 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - ], - {(2, 3), (2, 4), (3, 4)}, - ), - ], -) -def test_merge_yz_to_xy( - zx_graph: ZXGraphState, - initial_edges: set[tuple[int, int]], - measurements: list[tuple[int, Plane, float]], - exp_measurements: list[tuple[int, Plane, float]], - exp_edges: set[tuple[int, int]], -) -> None: - _initialize_graph(zx_graph, nodes=range(1, 5), edges=initial_edges) - _apply_measurements(zx_graph, measurements) - zx_graph.merge_yz_to_xy() - _test(zx_graph, exp_nodes={2, 3, 4}, exp_edges=exp_edges, exp_measurements=exp_measurements) - - -@pytest.mark.parametrize( - ("initial_edges", "measurements", "exp_zxgraph"), - [ - # 4(YZ) 4(YZ) - # / \ / \ - # 1(XY) - 2(XY) - 3(XY) -> 1(XY) - 2(XY) - 3(XY) - # \ / - # 5(YZ) - ( - {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - (5, Plane.YZ, 0.55 * np.pi), - ], - ( - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.99 * np.pi), - ], - {(1, 2), (1, 4), (2, 3), (3, 4)}, - {1, 2, 3, 4}, - ), - ), - # 4(YZ) - # / \ - # 1(XY) - 2(YZ) - 3(XY) -> 1(XY) - 2(YZ) - 3(XY) - # \ / - # 5(YZ) - ( - {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - (5, Plane.YZ, 0.55 * np.pi), - ], - ( - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 1.21 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - ], - {(1, 2), (2, 3)}, - {1, 2, 3}, - ), - ), - # 4(YZ) - # / \ - # 1(XY) - 2(YZ) - 3(XY) - 1(XY) -> 1(XY) - 2(YZ) - 3(XY) - 1(XY) - # \ / - # 5(YZ) - ( - {(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - (5, Plane.YZ, 0.55 * np.pi), - ], - ( - [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 1.21 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - ], - {(1, 2), (1, 3), (2, 3)}, - {1, 2, 3}, - ), - ), - ], -) -def test_merge_yz_nodes( - zx_graph: ZXGraphState, - initial_edges: set[tuple[int, int]], - measurements: list[tuple[int, Plane, float]], - exp_zxgraph: tuple[list[tuple[int, Plane, float]], set[tuple[int, int]], set[int]], -) -> None: - _initialize_graph(zx_graph, nodes=range(1, 6), edges=initial_edges) - _apply_measurements(zx_graph, measurements) - zx_graph.merge_yz_nodes() - exp_measurements, exp_edges, exp_nodes = exp_zxgraph - _test(zx_graph, exp_nodes, exp_edges, exp_measurements) - - -@pytest.mark.parametrize( - ("initial_zxgraph", "measurements", "exp_zxgraph"), - [ - # test for a phase gadget: apply merge_yz_to_xy then remove_cliffords - ( - (range(1, 5), {(1, 2), (2, 3), (2, 4)}), - [ - (1, Plane.YZ, 0.1 * np.pi), - (2, Plane.XY, 0.4 * np.pi), - (3, Plane.XY, 0.3 * np.pi), - (4, Plane.XY, 0.4 * np.pi), - ], - ( - [ - (3, Plane.XY, 1.8 * np.pi), - (4, Plane.XY, 1.9 * np.pi), - ], - {(3, 4)}, - {3, 4}, - ), - ), - # apply convert_to_phase_gadget, merge_yz_to_xy, then remove_cliffords - ( - (range(1, 5), {(1, 2), (2, 3), (2, 4)}), - [ - (1, Plane.YZ, 0.1 * np.pi), - (2, Plane.XY, 0.9 * np.pi), - (3, Plane.XZ, 0.8 * np.pi), - (4, Plane.XY, 0.4 * np.pi), - ], - ( - [ - (3, Plane.XY, 1.8 * np.pi), - (4, Plane.XY, 1.9 * np.pi), - ], - {(3, 4)}, - {3, 4}, - ), - ), - # apply remove_cliffords, convert_to_phase_gadget, merge_yz_to_xy, then remove_cliffords - ( - (range(1, 7), {(1, 2), (2, 3), (2, 4), (3, 6), (4, 5)}), - [ - (1, Plane.YZ, 0.1 * np.pi), - (2, Plane.XY, 0.9 * np.pi), - (3, Plane.YZ, 1.2 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.YZ, 1.0 * np.pi), - (6, Plane.XY, 0.5 * np.pi), - ], - ( - [ - (3, Plane.XY, 1.8 * np.pi), - (4, Plane.XY, 1.9 * np.pi), - ], - {(3, 4)}, - {3, 4}, - ), - ), - ], -) -def test_prune_non_cliffords( - zx_graph: ZXGraphState, - initial_zxgraph: tuple[range, set[tuple[int, int]]], - measurements: list[tuple[int, Plane, float]], - exp_zxgraph: tuple[list[tuple[int, Plane, float]], set[tuple[int, int]], set[int]], + _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) + + +@pytest.mark.parametrize("planes", plane_combinations(3)) +def test_local_complement_4_times( + zx_graph: ZXGraphState, planes: tuple[Plane, Plane, Plane], rng: np.random.Generator ) -> None: - nodes, edges = initial_zxgraph - _initialize_graph(zx_graph, nodes, edges) - exp_measurements, exp_edges, exp_nodes = exp_zxgraph - _apply_measurements(zx_graph, measurements) - zx_graph.prune_non_cliffords() - _test(zx_graph, exp_nodes, exp_edges, exp_measurements) + """Test local complement is applied 4 times and the graph goes back to the original shape.""" + for i in range(1, 4): + zx_graph.add_physical_node(i) + for i, j in [(1, 2), (2, 3)]: + zx_graph.add_physical_edge(i, j) + angles = [rng.random() * 2 * np.pi for _ in range(3)] + for i in range(1, 4): + zx_graph.set_meas_basis(i, PlannerMeasBasis(planes[i - 1], angles[i - 1])) + + for _ in range(4): + zx_graph.local_complement(2) + + exp_measurements = [ + (1, planes[0], angles[0]), + (2, planes[1], angles[1]), + (3, planes[2], angles[2]), + ] + _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) + + +def test_local_complement_with_h_shaped_graph(zx_graph: ZXGraphState) -> None: + pass if __name__ == "__main__": From 816114099231869acf5f30750c5caf1430e7997c Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 23 Apr 2025 20:37:10 +0900 Subject: [PATCH 34/72] :construction: Migrate local_complement & pivot into new version --- graphix_zx/zxgraphstate.py | 104 ++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 0fda4cbfb..b87b845de 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -12,7 +12,7 @@ import numpy as np from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import is_clifford_angle +from graphix_zx.euler import is_clifford_angle, LocalClifford from graphix_zx.graphstate import GraphState, bipartite_edges if TYPE_CHECKING: @@ -74,8 +74,9 @@ def _update_node_measurement( new_angle = new_angle_func(v) % (2.0 * np.pi) self.set_meas_basis(v, PlannerMeasBasis(new_plane, new_angle)) - def local_complement(self, node: int) -> None: + def old_local_complement(self, node: int) -> None: """Local complement operation on the graph state: G*u. + XXX: Duplicated with the new local complement. Remove after test completion. Parameters ---------- @@ -102,8 +103,8 @@ def local_complement(self, node: int) -> None: # update node measurement if not output node measurement_action = { - Plane.XY: (Plane.XZ, lambda v: (0.5 * np.pi - self.meas_bases[v].angle) % (2.0 * np.pi)), - Plane.XZ: (Plane.XY, lambda v: (self.meas_bases[v].angle - 0.5 * np.pi) % (2.0 * np.pi)), + Plane.XY: (Plane.XZ, lambda v: (0.5 * np.pi + self.meas_bases[v].angle) % (2.0 * np.pi)), + Plane.XZ: (Plane.XY, lambda v: (0.5 * np.pi - self.meas_bases[v].angle) % (2.0 * np.pi)), Plane.YZ: (Plane.YZ, lambda v: (self.meas_bases[v].angle + 0.5 * np.pi) % (2.0 * np.pi)), } if node not in self.output_nodes: @@ -111,7 +112,7 @@ def local_complement(self, node: int) -> None: # update neighbors measurement if not output node measurement_action = { - Plane.XY: (Plane.XY, lambda v: (self.meas_bases[v].angle - 0.5 * np.pi) % (2.0 * np.pi)), + Plane.XY: (Plane.XY, lambda v: (self.meas_bases[v].angle + 0.5 * np.pi) % (2.0 * np.pi)), Plane.XZ: (Plane.YZ, lambda v: self.meas_bases[v].angle), Plane.YZ: (Plane.XZ, lambda v: -self.meas_bases[v].angle % (2.0 * np.pi)), } @@ -119,6 +120,42 @@ def local_complement(self, node: int) -> None: for v in nbrs - self.output_nodes: self._update_node_measurement(measurement_action, v) + def local_complement(self, node: int) -> None: + """Local complement operation on the graph state: G*u. + + Parameters + ---------- + node : int + node index. The node must not be an input node. + + Raises + ------ + ValueError + If the node does not exist, is an input node, or the graph is not a ZX-diagram. + """ + self.ensure_node_exists(node) + if node in self.input_nodes: + msg = "Cannot apply local complement to input node." + raise ValueError(msg) + self.check_meas_basis() + + nbrs: set[int] = self.get_neighbors(node) + nbr_pairs = bipartite_edges(nbrs, nbrs) + new_edges = nbr_pairs - self.physical_edges + rmv_edges = self.physical_edges & nbr_pairs + + self._update_connections(rmv_edges, new_edges) + + # update node measurement if not output node + if node not in self.output_nodes: + lc = LocalClifford(0, np.pi / 2, 0) + self.apply_local_clifford(node, lc) + + # update neighbors measurement if not output node + lc = LocalClifford(-np.pi / 2, 0, 0) + for v in nbrs - self.output_nodes: + self.apply_local_clifford(v, lc) + def _swap(self, node1: int, node2: int) -> None: """Swap nodes u and v in the graph state. @@ -140,8 +177,9 @@ def _swap(self, node1: int, node2: int) -> None: self.remove_physical_edge(node2, c) self.add_physical_edge(node1, c) - def pivot(self, node1: int, node2: int) -> None: + def old_pivot(self, node1: int, node2: int) -> None: """Pivot operation on the graph state: G∧(uv) (= G*u*v*u = G*v*u*v) for neighboring nodes u and v. + XXX: Duplicated with the new pivot. Remove after test completion. In order to maintain the ZX-diagram simple, pi-spiders are shifted properly. @@ -182,9 +220,9 @@ def pivot(self, node1: int, node2: int) -> None: # update node1 and node2 measurement measurement_action = { - Plane.XY: (Plane.YZ, lambda v: self.meas_bases[v].angle), + Plane.XY: (Plane.YZ, lambda v: -self.meas_bases[v].angle), Plane.XZ: (Plane.XZ, lambda v: (0.5 * np.pi - self.meas_bases[v].angle)), - Plane.YZ: (Plane.XY, lambda v: self.meas_bases[v].angle), + Plane.YZ: (Plane.XY, lambda v: -self.meas_bases[v].angle), } for a in {node1, node2} - self.output_nodes: @@ -200,6 +238,56 @@ def pivot(self, node1: int, node2: int) -> None: for w in nbr_a - self.output_nodes: self._update_node_measurement(measurement_action, w) + def pivot(self, node1: int, node2: int) -> None: + """Pivot operation on the graph state: G∧(uv) (= G*u*v*u = G*v*u*v) for neighboring nodes u and v. + + In order to maintain the ZX-diagram simple, pi-spiders are shifted properly. + + Parameters + ---------- + node1 : int + node index. The node must not be an input node. + node2 : int + node index. The node must not be an input node. + + Raises + ------ + ValueError + If the nodes are input nodes, or the graph is not a ZX-diagram. + """ + self.ensure_node_exists(node1) + self.ensure_node_exists(node2) + if node1 in self.input_nodes or node2 in self.input_nodes: + msg = "Cannot apply pivot to input node" + raise ValueError(msg) + self.check_meas_basis() + + node1_nbrs = self.get_neighbors(node1) - {node2} + node2_nbrs = self.get_neighbors(node2) - {node1} + nbr_a = node1_nbrs & node2_nbrs + nbr_b = node1_nbrs - node2_nbrs + nbr_c = node2_nbrs - node1_nbrs + nbr_pairs = [ + bipartite_edges(nbr_a, nbr_b), + bipartite_edges(nbr_a, nbr_c), + bipartite_edges(nbr_b, nbr_c), + ] + rmv_edges = set().union(*(p & self.physical_edges for p in nbr_pairs)) + add_edges = set().union(*(p - self.physical_edges for p in nbr_pairs)) + + self._update_connections(rmv_edges, add_edges) + self._swap(node1, node2) + + # update node1 and node2 measurement + lc = LocalClifford(np.pi / 2, np.pi / 2, np.pi / 2) + for a in {node1, node2} - self.output_nodes: + self.apply_local_clifford(a, lc) + + # update nodes measurement of nbr_a + lc = LocalClifford(np.pi, 0, 0) + for w in nbr_a - self.output_nodes: + self.apply_local_clifford(w, lc) + def _needs_nop(self, node: int, atol: float = 1e-9) -> bool: """Check if the node does not need any operation in order to perform _remove_clifford. From fe1bd6d620d6325b40242e1bc5e13abb82ee08e0 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 24 Apr 2025 00:27:52 +0900 Subject: [PATCH 35/72] :art: Fix lc & import --- graphix_zx/zxgraphstate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index b87b845de..98b2b66ad 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -12,7 +12,7 @@ import numpy as np from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import is_clifford_angle, LocalClifford +from graphix_zx.euler import LocalClifford, is_clifford_angle from graphix_zx.graphstate import GraphState, bipartite_edges if TYPE_CHECKING: @@ -147,8 +147,8 @@ def local_complement(self, node: int) -> None: self._update_connections(rmv_edges, new_edges) # update node measurement if not output node + lc = LocalClifford(0, np.pi / 2, 0) if node not in self.output_nodes: - lc = LocalClifford(0, np.pi / 2, 0) self.apply_local_clifford(node, lc) # update neighbors measurement if not output node From f826a84964f32a9ae7485a6bb87b2f6b330763fd Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 24 Apr 2025 00:28:20 +0900 Subject: [PATCH 36/72] :fire: Remove old local_complement, pivot, remove_clifford --- graphix_zx/zxgraphstate.py | 135 ++----------------------------------- 1 file changed, 6 insertions(+), 129 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 98b2b66ad..e14a95919 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -74,52 +74,6 @@ def _update_node_measurement( new_angle = new_angle_func(v) % (2.0 * np.pi) self.set_meas_basis(v, PlannerMeasBasis(new_plane, new_angle)) - def old_local_complement(self, node: int) -> None: - """Local complement operation on the graph state: G*u. - XXX: Duplicated with the new local complement. Remove after test completion. - - Parameters - ---------- - node : int - node index. The node must not be an input node. - - Raises - ------ - ValueError - If the node does not exist, is an input node, or the graph is not a ZX-diagram. - """ - self.ensure_node_exists(node) - if node in self.input_nodes: - msg = "Cannot apply local complement to input node." - raise ValueError(msg) - self.check_meas_basis() - - nbrs: set[int] = self.get_neighbors(node) - nbr_pairs = bipartite_edges(nbrs, nbrs) - new_edges = nbr_pairs - self.physical_edges - rmv_edges = self.physical_edges & nbr_pairs - - self._update_connections(rmv_edges, new_edges) - - # update node measurement if not output node - measurement_action = { - Plane.XY: (Plane.XZ, lambda v: (0.5 * np.pi + self.meas_bases[v].angle) % (2.0 * np.pi)), - Plane.XZ: (Plane.XY, lambda v: (0.5 * np.pi - self.meas_bases[v].angle) % (2.0 * np.pi)), - Plane.YZ: (Plane.YZ, lambda v: (self.meas_bases[v].angle + 0.5 * np.pi) % (2.0 * np.pi)), - } - if node not in self.output_nodes: - self._update_node_measurement(measurement_action, node) - - # update neighbors measurement if not output node - measurement_action = { - Plane.XY: (Plane.XY, lambda v: (self.meas_bases[v].angle + 0.5 * np.pi) % (2.0 * np.pi)), - Plane.XZ: (Plane.YZ, lambda v: self.meas_bases[v].angle), - Plane.YZ: (Plane.XZ, lambda v: -self.meas_bases[v].angle % (2.0 * np.pi)), - } - - for v in nbrs - self.output_nodes: - self._update_node_measurement(measurement_action, v) - def local_complement(self, node: int) -> None: """Local complement operation on the graph state: G*u. @@ -177,67 +131,6 @@ def _swap(self, node1: int, node2: int) -> None: self.remove_physical_edge(node2, c) self.add_physical_edge(node1, c) - def old_pivot(self, node1: int, node2: int) -> None: - """Pivot operation on the graph state: G∧(uv) (= G*u*v*u = G*v*u*v) for neighboring nodes u and v. - XXX: Duplicated with the new pivot. Remove after test completion. - - In order to maintain the ZX-diagram simple, pi-spiders are shifted properly. - - Parameters - ---------- - node1 : int - node index. The node must not be an input node. - node2 : int - node index. The node must not be an input node. - - Raises - ------ - ValueError - If the nodes are input nodes, or the graph is not a ZX-diagram. - """ - self.ensure_node_exists(node1) - self.ensure_node_exists(node2) - if node1 in self.input_nodes or node2 in self.input_nodes: - msg = "Cannot apply pivot to input node" - raise ValueError(msg) - self.check_meas_basis() - - node1_nbrs = self.get_neighbors(node1) - {node2} - node2_nbrs = self.get_neighbors(node2) - {node1} - nbr_a = node1_nbrs & node2_nbrs - nbr_b = node1_nbrs - node2_nbrs - nbr_c = node2_nbrs - node1_nbrs - nbr_pairs = [ - bipartite_edges(nbr_a, nbr_b), - bipartite_edges(nbr_a, nbr_c), - bipartite_edges(nbr_b, nbr_c), - ] - rmv_edges = set().union(*(p & self.physical_edges for p in nbr_pairs)) - add_edges = set().union(*(p - self.physical_edges for p in nbr_pairs)) - - self._update_connections(rmv_edges, add_edges) - self._swap(node1, node2) - - # update node1 and node2 measurement - measurement_action = { - Plane.XY: (Plane.YZ, lambda v: -self.meas_bases[v].angle), - Plane.XZ: (Plane.XZ, lambda v: (0.5 * np.pi - self.meas_bases[v].angle)), - Plane.YZ: (Plane.XY, lambda v: -self.meas_bases[v].angle), - } - - for a in {node1, node2} - self.output_nodes: - self._update_node_measurement(measurement_action, a) - - # update nodes measurement of nbr_a - measurement_action = { - Plane.XY: (Plane.XY, lambda v: (self.meas_bases[v].angle + np.pi) % (2.0 * np.pi)), - Plane.XZ: (Plane.XZ, lambda v: -self.meas_bases[v].angle % (2.0 * np.pi)), - Plane.YZ: (Plane.YZ, lambda v: -self.meas_bases[v].angle % (2.0 * np.pi)), - } - - for w in nbr_a - self.output_nodes: - self._update_node_measurement(measurement_action, w) - def pivot(self, node1: int, node2: int) -> None: """Pivot operation on the graph state: G∧(uv) (= G*u*v*u = G*v*u*v) for neighboring nodes u and v. @@ -399,29 +292,13 @@ def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: atol : float, optional absolute tolerance, by default 1e-9 """ - alpha = self.meas_bases[node].angle % (2.0 * np.pi) - measurement_action = { - Plane.XY: ( - Plane.XY, - lambda v: self.meas_bases[v].angle - if abs(alpha % (2.0 * np.pi)) < atol - else (self.meas_bases[v].angle + np.pi) % (2.0 * np.pi), - ), - Plane.XZ: ( - Plane.XZ, - lambda v: self.meas_bases[v].angle - if abs(alpha % (2.0 * np.pi)) < atol - else -self.meas_bases[v].angle % (2.0 * np.pi), - ), - Plane.YZ: ( - Plane.YZ, - lambda v: self.meas_bases[v].angle - if abs(alpha % (2.0 * np.pi)) < atol - else -self.meas_bases[v].angle % (2.0 * np.pi), - ), - } + a_pi = self.meas_bases[node].angle % (2.0 * np.pi) + coeff = 1.0 + if abs(a_pi % (2 * np.pi)) < atol: + coeff = 0.0 + lc = LocalClifford(coeff * np.pi, 0, 0) for v in self.get_neighbors(node) - self.output_nodes: - self._update_node_measurement(measurement_action, v) + self.apply_local_clifford(v, lc) self.remove_physical_node(node) From 7d5a8d989243a6dc391159c02aa2d9bf36ddaf38 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 24 Apr 2025 00:28:57 +0900 Subject: [PATCH 37/72] :art: Fix typehints in test --- tests/test_zxgraphstate.py | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 255bf634f..45174288e 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1,35 +1,58 @@ from __future__ import annotations -from copy import deepcopy import itertools import operator +from copy import deepcopy from typing import TYPE_CHECKING import numpy as np import pytest from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import is_clifford_angle, _is_close_angle, LocalClifford, update_lc_basis +from graphix_zx.euler import _is_close_angle, is_clifford_angle from graphix_zx.random_objects import get_random_flow_graph from graphix_zx.zxgraphstate import ZXGraphState if TYPE_CHECKING: from typing import Callable - Func = Callable[[float], float] - MeasurementAction = dict[Plane, tuple[Plane, Func]] Measurements = list[tuple[int, Plane, float]] -measurement_action_lc_target: MeasurementAction = { +measurement_action_lc_target: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XZ, lambda angle: angle + np.pi / 2), Plane.XZ: (Plane.XY, lambda angle: -angle + np.pi / 2), Plane.YZ: (Plane.YZ, lambda angle: angle + np.pi / 2), } -measurement_action_lc_neighbors: MeasurementAction = { +measurement_action_lc_neighbors: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XY, lambda angle: angle + np.pi / 2), Plane.XZ: (Plane.YZ, lambda angle: angle), Plane.YZ: (Plane.XZ, operator.neg), } +measurement_action_pv_target: dict[Plane, tuple[Plane, Callable[[float], float]]] = { + Plane.XY: (Plane.YZ, operator.neg), + Plane.XZ: (Plane.XZ, lambda angle: (np.pi / 2 - angle)), + Plane.YZ: (Plane.XY, operator.neg), +} +measurement_action_pv_neighbors: dict[Plane, tuple[Plane, Callable[[float], float]]] = { + Plane.XY: (Plane.XY, lambda angle: (angle + np.pi) % (2.0 * np.pi)), + Plane.XZ: (Plane.XZ, lambda angle: -angle % (2.0 * np.pi)), + Plane.YZ: (Plane.YZ, lambda angle: -angle % (2.0 * np.pi)), +} +atol = 1e-9 +measurement_action_rc: dict[Plane, tuple[Plane, Callable[[float, float], float]]] = { + Plane.XY: ( + Plane.XY, + lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < atol else alpha + np.pi) % (2.0 * np.pi), + ), + Plane.XZ: ( + Plane.XZ, + lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < atol else -alpha) % (2.0 * np.pi), + ), + Plane.YZ: ( + Plane.YZ, + lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < atol else -alpha) % (2.0 * np.pi), + ), +} def plane_combinations(n: int) -> list[tuple[Plane, ...]]: @@ -157,7 +180,7 @@ def test_local_complement_with_no_edge(zx_graph: ZXGraphState, plane: Plane, rng assert _is_close_angle(zx_graph.meas_bases[1].angle, ref_angle) -@pytest.mark.parametrize("plane1, plane3", plane_combinations(2)) +@pytest.mark.parametrize(("plane1", "plane3"), plane_combinations(2)) def test_local_complement_on_output_node( zx_graph: ZXGraphState, plane1: Plane, plane3: Plane, rng: np.random.Generator ) -> None: @@ -179,7 +202,7 @@ def test_local_complement_on_output_node( assert zx_graph.meas_bases.get(2) is None -@pytest.mark.parametrize("plane1, plane2", plane_combinations(2)) +@pytest.mark.parametrize(("plane1", "plane2"), plane_combinations(2)) def test_local_complement_with_two_nodes_graph( zx_graph: ZXGraphState, plane1: Plane, plane2: Plane, rng: np.random.Generator ) -> None: From 45dfbc85eb11f0e552f2e39127bdd067851ad43c Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 24 Apr 2025 00:30:12 +0900 Subject: [PATCH 38/72] :recycle: Refactor test_zxgraphstate --- tests/test_zxgraphstate.py | 635 ++++++++++++++++++++++++++++++++++++- 1 file changed, 629 insertions(+), 6 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 45174288e..fb509afd7 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -276,16 +276,639 @@ def test_local_complement_4_times( for _ in range(4): zx_graph.local_complement(2) + exp_measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 4)] + _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) + + +# @pytest.mark.parametrize("planes", [(Plane.XY, Plane.XZ, Plane.YZ, Plane.XY, Plane.XZ, Plane.YZ)]) +# def test_local_complement_with_h_shaped_graph( +# zx_graph: ZXGraphState, planes: tuple[Plane, Plane, Plane, Plane, Plane, Plane], rng: np.random.Generator +# ) -> None: +# """Test local complement with an H-shaped graph.""" +# for i in range(1, 7): +# zx_graph.add_physical_node(i) + +# zx_graph.set_input(1) +# zx_graph.set_input(4) + +# for i, j in [(1, 2), (2, 3), (2, 5), (4, 5), (5, 6)]: +# zx_graph.add_physical_edge(i, j) + +# angles = [rng.random() * 2 * np.pi for _ in range(6)] +# measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 7)] +# _apply_measurements(zx_graph, measurements) + +# target = 2 +# zx_graph.local_complement(target) +# exp_measurements = [] +# for i in (1, 2, 3, 5): +# meas_ac = measurement_action_lc_neighbors +# if i == target: +# meas_ac = measurement_action_lc_target +# ref_plane, ref_angle_func = meas_ac[planes[i - 1]] +# ref_angle = ref_angle_func(angles[i - 1]) +# exp_measurements.append((i, ref_plane, ref_angle)) +# _test( +# zx_graph, +# exp_nodes={1, 2, 3, 4, 5, 6}, +# exp_edges={(1, 2), (1, 3), (1, 5), (2, 3), (2, 5), (3, 5), (4, 5), (5, 6)}, +# exp_measurements=exp_measurements, +# ) + +# zx_graph.local_complement(2) +# assert zx_graph.physical_edges == original_edges +# exp_measurements = [ +# (1, Plane.XY, 0.1 * np.pi), +# (2, Plane.XZ, 1.8 * np.pi), +# (3, Plane.YZ, 0.7 * np.pi), +# (4, Plane.XY, 1.4 * np.pi), +# (5, Plane.XZ, 0.5 * np.pi), +# (6, Plane.YZ, 1.6 * np.pi), +# ] +# for node_id, plane, angle in exp_measurements: +# assert zx_graph.meas_bases[node_id].plane == plane +# assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) + + +def test_pivot_fails_with_nonexistent_nodes(zx_graph: ZXGraphState) -> None: + """Test pivot fails with nonexistent nodes.""" + with pytest.raises(ValueError, match="Node does not exist node=1"): + zx_graph.pivot(1, 2) + zx_graph.add_physical_node(1) + with pytest.raises(ValueError, match="Node does not exist node=2"): + zx_graph.pivot(1, 2) + + +def test_pivot_fails_with_input_node(zx_graph: ZXGraphState) -> None: + """Test pivot fails with input node.""" + zx_graph.add_physical_node(1) + zx_graph.add_physical_node(2) + zx_graph.set_input(1) + with pytest.raises(ValueError, match="Cannot apply pivot to input node"): + zx_graph.pivot(1, 2) + + +def test_pivot_with_obvious_graph(zx_graph: ZXGraphState) -> None: + """Test pivot with an obvious graph.""" + # 1---2---3 + for i in range(1, 4): + zx_graph.add_physical_node(i) + + for i, j in [(1, 2), (2, 3)]: + zx_graph.add_physical_edge(i, j) + + measurements = [ + (1, Plane.XY, 1.1 * np.pi), + (2, Plane.XZ, 1.2 * np.pi), + (3, Plane.YZ, 1.3 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + + original_zx_graph = deepcopy(zx_graph) + zx_graph.pivot(2, 3) + original_zx_graph.local_complement(2) + original_zx_graph.local_complement(3) + original_zx_graph.local_complement(2) + assert zx_graph.physical_edges == original_zx_graph.physical_edges + original_planes = [original_zx_graph.meas_bases[i].plane for i in range(1, 4)] + planes = [zx_graph.meas_bases[i].plane for i in range(1, 4)] + assert planes == original_planes + + +@pytest.mark.parametrize("planes", plane_combinations(5)) +def test_pivot_with_minimal_graph( + zx_graph: ZXGraphState, planes: tuple[Plane, Plane, Plane, Plane, Plane], rng: np.random.Generator +) -> None: + """Test pivot with a minimal graph.""" + # 1---2---3---5 + # \ / + # 4 + for i in range(1, 6): + zx_graph.add_physical_node(i) + + for i, j in [(1, 2), (2, 3), (2, 4), (3, 4), (3, 5)]: + zx_graph.add_physical_edge(i, j) + + angles = [rng.random() * 2 * np.pi for _ in range(5)] + measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 6)] + _apply_measurements(zx_graph, measurements) + zx_graph_cp = deepcopy(zx_graph) + + zx_graph.pivot(2, 3) + zx_graph_cp.local_complement(2) + zx_graph_cp.local_complement(3) + zx_graph_cp.local_complement(2) + assert zx_graph.physical_edges == zx_graph_cp.physical_edges + assert zx_graph.meas_bases[2].plane == zx_graph_cp.meas_bases[2].plane + assert zx_graph.meas_bases[3].plane == zx_graph_cp.meas_bases[3].plane + + _, ref_angle_func2 = measurement_action_pv_target[planes[1]] + _, ref_angle_func3 = measurement_action_pv_target[planes[2]] + _, ref_angle_func4 = measurement_action_pv_neighbors[planes[3]] + ref_angle2 = ref_angle_func2(angles[1]) + ref_angle3 = ref_angle_func3(angles[2]) + ref_angle4 = ref_angle_func4(angles[3]) + assert _is_close_angle(zx_graph.meas_bases[2].angle, ref_angle2) + assert _is_close_angle(zx_graph.meas_bases[3].angle, ref_angle3) + assert _is_close_angle(zx_graph.meas_bases[4].angle, ref_angle4) + + +def test_remove_clifford_fails_if_nonexistent_node(zx_graph: ZXGraphState) -> None: + """Test remove_clifford raises an error if the node does not exist.""" + with pytest.raises(ValueError, match="Node does not exist node=1"): + zx_graph.remove_clifford(1) + + +def test_remove_clifford_fails_with_input_node(zx_graph: ZXGraphState) -> None: + zx_graph.add_physical_node(1) + zx_graph.set_input(1) + with pytest.raises(ValueError, match="Clifford vertex removal not allowed for input node"): + zx_graph.remove_clifford(1) + + +def test_remove_clifford_fails_with_invalid_plane(zx_graph: ZXGraphState) -> None: + """Test remove_clifford fails if the measurement plane is invalid.""" + zx_graph.add_physical_node(1) + zx_graph.set_meas_basis( + 1, + PlannerMeasBasis("test_plane", 0.5 * np.pi), # type: ignore[reportArgumentType, arg-type, unused-ignore] + ) + with pytest.raises(ValueError, match="This node is not a Clifford vertex"): + zx_graph.remove_clifford(1) + + +def test_remove_clifford_fails_for_non_clifford_vertex(zx_graph: ZXGraphState) -> None: + zx_graph.add_physical_node(1) + zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)) + with pytest.raises(ValueError, match="This node is not a Clifford vertex"): + zx_graph.remove_clifford(1) + + +def graph_1(zx_graph: ZXGraphState) -> None: + # _needs_nop + # 4---1---2 4 2 + # | -> + # 3 3 + _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) + + +def graph_2(zx_graph: ZXGraphState) -> None: + # _needs_lc + # 1---2---3 -> 1---3 + _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}) + + +def graph_3(zx_graph: ZXGraphState) -> None: + # _needs_pivot_1 on (2, 3) + # 4(I) 4(I) + # / \ / | \ + # 1(I) - 2 - 3 - 6 -> 1(I) - 3 6 - 1(I) + # \ / \ | / + # 5(I) 5(I) + _initialize_graph( + zx_graph, nodes=range(1, 7), edges={(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, inputs=(1, 4, 5) + ) + + +def graph_4(zx_graph: ZXGraphState) -> None: + # _needs_pivot_2 on (2, 4) + # 1(I) 3(O) 5(O) 1(I)-3(O)-5(O)-1(I) + # \ / \ / -> \ / + # 2(O)- 4 2(O) + _initialize_graph( + zx_graph, + nodes=range(1, 6), + edges={(1, 2), (2, 3), (2, 4), (3, 4), (4, 5)}, + inputs=(1,), + outputs=(2, 3, 5), + ) + + +def _test_remove_clifford( + zx_graph: ZXGraphState, + node: int, + measurements: Measurements, + exp_graph: tuple[set[int], set[tuple[int, int]]], + exp_measurements: Measurements, +) -> None: + _apply_measurements(zx_graph, measurements) + zx_graph.remove_clifford(node) + exp_nodes = exp_graph[0] + exp_edges = exp_graph[1] + _test(zx_graph, exp_nodes, exp_edges, exp_measurements) + + +@pytest.mark.parametrize( + "planes", + list(itertools.product([Plane.XY, Plane.XZ, Plane.YZ], [Plane.XZ, Plane.YZ], [Plane.XY, Plane.XZ, Plane.YZ])), +) +def test_remove_clifford( + zx_graph: ZXGraphState, + planes: tuple[Plane, Plane, Plane], + rng: np.random.Generator, +) -> None: + graph_2(zx_graph) + angles = [rng.random() * 2 * np.pi for _ in range(3)] + angles[1] = rng.choice([0.0, np.pi]) + measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 4)] + ref_plane1, ref_angle_func1 = measurement_action_rc[planes[0]] + ref_plane3, ref_angle_func3 = measurement_action_rc[planes[2]] + ref_angle1 = ref_angle_func1(angles[1], angles[0]) + ref_angle3 = ref_angle_func3(angles[1], angles[2]) exp_measurements = [ - (1, planes[0], angles[0]), - (2, planes[1], angles[1]), - (3, planes[2], angles[2]), + (1, ref_plane1, ref_angle1), + (3, ref_plane3, ref_angle3), ] - _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) + _test_remove_clifford( + zx_graph, node=2, measurements=measurements, exp_graph=({1, 3}, set()), exp_measurements=exp_measurements + ) + + +def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: + _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}, inputs=(1, 3)) + measurements = [ + (1, Plane.XY, 0.5 * np.pi), + (2, Plane.XY, np.pi), + (3, Plane.XY, 0.5 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + with pytest.raises(ValueError, match=r"This Clifford vertex is unremovable."): + zx_graph.remove_clifford(2) + + +def test_remove_cliffords(zx_graph: ZXGraphState) -> None: + """Test removing multiple Clifford vertices.""" + _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) + measurements = [ + (1, Plane.XY, 0.5 * np.pi), + (2, Plane.XY, 0.5 * np.pi), + (3, Plane.XY, 0.5 * np.pi), + (4, Plane.XY, 0.5 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + zx_graph.remove_cliffords() + _test(zx_graph, {3}, set(), []) + + +def test_remove_cliffords_graph1(zx_graph: ZXGraphState) -> None: + """Test removing multiple Clifford vertices.""" + graph_1(zx_graph) + measurements = [ + (1, Plane.YZ, np.pi), + (2, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + ] + exp_measurements = [ + (2, Plane.XY, 1.1 * np.pi), + (3, Plane.XZ, 1.8 * np.pi), + (4, Plane.YZ, 1.7 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + zx_graph.remove_cliffords() + _test(zx_graph, {2, 3, 4}, set(), exp_measurements=exp_measurements) -def test_local_complement_with_h_shaped_graph(zx_graph: ZXGraphState) -> None: - pass +def test_remove_cliffords_graph2(zx_graph: ZXGraphState) -> None: + graph_2(zx_graph) + measurements = [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.YZ, 1.5 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + ] + exp_measurements = [ + (1, Plane.XY, 0.6 * np.pi), + (3, Plane.YZ, 0.2 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + zx_graph.remove_cliffords() + _test(zx_graph, {1, 3}, {(1, 3)}, exp_measurements=exp_measurements) + + +def test_remove_cliffords_graph3(zx_graph: ZXGraphState) -> None: + graph_3(zx_graph) + measurements = [ + (1, Plane.XY, 0.1 * np.pi), + (2, Plane.XZ, 1.5 * np.pi), + (3, Plane.XZ, 0.2 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 0.5 * np.pi), + ] + exp_measurements = [ + (1, Plane.XY, 0.1 * np.pi), + (3, Plane.XZ, 1.7 * np.pi), + (4, Plane.YZ, 0.3 * np.pi), + (5, Plane.XY, 0.4 * np.pi), + (6, Plane.XZ, 1.5 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + zx_graph.remove_cliffords() + _test( + zx_graph, + {1, 3, 4, 5, 6}, + {(1, 3), (1, 4), (1, 5), (1, 6), (3, 4), (3, 5), (4, 6), (5, 6)}, + exp_measurements=exp_measurements, + ) + + +def test_remove_cliffords_graph4(zx_graph: ZXGraphState) -> None: + """Test removing multiple Clifford vertices.""" + graph_4(zx_graph) + measurements = [ + (1, Plane.XY, np.pi), + (4, Plane.XZ, 0.5 * np.pi), + ] + _apply_measurements(zx_graph, measurements) + zx_graph.remove_cliffords() + _test(zx_graph, {1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}, [(1, Plane.XY, np.pi)]) + + +def test_random_graph(zx_graph: ZXGraphState) -> None: + """Test removing multiple Clifford vertices from a random graph.""" + random_graph, _ = get_random_flow_graph(5, 5) + zx_graph.append(random_graph) + + for i in zx_graph.physical_nodes - zx_graph.output_nodes: + rng = np.random.default_rng(seed=0) + rnd = rng.random() + if 0 <= rnd < 0.33: + pass + elif 0.33 <= rnd < 0.66: + angle = zx_graph.meas_bases[i].angle + zx_graph.set_meas_basis(i, PlannerMeasBasis(Plane.XZ, angle)) + else: + angle = zx_graph.meas_bases[i].angle + zx_graph.set_meas_basis(i, PlannerMeasBasis(Plane.YZ, angle)) + + zx_graph.remove_cliffords() + atol = 1e-9 + nodes = zx_graph.physical_nodes - zx_graph.input_nodes - zx_graph.output_nodes + clifford_nodes = [ + node + for node in nodes + if is_clifford_angle(zx_graph.meas_bases[node].angle, atol) and zx_graph.is_removable_clifford(node, atol) + ] + assert clifford_nodes == [] + + +@pytest.mark.parametrize( + ("measurements", "exp_measurements", "exp_edges"), + [ + # no pair of adjacent nodes with YZ measurements + # and no node with XZ measurement + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + (5, Plane.XY, 0.55 * np.pi), + (6, Plane.XY, 0.66 * np.pi), + ], + {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, + ), + ], +) +def test_convert_to_phase_gadget( + zx_graph: ZXGraphState, + measurements: Measurements, + exp_measurements: Measurements, + exp_edges: set[tuple[int, int]], +) -> None: + initial_edges = {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)} + _initialize_graph(zx_graph, nodes=range(1, 7), edges=initial_edges) + _apply_measurements(zx_graph, measurements) + zx_graph.convert_to_phase_gadget() + _test(zx_graph, exp_nodes={1, 2, 3, 4, 5, 6}, exp_edges=exp_edges, exp_measurements=exp_measurements) + + +@pytest.mark.parametrize( + ("initial_edges", "measurements", "exp_measurements", "exp_edges"), + [ + # 4(XY) 4(XY) + # | -> | + # 1(YZ) - 2(XY) - 3(XY) 2(XY) - 3(XY) + ( + {(1, 2), (2, 3), (2, 4)}, + [ + (1, Plane.YZ, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + ], + [ + (2, Plane.XY, 0.33 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.XY, 0.44 * np.pi), + ], + {(2, 3), (2, 4)}, + ), + # 4(YZ) 4(YZ) + # | \ -> | \ + # 1(YZ) - 2(XY) - 3(XY) 2(XY) - 3(XY) + ( + {(1, 2), (2, 3), (2, 4), (3, 4)}, + [ + (1, Plane.YZ, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + ], + [ + (2, Plane.XY, 0.33 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + ], + {(2, 3), (2, 4), (3, 4)}, + ), + ], +) +def test_merge_yz_to_xy( + zx_graph: ZXGraphState, + initial_edges: set[tuple[int, int]], + measurements: Measurements, + exp_measurements: Measurements, + exp_edges: set[tuple[int, int]], +) -> None: + _initialize_graph(zx_graph, nodes=range(1, 5), edges=initial_edges) + _apply_measurements(zx_graph, measurements) + zx_graph.merge_yz_to_xy() + _test(zx_graph, exp_nodes={2, 3, 4}, exp_edges=exp_edges, exp_measurements=exp_measurements) + + +@pytest.mark.parametrize( + ("initial_edges", "measurements", "exp_zxgraph"), + [ + # 4(YZ) 4(YZ) + # / \ / \ + # 1(XY) - 2(XY) - 3(XY) -> 1(XY) - 2(XY) - 3(XY) + # \ / + # 5(YZ) + ( + {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + (5, Plane.YZ, 0.55 * np.pi), + ], + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.XY, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.99 * np.pi), + ], + {(1, 2), (1, 4), (2, 3), (3, 4)}, + {1, 2, 3, 4}, + ), + ), + # 4(YZ) + # / \ + # 1(XY) - 2(YZ) - 3(XY) -> 1(XY) - 2(YZ) - 3(XY) + # \ / + # 5(YZ) + ( + {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + (5, Plane.YZ, 0.55 * np.pi), + ], + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 1.21 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + ], + {(1, 2), (2, 3)}, + {1, 2, 3}, + ), + ), + # 4(YZ) + # / \ + # 1(XY) - 2(YZ) - 3(XY) - 1(XY) -> 1(XY) - 2(YZ) - 3(XY) - 1(XY) + # \ / + # 5(YZ) + ( + {(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 0.22 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + (4, Plane.YZ, 0.44 * np.pi), + (5, Plane.YZ, 0.55 * np.pi), + ], + ( + [ + (1, Plane.XY, 0.11 * np.pi), + (2, Plane.YZ, 1.21 * np.pi), + (3, Plane.XY, 0.33 * np.pi), + ], + {(1, 2), (1, 3), (2, 3)}, + {1, 2, 3}, + ), + ), + ], +) +def test_merge_yz_nodes( + zx_graph: ZXGraphState, + initial_edges: set[tuple[int, int]], + measurements: Measurements, + exp_zxgraph: tuple[Measurements, set[tuple[int, int]], set[int]], +) -> None: + _initialize_graph(zx_graph, nodes=range(1, 6), edges=initial_edges) + _apply_measurements(zx_graph, measurements) + zx_graph.merge_yz_nodes() + exp_measurements, exp_edges, exp_nodes = exp_zxgraph + _test(zx_graph, exp_nodes, exp_edges, exp_measurements) + + +@pytest.mark.parametrize( + ("initial_zxgraph", "measurements", "exp_zxgraph"), + [ + # test for a phase gadget: apply merge_yz_to_xy then remove_cliffords + ( + (range(1, 5), {(1, 2), (2, 3), (2, 4)}), + [ + (1, Plane.YZ, 0.1 * np.pi), + (2, Plane.XY, 0.4 * np.pi), + (3, Plane.XY, 0.3 * np.pi), + (4, Plane.XY, 0.4 * np.pi), + ], + ( + [ + (3, Plane.XY, 1.8 * np.pi), + (4, Plane.XY, 1.9 * np.pi), + ], + {(3, 4)}, + {3, 4}, + ), + ), + # apply convert_to_phase_gadget, merge_yz_to_xy, then remove_cliffords + ( + (range(1, 5), {(1, 2), (2, 3), (2, 4)}), + [ + (1, Plane.YZ, 0.1 * np.pi), + (2, Plane.XY, 0.9 * np.pi), + (3, Plane.XZ, 0.8 * np.pi), + (4, Plane.XY, 0.4 * np.pi), + ], + ( + [ + (3, Plane.XY, 0.2 * np.pi), + (4, Plane.XY, 0.9 * np.pi), + ], + {(3, 4)}, + {3, 4}, + ), + ), + # apply remove_cliffords, convert_to_phase_gadget, merge_yz_to_xy, then remove_cliffords + ( + (range(1, 7), {(1, 2), (2, 3), (2, 4), (3, 6), (4, 5)}), + [ + (1, Plane.YZ, 0.1 * np.pi), + (2, Plane.XY, 0.9 * np.pi), + (3, Plane.YZ, 1.2 * np.pi), + (4, Plane.XY, 1.4 * np.pi), + (5, Plane.YZ, 1.0 * np.pi), + (6, Plane.XY, 0.5 * np.pi), + ], + ( + [ + (3, Plane.XY, 1.8 * np.pi), + (4, Plane.XY, 0.9 * np.pi), + ], + {(3, 4)}, + {3, 4}, + ), + ), + ], +) +def test_prune_non_cliffords( + zx_graph: ZXGraphState, + initial_zxgraph: tuple[range, set[tuple[int, int]]], + measurements: Measurements, + exp_zxgraph: tuple[Measurements, set[tuple[int, int]], set[int]], +) -> None: + nodes, edges = initial_zxgraph + _initialize_graph(zx_graph, nodes, edges) + exp_measurements, exp_edges, exp_nodes = exp_zxgraph + _apply_measurements(zx_graph, measurements) + zx_graph.prune_non_cliffords() + _test(zx_graph, exp_nodes, exp_edges, exp_measurements) if __name__ == "__main__": From 44131ec5b59d23d3e712c011464255e838d3361a Mon Sep 17 00:00:00 2001 From: nabe98 Date: Thu, 24 Apr 2025 00:32:46 +0900 Subject: [PATCH 39/72] :fire: Delete unneccesary tests --- tests/test_zxgraphstate.py | 50 -------------------------------------- 1 file changed, 50 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index fb509afd7..81ce6bbb8 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -280,56 +280,6 @@ def test_local_complement_4_times( _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) -# @pytest.mark.parametrize("planes", [(Plane.XY, Plane.XZ, Plane.YZ, Plane.XY, Plane.XZ, Plane.YZ)]) -# def test_local_complement_with_h_shaped_graph( -# zx_graph: ZXGraphState, planes: tuple[Plane, Plane, Plane, Plane, Plane, Plane], rng: np.random.Generator -# ) -> None: -# """Test local complement with an H-shaped graph.""" -# for i in range(1, 7): -# zx_graph.add_physical_node(i) - -# zx_graph.set_input(1) -# zx_graph.set_input(4) - -# for i, j in [(1, 2), (2, 3), (2, 5), (4, 5), (5, 6)]: -# zx_graph.add_physical_edge(i, j) - -# angles = [rng.random() * 2 * np.pi for _ in range(6)] -# measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 7)] -# _apply_measurements(zx_graph, measurements) - -# target = 2 -# zx_graph.local_complement(target) -# exp_measurements = [] -# for i in (1, 2, 3, 5): -# meas_ac = measurement_action_lc_neighbors -# if i == target: -# meas_ac = measurement_action_lc_target -# ref_plane, ref_angle_func = meas_ac[planes[i - 1]] -# ref_angle = ref_angle_func(angles[i - 1]) -# exp_measurements.append((i, ref_plane, ref_angle)) -# _test( -# zx_graph, -# exp_nodes={1, 2, 3, 4, 5, 6}, -# exp_edges={(1, 2), (1, 3), (1, 5), (2, 3), (2, 5), (3, 5), (4, 5), (5, 6)}, -# exp_measurements=exp_measurements, -# ) - -# zx_graph.local_complement(2) -# assert zx_graph.physical_edges == original_edges -# exp_measurements = [ -# (1, Plane.XY, 0.1 * np.pi), -# (2, Plane.XZ, 1.8 * np.pi), -# (3, Plane.YZ, 0.7 * np.pi), -# (4, Plane.XY, 1.4 * np.pi), -# (5, Plane.XZ, 0.5 * np.pi), -# (6, Plane.YZ, 1.6 * np.pi), -# ] -# for node_id, plane, angle in exp_measurements: -# assert zx_graph.meas_bases[node_id].plane == plane -# assert np.isclose(zx_graph.meas_bases[node_id].angle, angle) - - def test_pivot_fails_with_nonexistent_nodes(zx_graph: ZXGraphState) -> None: """Test pivot fails with nonexistent nodes.""" with pytest.raises(ValueError, match="Node does not exist node=1"): From 9abe2cd9d993a250021f0db91875e29cc688cc6a Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 20:47:27 +0900 Subject: [PATCH 40/72] :art: Fix measurement action readability --- tests/test_zxgraphstate.py | 62 +++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 81ce6bbb8..a43a68c23 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -1,3 +1,15 @@ +"""Tests for ZXGraphState + +Measurement actions for the followings are used: + - Local complement (LC): MEAS_ACTION_LC_* + - Pivot (PV): MEAS_ACTION_PV_* + - Remove Cliffords (RC): MEAS_ACTION_RC + +Reference: + M. Backens et al., Quantum 5, 421 (2021). + https://doi.org/10.22331/q-2021-03-25-421 +""" + from __future__ import annotations import itertools @@ -18,39 +30,39 @@ Measurements = list[tuple[int, Plane, float]] -measurement_action_lc_target: dict[Plane, tuple[Plane, Callable[[float], float]]] = { +MEAS_ACTION_LC_TARGET: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XZ, lambda angle: angle + np.pi / 2), Plane.XZ: (Plane.XY, lambda angle: -angle + np.pi / 2), Plane.YZ: (Plane.YZ, lambda angle: angle + np.pi / 2), } -measurement_action_lc_neighbors: dict[Plane, tuple[Plane, Callable[[float], float]]] = { +MEAS_ACTION_LC_NEIGHBORS: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XY, lambda angle: angle + np.pi / 2), Plane.XZ: (Plane.YZ, lambda angle: angle), Plane.YZ: (Plane.XZ, operator.neg), } -measurement_action_pv_target: dict[Plane, tuple[Plane, Callable[[float], float]]] = { +MEAS_ACTION_PV_TARGET: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.YZ, operator.neg), Plane.XZ: (Plane.XZ, lambda angle: (np.pi / 2 - angle)), Plane.YZ: (Plane.XY, operator.neg), } -measurement_action_pv_neighbors: dict[Plane, tuple[Plane, Callable[[float], float]]] = { +MEAS_ACTION_PV_NEIGHBORS: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XY, lambda angle: (angle + np.pi) % (2.0 * np.pi)), Plane.XZ: (Plane.XZ, lambda angle: -angle % (2.0 * np.pi)), Plane.YZ: (Plane.YZ, lambda angle: -angle % (2.0 * np.pi)), } -atol = 1e-9 -measurement_action_rc: dict[Plane, tuple[Plane, Callable[[float, float], float]]] = { +ATOL = 1e-9 +MEAS_ACTION_RC: dict[Plane, tuple[Plane, Callable[[float, float], float]]] = { Plane.XY: ( Plane.XY, - lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < atol else alpha + np.pi) % (2.0 * np.pi), + lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < ATOL else alpha + np.pi) % (2.0 * np.pi), ), Plane.XZ: ( Plane.XZ, - lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < atol else -alpha) % (2.0 * np.pi), + lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < ATOL else -alpha) % (2.0 * np.pi), ), Plane.YZ: ( Plane.YZ, - lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < atol else -alpha) % (2.0 * np.pi), + lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < ATOL else -alpha) % (2.0 * np.pi), ), } @@ -169,7 +181,7 @@ def test_local_complement_fails_with_input_node(zx_graph: ZXGraphState) -> None: @pytest.mark.parametrize("plane", [Plane.XY, Plane.XZ, Plane.YZ]) def test_local_complement_with_no_edge(zx_graph: ZXGraphState, plane: Plane, rng: np.random.Generator) -> None: angle = rng.random() * 2 * np.pi - ref_plane, ref_angle_func = measurement_action_lc_target[plane] + ref_plane, ref_angle_func = MEAS_ACTION_LC_TARGET[plane] ref_angle = ref_angle_func(angle) zx_graph.add_physical_node(1) zx_graph.set_meas_basis(1, PlannerMeasBasis(plane, angle)) @@ -192,8 +204,8 @@ def test_local_complement_on_output_node( _apply_measurements(zx_graph, measurements) zx_graph.local_complement(2) - ref_plane1, ref_angle_func1 = measurement_action_lc_neighbors[plane1] - ref_plane3, ref_angle_func3 = measurement_action_lc_neighbors[plane3] + ref_plane1, ref_angle_func1 = MEAS_ACTION_LC_NEIGHBORS[plane1] + ref_plane3, ref_angle_func3 = MEAS_ACTION_LC_NEIGHBORS[plane3] exp_measurements = [ (1, ref_plane1, ref_angle_func1(measurements[0][2])), (3, ref_plane3, ref_angle_func3(measurements[1][2])), @@ -216,8 +228,8 @@ def test_local_complement_with_two_nodes_graph( zx_graph.set_meas_basis(2, PlannerMeasBasis(plane2, angle2)) zx_graph.local_complement(1) - ref_plane1, ref_angle_func1 = measurement_action_lc_target[plane1] - ref_plane2, ref_angle_func2 = measurement_action_lc_neighbors[plane2] + ref_plane1, ref_angle_func1 = MEAS_ACTION_LC_TARGET[plane1] + ref_plane2, ref_angle_func2 = MEAS_ACTION_LC_NEIGHBORS[plane2] exp_measurements = [(1, ref_plane1, ref_angle_func1(angle1)), (2, ref_plane2, ref_angle_func2(angle2))] _test(zx_graph, exp_nodes={1, 2}, exp_edges={(1, 2)}, exp_measurements=exp_measurements) @@ -235,9 +247,9 @@ def test_local_complement_with_minimal_graph( for i in range(1, 4): zx_graph.set_meas_basis(i, PlannerMeasBasis(planes[i - 1], angles[i - 1])) zx_graph.local_complement(2) - ref_plane1, ref_angle_func1 = measurement_action_lc_neighbors[planes[0]] - ref_plane2, ref_angle_func2 = measurement_action_lc_target[planes[1]] - ref_plane3, ref_angle_func3 = measurement_action_lc_neighbors[planes[2]] + ref_plane1, ref_angle_func1 = MEAS_ACTION_LC_NEIGHBORS[planes[0]] + ref_plane2, ref_angle_func2 = MEAS_ACTION_LC_TARGET[planes[1]] + ref_plane3, ref_angle_func3 = MEAS_ACTION_LC_NEIGHBORS[planes[2]] ref_angle1 = ref_angle_func1(angles[0]) ref_angle2 = ref_angle_func2(angles[1]) ref_angle3 = ref_angle_func3(angles[2]) @@ -249,9 +261,9 @@ def test_local_complement_with_minimal_graph( _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3), (1, 3)}, exp_measurements=exp_measurements) zx_graph.local_complement(2) - ref_plane1, ref_angle_func1 = measurement_action_lc_neighbors[ref_plane1] - ref_plane2, ref_angle_func2 = measurement_action_lc_target[ref_plane2] - ref_plane3, ref_angle_func3 = measurement_action_lc_neighbors[ref_plane3] + ref_plane1, ref_angle_func1 = MEAS_ACTION_LC_NEIGHBORS[ref_plane1] + ref_plane2, ref_angle_func2 = MEAS_ACTION_LC_TARGET[ref_plane2] + ref_plane3, ref_angle_func3 = MEAS_ACTION_LC_NEIGHBORS[ref_plane3] exp_measurements = [ (1, ref_plane1, ref_angle_func1(ref_angle1)), (2, ref_plane2, ref_angle_func2(ref_angle2)), @@ -352,9 +364,9 @@ def test_pivot_with_minimal_graph( assert zx_graph.meas_bases[2].plane == zx_graph_cp.meas_bases[2].plane assert zx_graph.meas_bases[3].plane == zx_graph_cp.meas_bases[3].plane - _, ref_angle_func2 = measurement_action_pv_target[planes[1]] - _, ref_angle_func3 = measurement_action_pv_target[planes[2]] - _, ref_angle_func4 = measurement_action_pv_neighbors[planes[3]] + _, ref_angle_func2 = MEAS_ACTION_PV_TARGET[planes[1]] + _, ref_angle_func3 = MEAS_ACTION_PV_TARGET[planes[2]] + _, ref_angle_func4 = MEAS_ACTION_PV_NEIGHBORS[planes[3]] ref_angle2 = ref_angle_func2(angles[1]) ref_angle3 = ref_angle_func3(angles[2]) ref_angle4 = ref_angle_func4(angles[3]) @@ -461,8 +473,8 @@ def test_remove_clifford( angles = [rng.random() * 2 * np.pi for _ in range(3)] angles[1] = rng.choice([0.0, np.pi]) measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 4)] - ref_plane1, ref_angle_func1 = measurement_action_rc[planes[0]] - ref_plane3, ref_angle_func3 = measurement_action_rc[planes[2]] + ref_plane1, ref_angle_func1 = MEAS_ACTION_RC[planes[0]] + ref_plane3, ref_angle_func3 = MEAS_ACTION_RC[planes[2]] ref_angle1 = ref_angle_func1(angles[1], angles[0]) ref_angle3 = ref_angle_func3(angles[1], angles[2]) exp_measurements = [ From 81001bc98148b5b4b1ff0a80513823cc6d9bf423 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 20:59:26 +0900 Subject: [PATCH 41/72] :art: Rename get_random_gflow_circ into random_circ --- graphix_zx/random_objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index 7e9d44984..3ca912233 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -2,6 +2,7 @@ This module provides: - get_random_flow_graph: Generate a random flow graph. +- random_circ: Generate a random MBQC circuit with gflow. """ from __future__ import annotations @@ -85,7 +86,7 @@ def get_random_flow_graph( return graph, flow -def get_random_gflow_circ( +def random_circ( width: int, depth: int, rng: np.random.Generator | None = None, From 1e8870d7c2dcdcd05f196734cd5368d25ab3681f Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 20:59:50 +0900 Subject: [PATCH 42/72] :art: Fix typehints for random_circ --- graphix_zx/random_objects.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index 3ca912233..ae16a7c67 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from numpy.random import Generator + from collections.abc import AbstractSet def get_random_flow_graph( @@ -91,7 +92,7 @@ def random_circ( depth: int, rng: np.random.Generator | None = None, edge_p: float = 0.5, - angle_list: list | None = None, + angle_list: AbstractSet[float] = [0, np.pi / 3, 2 * np.pi / 3, np.pi], ) -> MBQCCircuit: """Generate a random MBQC circuit which has gflow. @@ -105,7 +106,7 @@ def random_circ( random number generator, by default np.random.default_rng() edge_p : float, optional probability of adding CZ gate, by default 0.5 - angle_list : list, optional + angle_list : AbstractSet[float], optional list of angles, by default [0, np.pi / 3, 2 * np.pi / 3, np.pi] Returns @@ -115,8 +116,6 @@ def random_circ( """ if rng is None: rng = np.random.default_rng() - if angle_list is None: - angle_list = [0, np.pi / 3, 2 * np.pi / 3, np.pi] circ = MBQCCircuit(width) for d in range(depth): for j in range(width): From b7b0a8c1f4f8c192f85d8340aa1cb94c56d11eb2 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 21:01:41 +0900 Subject: [PATCH 43/72] :fire: Remove unnecessary del sentences --- graphix_zx/zxgraphstate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index e14a95919..26202165f 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -512,11 +512,9 @@ def convert_to_phase_gadget(self) -> None: while True: if pair := self._extract_yz_adjacent_pair(): self.pivot(*pair) - del pair continue if u := self._extract_xz_node(): self.local_complement(u) - del u continue break From 27e56f9835d22de67119e01bb9d1d3e32ee76560 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 21:08:48 +0900 Subject: [PATCH 44/72] :art: Rename prune_non_cliffords method into full_reduce --- examples/measurement_pattern_simplification.py | 4 ++-- graphix_zx/zxgraphstate.py | 4 ++-- tests/test_zxgraphstate.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/measurement_pattern_simplification.py b/examples/measurement_pattern_simplification.py index 5f5dc32b6..b218c7fc2 100644 --- a/examples/measurement_pattern_simplification.py +++ b/examples/measurement_pattern_simplification.py @@ -1,6 +1,6 @@ """Basic example of simplifying a measurement pattern via a ZX-diagram simplification. -By using the `prune_non_cliffords` method, +By using the `full_reduce` method, we can remove certain Clifford nodes and non-Clifford nodes from the ZX-diagram, which can simplify the resulting measurement pattern. """ @@ -30,7 +30,7 @@ # %% zx_graph_smp = deepcopy(zx_graph) -zx_graph_smp.prune_non_cliffords() +zx_graph_smp.full_reduce() visualize(zx_graph_smp) print("node | plane | angle (/pi)") diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 26202165f..4fd55b8f1 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -568,8 +568,8 @@ def merge_yz_nodes(self) -> None: if not merged: break - def prune_non_cliffords(self, atol: float = 1e-9) -> None: - """Prune non-Clifford vertices from the graph state. + def full_reduce(self, atol: float = 1e-9) -> None: + """Reduce removable non-Clifford vertices from the graph state. Repeat the following steps until there are no non-Clifford vertices: 1. remove_cliffords diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index a43a68c23..c3c754364 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -859,7 +859,7 @@ def test_merge_yz_nodes( ), ], ) -def test_prune_non_cliffords( +def test_full_reduce( zx_graph: ZXGraphState, initial_zxgraph: tuple[range, set[tuple[int, int]]], measurements: Measurements, @@ -869,7 +869,7 @@ def test_prune_non_cliffords( _initialize_graph(zx_graph, nodes, edges) exp_measurements, exp_edges, exp_nodes = exp_zxgraph _apply_measurements(zx_graph, measurements) - zx_graph.prune_non_cliffords() + zx_graph.full_reduce() _test(zx_graph, exp_nodes, exp_edges, exp_measurements) From cf8892bf19e656121277ad4517d6e733897c9e98 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 21:19:41 +0900 Subject: [PATCH 45/72] :art: Format docstring --- tests/test_zxgraphstate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index c3c754364..1fd225b95 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -77,7 +77,8 @@ def plane_combinations(n: int) -> list[tuple[Plane, ...]]: Returns ------- - list[tuple[Plane, ...]]: A list of tuples containing all combinations of planes of length n. + : list[tuple[Plane, ...]] + A list of tuples containing all combinations of planes of length n. """ return list(itertools.product(Plane, repeat=n)) From d0cfd88551917301b6fb5472145ca303011043ef Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 21:27:20 +0900 Subject: [PATCH 46/72] :recycle: Merge if block into while block --- graphix_zx/zxgraphstate.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 4fd55b8f1..07a410ea8 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -547,10 +547,9 @@ def merge_yz_nodes(self) -> None: then u, v can be merged into a single node. """ min_yz_nodes = 2 - while True: - yz_nodes = {u for u, basis in self.meas_bases.items() if basis.plane == Plane.YZ} - if len(yz_nodes) < min_yz_nodes: - break + while (yz_nodes := {u for u, basis in self.meas_bases.items() if basis.plane == Plane.YZ}) and len( + yz_nodes + ) >= min_yz_nodes: merged = False for u in sorted(yz_nodes): for v in sorted(yz_nodes - {u}): From e67f8a0224b84da48fa3dfdacf4d37f51ab3d4b1 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 22:12:32 +0900 Subject: [PATCH 47/72] :recycle: Refactor check conditions --- graphix_zx/zxgraphstate.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 07a410ea8..ab0ebcea4 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -457,15 +457,9 @@ def remove_cliffords(self, atol: float = 1e-9) -> None: absolute tolerance, by default 1e-9 """ self.check_meas_basis() - while True: - nodes = self.physical_nodes - self.input_nodes - self.output_nodes - clifford_nodes = [ - node - for node in nodes - if is_clifford_angle(self.meas_bases[node].angle, atol) and self.is_removable_clifford(node, atol) - ] - if clifford_nodes == []: - break + while any( + self.is_removable_clifford(n, atol) for n in (self.physical_nodes - self.input_nodes - self.output_nodes) + ): steps = [ (self._step1_action, self._needs_lc), (self._step2_action, self._needs_nop), @@ -525,14 +519,12 @@ def merge_yz_to_xy(self) -> None: then the node u can be merged into the node v. """ target_candidates = { - u - for u, basis in self.meas_bases.items() - if (basis.plane == Plane.YZ and len(self.get_neighbors(u)) == 1 and (u not in self.output_nodes)) + u for u, basis in self.meas_bases.items() if (basis.plane == Plane.YZ and len(self.get_neighbors(u)) == 1) } target_nodes = { u for u in target_candidates - if any(self.meas_bases[v].plane == Plane.XY for v in self.get_neighbors(u) - self.output_nodes) + if (v := next(iter(self.get_neighbors(u)))) and self.meas_bases[v].plane == Plane.XY } for u in target_nodes: v = self.get_neighbors(u).pop() @@ -588,7 +580,7 @@ def full_reduce(self, atol: float = 1e-9) -> None: self.merge_yz_to_xy() self.merge_yz_nodes() if not any( - is_clifford_angle(self.meas_bases[node].angle, atol) and self.is_removable_clifford(node, atol) + self.is_removable_clifford(node, atol) for node in self.physical_nodes - self.input_nodes - self.output_nodes ): break From 50af11d45ac35029c37514f0532dfa0e18b4dc42 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 22:24:08 +0900 Subject: [PATCH 48/72] :art: Migrate from get_random_gflow_circ to random_circ --- examples/measurement_pattern_simplification.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/measurement_pattern_simplification.py b/examples/measurement_pattern_simplification.py index b218c7fc2..796ea0e8c 100644 --- a/examples/measurement_pattern_simplification.py +++ b/examples/measurement_pattern_simplification.py @@ -1,6 +1,6 @@ """Basic example of simplifying a measurement pattern via a ZX-diagram simplification. -By using the `full_reduce` method, +By using the `prune_non_cliffords` method, we can remove certain Clifford nodes and non-Clifford nodes from the ZX-diagram, which can simplify the resulting measurement pattern. """ @@ -11,12 +11,12 @@ import numpy as np from graphix_zx.circuit import circuit2graph -from graphix_zx.random_objects import get_random_gflow_circ +from graphix_zx.random_objects import random_circ from graphix_zx.visualizer import visualize from graphix_zx.zxgraphstate import ZXGraphState # %% -circ = get_random_gflow_circ(4, 4, angle_list=[0, np.pi / 3, 2 * np.pi / 3, np.pi]) +circ = random_circ(4, 4) graph, flow = circuit2graph(circ) zx_graph = ZXGraphState() zx_graph.append(graph) From e4c0e0ec23b5cea032a2f70a190c23a0f6af4c8e Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 22:25:03 +0900 Subject: [PATCH 49/72] :memo: Fix docstrings --- graphix_zx/random_objects.py | 2 +- tests/test_zxgraphstate.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index ae16a7c67..a6b869dbb 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -94,7 +94,7 @@ def random_circ( edge_p: float = 0.5, angle_list: AbstractSet[float] = [0, np.pi / 3, 2 * np.pi / 3, np.pi], ) -> MBQCCircuit: - """Generate a random MBQC circuit which has gflow. + """Generate a random MBQC circuit. Parameters ---------- diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 1fd225b95..d66d4e505 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -77,8 +77,8 @@ def plane_combinations(n: int) -> list[tuple[Plane, ...]]: Returns ------- - : list[tuple[Plane, ...]] - A list of tuples containing all combinations of planes of length n. + list[tuple[Plane, ...]] + A list of tuples containing all combinations of planes of length n. """ return list(itertools.product(Plane, repeat=n)) From 8802575ad9ddf95b29372528a4bf6dfe674bbc79 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 22:28:06 +0900 Subject: [PATCH 50/72] :bug: Fix typehint in random_objects --- graphix_zx/random_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index a6b869dbb..cb0de86a9 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from numpy.random import Generator - from collections.abc import AbstractSet + from collections.abc import Set as AbstractSet def get_random_flow_graph( From 7df1c8fc5330dfbcbe1a31782d712d8dfa6ec152 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 28 Apr 2025 22:43:05 +0900 Subject: [PATCH 51/72] :art: Fix typehints --- graphix_zx/random_objects.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index cb0de86a9..fdd263965 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -16,8 +16,9 @@ from graphix_zx.graphstate import GraphState if TYPE_CHECKING: + from collections.abc import Sequence + from numpy.random import Generator - from collections.abc import Set as AbstractSet def get_random_flow_graph( @@ -92,7 +93,7 @@ def random_circ( depth: int, rng: np.random.Generator | None = None, edge_p: float = 0.5, - angle_list: AbstractSet[float] = [0, np.pi / 3, 2 * np.pi / 3, np.pi], + angle_list: Sequence[float] = (0.0, np.pi / 3, 2 * np.pi / 3, np.pi), ) -> MBQCCircuit: """Generate a random MBQC circuit. @@ -106,7 +107,7 @@ def random_circ( random number generator, by default np.random.default_rng() edge_p : float, optional probability of adding CZ gate, by default 0.5 - angle_list : AbstractSet[float], optional + angle_list : Sequence[float], optional list of angles, by default [0, np.pi / 3, 2 * np.pi / 3, np.pi] Returns From 30bcf55f35f131d2a25116f7d7ace70db1a46e56 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 3 May 2025 15:47:52 +0900 Subject: [PATCH 52/72] :art: Rewrite angle condition with _is_close_angle --- graphix_zx/zxgraphstate.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index ab0ebcea4..2fb9b4d0b 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -12,7 +12,7 @@ import numpy as np from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import LocalClifford, is_clifford_angle +from graphix_zx.euler import LocalClifford, is_clifford_angle, _is_close_angle from graphix_zx.graphstate import GraphState, bipartite_edges if TYPE_CHECKING: @@ -200,7 +200,9 @@ def _needs_nop(self, node: int, atol: float = 1e-9) -> bool: True if the node is a removable Clifford vertex. """ alpha = self.meas_bases[node].angle % (2.0 * np.pi) - return abs(alpha % np.pi) < atol and (self.meas_bases[node].plane in {Plane.YZ, Plane.XZ}) + return any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) and ( + self.meas_bases[node].plane in {Plane.YZ, Plane.XZ} + ) def _needs_lc(self, node: int, atol: float = 1e-9) -> bool: """Check if the node needs a local complementation in order to perform _remove_clifford. @@ -221,7 +223,9 @@ def _needs_lc(self, node: int, atol: float = 1e-9) -> bool: True if the node needs a local complementation. """ alpha = self.meas_bases[node].angle % (2.0 * np.pi) - return abs((alpha + 0.5 * np.pi) % np.pi) < atol and self.meas_bases[node].plane in {Plane.YZ, Plane.XY} + return any( + (_is_close_angle(alpha, np.pi / 2, atol), _is_close_angle(alpha, 3 * np.pi / 2, atol)) + ) and self.meas_bases[node].plane in {Plane.YZ, Plane.XY} def _needs_pivot_1(self, node: int, atol: float = 1e-9) -> bool: """Check if the nodes need a pivot operation in order to perform _remove_clifford. @@ -248,8 +252,14 @@ def _needs_pivot_1(self, node: int, atol: float = 1e-9) -> bool: return False alpha = self.meas_bases[node].angle % (2.0 * np.pi) - case_a = abs(alpha % np.pi) < atol and self.meas_bases[node].plane == Plane.XY - case_b = abs((alpha + 0.5 * np.pi) % np.pi) < atol and self.meas_bases[node].plane == Plane.XZ + case_a = ( + any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) + and self.meas_bases[node].plane == Plane.XY + ) + case_b = ( + any((_is_close_angle(alpha, np.pi / 2, atol), _is_close_angle(alpha, 3 * np.pi / 2, atol))) + and self.meas_bases[node].plane == Plane.XZ + ) return case_a or case_b def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: @@ -278,8 +288,14 @@ def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: return False alpha = self.meas_bases[node].angle % (2.0 * np.pi) - case_a = abs(alpha % np.pi) < atol and self.meas_bases[node].plane == Plane.XY - case_b = abs((alpha + 0.5 * np.pi) % np.pi) < atol and self.meas_bases[node].plane == Plane.XZ + case_a = ( + any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) + and self.meas_bases[node].plane == Plane.XY + ) + case_b = ( + any((_is_close_angle(alpha, np.pi / 2, atol), _is_close_angle(alpha, 3 * np.pi / 2, atol))) + and self.meas_bases[node].plane == Plane.XZ + ) return case_a or case_b def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: @@ -294,7 +310,7 @@ def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: """ a_pi = self.meas_bases[node].angle % (2.0 * np.pi) coeff = 1.0 - if abs(a_pi % (2 * np.pi)) < atol: + if _is_close_angle(a_pi, 0, atol): coeff = 0.0 lc = LocalClifford(coeff * np.pi, 0, 0) for v in self.get_neighbors(node) - self.output_nodes: From 3b29d82672d0534d2f103cc5d6aabc37087fa6ec Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 3 May 2025 16:02:50 +0900 Subject: [PATCH 53/72] :art: Improve readability --- tests/test_zxgraphstate.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index d66d4e505..05a74bd97 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -21,7 +21,7 @@ import pytest from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import _is_close_angle, is_clifford_angle +from graphix_zx.euler import _is_close_angle from graphix_zx.random_objects import get_random_flow_graph from graphix_zx.zxgraphstate import ZXGraphState @@ -54,15 +54,15 @@ MEAS_ACTION_RC: dict[Plane, tuple[Plane, Callable[[float, float], float]]] = { Plane.XY: ( Plane.XY, - lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < ATOL else alpha + np.pi) % (2.0 * np.pi), + lambda a_pi, alpha: (alpha if _is_close_angle(a_pi, 0, ATOL) else alpha + np.pi) % (2.0 * np.pi), ), Plane.XZ: ( Plane.XZ, - lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < ATOL else -alpha) % (2.0 * np.pi), + lambda a_pi, alpha: (alpha if _is_close_angle(a_pi, 0, ATOL) else -alpha) % (2.0 * np.pi), ), Plane.YZ: ( Plane.YZ, - lambda a_pi, alpha: (alpha if abs(a_pi % (2.0 * np.pi)) < ATOL else -alpha) % (2.0 * np.pi), + lambda a_pi, alpha: (alpha if _is_close_angle(a_pi, 0, ATOL) else -alpha) % (2.0 * np.pi), ), } @@ -179,7 +179,7 @@ def test_local_complement_fails_with_input_node(zx_graph: ZXGraphState) -> None: zx_graph.local_complement(1) -@pytest.mark.parametrize("plane", [Plane.XY, Plane.XZ, Plane.YZ]) +@pytest.mark.parametrize("plane", list(Plane)) def test_local_complement_with_no_edge(zx_graph: ZXGraphState, plane: Plane, rng: np.random.Generator) -> None: angle = rng.random() * 2 * np.pi ref_plane, ref_angle_func = MEAS_ACTION_LC_TARGET[plane] @@ -463,7 +463,7 @@ def _test_remove_clifford( @pytest.mark.parametrize( "planes", - list(itertools.product([Plane.XY, Plane.XZ, Plane.YZ], [Plane.XZ, Plane.YZ], [Plane.XY, Plane.XZ, Plane.YZ])), + list(itertools.product(list(Plane), [Plane.XZ, Plane.YZ], list(Plane))), ) def test_remove_clifford( zx_graph: ZXGraphState, @@ -607,11 +607,7 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: zx_graph.remove_cliffords() atol = 1e-9 nodes = zx_graph.physical_nodes - zx_graph.input_nodes - zx_graph.output_nodes - clifford_nodes = [ - node - for node in nodes - if is_clifford_angle(zx_graph.meas_bases[node].angle, atol) and zx_graph.is_removable_clifford(node, atol) - ] + clifford_nodes = [node for node in nodes if zx_graph.is_removable_clifford(node, atol)] assert clifford_nodes == [] From 3d277ef7ff80590e1d28db1398f7c82c72a8c62d Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 3 May 2025 16:08:12 +0900 Subject: [PATCH 54/72] :white_check_mark: Add corner case for test_remove_clifford --- tests/test_zxgraphstate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 05a74bd97..153803600 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -472,7 +472,8 @@ def test_remove_clifford( ) -> None: graph_2(zx_graph) angles = [rng.random() * 2 * np.pi for _ in range(3)] - angles[1] = rng.choice([0.0, np.pi]) + EPSILON = 1e-10 + angles[1] = rng.choice([0.0, np.pi, 2 * np.pi - EPSILON]) measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 4)] ref_plane1, ref_angle_func1 = MEAS_ACTION_RC[planes[0]] ref_plane3, ref_angle_func3 = MEAS_ACTION_RC[planes[2]] From aafe3fb73d5fbb3e5349853e32c43bee90f3b667 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 3 May 2025 16:11:33 +0900 Subject: [PATCH 55/72] :art: Apply ruff --- graphix_zx/zxgraphstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 2fb9b4d0b..e8cc351bd 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -12,7 +12,7 @@ import numpy as np from graphix_zx.common import Plane, PlannerMeasBasis -from graphix_zx.euler import LocalClifford, is_clifford_angle, _is_close_angle +from graphix_zx.euler import LocalClifford, _is_close_angle, is_clifford_angle from graphix_zx.graphstate import GraphState, bipartite_edges if TYPE_CHECKING: From 22861f0d7eddebe4bfd8b393b13e0c640a441da0 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 3 May 2025 16:39:58 +0900 Subject: [PATCH 56/72] :recycle: Refactor test_zxgraphstate --- tests/test_zxgraphstate.py | 263 +++++++++++++++++++------------------ 1 file changed, 133 insertions(+), 130 deletions(-) diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index 153803600..dd189ef59 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -28,7 +28,7 @@ if TYPE_CHECKING: from typing import Callable - Measurements = list[tuple[int, Plane, float]] + Measurements = list[tuple[int, PlannerMeasBasis]] MEAS_ACTION_LC_TARGET: dict[Plane, tuple[Plane, Callable[[float], float]]] = { Plane.XY: (Plane.XZ, lambda angle: angle + np.pi / 2), @@ -133,10 +133,10 @@ def _initialize_graph( def _apply_measurements(zx_graph: ZXGraphState, measurements: Measurements) -> None: - for node_id, plane, angle in measurements: + for node_id, planner_meas_basis in measurements: if node_id in zx_graph.output_nodes: continue - zx_graph.set_meas_basis(node_id, PlannerMeasBasis(plane, angle)) + zx_graph.set_meas_basis(node_id, planner_meas_basis) def _test( @@ -147,9 +147,9 @@ def _test( ) -> None: assert zx_graph.physical_nodes == exp_nodes assert zx_graph.physical_edges == exp_edges - for node_id, plane, angle in exp_measurements: - assert zx_graph.meas_bases[node_id].plane == plane - assert _is_close_angle(zx_graph.meas_bases[node_id].angle, angle) + for node_id, planner_meas_basis in exp_measurements: + assert zx_graph.meas_bases[node_id].plane == planner_meas_basis.plane + assert _is_close_angle(zx_graph.meas_bases[node_id].angle, planner_meas_basis.angle) def test_local_complement_fails_if_nonexistent_node(zx_graph: ZXGraphState) -> None: @@ -201,15 +201,15 @@ def test_local_complement_on_output_node( _initialize_graph(zx_graph, range(1, 4), {(1, 2), (2, 3)}, outputs=(2,)) angle1 = rng.random() * 2 * np.pi angle3 = rng.random() * 2 * np.pi - measurements = [(1, plane1, angle1), (3, plane3, angle3)] + measurements = [(1, PlannerMeasBasis(plane1, angle1)), (3, PlannerMeasBasis(plane3, angle3))] _apply_measurements(zx_graph, measurements) zx_graph.local_complement(2) ref_plane1, ref_angle_func1 = MEAS_ACTION_LC_NEIGHBORS[plane1] ref_plane3, ref_angle_func3 = MEAS_ACTION_LC_NEIGHBORS[plane3] exp_measurements = [ - (1, ref_plane1, ref_angle_func1(measurements[0][2])), - (3, ref_plane3, ref_angle_func3(measurements[1][2])), + (1, PlannerMeasBasis(ref_plane1, ref_angle_func1(measurements[0][1].angle))), + (3, PlannerMeasBasis(ref_plane3, ref_angle_func3(measurements[1][1].angle))), ] _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (1, 3), (2, 3)}, exp_measurements=exp_measurements) assert zx_graph.meas_bases.get(2) is None @@ -231,7 +231,10 @@ def test_local_complement_with_two_nodes_graph( ref_plane1, ref_angle_func1 = MEAS_ACTION_LC_TARGET[plane1] ref_plane2, ref_angle_func2 = MEAS_ACTION_LC_NEIGHBORS[plane2] - exp_measurements = [(1, ref_plane1, ref_angle_func1(angle1)), (2, ref_plane2, ref_angle_func2(angle2))] + exp_measurements = [ + (1, PlannerMeasBasis(ref_plane1, ref_angle_func1(angle1))), + (2, PlannerMeasBasis(ref_plane2, ref_angle_func2(angle2))), + ] _test(zx_graph, exp_nodes={1, 2}, exp_edges={(1, 2)}, exp_measurements=exp_measurements) @@ -255,9 +258,9 @@ def test_local_complement_with_minimal_graph( ref_angle2 = ref_angle_func2(angles[1]) ref_angle3 = ref_angle_func3(angles[2]) exp_measurements = [ - (1, ref_plane1, ref_angle1), - (2, ref_plane2, ref_angle2), - (3, ref_plane3, ref_angle3), + (1, PlannerMeasBasis(ref_plane1, ref_angle1)), + (2, PlannerMeasBasis(ref_plane2, ref_angle2)), + (3, PlannerMeasBasis(ref_plane3, ref_angle3)), ] _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3), (1, 3)}, exp_measurements=exp_measurements) @@ -266,9 +269,9 @@ def test_local_complement_with_minimal_graph( ref_plane2, ref_angle_func2 = MEAS_ACTION_LC_TARGET[ref_plane2] ref_plane3, ref_angle_func3 = MEAS_ACTION_LC_NEIGHBORS[ref_plane3] exp_measurements = [ - (1, ref_plane1, ref_angle_func1(ref_angle1)), - (2, ref_plane2, ref_angle_func2(ref_angle2)), - (3, ref_plane3, ref_angle_func3(ref_angle3)), + (1, PlannerMeasBasis(ref_plane1, ref_angle_func1(ref_angle1))), + (2, PlannerMeasBasis(ref_plane2, ref_angle_func2(ref_angle2))), + (3, PlannerMeasBasis(ref_plane3, ref_angle_func3(ref_angle3))), ] _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) @@ -289,7 +292,7 @@ def test_local_complement_4_times( for _ in range(4): zx_graph.local_complement(2) - exp_measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 4)] + exp_measurements = [(i, PlannerMeasBasis(planes[i - 1], angles[i - 1])) for i in range(1, 4)] _test(zx_graph, exp_nodes={1, 2, 3}, exp_edges={(1, 2), (2, 3)}, exp_measurements=exp_measurements) @@ -321,9 +324,9 @@ def test_pivot_with_obvious_graph(zx_graph: ZXGraphState) -> None: zx_graph.add_physical_edge(i, j) measurements = [ - (1, Plane.XY, 1.1 * np.pi), - (2, Plane.XZ, 1.2 * np.pi), - (3, Plane.YZ, 1.3 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 1.1 * np.pi)), + (2, PlannerMeasBasis(Plane.XZ, 1.2 * np.pi)), + (3, PlannerMeasBasis(Plane.YZ, 1.3 * np.pi)), ] _apply_measurements(zx_graph, measurements) @@ -353,7 +356,7 @@ def test_pivot_with_minimal_graph( zx_graph.add_physical_edge(i, j) angles = [rng.random() * 2 * np.pi for _ in range(5)] - measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 6)] + measurements = [(i, PlannerMeasBasis(planes[i - 1], angles[i - 1])) for i in range(1, 6)] _apply_measurements(zx_graph, measurements) zx_graph_cp = deepcopy(zx_graph) @@ -472,16 +475,16 @@ def test_remove_clifford( ) -> None: graph_2(zx_graph) angles = [rng.random() * 2 * np.pi for _ in range(3)] - EPSILON = 1e-10 - angles[1] = rng.choice([0.0, np.pi, 2 * np.pi - EPSILON]) - measurements = [(i, planes[i - 1], angles[i - 1]) for i in range(1, 4)] + epsilon = 1e-10 + angles[1] = rng.choice([0.0, np.pi, 2 * np.pi - epsilon]) + measurements = [(i, PlannerMeasBasis(planes[i - 1], angles[i - 1])) for i in range(1, 4)] ref_plane1, ref_angle_func1 = MEAS_ACTION_RC[planes[0]] ref_plane3, ref_angle_func3 = MEAS_ACTION_RC[planes[2]] ref_angle1 = ref_angle_func1(angles[1], angles[0]) ref_angle3 = ref_angle_func3(angles[1], angles[2]) exp_measurements = [ - (1, ref_plane1, ref_angle1), - (3, ref_plane3, ref_angle3), + (1, PlannerMeasBasis(ref_plane1, ref_angle1)), + (3, PlannerMeasBasis(ref_plane3, ref_angle3)), ] _test_remove_clifford( zx_graph, node=2, measurements=measurements, exp_graph=({1, 3}, set()), exp_measurements=exp_measurements @@ -491,9 +494,9 @@ def test_remove_clifford( def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}, inputs=(1, 3)) measurements = [ - (1, Plane.XY, 0.5 * np.pi), - (2, Plane.XY, np.pi), - (3, Plane.XY, 0.5 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), ] _apply_measurements(zx_graph, measurements) with pytest.raises(ValueError, match=r"This Clifford vertex is unremovable."): @@ -504,10 +507,10 @@ def test_remove_cliffords(zx_graph: ZXGraphState) -> None: """Test removing multiple Clifford vertices.""" _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) measurements = [ - (1, Plane.XY, 0.5 * np.pi), - (2, Plane.XY, 0.5 * np.pi), - (3, Plane.XY, 0.5 * np.pi), - (4, Plane.XY, 0.5 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), ] _apply_measurements(zx_graph, measurements) zx_graph.remove_cliffords() @@ -518,15 +521,15 @@ def test_remove_cliffords_graph1(zx_graph: ZXGraphState) -> None: """Test removing multiple Clifford vertices.""" graph_1(zx_graph) measurements = [ - (1, Plane.YZ, np.pi), - (2, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), + (1, PlannerMeasBasis(Plane.YZ, np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)), + (3, PlannerMeasBasis(Plane.XZ, 0.2 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.3 * np.pi)), ] exp_measurements = [ - (2, Plane.XY, 1.1 * np.pi), - (3, Plane.XZ, 1.8 * np.pi), - (4, Plane.YZ, 1.7 * np.pi), + (2, PlannerMeasBasis(Plane.XY, 1.1 * np.pi)), + (3, PlannerMeasBasis(Plane.XZ, 1.8 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 1.7 * np.pi)), ] _apply_measurements(zx_graph, measurements) zx_graph.remove_cliffords() @@ -536,13 +539,13 @@ def test_remove_cliffords_graph1(zx_graph: ZXGraphState) -> None: def test_remove_cliffords_graph2(zx_graph: ZXGraphState) -> None: graph_2(zx_graph) measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.YZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)), + (2, PlannerMeasBasis(Plane.YZ, 1.5 * np.pi)), + (3, PlannerMeasBasis(Plane.XZ, 0.2 * np.pi)), ] exp_measurements = [ - (1, Plane.XY, 0.6 * np.pi), - (3, Plane.YZ, 0.2 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.6 * np.pi)), + (3, PlannerMeasBasis(Plane.YZ, 0.2 * np.pi)), ] _apply_measurements(zx_graph, measurements) zx_graph.remove_cliffords() @@ -552,19 +555,19 @@ def test_remove_cliffords_graph2(zx_graph: ZXGraphState) -> None: def test_remove_cliffords_graph3(zx_graph: ZXGraphState) -> None: graph_3(zx_graph) measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (2, Plane.XZ, 1.5 * np.pi), - (3, Plane.XZ, 0.2 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 0.5 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)), + (2, PlannerMeasBasis(Plane.XZ, 1.5 * np.pi)), + (3, PlannerMeasBasis(Plane.XZ, 0.2 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.3 * np.pi)), + (5, PlannerMeasBasis(Plane.XY, 0.4 * np.pi)), + (6, PlannerMeasBasis(Plane.XZ, 0.5 * np.pi)), ] exp_measurements = [ - (1, Plane.XY, 0.1 * np.pi), - (3, Plane.XZ, 1.7 * np.pi), - (4, Plane.YZ, 0.3 * np.pi), - (5, Plane.XY, 0.4 * np.pi), - (6, Plane.XZ, 1.5 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)), + (3, PlannerMeasBasis(Plane.XZ, 1.7 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.3 * np.pi)), + (5, PlannerMeasBasis(Plane.XY, 0.4 * np.pi)), + (6, PlannerMeasBasis(Plane.XZ, 1.5 * np.pi)), ] _apply_measurements(zx_graph, measurements) zx_graph.remove_cliffords() @@ -580,12 +583,12 @@ def test_remove_cliffords_graph4(zx_graph: ZXGraphState) -> None: """Test removing multiple Clifford vertices.""" graph_4(zx_graph) measurements = [ - (1, Plane.XY, np.pi), - (4, Plane.XZ, 0.5 * np.pi), + (1, PlannerMeasBasis(Plane.XY, np.pi)), + (4, PlannerMeasBasis(Plane.XZ, 0.5 * np.pi)), ] _apply_measurements(zx_graph, measurements) zx_graph.remove_cliffords() - _test(zx_graph, {1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}, [(1, Plane.XY, np.pi)]) + _test(zx_graph, {1, 2, 3, 5}, {(1, 3), (1, 5), (2, 3), (2, 5), (3, 5)}, [(1, PlannerMeasBasis(Plane.XY, np.pi))]) def test_random_graph(zx_graph: ZXGraphState) -> None: @@ -619,20 +622,20 @@ def test_random_graph(zx_graph: ZXGraphState) -> None: # and no node with XZ measurement ( [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.44 * np.pi)), + (5, PlannerMeasBasis(Plane.XY, 0.55 * np.pi)), + (6, PlannerMeasBasis(Plane.XY, 0.66 * np.pi)), ], [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), - (5, Plane.XY, 0.55 * np.pi), - (6, Plane.XY, 0.66 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.44 * np.pi)), + (5, PlannerMeasBasis(Plane.XY, 0.55 * np.pi)), + (6, PlannerMeasBasis(Plane.XY, 0.66 * np.pi)), ], {(1, 2), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (3, 6)}, ), @@ -660,15 +663,15 @@ def test_convert_to_phase_gadget( ( {(1, 2), (2, 3), (2, 4)}, [ - (1, Plane.YZ, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), + (1, PlannerMeasBasis(Plane.YZ, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.44 * np.pi)), ], [ - (2, Plane.XY, 0.33 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.XY, 0.44 * np.pi), + (2, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.44 * np.pi)), ], {(2, 3), (2, 4)}, ), @@ -678,15 +681,15 @@ def test_convert_to_phase_gadget( ( {(1, 2), (2, 3), (2, 4), (3, 4)}, [ - (1, Plane.YZ, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), + (1, PlannerMeasBasis(Plane.YZ, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.44 * np.pi)), ], [ - (2, Plane.XY, 0.33 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), + (2, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.44 * np.pi)), ], {(2, 3), (2, 4), (3, 4)}, ), @@ -716,18 +719,18 @@ def test_merge_yz_to_xy( ( {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - (5, Plane.YZ, 0.55 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.44 * np.pi)), + (5, PlannerMeasBasis(Plane.YZ, 0.55 * np.pi)), ], ( [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.XY, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.99 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.99 * np.pi)), ], {(1, 2), (1, 4), (2, 3), (3, 4)}, {1, 2, 3, 4}, @@ -741,17 +744,17 @@ def test_merge_yz_to_xy( ( {(1, 2), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - (5, Plane.YZ, 0.55 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.YZ, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.44 * np.pi)), + (5, PlannerMeasBasis(Plane.YZ, 0.55 * np.pi)), ], ( [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 1.21 * np.pi), - (3, Plane.XY, 0.33 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.YZ, 1.21 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), ], {(1, 2), (2, 3)}, {1, 2, 3}, @@ -765,17 +768,17 @@ def test_merge_yz_to_xy( ( {(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (3, 4), (3, 5)}, [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 0.22 * np.pi), - (3, Plane.XY, 0.33 * np.pi), - (4, Plane.YZ, 0.44 * np.pi), - (5, Plane.YZ, 0.55 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.YZ, 0.22 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), + (4, PlannerMeasBasis(Plane.YZ, 0.44 * np.pi)), + (5, PlannerMeasBasis(Plane.YZ, 0.55 * np.pi)), ], ( [ - (1, Plane.XY, 0.11 * np.pi), - (2, Plane.YZ, 1.21 * np.pi), - (3, Plane.XY, 0.33 * np.pi), + (1, PlannerMeasBasis(Plane.XY, 0.11 * np.pi)), + (2, PlannerMeasBasis(Plane.YZ, 1.21 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.33 * np.pi)), ], {(1, 2), (1, 3), (2, 3)}, {1, 2, 3}, @@ -803,15 +806,15 @@ def test_merge_yz_nodes( ( (range(1, 5), {(1, 2), (2, 3), (2, 4)}), [ - (1, Plane.YZ, 0.1 * np.pi), - (2, Plane.XY, 0.4 * np.pi), - (3, Plane.XY, 0.3 * np.pi), - (4, Plane.XY, 0.4 * np.pi), + (1, PlannerMeasBasis(Plane.YZ, 0.1 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.4 * np.pi)), + (3, PlannerMeasBasis(Plane.XY, 0.3 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.4 * np.pi)), ], ( [ - (3, Plane.XY, 1.8 * np.pi), - (4, Plane.XY, 1.9 * np.pi), + (3, PlannerMeasBasis(Plane.XY, 1.8 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 1.9 * np.pi)), ], {(3, 4)}, {3, 4}, @@ -821,15 +824,15 @@ def test_merge_yz_nodes( ( (range(1, 5), {(1, 2), (2, 3), (2, 4)}), [ - (1, Plane.YZ, 0.1 * np.pi), - (2, Plane.XY, 0.9 * np.pi), - (3, Plane.XZ, 0.8 * np.pi), - (4, Plane.XY, 0.4 * np.pi), + (1, PlannerMeasBasis(Plane.YZ, 0.1 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.9 * np.pi)), + (3, PlannerMeasBasis(Plane.XZ, 0.8 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.4 * np.pi)), ], ( [ - (3, Plane.XY, 0.2 * np.pi), - (4, Plane.XY, 0.9 * np.pi), + (3, PlannerMeasBasis(Plane.XY, 0.2 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.9 * np.pi)), ], {(3, 4)}, {3, 4}, @@ -839,17 +842,17 @@ def test_merge_yz_nodes( ( (range(1, 7), {(1, 2), (2, 3), (2, 4), (3, 6), (4, 5)}), [ - (1, Plane.YZ, 0.1 * np.pi), - (2, Plane.XY, 0.9 * np.pi), - (3, Plane.YZ, 1.2 * np.pi), - (4, Plane.XY, 1.4 * np.pi), - (5, Plane.YZ, 1.0 * np.pi), - (6, Plane.XY, 0.5 * np.pi), + (1, PlannerMeasBasis(Plane.YZ, 0.1 * np.pi)), + (2, PlannerMeasBasis(Plane.XY, 0.9 * np.pi)), + (3, PlannerMeasBasis(Plane.YZ, 1.2 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 1.4 * np.pi)), + (5, PlannerMeasBasis(Plane.YZ, 1.0 * np.pi)), + (6, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), ], ( [ - (3, Plane.XY, 1.8 * np.pi), - (4, Plane.XY, 0.9 * np.pi), + (3, PlannerMeasBasis(Plane.XY, 1.8 * np.pi)), + (4, PlannerMeasBasis(Plane.XY, 0.9 * np.pi)), ], {(3, 4)}, {3, 4}, From faaba1a3adfd924f2aa772bdc410d470171700a2 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sat, 3 May 2025 17:11:50 +0900 Subject: [PATCH 57/72] :zap: Improve performance for merge_yz_nodes --- graphix_zx/zxgraphstate.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index e8cc351bd..dec5a7f36 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -7,6 +7,7 @@ from __future__ import annotations +from collections import defaultdict from typing import TYPE_CHECKING import numpy as np @@ -554,26 +555,23 @@ def merge_yz_nodes(self) -> None: If u, v nodes are measured in the YZ-plane and u, v have the same neighbors, then u, v can be merged into a single node. """ - min_yz_nodes = 2 - while (yz_nodes := {u for u, basis in self.meas_bases.items() if basis.plane == Plane.YZ}) and len( - yz_nodes - ) >= min_yz_nodes: - merged = False - for u in sorted(yz_nodes): - for v in sorted(yz_nodes - {u}): - if self.get_neighbors(u) != self.get_neighbors(v): - continue - - new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) - self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) - self.remove_physical_node(v) - - merged = True - break - if merged: - break - if not merged: - break + min_nodes = 2 + yz_nodes = {u for u, basis in self.meas_bases.items() if basis.plane == Plane.YZ} + if len(yz_nodes) < min_nodes: + return + neighbor_groups: dict[frozenset[int], list[int]] = defaultdict(list) + for u in yz_nodes: + neighbors = frozenset(self.get_neighbors(u)) + neighbor_groups[neighbors].append(u) + + for neighbors, nodes in neighbor_groups.items(): + if len(nodes) < min_nodes or len(neighbors) < min_nodes: + continue + u = nodes[0] + for v in nodes[1:]: + new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) + self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) + self.remove_physical_node(v) def full_reduce(self, atol: float = 1e-9) -> None: """Reduce removable non-Clifford vertices from the graph state. From af2aba83f99c8564f0dda365d5e76919ccca27b5 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:05:05 +0900 Subject: [PATCH 58/72] :memo: Fix docstrings --- examples/measurement_pattern_simplification.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/measurement_pattern_simplification.py b/examples/measurement_pattern_simplification.py index 796ea0e8c..d3090b9fc 100644 --- a/examples/measurement_pattern_simplification.py +++ b/examples/measurement_pattern_simplification.py @@ -1,8 +1,9 @@ -"""Basic example of simplifying a measurement pattern via a ZX-diagram simplification. +"""Basic example of simplifying a ZX-diagram. -By using the `prune_non_cliffords` method, -we can remove certain Clifford nodes and non-Clifford nodes from the ZX-diagram, -which can simplify the resulting measurement pattern. +By using the `full_reduce` method, +we can remove all the internal Clifford nodes and some non-Clifford nodes from the graph state, +which generates a simpler ZX-diagram. +This example is a simple demonstration of the simplification process. """ # %% From 611f142bca54db601f029e8bc8f0d0249b0cad90 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:27:41 +0900 Subject: [PATCH 59/72] :art: Integrate the term 'vertex' into 'node' --- graphix_zx/zxgraphstate.py | 26 +++++++++++++------------- tests/test_zxgraphstate.py | 20 ++++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index dec5a7f36..f961d44a9 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -198,7 +198,7 @@ def _needs_nop(self, node: int, atol: float = 1e-9) -> bool: Returns ------- bool - True if the node is a removable Clifford vertex. + True if the node is a removable Clifford node. """ alpha = self.meas_bases[node].angle % (2.0 * np.pi) return any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) and ( @@ -300,7 +300,7 @@ def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: return case_a or case_b def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: - """Perform the Clifford vertex removal. + """Perform the Clifford node removal. Parameters ---------- @@ -320,7 +320,7 @@ def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: self.remove_physical_node(node) def remove_clifford(self, node: int, atol: float = 1e-9) -> None: - """Remove the local clifford node. + """Remove the local Clifford node. Parameters ---------- @@ -333,21 +333,21 @@ def remove_clifford(self, node: int, atol: float = 1e-9) -> None: ------ ValueError 1. If the node is an input node. - 2. If the node is not a Clifford vertex. + 2. If the node is not a Clifford node. 3. If all neighbors are input nodes in some special cases ((meas_plane, meas_angle) = (XY, a pi), (XZ, a pi/2) for a = 0, 1). 4. If the node has no neighbors that are not connected only to output nodes. """ self.ensure_node_exists(node) if node in self.input_nodes or node in self.output_nodes: - msg = "Clifford vertex removal not allowed for input node" + msg = "Clifford node removal not allowed for input node" raise ValueError(msg) if not ( is_clifford_angle(self.meas_bases[node].angle, atol) and self.meas_bases[node].plane in {Plane.XY, Plane.XZ, Plane.YZ} ): - msg = "This node is not a Clifford vertex." + msg = "This node is not a Clifford node." raise ValueError(msg) if self._needs_nop(node, atol): @@ -360,13 +360,13 @@ def remove_clifford(self, node: int, atol: float = 1e-9) -> None: nbrs.remove(v) self.pivot(node, v) else: - msg = "This Clifford vertex is unremovable." + msg = "This Clifford node is unremovable." raise ValueError(msg) self._remove_clifford(node, atol) def is_removable_clifford(self, node: int, atol: float = 1e-9) -> bool: - """Check if the node is a removable Clifford vertex. + """Check if the node is a removable Clifford node. Parameters ---------- @@ -378,7 +378,7 @@ def is_removable_clifford(self, node: int, atol: float = 1e-9) -> bool: Returns ------- bool - True if the node is a removable Clifford vertex. + True if the node is a removable Clifford node. """ return any( [ @@ -399,7 +399,7 @@ def _remove_cliffords( action_func : Callable[[int, float], None] action to perform on the node check_func : Callable[[int, float], bool] - check if the node is a removable Clifford vertex + check if the node is a removable Clifford node """ self.check_meas_basis() while True: @@ -574,14 +574,14 @@ def merge_yz_nodes(self) -> None: self.remove_physical_node(v) def full_reduce(self, atol: float = 1e-9) -> None: - """Reduce removable non-Clifford vertices from the graph state. + """Reduce all Clifford - Repeat the following steps until there are no non-Clifford vertices: + Repeat the following steps until there are no non-Clifford nodes: 1. remove_cliffords 2. convert_to_phase_gadget 3. merge_yz_to_xy 4. merge_yz_nodes - 5. if there are some removable Clifford vertices, back to step 1. + 5. if there are some removable Clifford nodes, back to step 1. Parameters ---------- diff --git a/tests/test_zxgraphstate.py b/tests/test_zxgraphstate.py index dd189ef59..7f947eb24 100644 --- a/tests/test_zxgraphstate.py +++ b/tests/test_zxgraphstate.py @@ -388,7 +388,7 @@ def test_remove_clifford_fails_if_nonexistent_node(zx_graph: ZXGraphState) -> No def test_remove_clifford_fails_with_input_node(zx_graph: ZXGraphState) -> None: zx_graph.add_physical_node(1) zx_graph.set_input(1) - with pytest.raises(ValueError, match="Clifford vertex removal not allowed for input node"): + with pytest.raises(ValueError, match="Clifford node removal not allowed for input node"): zx_graph.remove_clifford(1) @@ -399,14 +399,14 @@ def test_remove_clifford_fails_with_invalid_plane(zx_graph: ZXGraphState) -> Non 1, PlannerMeasBasis("test_plane", 0.5 * np.pi), # type: ignore[reportArgumentType, arg-type, unused-ignore] ) - with pytest.raises(ValueError, match="This node is not a Clifford vertex"): + with pytest.raises(ValueError, match="This node is not a Clifford node"): zx_graph.remove_clifford(1) -def test_remove_clifford_fails_for_non_clifford_vertex(zx_graph: ZXGraphState) -> None: +def test_remove_clifford_fails_for_non_clifford_node(zx_graph: ZXGraphState) -> None: zx_graph.add_physical_node(1) zx_graph.set_meas_basis(1, PlannerMeasBasis(Plane.XY, 0.1 * np.pi)) - with pytest.raises(ValueError, match="This node is not a Clifford vertex"): + with pytest.raises(ValueError, match="This node is not a Clifford node"): zx_graph.remove_clifford(1) @@ -491,7 +491,7 @@ def test_remove_clifford( ) -def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: +def test_unremovable_clifford_node(zx_graph: ZXGraphState) -> None: _initialize_graph(zx_graph, nodes=range(1, 4), edges={(1, 2), (2, 3)}, inputs=(1, 3)) measurements = [ (1, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), @@ -499,12 +499,12 @@ def test_unremovable_clifford_vertex(zx_graph: ZXGraphState) -> None: (3, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), ] _apply_measurements(zx_graph, measurements) - with pytest.raises(ValueError, match=r"This Clifford vertex is unremovable."): + with pytest.raises(ValueError, match=r"This Clifford node is unremovable."): zx_graph.remove_clifford(2) def test_remove_cliffords(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices.""" + """Test removing multiple Clifford nodes.""" _initialize_graph(zx_graph, nodes=range(1, 5), edges={(1, 2), (1, 3), (1, 4)}) measurements = [ (1, PlannerMeasBasis(Plane.XY, 0.5 * np.pi)), @@ -518,7 +518,7 @@ def test_remove_cliffords(zx_graph: ZXGraphState) -> None: def test_remove_cliffords_graph1(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices.""" + """Test removing multiple Clifford nodes.""" graph_1(zx_graph) measurements = [ (1, PlannerMeasBasis(Plane.YZ, np.pi)), @@ -580,7 +580,7 @@ def test_remove_cliffords_graph3(zx_graph: ZXGraphState) -> None: def test_remove_cliffords_graph4(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices.""" + """Test removing multiple Clifford nodes.""" graph_4(zx_graph) measurements = [ (1, PlannerMeasBasis(Plane.XY, np.pi)), @@ -592,7 +592,7 @@ def test_remove_cliffords_graph4(zx_graph: ZXGraphState) -> None: def test_random_graph(zx_graph: ZXGraphState) -> None: - """Test removing multiple Clifford vertices from a random graph.""" + """Test removing multiple Clifford nodes from a random graph.""" random_graph, _ = get_random_flow_graph(5, 5) zx_graph.append(random_graph) From a0baef722e8ebc82055b9b40b2fdfa47920bc523 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:28:12 +0900 Subject: [PATCH 60/72] :fire: Remove _update_new_measurement method --- graphix_zx/zxgraphstate.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index f961d44a9..79372a7dd 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -58,23 +58,6 @@ def _update_connections(self, rmv_edges: Iterable[tuple[int, int]], new_edges: I for edge in new_edges: self.add_physical_edge(edge[0], edge[1]) - def _update_node_measurement( - self, measurement_action: Mapping[Plane, tuple[Plane, Callable[[float], float]]], v: int - ) -> None: - """Update the measurement action of the node. - - Parameters - ---------- - measurement_action : Mapping[Plane, tuple[Plane, Callable[[float], float]]] - mapping of the measurement plane to the new measurement plane and function to update the angle - v : int - node index - """ - new_plane, new_angle_func = measurement_action[self.meas_bases[v].plane] - if new_plane: - new_angle = new_angle_func(v) % (2.0 * np.pi) - self.set_meas_basis(v, PlannerMeasBasis(new_plane, new_angle)) - def local_complement(self, node: int) -> None: """Local complement operation on the graph state: G*u. From 4fb8a61105dd953b16ac822733a32b08cde3e9c7 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:35:44 +0900 Subject: [PATCH 61/72] :art: Simplify --- graphix_zx/zxgraphstate.py | 48 ++++++++++++++------------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 79372a7dd..b58f260c8 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -184,9 +184,7 @@ def _needs_nop(self, node: int, atol: float = 1e-9) -> bool: True if the node is a removable Clifford node. """ alpha = self.meas_bases[node].angle % (2.0 * np.pi) - return any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) and ( - self.meas_bases[node].plane in {Plane.YZ, Plane.XZ} - ) + return _is_close_angle(2 * alpha, 0, atol) and (self.meas_bases[node].plane in {Plane.YZ, Plane.XZ}) def _needs_lc(self, node: int, atol: float = 1e-9) -> bool: """Check if the node needs a local complementation in order to perform _remove_clifford. @@ -207,18 +205,18 @@ def _needs_lc(self, node: int, atol: float = 1e-9) -> bool: True if the node needs a local complementation. """ alpha = self.meas_bases[node].angle % (2.0 * np.pi) - return any( - (_is_close_angle(alpha, np.pi / 2, atol), _is_close_angle(alpha, 3 * np.pi / 2, atol)) - ) and self.meas_bases[node].plane in {Plane.YZ, Plane.XY} + return _is_close_angle(2 * (alpha - np.pi / 2), 0, atol) and ( + self.meas_bases[node].plane in {Plane.YZ, Plane.XY} + ) def _needs_pivot_1(self, node: int, atol: float = 1e-9) -> bool: """Check if the nodes need a pivot operation in order to perform _remove_clifford. The pivot operation is performed on the non-input neighbor of the node. For this operation, - (i) the measurement angle must be 0 or pi (mod 2pi) and the measurement plane must be XY, + (a) the measurement angle must be 0 or pi (mod 2pi) and the measurement plane must be XY, or - (ii) the measurement angle must be 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane must be XZ. + (b) the measurement angle must be 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane must be XZ. Parameters ---------- @@ -236,14 +234,10 @@ def _needs_pivot_1(self, node: int, atol: float = 1e-9) -> bool: return False alpha = self.meas_bases[node].angle % (2.0 * np.pi) - case_a = ( - any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) - and self.meas_bases[node].plane == Plane.XY - ) - case_b = ( - any((_is_close_angle(alpha, np.pi / 2, atol), _is_close_angle(alpha, 3 * np.pi / 2, atol))) - and self.meas_bases[node].plane == Plane.XZ - ) + # (a) the measurement angle is 0 or pi (mod 2pi) and the measurement plane is XY + case_a = _is_close_angle(2 * alpha, 0, atol) and self.meas_bases[node].plane == Plane.XY + # (b) the measurement angle is 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane is XZ + case_b = _is_close_angle(2 * (alpha - np.pi / 2), 0, atol) and self.meas_bases[node].plane == Plane.XZ return case_a or case_b def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: @@ -251,9 +245,9 @@ def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: The pivot operation is performed on the non-input but output neighbor of the node. For this operation, - (i) the measurement angle must be 0 or pi (mod 2pi) and the measurement plane must be XY, + (a) the measurement angle must be 0 or pi (mod 2pi) and the measurement plane must be XY, or - (ii) the measurement angle must be 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane must be XZ. + (b) the measurement angle must be 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane must be XZ. Parameters ---------- @@ -272,14 +266,10 @@ def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: return False alpha = self.meas_bases[node].angle % (2.0 * np.pi) - case_a = ( - any((_is_close_angle(alpha, 0, atol), _is_close_angle(alpha, np.pi, atol))) - and self.meas_bases[node].plane == Plane.XY - ) - case_b = ( - any((_is_close_angle(alpha, np.pi / 2, atol), _is_close_angle(alpha, 3 * np.pi / 2, atol))) - and self.meas_bases[node].plane == Plane.XZ - ) + # (a) the measurement angle is 0 or pi (mod 2pi) and the measurement plane is XY + case_a = _is_close_angle(2 * alpha, 0, atol) and self.meas_bases[node].plane == Plane.XY + # (b) the measurement angle is 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane is XZ + case_b = _is_close_angle(2 * (alpha - np.pi / 2), 0, atol) and self.meas_bases[node].plane == Plane.XZ return case_a or case_b def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: @@ -293,9 +283,7 @@ def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: absolute tolerance, by default 1e-9 """ a_pi = self.meas_bases[node].angle % (2.0 * np.pi) - coeff = 1.0 - if _is_close_angle(a_pi, 0, atol): - coeff = 0.0 + coeff = 0.0 if _is_close_angle(a_pi, np.pi, atol) else 1.0 lc = LocalClifford(coeff * np.pi, 0, 0) for v in self.get_neighbors(node) - self.output_nodes: self.apply_local_clifford(v, lc) @@ -557,7 +545,7 @@ def merge_yz_nodes(self) -> None: self.remove_physical_node(v) def full_reduce(self, atol: float = 1e-9) -> None: - """Reduce all Clifford + """Reduce all Clifford nodes and some non-Clifford nodes. Repeat the following steps until there are no non-Clifford nodes: 1. remove_cliffords From 69e12f49d10297ee849582dcffeee92058da48d5 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:37:29 +0900 Subject: [PATCH 62/72] :art: Apply ruff --- graphix_zx/zxgraphstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index b58f260c8..fb1496528 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -17,7 +17,7 @@ from graphix_zx.graphstate import GraphState, bipartite_edges if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Mapping + from collections.abc import Callable, Iterable class ZXGraphState(GraphState): From 63a6526d3ff86b34625c83d08ec505ed184f572b Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:38:48 +0900 Subject: [PATCH 63/72] :art: Improve docstrings and variable name --- graphix_zx/random_objects.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/graphix_zx/random_objects.py b/graphix_zx/random_objects.py index fdd263965..3550ac555 100644 --- a/graphix_zx/random_objects.py +++ b/graphix_zx/random_objects.py @@ -2,7 +2,7 @@ This module provides: - get_random_flow_graph: Generate a random flow graph. -- random_circ: Generate a random MBQC circuit with gflow. +- random_circ: Generate a random MBQC circuit. """ from __future__ import annotations @@ -93,7 +93,7 @@ def random_circ( depth: int, rng: np.random.Generator | None = None, edge_p: float = 0.5, - angle_list: Sequence[float] = (0.0, np.pi / 3, 2 * np.pi / 3, np.pi), + angle_candidates: Sequence[float] = (0.0, np.pi / 3, 2 * np.pi / 3, np.pi), ) -> MBQCCircuit: """Generate a random MBQC circuit. @@ -103,12 +103,12 @@ def random_circ( circuit width depth : int circuit depth - rng : np.random.Generator, optional - random number generator, by default np.random.default_rng() + rng : numpy.random.Generator, optional + random number generator, by default numpy.random.default_rng() edge_p : float, optional probability of adding CZ gate, by default 0.5 - angle_list : Sequence[float], optional - list of angles, by default [0, np.pi / 3, 2 * np.pi / 3, np.pi] + angle_candidates : collections.abc.Sequence[float], optional + list of angles, by default (0, np.pi / 3, 2 * np.pi / 3, np.pi) Returns ------- @@ -120,14 +120,14 @@ def random_circ( circ = MBQCCircuit(width) for d in range(depth): for j in range(width): - circ.j(j, rng.choice(angle_list)) + circ.j(j, rng.choice(angle_candidates)) if d < depth - 1: for j in range(width): if rng.random() < edge_p: circ.cz(j, (j + 1) % width) num = rng.integers(0, width) if num > 0: - target = set(rng.choice(list(range(width)), num)) - circ.phase_gadget(target, rng.choice(angle_list)) + target = set(rng.choice(range(width), num)) + circ.phase_gadget(target, rng.choice(angle_candidates)) return circ From 1655757a7e49d8a04a642f6d9da8bbe04c840aa7 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 01:56:25 +0900 Subject: [PATCH 64/72] :bug: Fix bug in _remove_clifford --- graphix_zx/zxgraphstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index fb1496528..b54e48eb6 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -283,7 +283,7 @@ def _remove_clifford(self, node: int, atol: float = 1e-9) -> None: absolute tolerance, by default 1e-9 """ a_pi = self.meas_bases[node].angle % (2.0 * np.pi) - coeff = 0.0 if _is_close_angle(a_pi, np.pi, atol) else 1.0 + coeff = 0.0 if _is_close_angle(a_pi, 0, atol) else 1.0 lc = LocalClifford(coeff * np.pi, 0, 0) for v in self.get_neighbors(node) - self.output_nodes: self.apply_local_clifford(v, lc) From ddd4c6fc0ffff017a79e7aa29e33306dcf875d77 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 02:12:49 +0900 Subject: [PATCH 65/72] :recycle: Refactor merge_yz_nodes --- graphix_zx/zxgraphstate.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index b54e48eb6..e8c4dd4b7 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -538,10 +538,9 @@ def merge_yz_nodes(self) -> None: for neighbors, nodes in neighbor_groups.items(): if len(nodes) < min_nodes or len(neighbors) < min_nodes: continue - u = nodes[0] + new_angle = sum(self.meas_bases[v].angle for v in nodes) % (2.0 * np.pi) + self.set_meas_basis(nodes[0], PlannerMeasBasis(Plane.YZ, new_angle)) for v in nodes[1:]: - new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) - self.set_meas_basis(u, PlannerMeasBasis(Plane.YZ, new_angle)) self.remove_physical_node(v) def full_reduce(self, atol: float = 1e-9) -> None: From 3576a637e56b558d5dbe9b1f87b9ee75ffdda293 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Sun, 4 May 2025 03:05:04 +0900 Subject: [PATCH 66/72] :recycle: Refactor _extract_yz_adjacent_pair --- graphix_zx/zxgraphstate.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index e8c4dd4b7..71e60ca55 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -468,10 +468,9 @@ def _extract_yz_adjacent_pair(self) -> tuple[int, int] | None: A pair of adjacent nodes that are both measured in the YZ-plane, or None if no such pair exists. """ yz_nodes = {node for node, basis in self.meas_bases.items() if basis.plane == Plane.YZ} - for u in yz_nodes: - for v in self.get_neighbors(u): - if v in yz_nodes: - return (min(u, v), max(u, v)) + for u, v in self.physical_edges: + if u in yz_nodes and v in yz_nodes: + return (min(u, v), max(u, v)) return None def _extract_xz_node(self) -> int | None: From 6bf99464623d10f83f407eb10275d7383fc7d4cf Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 5 May 2025 16:57:04 +0900 Subject: [PATCH 67/72] :fire: Delete an uneccesary line --- graphix_zx/zxgraphstate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 71e60ca55..49c77fdee 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -328,7 +328,6 @@ def remove_clifford(self, node: int, atol: float = 1e-9) -> None: elif self._needs_pivot_1(node, atol) or self._needs_pivot_2(node, atol): nbrs = self.get_neighbors(node) - self.input_nodes v = min(nbrs) - nbrs.remove(v) self.pivot(node, v) else: msg = "This Clifford node is unremovable." From dc7375b8ba72c14de55e4a08443b06b92fcd6238 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 5 May 2025 18:15:24 +0900 Subject: [PATCH 68/72] :recycle: Refactor ZXGraphState --- graphix_zx/zxgraphstate.py | 120 ++++++++----------------------------- 1 file changed, 24 insertions(+), 96 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 49c77fdee..612b324d8 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -38,10 +38,18 @@ class ZXGraphState(GraphState): qubit indices local_cliffords : dict[int, LocalClifford] local clifford operators + _clifford_rules : list[tuple[Callable[[int, float], bool], Callable[[int], None]]] + list of rules (check_func, action_func) for removing local clifford nodes """ def __init__(self) -> None: super().__init__() + self._clifford_rules: list[tuple[Callable[[int, float], bool], Callable[[int], None]]] = [ + (self._needs_nop, lambda _: None), + (self._needs_lc, self.local_complement), + (self._needs_pivot_1, lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes))), + (self._needs_pivot_2, lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes))), + ] def _update_connections(self, rmv_edges: Iterable[tuple[int, int]], new_edges: Iterable[tuple[int, int]]) -> None: """Update the physical edges of the graph state. @@ -321,19 +329,15 @@ def remove_clifford(self, node: int, atol: float = 1e-9) -> None: msg = "This node is not a Clifford node." raise ValueError(msg) - if self._needs_nop(node, atol): - pass - elif self._needs_lc(node, atol): - self.local_complement(node) - elif self._needs_pivot_1(node, atol) or self._needs_pivot_2(node, atol): - nbrs = self.get_neighbors(node) - self.input_nodes - v = min(nbrs) - self.pivot(node, v) - else: - msg = "This Clifford node is unremovable." - raise ValueError(msg) + for check, action in self._clifford_rules: + if not check(node, atol): + continue + action(node) + self._remove_clifford(node, atol) + return - self._remove_clifford(node, atol) + msg = "This Clifford node is unremovable." + raise ValueError(msg) def is_removable_clifford(self, node: int, atol: float = 1e-9) -> bool: """Check if the node is a removable Clifford node. @@ -359,82 +363,6 @@ def is_removable_clifford(self, node: int, atol: float = 1e-9) -> bool: ] ) - def _remove_cliffords( - self, action_func: Callable[[int, float], None], check_func: Callable[[int, float], bool], atol: float = 1e-9 - ) -> None: - """Remove all local clifford nodes which are specified by the check_func and action_func. - - Parameters - ---------- - action_func : Callable[[int, float], None] - action to perform on the node - check_func : Callable[[int, float], bool] - check if the node is a removable Clifford node - """ - self.check_meas_basis() - while True: - nodes = self.physical_nodes - self.input_nodes - self.output_nodes - clifford_nodes = [node for node in nodes if check_func(node, atol)] - clifford_node = min(clifford_nodes, default=None) - if clifford_node is None: - break - action_func(clifford_node, atol) - - def _step1_action(self, node: int, atol: float = 1e-9) -> None: - """If _needs_lc is True, apply local complement to the node, and remove it. - - Parameters - ---------- - node : int - node index - atol : float, optional - absolute tolerance, by default 1e-9 - """ - self.local_complement(node) - self._remove_clifford(node, atol) - - def _step2_action(self, node: int, atol: float = 1e-9) -> None: - """If _needs_nop is True, remove the node. - - Parameters - ---------- - node : int - node index - atol : float, optional - absolute tolerance, by default 1e-9 - """ - self._remove_clifford(node, atol) - - def _step3_action(self, node: int, atol: float = 1e-9) -> None: - """If _needs_pivot_1 is True, apply pivot operation to the node, and remove it. - - Parameters - ---------- - node : int - node index - atol : float, optional - absolute tolerance, by default 1e-9 - """ - nbrs = self.get_neighbors(node) - self.input_nodes - nbr = min(nbrs) - self.pivot(node, nbr) - self._remove_clifford(node, atol) - - def _step4_action(self, node: int, atol: float = 1e-9) -> None: - """If _needs_pivot_2 is True, apply pivot operation to the node, and remove it. - - Parameters - ---------- - node : int - node index - atol : float, optional - absolute tolerance, by default 1e-9 - """ - nbrs = self.get_neighbors(node) - self.input_nodes - nbr = min(nbrs) - self.pivot(node, nbr) - self._remove_clifford(node, atol) - def remove_cliffords(self, atol: float = 1e-9) -> None: """Remove all local clifford nodes which are removable. @@ -447,14 +375,14 @@ def remove_cliffords(self, atol: float = 1e-9) -> None: while any( self.is_removable_clifford(n, atol) for n in (self.physical_nodes - self.input_nodes - self.output_nodes) ): - steps = [ - (self._step1_action, self._needs_lc), - (self._step2_action, self._needs_nop), - (self._step3_action, self._needs_pivot_1), - (self._step4_action, self._needs_pivot_2), - ] - for action_func, check_func in steps: - self._remove_cliffords(action_func, check_func, atol) + for check, action in self._clifford_rules: + while True: + candidates = self.physical_nodes - self.input_nodes - self.output_nodes + clifford_node = next((node for node in candidates if check(node, atol)), None) + if clifford_node is None: + break + action(clifford_node) + self._remove_clifford(clifford_node, atol) def _extract_yz_adjacent_pair(self) -> tuple[int, int] | None: """Call inside convert_to_phase_gadget. From 0927f1c4cff75dfe5c1f2360937bcecb4d13b808 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Mon, 5 May 2025 18:35:07 +0900 Subject: [PATCH 69/72] :recycle: Refactor ZXGraphState --- graphix_zx/zxgraphstate.py | 46 ++++++-------------------------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index 612b324d8..c3af6d628 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -47,8 +47,7 @@ def __init__(self) -> None: self._clifford_rules: list[tuple[Callable[[int, float], bool], Callable[[int], None]]] = [ (self._needs_nop, lambda _: None), (self._needs_lc, self.local_complement), - (self._needs_pivot_1, lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes))), - (self._needs_pivot_2, lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes))), + (self._needs_pivot, lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes))), ] def _update_connections(self, rmv_edges: Iterable[tuple[int, int]], new_edges: Iterable[tuple[int, int]]) -> None: @@ -217,7 +216,7 @@ def _needs_lc(self, node: int, atol: float = 1e-9) -> bool: self.meas_bases[node].plane in {Plane.YZ, Plane.XY} ) - def _needs_pivot_1(self, node: int, atol: float = 1e-9) -> bool: + def _needs_pivot(self, node: int, atol: float = 1e-9) -> bool: """Check if the nodes need a pivot operation in order to perform _remove_clifford. The pivot operation is performed on the non-input neighbor of the node. @@ -238,40 +237,10 @@ def _needs_pivot_1(self, node: int, atol: float = 1e-9) -> bool: bool True if the nodes need a pivot operation. """ - if not self.get_neighbors(node) - self.input_nodes: - return False - - alpha = self.meas_bases[node].angle % (2.0 * np.pi) - # (a) the measurement angle is 0 or pi (mod 2pi) and the measurement plane is XY - case_a = _is_close_angle(2 * alpha, 0, atol) and self.meas_bases[node].plane == Plane.XY - # (b) the measurement angle is 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane is XZ - case_b = _is_close_angle(2 * (alpha - np.pi / 2), 0, atol) and self.meas_bases[node].plane == Plane.XZ - return case_a or case_b - - def _needs_pivot_2(self, node: int, atol: float = 1e-9) -> bool: - """Check if the node needs a pivot operation on output nodes in order to perform _remove_clifford. - - The pivot operation is performed on the non-input but output neighbor of the node. - For this operation, - (a) the measurement angle must be 0 or pi (mod 2pi) and the measurement plane must be XY, - or - (b) the measurement angle must be 0.5 pi or 1.5 pi (mod 2pi) and the measurement plane must be XZ. - - Parameters - ---------- - node : int - node index - atol : float, optional - absolute tolerance, by default 1e-9 - - Returns - ------- - bool - True if the node needs a pivot operation on output nodes. - """ - nbrs = self.get_neighbors(node) - if not (nbrs.issubset(self.output_nodes) and nbrs): - return False + if not (self.get_neighbors(node) - self.input_nodes): + nbrs = self.get_neighbors(node) + if not (nbrs.issubset(self.output_nodes) and nbrs): + return False alpha = self.meas_bases[node].angle % (2.0 * np.pi) # (a) the measurement angle is 0 or pi (mod 2pi) and the measurement plane is XY @@ -358,8 +327,7 @@ def is_removable_clifford(self, node: int, atol: float = 1e-9) -> bool: [ self._needs_nop(node, atol), self._needs_lc(node, atol), - self._needs_pivot_1(node, atol), - self._needs_pivot_2(node, atol), + self._needs_pivot(node, atol), ] ) From 7421cbd258af007de77875d034a13d5ec24a6d76 Mon Sep 17 00:00:00 2001 From: nabe98 Date: Tue, 6 May 2025 22:16:05 +0900 Subject: [PATCH 70/72] :bug: Fix bug in merge_yz_to_xy --- graphix_zx/zxgraphstate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index c3af6d628..cb9478fb7 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -406,10 +406,14 @@ def merge_yz_to_xy(self) -> None: target_nodes = { u for u in target_candidates - if (v := next(iter(self.get_neighbors(u)))) and self.meas_bases[v].plane == Plane.XY + if ( + (v := next(iter(self.get_neighbors(u)))) + and (mb := self.meas_bases.get(v, None)) is not None + and mb.plane == Plane.XY + ) } for u in target_nodes: - v = self.get_neighbors(u).pop() + (v,) = self.get_neighbors(u) new_angle = (self.meas_bases[u].angle + self.meas_bases[v].angle) % (2.0 * np.pi) self.set_meas_basis(v, PlannerMeasBasis(Plane.XY, new_angle)) self.remove_physical_node(u) From b3cb2c7fc5fbcf6a7eba75085ff9c8b6c17c19fb Mon Sep 17 00:00:00 2001 From: nabe98 Date: Tue, 6 May 2025 22:23:28 +0900 Subject: [PATCH 71/72] :bug: Fix bug when referencing _clifford_rules --- graphix_zx/zxgraphstate.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/graphix_zx/zxgraphstate.py b/graphix_zx/zxgraphstate.py index cb9478fb7..a65bb4fb4 100644 --- a/graphix_zx/zxgraphstate.py +++ b/graphix_zx/zxgraphstate.py @@ -8,6 +8,7 @@ from __future__ import annotations from collections import defaultdict +from functools import cached_property from typing import TYPE_CHECKING import numpy as np @@ -38,17 +39,27 @@ class ZXGraphState(GraphState): qubit indices local_cliffords : dict[int, LocalClifford] local clifford operators - _clifford_rules : list[tuple[Callable[[int, float], bool], Callable[[int], None]]] - list of rules (check_func, action_func) for removing local clifford nodes + _clifford_rules : tuple[tuple[Callable[[int, float], bool], Callable[[int], None]], ...] + tuple of rules (check_func, action_func) for removing local clifford nodes """ def __init__(self) -> None: super().__init__() - self._clifford_rules: list[tuple[Callable[[int, float], bool], Callable[[int], None]]] = [ - (self._needs_nop, lambda _: None), + + @cached_property + def _clifford_rules(self) -> tuple[tuple[Callable[[int, float], bool], Callable[[int], None]], ...]: + """List of rules (check_func, action_func) for removing local clifford nodes. + + The rules are applied in the order they are defined. + """ + return ( (self._needs_lc, self.local_complement), - (self._needs_pivot, lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes))), - ] + (self._needs_nop, lambda _: None), + ( + self._needs_pivot, + lambda node: self.pivot(node, min(self.get_neighbors(node) - self.input_nodes)), + ), + ) def _update_connections(self, rmv_edges: Iterable[tuple[int, int]], new_edges: Iterable[tuple[int, int]]) -> None: """Update the physical edges of the graph state. From b02df261875ec794b5ef022b9a251a8d2fd56c9e Mon Sep 17 00:00:00 2001 From: nabe98 Date: Wed, 7 May 2025 00:05:08 +0900 Subject: [PATCH 72/72] :truck: Rename --- ...rement_pattern_simplification.py => zxgraph_simplification.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{measurement_pattern_simplification.py => zxgraph_simplification.py} (100%) diff --git a/examples/measurement_pattern_simplification.py b/examples/zxgraph_simplification.py similarity index 100% rename from examples/measurement_pattern_simplification.py rename to examples/zxgraph_simplification.py