From bff304133f46218d1b4d1c5d1eb9b88d2250b362 Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:01:57 +0100 Subject: [PATCH 1/8] Added ave_pos function Added a small function to return the averaged position between two points (just basic trig) --- pyzx/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyzx/utils.py b/pyzx/utils.py index 46464c73..3314056b 100644 --- a/pyzx/utils.py +++ b/pyzx/utils.py @@ -252,3 +252,6 @@ def get_z_box_label(g, v): def set_z_box_label(g, v, label): assert g.type(v) == VertexType.Z_BOX g.set_vdata(v, 'label', label) + +# Return position 'perc'%-distance between 2 points: +def ave_pos(a,b,perc=1/2): return (abs(a-b))*(perc) + min(a,b) From efaddc8f6a86920e534ee50dbfc108ae11e6e058 Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:03:37 +0100 Subject: [PATCH 2/8] Added cut_vertex and cut_edge Added functions to apply the basic vertex and edge cutting decompositions --- pyzx/simulate.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pyzx/simulate.py b/pyzx/simulate.py index 89e1771f..e274120d 100644 --- a/pyzx/simulate.py +++ b/pyzx/simulate.py @@ -482,3 +482,65 @@ def replace_1_1(g: BaseGraph[VT,ET], verts: List[VT]) -> BaseGraph[VT,ET]: g.add_edge(g.edge(verts[0],w),EdgeType.HADAMARD) for v in verts: g.add_to_phase(v,Fraction(-1,4)) return g + +def cut_vertex(g,v): + g = g.copy() + g0 = g.copy() + g1 = g.copy() + g0.remove_vertex(v) + g1.remove_vertex(v) + + n = len(g.neighbors(v)) + g0.scalar.add_power(-n) + g1.scalar.add_power(-n) + g1.scalar.add_phase(g.phase(v)) # account for e^(i*pi*alpha) on right branch + + vtype = -1 + match g.type(v): + case 1: vtype = 2 + case 2: vtype = 1 + case _: raise ValueError("Attempted illegal cut on boundary vertex "+str(v)) + + for i in g.neighbors(v): + etype = g.edge_type((v,i)) # maintain edge type + qubit = ave_pos(g.qubit(v),g.qubit(i),1/2) + row = ave_pos(g.row(v),g.row(i),1/2) + + newV = g0.add_vertex(vtype,qubit,row,0) # add and connect the new vertices + g0.add_edge((newV,i),etype) + newV = g1.add_vertex(vtype,qubit,row,1) + g1.add_edge((newV,i),etype) + + return (g0,g1) + +def cut_edge(g,e,ty=1): + g = g.copy() + g0 = g.copy() + g1 = g.copy() + g0.remove_edge(e) + g1.remove_edge(e) + + etype = g.edge_type(e) + + g0.scalar.add_power(-2) + g1.scalar.add_power(-2) + + x0,x1 = g.row(e[0]), g.row(e[1]) + y0,y1 = g.qubit(e[0]), g.qubit(e[1]) + + qubit1 = ave_pos(y0,y1,1/3) + row1 = ave_pos(x0,x1,1/3) + qubit2 = ave_pos(y0,y1,2/3) + row2 = ave_pos(x0,x1,2/3) + + v = g0.add_vertex(ty=ty,qubit=qubit1,row=row1,phase=0) + g0.add_edge((v,e[0]),1) + v = g0.add_vertex(ty=ty,qubit=qubit2,row=row2,phase=0) + g0.add_edge((v,e[1]),etype) + + v = g1.add_vertex(ty=ty,qubit=qubit1,row=row1,phase=1) + g1.add_edge((v,e[0]),1) + v = g1.add_vertex(ty=ty,qubit=qubit2,row=row2,phase=1) + g1.add_edge((v,e[1]),etype) + + return (g0,g1) From b3bf8d604e32c9d9ff13a6130c28b73ac2ba22f8 Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:05:27 +0100 Subject: [PATCH 3/8] imports ave_pos --- pyzx/simulate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyzx/simulate.py b/pyzx/simulate.py index e274120d..9db6bd3e 100644 --- a/pyzx/simulate.py +++ b/pyzx/simulate.py @@ -28,7 +28,7 @@ import numpy as np -from .utils import EdgeType, VertexType, toggle_edge +from .utils import EdgeType, VertexType, toggle_edge, ave_pos from . import simplify from .circuit import Circuit from .graph import Graph From d7eec38fb1aeb83699bb4ffb0d61ffbc3155a8b7 Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:41:53 +0100 Subject: [PATCH 4/8] Added tests for cutting Added tests for vertex and edge cutting on random graphs to verify the scalar results are correct --- tests/test_simulate.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/test_simulate.py b/tests/test_simulate.py index 6a29ab81..db548049 100644 --- a/tests/test_simulate.py +++ b/tests/test_simulate.py @@ -17,6 +17,7 @@ import unittest import sys +import random from types import ModuleType from typing import Optional @@ -24,7 +25,9 @@ sys.path.append('..') sys.path.append('.') from pyzx.circuit import Circuit -from pyzx.simulate import replace_magic_states +from pyzx.simulate import replace_magic_states, cut_vertex, cut_edge +from pyzx.generate import cliffords +from pyzx.simplify import full_reduce np: Optional[ModuleType] try: @@ -32,6 +35,14 @@ except ImportError: np = None +def rand_graph(qubits=5,depth=10): + g = cliffords(qubits,depth) + g.apply_state('0'*qubits) + g.apply_effect('0'*qubits) + return g + +def round_complex(scalar,decimal_places): + return round(scalar.real,decimal_places) + round(scalar.imag,decimal_places)*1j @unittest.skipUnless(np, "numpy needs to be installed for this to run") class TestSimulate(unittest.TestCase): @@ -43,6 +54,32 @@ def test_magic_state_decomposition_is_correct(self): g = c.to_graph() gsum = replace_magic_states(g) self.assertTrue(np.allclose(g.to_tensor(), gsum.to_tensor())) + + def test_vertex_cut(self,repeats=20): + for i in range(1,repeats): + g = rand_graph() # generate random Clifford graph + v_cut = random.randrange(len(g.vertices())) + g0,g1 = cut_vertex(g,v_cut) # apply random vertex cut + + for g_i in (g,g0,g1): full_reduce(g_i) + + scal = round_complex(g.scalar.to_number(),3) # the scalar from fully reducing g + scalCut = round_complex(g0.scalar.to_number()+g1.scalar.to_number(),3) # the sum of scalars from the cut graph + assert(scal == scalCut) + + def test_edge_cut(self,repeats=20): + for i in range(1,repeats): + g = rand_graph() # generate random Clifford graph + rand_v = random.randrange(len(g.vertices())) + rand_neigh = list(g.neighbors(rand_v))[random.randrange(len(g.neighbors(rand_v)))] + e_cut = (rand_v,rand_neigh) # apply random edge cut + + g0,g1 = cut_edge(g,e_cut) + for g_i in (g,g0,g1): full_reduce(g_i) + + scal = round_complex(g.scalar.to_number(),3) # the scalar from fully reducing g + scalCut = round_complex(g0.scalar.to_number()+g1.scalar.to_number(),3) # the sum of scalars from the cut graph + assert(scal == scalCut) if __name__ == '__main__': From 1ab1cf792190e0211a2e8987476a6fd82adb4e0b Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:49:20 +0100 Subject: [PATCH 5/8] Add docstrings to cutting functions --- pyzx/simulate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyzx/simulate.py b/pyzx/simulate.py index 9db6bd3e..18d36d49 100644 --- a/pyzx/simulate.py +++ b/pyzx/simulate.py @@ -484,6 +484,7 @@ def replace_1_1(g: BaseGraph[VT,ET], verts: List[VT]) -> BaseGraph[VT,ET]: return g def cut_vertex(g,v): + '''Applies the "cutting" decomposition to a vertex, as used in, for example: https://arxiv.org/pdf/2403.10964.''' g = g.copy() g0 = g.copy() g1 = g.copy() @@ -514,6 +515,7 @@ def cut_vertex(g,v): return (g0,g1) def cut_edge(g,e,ty=1): + '''Applies the "cutting" decomposition to an edge, as used in, for example: https://arxiv.org/pdf/2403.10964. The type ty decides whether to cut with Z- branches or X- branches.''' g = g.copy() g0 = g.copy() g1 = g.copy() From 5d5cdefef7af306803a9bf32a5bc034fd6d9104e Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:14:10 +0100 Subject: [PATCH 6/8] Fix docstrings syntax --- pyzx/simulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyzx/simulate.py b/pyzx/simulate.py index 18d36d49..8cd6c654 100644 --- a/pyzx/simulate.py +++ b/pyzx/simulate.py @@ -484,7 +484,7 @@ def replace_1_1(g: BaseGraph[VT,ET], verts: List[VT]) -> BaseGraph[VT,ET]: return g def cut_vertex(g,v): - '''Applies the "cutting" decomposition to a vertex, as used in, for example: https://arxiv.org/pdf/2403.10964.''' + """Applies the ``cutting'' decomposition to a vertex, as used in, for example: https://arxiv.org/pdf/2403.10964.""" g = g.copy() g0 = g.copy() g1 = g.copy() @@ -515,7 +515,7 @@ def cut_vertex(g,v): return (g0,g1) def cut_edge(g,e,ty=1): - '''Applies the "cutting" decomposition to an edge, as used in, for example: https://arxiv.org/pdf/2403.10964. The type ty decides whether to cut with Z- branches or X- branches.''' + """Applies the ``cutting'' decomposition to an edge, as used in, for example: https://arxiv.org/pdf/2403.10964. The type ty decides whether to cut with Z- branches or X- branches.""" g = g.copy() g0 = g.copy() g1 = g.copy() From 4e13bc0e6ed6fd9d0392f555b08a1d41d598edd6 Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:12:42 +0100 Subject: [PATCH 7/8] Fix new cutting functions --- pyzx/simulate.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pyzx/simulate.py b/pyzx/simulate.py index 8cd6c654..a40642e8 100644 --- a/pyzx/simulate.py +++ b/pyzx/simulate.py @@ -28,7 +28,7 @@ import numpy as np -from .utils import EdgeType, VertexType, toggle_edge, ave_pos +from .utils import EdgeType, VertexType, toggle_vertex, toggle_edge, ave_pos from . import simplify from .circuit import Circuit from .graph import Graph @@ -485,9 +485,9 @@ def replace_1_1(g: BaseGraph[VT,ET], verts: List[VT]) -> BaseGraph[VT,ET]: def cut_vertex(g,v): """Applies the ``cutting'' decomposition to a vertex, as used in, for example: https://arxiv.org/pdf/2403.10964.""" - g = g.copy() - g0 = g.copy() - g1 = g.copy() + g = g.clone() + g0 = g.clone() + g1 = g.clone() g0.remove_vertex(v) g1.remove_vertex(v) @@ -496,11 +496,7 @@ def cut_vertex(g,v): g1.scalar.add_power(-n) g1.scalar.add_phase(g.phase(v)) # account for e^(i*pi*alpha) on right branch - vtype = -1 - match g.type(v): - case 1: vtype = 2 - case 2: vtype = 1 - case _: raise ValueError("Attempted illegal cut on boundary vertex "+str(v)) + vtype = toggle_vertex(g.type(v)) for i in g.neighbors(v): etype = g.edge_type((v,i)) # maintain edge type @@ -516,9 +512,9 @@ def cut_vertex(g,v): def cut_edge(g,e,ty=1): """Applies the ``cutting'' decomposition to an edge, as used in, for example: https://arxiv.org/pdf/2403.10964. The type ty decides whether to cut with Z- branches or X- branches.""" - g = g.copy() - g0 = g.copy() - g1 = g.copy() + g = g.clone() + g0 = g.clone() + g1 = g.clone() g0.remove_edge(e) g1.remove_edge(e) From 2aecd70b0d29481bdef9b3475bc977fadf2a6e48 Mon Sep 17 00:00:00 2001 From: mjsutcliffe99 <105458229+mjsutcliffe99@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:49:21 +0100 Subject: [PATCH 8/8] Add the 'wishbone' cutting variant Add new 'wishbone' (or 'separator') cutting variant for unfusing a vertex and cutting this new common edge (in the same colour as the original vertex) - this acts to separate a vertex into two, via one cut, with some neighbours on one side and some on another. --- pyzx/simulate.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pyzx/simulate.py b/pyzx/simulate.py index a40642e8..d7f99f26 100644 --- a/pyzx/simulate.py +++ b/pyzx/simulate.py @@ -542,3 +542,35 @@ def cut_edge(g,e,ty=1): g1.add_edge((v,e[1]),etype) return (g0,g1) + +def cut_wishbone(g,v,neighs,ph): + """Applies the ``wishbone cut'' (or ``separator cut'') decomposition to vertex v of graph g, pulling out the neighbours ``neighs'' and a phase ``ph'', as used in: [PAPER UPCOMING].""" + g = g.clone() + + for i in neighs: + if not i in g.neighbors(v): + raise ValueError("Attempted illegal wishbone cut. Vertex " + str(i) + " is not a neighbor of target vertex " + str(v) + ".") + + neighs_left = set(g.neighbors(v)).symmetric_difference(neighs) + neighs_right = neighs + + phase_left = g.phase(v) - ph + phase_right = ph + + v_left = g.add_vertex(qubit=g.qubit(v),row=g.row(v)-0.5,ty=g.type(v),phase=phase_left) + v_right = g.add_vertex(qubit=g.qubit(v),row=g.row(v)+0.5,ty=g.type(v),phase=phase_right) + + for i in neighs_left: g.add_edge((v_left,i),g.edge_type((v,i))) + for i in neighs_right: g.add_edge((v_right,i),g.edge_type((v,i))) + + g.remove_vertex(v) + + #-- + + gLeft = g.clone() + gRight = g.clone() + + gRight.add_to_phase(v_left,1) + gRight.add_to_phase(v_right,1) + + return (gLeft,gRight)