diff --git a/demos/PauliWebs.ipynb b/demos/PauliWebs.ipynb index c382e4a3..84236966 100644 --- a/demos/PauliWebs.ipynb +++ b/demos/PauliWebs.ipynb @@ -28,7 +28,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -80,7 +80,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -466,386 +466,21 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "metadata": {}, "outputs": [ { - "data": { - "text/html": [ - "
\n", - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "ename": "KeyError", + "evalue": "58", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[3], line 10\u001b[0m\n\u001b[1;32m 6\u001b[0m g\u001b[38;5;241m.\u001b[39mnormalize()\n\u001b[1;32m 8\u001b[0m \u001b[38;5;66;03m# Compute the time-ordering on nodes (which is only important for the non-Clifford nodes) and compute the Pauli\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;66;03m# webs for every node.\u001b[39;00m\n\u001b[0;32m---> 10\u001b[0m order, zwebs, xwebs \u001b[38;5;241m=\u001b[39m \u001b[43mcompute_pauli_webs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mg\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;66;03m# Draw the simplified ZX diagram. Note blue edges correspond to edges with Hadamard gates\u001b[39;00m\n\u001b[1;32m 13\u001b[0m zx\u001b[38;5;241m.\u001b[39mdraw(g, labels\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "File \u001b[0;32m~/git/pyzx/pyzx/pauliweb.py:191\u001b[0m, in \u001b[0;36mcompute_pauli_webs\u001b[0;34m(g)\u001b[0m\n\u001b[1;32m 188\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 189\u001b[0m ref \u001b[38;5;241m=\u001b[39m v\n\u001b[0;32m--> 191\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[43mg1\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtype\u001b[49m\u001b[43m(\u001b[49m\u001b[43mv\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;241m==\u001b[39m VertexType\u001b[38;5;241m.\u001b[39mZ: zwebs[ref] \u001b[38;5;241m=\u001b[39m pw\n\u001b[1;32m 192\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m g1\u001b[38;5;241m.\u001b[39mtype(v) \u001b[38;5;241m==\u001b[39m VertexType\u001b[38;5;241m.\u001b[39mX: xwebs[ref] \u001b[38;5;241m=\u001b[39m pw\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (order, zwebs, xwebs)\n", + "File \u001b[0;32m~/git/pyzx/pyzx/graph/graph_s.py:293\u001b[0m, in \u001b[0;36mGraphS.type\u001b[0;34m(self, vertex)\u001b[0m\n\u001b[1;32m 292\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtype\u001b[39m(\u001b[38;5;28mself\u001b[39m, vertex):\n\u001b[0;32m--> 293\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mty\u001b[49m\u001b[43m[\u001b[49m\u001b[43mvertex\u001b[49m\u001b[43m]\u001b[49m\n", + "\u001b[0;31mKeyError\u001b[0m: 58" + ] } ], "source": [ @@ -856,13 +491,9 @@ "# Normalise compacts the circuit visually and ensures every input/output is connected to a Z spider\n", "g.normalize()\n", "\n", - "# For simplicity, preprocess strips off any local Clifford unitaries and saves them in in_circ and out_circ,\n", - "# then introduces a dummy Z and X node at every output, so we can compute Z- and X-bounded Pauli webs there\n", - "in_circ, out_circ = preprocess(g)\n", - "\n", "# Compute the time-ordering on nodes (which is only important for the non-Clifford nodes) and compute the Pauli\n", "# webs for every node.\n", - "order, webs = compute_pauli_webs(g)\n", + "order, zwebs, xwebs = compute_pauli_webs(g)\n", "\n", "# Draw the simplified ZX diagram. Note blue edges correspond to edges with Hadamard gates\n", "zx.draw(g, labels=True)" @@ -2045,9 +1676,7 @@ "\n", "g = c.to_graph()\n", "zx.full_reduce(g)\n", - "in_circ, out_circ = preprocess(g)\n", - "order, webs = compute_pauli_webs(g)\n", - "print(f'{in_circ.gates} {out_circ.gates}')\n", + "order, zwebs, xwebs = compute_pauli_webs(g)\n", "\n", "# highlight the web associated to the T spider\n", "zx.draw(g, labels=True, pauli_web=webs[3])" @@ -2850,9 +2479,7 @@ "zx.simplify.id_simp(g, quiet=True)\n", "\n", "# pauli web calculation\n", - "in_circ, out_circ = preprocess(g)\n", - "order, webs = compute_pauli_webs(g)\n", - "print(f'{in_circ.gates} {out_circ.gates}')\n", + "order, zwebs, xwebs = compute_pauli_webs(g)\n", "\n", "# highlight the web associated to the T spider\n", "zx.draw(g, labels=True, pauli_web=webs[15])" diff --git a/pyzx/graph/base.py b/pyzx/graph/base.py index 0c8f2ad3..39782671 100644 --- a/pyzx/graph/base.py +++ b/pyzx/graph/base.py @@ -196,13 +196,11 @@ def adjoint(self) -> 'BaseGraph': def clone(self) -> 'BaseGraph': """ - This method should return an identical copy of the graph, without any relabeling + This method should return an identical copy of the graph, without any relabeling. - FIXME: this currently *does* change lables. - - Used in lookahead extraction. + Note this needs to be implemented in the backend, since different backends deal with names differently. """ - return self.copy() + raise NotImplementedError() def map_qubits(self, qubit_map:Mapping[int,Tuple[float,float]]) -> None: for v in self.vertices(): diff --git a/pyzx/pauliweb.py b/pyzx/pauliweb.py index e0531509..551245c9 100644 --- a/pyzx/pauliweb.py +++ b/pyzx/pauliweb.py @@ -31,6 +31,11 @@ def multiply_paulis(p1: str, p2: str) -> str: elif p1 != 'Z' and p2 != 'Z': return 'Z' else: raise ValueError('Expected: I, X, Y, or Z') +def h_pauli(p: str) -> str: + if p == 'I': return 'I' + elif p == 'X': return 'Z' + elif p == 'Y': return 'Y' + else: return 'X' class PauliWeb(Generic[VT, ET]): @@ -40,51 +45,42 @@ class PauliWeb(Generic[VT, ET]): all the edges incident to the vertices in `c`. To account for Hadamard edges, edge typing is assigned to "half-edges". """ - def __init__(self, g: BaseGraph[VT,ET], c: Set[VT]): + def __init__(self, g: BaseGraph[VT,ET]): self.g = g - self.c = c - - def vertices(self) -> Set[VT]: - vs = self.c.copy() - for v in self.c: - vs |= set(self.g.neighbors(v)) - return vs + self.es: Dict[Tuple[VT,VT], str] = dict() + self.vs: Set[VT] = set() + + def add_edge(self, v_pair: Tuple[VT, VT], pauli: str): + s, t = v_pair + self.vs.add(s) + self.vs.add(t) + et = self.g.edge_type(self.g.edge(s, t)) + t1 = self.es.get((s,t), 'I') + t2 = self.es.get((t,s), 'I') + self.es[(s,t)] = multiply_paulis(t1, pauli) + self.es[(t,s)] = multiply_paulis(t2, pauli if et == EdgeType.SIMPLE else h_pauli(pauli)) + + def add_vertex(self, v: VT): + p = 'X' if self.g.type(v) == VertexType.Z else 'Z' + for v1 in self.g.neighbors(v): + self.add_edge((v, v1), p) + + def vertices(self): + return self.vs def half_edges(self) -> Dict[Tuple[VT,VT],str]: - es: Dict[Tuple[VT,VT],str] = dict() - for v in self.c: - for e in self.g.incident_edges(v): - s, t = self.g.edge_st(e) - v1 = s if s != v else t - ty = 'Z' if self.g.type(v) == VertexType.Z else 'X' - oty = 'X' if ty == 'Z' else 'Z' - if self.g.edge_type(e) == EdgeType.SIMPLE: - ty = oty - - t1 = es.get((v,v1), 'I') - t2 = es.get((v1,v), 'I') - es[(v,v1)] = multiply_paulis(t1, oty) - es[(v1,v)] = multiply_paulis(t2, ty) - return es - - def boundary(self) -> Set[VT]: - b: Dict[VT, int] = dict() - for v in self.c: - for n in self.g.neighbors(v): - if n in b: b[n] += 1 - else: b[n] = 1 - return set(n for n,k in b.items() if k%2 == 1) + return self.es def __repr__(self): - return 'PauliWeb' + repr(self.c) + return 'PauliWeb' + str(self.vs) def preprocess(g: BaseGraph[VT,ET]): #g.normalize() - gadgetize(g) - gadgets = set(v for v in g.vertices() if g.is_phase_gadget(v)) + #gadgetize(g) + #gadgets = set(v for v in g.vertices() if g.is_phase_gadget(v)) #boundary_spiders = set(v for v in g.vertices() if any(g.type(w) == VertexType.BOUNDARY for w in g.neighbors(v))) - to_rg(g, init_z=None, init_x=gadgets) + #to_rg(g, init_z=None, init_x=gadgets) in_circ = Circuit(len(g.inputs())) for j,i in enumerate(g.inputs()): @@ -144,10 +140,57 @@ def transpose_corrections(c: Dict[VT, Set[VT]]) -> Dict[VT, Set[VT]]: ct[v].add(k) return ct -def compute_pauli_webs(g: BaseGraph[VT,ET]) -> Tuple[Dict[VT, int], Dict[VT, PauliWeb[VT,ET]]]: - gf = gflow(g, focus=True, pauli=True) +def compute_pauli_webs(g: BaseGraph[VT,ET]) -> Tuple[Dict[VT, int], Dict[VT, PauliWeb[VT,ET]], Dict[VT, PauliWeb[VT,ET]]]: + g1 = g.clone() + out_edge: Dict[VT, Tuple[VT,VT]] = dict() + for j,o in enumerate(g1.outputs()): + e = g1.incident_edges(o)[0] + v = next(iter(g1.neighbors(o))) + vt = g1.type(v) + et = g1.edge_type(e) + g1.remove_edge(e) + if ((vt == VertexType.Z and et == EdgeType.SIMPLE) or + (vt == VertexType.X and et == EdgeType.HADAMARD)): + v0 = g1.add_vertex(VertexType.X) + v1 = g1.add_vertex(VertexType.Z) + if ((vt == VertexType.X and et == EdgeType.SIMPLE) or + (vt == VertexType.Z and et == EdgeType.HADAMARD)): + v0 = g1.add_vertex(VertexType.Z) + v1 = g1.add_vertex(VertexType.X) + g1.add_edge((v, v0), et) + g1.add_edge((v0, v1), EdgeType.SIMPLE) + g1.add_edge((v1, o), EdgeType.SIMPLE) + out_edge[v0] = (o, v) + out_edge[v1] = (o, v) + + gf = gflow(g1, focus=True, pauli=True) if not gf: raise ValueError("Graph must have gFlow") - order, c = gf + order, corr = gf + + zwebs: Dict[VT, PauliWeb[VT,ET]] = dict() + xwebs: Dict[VT, PauliWeb[VT,ET]] = dict() + vset = g.vertex_set() + corr_t = transpose_corrections(corr) + print(corr_t) + + for v,c in corr_t.items(): + pw = PauliWeb(g) + for v1 in c: + if v1 in vset: + pw.add_vertex(v1) + elif v1 in out_edge: + o, vo = out_edge[v1] + pw.add_edge((o,vo), 'Z' if g1.type(v1) == VertexType.X else 'X') + if v in out_edge: + # o, vo = out_edge[v] + # pw.add_edge((o,vo), 'Z' if g1.type(v) == VertexType.Z else 'X') + ref = out_edge[v][0] + else: + ref = v + + if g1.type(v) == VertexType.Z: zwebs[ref] = pw + elif g1.type(v) == VertexType.X: xwebs[ref] = pw + - return (order, { v: PauliWeb(g, c) for v,c in transpose_corrections(c).items() }) \ No newline at end of file + return (order, zwebs, xwebs) \ No newline at end of file