diff --git a/pyzx/drawing.py b/pyzx/drawing.py index e03c896aa..5c7a67f6e 100644 --- a/pyzx/drawing.py +++ b/pyzx/drawing.py @@ -209,6 +209,8 @@ def draw_matplotlib( ecol = '#0099ff' elif et == 3: ecol = 'gray' + elif et == 4: + ecol = '#8B0000' # Dark red for FAULT_EDGE else: ecol = 'black' diff --git a/pyzx/editor_actions.py b/pyzx/editor_actions.py index 8a1b5227d..674b72149 100644 --- a/pyzx/editor_actions.py +++ b/pyzx/editor_actions.py @@ -128,7 +128,12 @@ def pauli_push(g: BaseGraph[VT,ET], rem_edges.append(edge) w2 = g.add_vertex(t,q,r,p) etab[upair(v,w2)] = [1,0] - etab[upair(n,w2)] = [1,0] if et == EdgeType.SIMPLE else [0,1] + if et == EdgeType.SIMPLE: + etab[upair(n,w2)] = [1,0] + elif et == EdgeType.HADAMARD: + etab[upair(n,w2)] = [0,1] + else: + raise ValueError(f"Cannot apply Pauli commutation through {et} edge") new_verts.append(w2) if not vertex_is_zx(g.type(v)): # v is H_BOX if len(new_verts) == 2: diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index 6ae7e3e56..34561abf0 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -159,6 +159,9 @@ def json_to_graph_old(js: Union[str,Dict[str,Any]], backend:Optional[str]=None) if 'type' in edge and edge['type'] == 'w_io': g.add_edge((names[n1],names[n2]), EdgeType.W_IO) continue + if 'type' in edge and edge['type'] == 'fault_edge': + g.add_edge((names[n1],names[n2]), EdgeType.FAULT_EDGE) + continue amount = edges.get((names[n1],names[n2]),[0,0]) amount[0] += 1 @@ -321,6 +324,9 @@ def graph_to_dict_old(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[st elif et == EdgeType.W_IO: edges["e"+str(i)] = {"src": names[src],"tgt": names[tgt], "type": "w_io"} i += 1 + elif et == EdgeType.FAULT_EDGE: + edges["e"+str(i)] = {"src": names[src],"tgt": names[tgt], "type": "fault_edge"} + i += 1 else: raise TypeError("Edge of type 0") diff --git a/pyzx/graph/multigraph.py b/pyzx/graph/multigraph.py index de6796361..6aa53c38f 100644 --- a/pyzx/graph/multigraph.py +++ b/pyzx/graph/multigraph.py @@ -29,33 +29,38 @@ class Edge: s: int h: int w_io: int + fault_edge: int - def __init__(self, s: int=0, h: int=0, w_io: int=0): + def __init__(self, s: int=0, h: int=0, w_io: int=0, fault_edge: int=0): self.s = s self.h = h self.w_io = w_io + self.fault_edge = fault_edge - def add(self, s: int=0, h: int=0, w_io: int=0): + def add(self, s: int=0, h: int=0, w_io: int=0, fault_edge: int=0): self.s += s self.h += h self.w_io += w_io - if self.s < 0 or self.h < 0: + self.fault_edge += fault_edge + if self.s < 0 or self.h < 0 or self.w_io < 0 or self.fault_edge < 0: raise ValueError('Cannot have negative edges') if self.w_io not in (0,1): raise ValueError('Invalid number of W-IO edges') - if self.w_io == 1 and self.s + self.h > 0: + if self.w_io == 1 and self.s + self.h + self.fault_edge > 0: raise ValueError('Cannot have W-IO edge and other edges') - def remove(self, s: int=0, h: int=0, w_io: int=0): - self.add(s=-s, h=-h, w_io=-w_io) + def remove(self, s: int=0, h: int=0, w_io: int=0, fault_edge: int=0): + self.add(s=-s, h=-h, w_io=-w_io, fault_edge=-fault_edge) def is_empty(self) -> bool: - return self.s == 0 and self.h == 0 and self.w_io == 0 + return self.s == 0 and self.h == 0 and self.w_io == 0 and self.fault_edge == 0 def get_edge_count(self, ty: EdgeType) -> int: if ty == EdgeType.SIMPLE: return self.s elif ty == EdgeType.HADAMARD: return self.h - else: return self.w_io + elif ty == EdgeType.W_IO: return self.w_io + elif ty == EdgeType.FAULT_EDGE: return self.fault_edge + else: return 0 class Multigraph(BaseGraph[int,Tuple[int,int,EdgeType]]): """Purely Pythonic multigraph implementation of :class:`~graph.base.BaseGraph`.""" @@ -177,7 +182,9 @@ def add_edge(self, edge_pair, edgetype=EdgeType.SIMPLE): if edgetype == EdgeType.SIMPLE: e.add(s=1) elif edgetype == EdgeType.HADAMARD: e.add(h=1) - else: e.add(w_io=1) + elif edgetype == EdgeType.W_IO: e.add(w_io=1) + elif edgetype == EdgeType.FAULT_EDGE: e.add(fault_edge=1) + else: raise ValueError(f"Unknown edge type: {edgetype}") if self._auto_simplify: # This currently can keep a parallel regular and Hadamard edge t1 = self.ty[s] @@ -198,6 +205,9 @@ def add_edge(self, edge_pair, edgetype=EdgeType.SIMPLE): if e.s > 0: self.nedges = self.nedges - e.s + 1 e.s = 1 + if e.fault_edge > 0: + self.nedges = self.nedges - e.fault_edge + 1 + e.fault_edge = 1 if e.h > 0: self.nedges = self.nedges - (e.h - e.h % 2) self.scalar.add_power(-2 * (e.h - (e.h % 2))) @@ -224,7 +234,7 @@ def remove_vertices(self, vertices): # remove all edges for v1 in vs: e = self.graph[v][v1] - self.nedges -= e.s + e.h + self.nedges -= e.s + e.h + e.w_io + e.fault_edge # Remove all edata for all edge types between v and v1 for ty in EdgeType: self._edata.pop((min(v, v1), max(v, v1), ty), None) @@ -260,7 +270,9 @@ def remove_edge(self, edge): e = self.graph[s][t] if ty == EdgeType.SIMPLE: e.remove(s=1) elif ty == EdgeType.HADAMARD: e.remove(h=1) - else: e.remove(w_io=1) + elif ty == EdgeType.W_IO: e.remove(w_io=1) + elif ty == EdgeType.FAULT_EDGE: e.remove(fault_edge=1) + else: raise ValueError(f"Unknown edge type: {ty}") if e.is_empty(): del self.graph[s][t] @@ -284,6 +296,8 @@ def num_edges(self, s=None, t=None, et=None): return self.graph[s][t].h elif et == EdgeType.W_IO: return self.graph[s][t].w_io + elif et == EdgeType.FAULT_EDGE: + return self.graph[s][t].fault_edge else: raise ValueError("Unkown EdgeType: %s" % repr(et)) else: @@ -308,6 +322,7 @@ def edges(self, s=None, t=None): for _ in range(e.s): yield (v0, v1, EdgeType.SIMPLE) for _ in range(e.h): yield (v0, v1, EdgeType.HADAMARD) for _ in range(e.w_io): yield (v0, v1, EdgeType.W_IO) + for _ in range(e.fault_edge): yield (v0, v1, EdgeType.FAULT_EDGE) elif t != None: s, t = (s, t) if s < t else (t, s) if t not in self.graph[s]: @@ -316,6 +331,7 @@ def edges(self, s=None, t=None): for _ in range(e.s): yield (s, t, EdgeType.SIMPLE) for _ in range(e.h): yield (s, t, EdgeType.HADAMARD) for _ in range(e.w_io): yield (s, t, EdgeType.W_IO) + for _ in range(e.fault_edge): yield (s, t, EdgeType.FAULT_EDGE) # def edges_in_range(self, start, end, safe=False): # """like self.edges, but only returns edges that belong to vertices @@ -392,10 +408,14 @@ def set_edge_type(self, edge, t): # decrement the old type and increment the new type if ty == EdgeType.SIMPLE: e.add(s=-1) elif ty == EdgeType.HADAMARD: e.add(h=-1) - else: e.add(w_io=-1) + elif ty == EdgeType.W_IO: e.add(w_io=-1) + elif ty == EdgeType.FAULT_EDGE: e.add(fault_edge=-1) + else: raise ValueError(f"Unknown edge type: {ty}") if t == EdgeType.SIMPLE: e.add(s=1) elif t == EdgeType.HADAMARD: e.add(h=1) - else: e.add(w_io=1) + elif t == EdgeType.W_IO: e.add(w_io=1) + elif t == EdgeType.FAULT_EDGE: e.add(fault_edge=1) + else: raise ValueError(f"Unknown edge type: {t}") def type(self, vertex): return self.ty[vertex] diff --git a/pyzx/js/zx_viewer.inline.js b/pyzx/js/zx_viewer.inline.js index 3bb9e077e..8f9aa6d4a 100644 --- a/pyzx/js/zx_viewer.inline.js +++ b/pyzx/js/zx_viewer.inline.js @@ -29,6 +29,7 @@ function edgeColor(t) { if (t == 1) return _settings_colors['edge']; //"black"; else if (t == 2) return _settings_colors['Hedge']; // "#08f"; else if (t == 3) return _settings_colors['Xedge']; // "gray"; + else if (t == 4) return _settings_colors['FaultEdge']; // "dark red"; } function webColor(t) { diff --git a/pyzx/js/zx_viewer.js b/pyzx/js/zx_viewer.js index d65b6f723..5412e1d88 100644 --- a/pyzx/js/zx_viewer.js +++ b/pyzx/js/zx_viewer.js @@ -35,6 +35,7 @@ function edgeColor(t) { if (t == 1) return _settings_colors['edge']; //"black"; else if (t == 2) return _settings_colors['Hedge']; // "#08f"; else if (t == 3) return _settings_colors['Xedge']; // "gray"; + else if (t == 4) return _settings_colors['FaultEdge']; // "dark red"; } function webColor(t) { diff --git a/pyzx/rewrite_rules/editor_actions.py b/pyzx/rewrite_rules/editor_actions.py index 753061497..53f19c43f 100644 --- a/pyzx/rewrite_rules/editor_actions.py +++ b/pyzx/rewrite_rules/editor_actions.py @@ -130,7 +130,12 @@ def pauli_push(g: BaseGraph[VT,ET], rem_edges.append(edge) w2 = g.add_vertex(t,q,r,p) etab[upair(v,w2)] = [1,0] - etab[upair(n,w2)] = [1,0] if et == EdgeType.SIMPLE else [0,1] + if et == EdgeType.SIMPLE: + etab[upair(n,w2)] = [1,0] + elif et == EdgeType.HADAMARD: + etab[upair(n,w2)] = [0,1] + else: + raise ValueError(f"Cannot apply Pauli commutation through {et} edge") new_verts.append(w2) if not vertex_is_zx(g.type(v)): # v is H_BOX if len(new_verts) == 2: @@ -219,7 +224,12 @@ def add_Z_identity(g: BaseGraph[VT,ET], r = 0.5*(g.row(v1) + g.row(v2)) q = 0.5*(g.qubit(v1) + g.qubit(v2)) w = g.add_vertex(VertexType.Z, q,r, 0) - etab[upair(v1,w)] = [1,0] if et == EdgeType.SIMPLE else [0,1] + if et == EdgeType.SIMPLE: + etab[upair(v1,w)] = [1,0] + elif et == EdgeType.HADAMARD: + etab[upair(v1,w)] = [0,1] + else: + raise ValueError(f"Cannot add Z identity on {et} edge") etab[upair(v2,w)] = [1,0] return (etab, [], rem_edges, False) diff --git a/pyzx/rewrite_rules/rules.py b/pyzx/rewrite_rules/rules.py index 019e81281..16bac67cc 100644 --- a/pyzx/rewrite_rules/rules.py +++ b/pyzx/rewrite_rules/rules.py @@ -837,7 +837,8 @@ def remove_ids(g: BaseGraph[VT,ET], matches: List[MatchIdType[VT]]) -> RewriteOu e = (v0,v1) if not e in etab: etab[e] = [0,0] if et == EdgeType.SIMPLE: etab[e][0] += 1 - else: etab[e][1] += 1 + elif et == EdgeType.HADAMARD: etab[e][1] += 1 + else: raise ValueError(f"Invalid edge type: {et}") return (etab, rem, [], False) diff --git a/pyzx/tensor.py b/pyzx/tensor.py index b913e89e4..d086fb0ac 100644 --- a/pyzx/tensor.py +++ b/pyzx/tensor.py @@ -171,6 +171,10 @@ def tensorfy(g: 'BaseGraph[VT,ET]', preserve_scalar:bool=True) -> np.ndarray: t = np.tensordot(t,had) elif g.edge_type(sl) == EdgeType.SIMPLE: t = np.trace(t) + elif g.edge_type(sl) == EdgeType.W_IO: + raise NotImplementedError(f"Tensor contraction with W_IO self-loops is not implemented.") + elif g.edge_type(sl) == EdgeType.FAULT_EDGE: + raise NotImplementedError(f"Tensor contraction with FAULT_EDGE self-loops is not implemented.") else: raise NotImplementedError(f"Tensor contraction with {repr(sl)} self-loops is not implemented.") nn = list(filter(lambda n: rows[n] No synonyms_edge = ['empty', 'simple', 'none'] synonyms_hedge = ['hadamard edge'] synonyms_wedge = ['w edge', 'w io edge'] +synonyms_fault_edge = ['fault edge'] tikz_error_message = "Not a valid tikz picture. Please use Tikzit to generate correct output." def tikz_to_graph( @@ -396,6 +400,8 @@ def tikz_to_graph( etab[e] = [0,1] elif style.lower() in synonyms_wedge: g.add_edge(e, EdgeType.W_IO) + elif style.lower() in synonyms_fault_edge: + g.add_edge(e, EdgeType.FAULT_EDGE) else: if ignore_nonzx: if e in etab: diff --git a/pyzx/utils.py b/pyzx/utils.py index 0cfb8d706..ff336cdcf 100644 --- a/pyzx/utils.py +++ b/pyzx/utils.py @@ -80,9 +80,12 @@ class EdgeType(IntEnum): SIMPLE = 1 HADAMARD = 2 W_IO = 3 + FAULT_EDGE = 4 def toggle_edge(ty: EdgeType) -> EdgeType: """Swap the regular and Hadamard edge types.""" + if ty not in (EdgeType.SIMPLE, EdgeType.HADAMARD): + raise ValueError(f"Unknown edge type: {ty}") return EdgeType.HADAMARD if ty == EdgeType.SIMPLE else EdgeType.SIMPLE def phase_to_s(a: FractionLike, t:VertexType=VertexType.Z, poly_with_pi:bool=False) -> str: @@ -138,13 +141,15 @@ def phase_is_pauli(phase: FractionLike): 'dummy': 'text', 'edge': '', 'H-edge': 'hadamard edge', - 'W-io-edge': 'W io edge' + 'W-io-edge': 'W io edge', + 'Fault-edge': 'fault edge' } original_colors = { 'edge': '#000000', 'Hedge': '#0088ff', 'Xedge': '#999999', + 'FaultEdge': '#8B0000', 'boundary': '#000000', 'X': '#ff8888', 'Y': '#aabbff', @@ -169,6 +174,7 @@ def phase_is_pauli(phase: FractionLike): 'edge': '#000000', 'Hedge': '#888888', 'Xedge': '#dddddd', + 'FaultEdge': '#8B0000', 'boundary': '#000000', 'X': '#666666', 'Y': '#9999dd',