diff --git a/pyzx/basicrules.py b/pyzx/basicrules.py index 463d058a..18eebc0e 100644 --- a/pyzx/basicrules.py +++ b/pyzx/basicrules.py @@ -49,7 +49,7 @@ from .graph.base import BaseGraph, VT, ET from .rules import apply_rule, w_fusion, z_to_z_box from .utils import (EdgeType, VertexType, get_w_io, get_z_box_label, is_pauli, - set_z_box_label, vertex_is_w, vertex_is_z_like) + set_z_box_label, vertex_is_w, vertex_is_z_like, toggle_vertex, toggle_edge) def color_change_diagram(g: BaseGraph[VT,ET]): """Color-change an entire diagram by applying Hadamards to the inputs and ouputs.""" @@ -57,15 +57,10 @@ def color_change_diagram(g: BaseGraph[VT,ET]): if g.type(v) == VertexType.BOUNDARY: if g.vertex_degree(v) != 1: raise ValueError("Boundary should only have 1 neighbor.") - v1 = next(iter(g.neighbors(v))) - e = g.edge(v,v1) - g.set_edge_type(e, EdgeType.SIMPLE - if g.edge_type(e) == EdgeType.HADAMARD - else EdgeType.HADAMARD) - elif g.type(v) == VertexType.Z: - g.set_type(v, VertexType.X) - elif g.type(v) == VertexType.X: - g.set_type(v, VertexType.Z) + for e in g.incident_edges(v): + g.set_edge_type(e, toggle_edge(g.edge_type(e))) + elif check_color_change(g, v): + color_change(g, v) def check_color_change(g: BaseGraph[VT,ET], v: VT) -> bool: if not (g.type(v) == VertexType.Z or g.type(v) == VertexType.X): @@ -77,12 +72,9 @@ def color_change(g: BaseGraph[VT,ET], v: VT) -> bool: if not (g.type(v) == VertexType.Z or g.type(v) == VertexType.X): return False - g.set_type(v, VertexType.Z if g.type(v) == VertexType.X else VertexType.X) - for v1 in g.neighbors(v): - e = g.edge(v,v1) - g.set_edge_type(e, EdgeType.SIMPLE - if g.edge_type(e) == EdgeType.HADAMARD - else EdgeType.HADAMARD) + g.set_type(v, toggle_vertex(g.type(v))) + for e in g.incident_edges(v): + g.set_edge_type(e, toggle_edge(g.edge_type(e))) return True @@ -196,7 +188,7 @@ def check_fuse(g: BaseGraph[VT,ET], v1: VT, v2: VT) -> bool: if not (g.connected(v1,v2) and ((g.type(v1) == VertexType.X and g.type(v2) == VertexType.X) or (vertex_is_z_like(g.type(v1)) and vertex_is_z_like(g.type(v2)))) and - g.edge_type(g.edge(v1,v2)) == EdgeType.SIMPLE): + EdgeType.SIMPLE in [g.edge_type(edge) for edge in g.edges(v1,v2)]): return False return True @@ -212,9 +204,18 @@ def fuse(g: BaseGraph[VT,ET], v1: VT, v2: VT) -> bool: set_z_box_label(g, v1, get_z_box_label(g, v1) * get_z_box_label(g, v2)) else: g.add_to_phase(v1, g.phase(v2)) - for v3 in g.neighbors(v2): - if v3 != v1: - g.add_edge((v1,v3), edgetype=g.edge_type(g.edge(v2,v3))) + for e in g.incident_edges(v2): + source, target = g.edge_st(e) + other_vertex = source if source != v2 else target + if source != v2: + other_vertex = source + elif target != v2: + other_vertex = target + else: #self-loop + other_vertex = v1 + if other_vertex == v1 and g.edge_type(e) == EdgeType.SIMPLE: + continue + g.add_edge((v1,other_vertex), edgetype=g.edge_type(e)) g.remove_vertex(v2) return True diff --git a/pyzx/editor_actions.py b/pyzx/editor_actions.py index 67481b2c..25b119f4 100644 --- a/pyzx/editor_actions.py +++ b/pyzx/editor_actions.py @@ -53,8 +53,7 @@ def color_change(g: BaseGraph[VT,ET], matches: List[VT]) -> rules.RewriteOutputT for v in matches: g.set_type(v, toggle_vertex(g.type(v))) for e in g.incident_edges(v): - et = g.edge_type(e) - g.set_edge_type(e, toggle_edge(et)) + g.set_edge_type(e, toggle_edge(g.edge_type(e))) return ({}, [],[],False) diff --git a/pyzx/graph/graph_s.py b/pyzx/graph/graph_s.py index 759c8105..902479c6 100644 --- a/pyzx/graph/graph_s.py +++ b/pyzx/graph/graph_s.py @@ -217,10 +217,16 @@ def vertices_in_range(self, start, end): if all(start v0: yield (v0,v1) + def edges(self, s=None, t=None): + if s is not None and t is not None: + yield (s,t) if s < t else (t,s) + elif s is not None: + for t in self.graph[s]: + yield (s,t) if s < t else (t,s) + else: + for v0,adj in self.graph.items(): + for v1 in adj: + if v1 > v0: yield (v0,v1) def edges_in_range(self, start, end, safe=False): """like self.edges, but only returns edges that belong to vertices diff --git a/pyzx/graph/multigraph.py b/pyzx/graph/multigraph.py index ef263a3b..e3eaf1a2 100644 --- a/pyzx/graph/multigraph.py +++ b/pyzx/graph/multigraph.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import itertools from collections import Counter from fractions import Fraction from typing import Tuple, Dict, Set, Any @@ -323,7 +324,9 @@ def vertex_degree(self, vertex): return d def incident_edges(self, vertex): - return [(vertex, v1) if v1 > vertex else (v1, vertex) for v1 in self.graph[vertex]] + return list(itertools.chain.from_iterable( + self.edges(vertex, v1) for v1 in self.graph[vertex] + )) def connected(self,v1,v2): return v2 in self.graph[v1] diff --git a/pyzx/rules.py b/pyzx/rules.py index 89b2b789..708a6248 100644 --- a/pyzx/rules.py +++ b/pyzx/rules.py @@ -168,6 +168,7 @@ def match_spider_parallel( """ if matchf is not None: candidates = set([e for e in g.edges() if matchf(e)]) else: candidates = g.edge_set() + candidates = set(candidates) types = g.types() i = 0 @@ -176,6 +177,7 @@ def match_spider_parallel( e = candidates.pop() if g.edge_type(e) != EdgeType.SIMPLE: continue v0, v1 = g.edge_st(e) + if v0 == v1: continue v0t = types[v0] v1t = types[v1] if (v0t == v1t and vertex_is_zx(v0t)) or \ @@ -223,11 +225,18 @@ def spider(g: BaseGraph[VT,ET], matches: List[MatchSpiderType[VT]]) -> RewriteOu rem_verts.append(v1) # edges from the second vertex are transferred to the first - for w in g.neighbors(v1): - if v0 == w: continue - e = (v0,w) - if e not in etab: etab[e] = [0,0] - etab[e][g.edge_type(g.edge(v1,w)) - 1] += 1 + for e in g.incident_edges(v1): + source, target = g.edge_st(e) + if source != v1: + other_vertex = source + elif target != v1: + other_vertex = target + else: #self-loop + other_vertex = v0 + new_e = (v0, other_vertex) + if new_e not in etab: etab[new_e] = [0,0] + etab[new_e][g.edge_type(e)-1] += 1 + etab[(v0,v0)][0] = 0 # remove simple edge loops return (etab, rem_verts, [], True) def unspider(g: BaseGraph[VT,ET], m: List[Any], qubit:FloatInt=-1, row:FloatInt=-1) -> VT: @@ -780,7 +789,7 @@ def match_ids_parallel( while (num == -1 or i < num) and len(candidates) > 0: v = candidates.pop() - if phases[v] != 0 or not vertex_is_zx(types[v]) or g.is_ground(v): + if phases[v] != 0 or not vertex_is_zx(types[v]) or g.is_ground(v) or g.vertex_degree(v) != 2: continue neigh = g.neighbors(v) if len(neigh) != 2: continue