From 4d37f3e2d2db14a96a7bc4488ab483c81d3bbb69 Mon Sep 17 00:00:00 2001 From: Aleks Kissinger Date: Sun, 29 Dec 2024 22:35:22 +0000 Subject: [PATCH] started writing code for errors and corrections --- demos/PauliWebs.ipynb | 1278 ++++++++++++++++++++++++++++++++++------- pyzx/pauliweb.py | 88 ++- 2 files changed, 1101 insertions(+), 265 deletions(-) diff --git a/demos/PauliWebs.ipynb b/demos/PauliWebs.ipynb index fb959b81..ba6738c8 100644 --- a/demos/PauliWebs.ipynb +++ b/demos/PauliWebs.ipynb @@ -18,21 +18,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This notebook demonstrates using PyZX's methods for automatically computing and drawing (bounded) Pauli webs. A Pauli web (a.k.a. _correlation surface_) is a coloring of the edges of ZX-diagram with the following properties for every Z spider `v`:\n", + "This notebook demonstrates using PyZX's methods for automatically computing and drawing bounded Pauli webs. A Pauli web (a.k.a. _correlation surface_) is a labelling of the edges of ZX-diagram from the set $\\{I, X, Y, Z\\}$ with certain properties. To explain these properties, it is useful to make some definitions.\n", "\n", - "- _all-or-nothing_: either no incident edges or all incident edges of `v` are labelled X or Y\n", - "- _parity_: an even number of incident edges of `v` are labelled Y or Z\n", + "To account for Hadmard edges, we label \"half-edges\" of a diagram. For a pair of connected vertices `v`, `w`, we represent the half-edge closest to `v` as `(v,w)` and the half-edge closest to `w` as `(w,v)`.\n", "\n", - "All X spiders satisfy analogous conditions, swapping the role of X and Z. A _bounded Pauli web_ is a Pauli web with a chosen set of spiders, called the _boundary_ where the parity condition is allowed to be violated.\n", + "We say a spider `v` is _stabilised_ by a Pauli web if `v` (considered as a state) is a +1 eigenstate of the Pauli string labelling its adjacent half-edges and we say `v` is _anti-stabilised_ by a Pauli web if it is a -1 eigenstate of its adjacent half-edges.\n", "\n", - "We say a spider `anti-commutes` with a Pauli web if it is touching an edge of a different colour.\n", + "A Pauli web is said to be _closed_ if:\n", + "1. every spider `v` in the ZX-diagram is stabilised or anti-stabilised\n", + "2. for every half-edge `(v,w)` labelled by a Pauli $P$, the other half-edge `(w,v)` is labelled by $P$ for a simple edge and $HPH$ for a Hadamard edge\n", + "\n", + "Otherwise, we say a Pauli web is _open_. The spiders and edges violating conditions 1 and 2 above are called the _boundary_ of a Pauli web.\n", "\n", "The function `compute_pauli_webs` automitically associates each non-input vertex `v` in the diagram to an integer `order[v]` giving a time-ordering, and one or two bounded Pauli webs, with the following properties:\n", - "1. the boundary of the web consists of only `v` itself and inputs\n", + "1. the boundary of the web consists of only `v` itself, earlier non-Clifford spiders `w` (`order[v] < order[w]>`), and inputs\n", "2. Z spiders have a web `zweb[v]` with a Z-colored edge incident to `v`\n", "3. X spiders have a web `xweb[v]` with a X-colored edge incident to `v`\n", "4. output vertices have two webs `zweb[v]` and `xweb[v]` corresponding to both colors at `v`\n", - "5. for non-Pauli spiders `v, w`, if `v` anti-commutes with the Pauli web of `w` then `order[v] < order[w]`\n", "\n", "Under the hood, this is using PyZX's gflow-finding algorithm to compute a focussed Pauli flow and translate this data into Pauli webs." ] @@ -52,7 +54,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -449,7 +451,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -828,7 +830,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -1207,7 +1209,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -1586,7 +1588,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -1979,7 +1981,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -2358,7 +2360,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -2737,7 +2739,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -3116,7 +3118,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -3513,7 +3515,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -3915,7 +3917,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -4294,7 +4296,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -4673,7 +4675,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -5052,7 +5054,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -5431,7 +5433,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -5810,7 +5812,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -6205,7 +6207,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -6597,7 +6599,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -6976,7 +6978,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -7355,7 +7357,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -7734,7 +7736,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -8113,7 +8115,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -8492,7 +8494,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -8896,7 +8898,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -9275,7 +9277,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -9678,7 +9680,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -10057,7 +10059,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -10461,7 +10463,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -10868,7 +10870,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -11247,7 +11249,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -11626,7 +11628,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -12005,7 +12007,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -12384,7 +12386,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -12763,7 +12765,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -13159,7 +13161,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -13538,7 +13540,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -13936,7 +13938,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -14315,7 +14317,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -14694,7 +14696,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -15093,7 +15095,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -15472,7 +15474,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -15851,7 +15853,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -16252,18 +16254,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Clifford+T Examples" + "# Errors and corrections\n", + "\n", + "Before moving on to Clifford+T examples, we'll show how to introduce errors into ZX-diagrams, correct them, and prove correctness.\n", + "\n", + "We can use the `PauliWeb` class to represent arbitrary Pauli errors on half edges. Note this will _not_ usually be a valid, closed Pauli web, but we can still use the `PauliWeb` class to hold this data." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", "" ], "text/plain": [ @@ -16638,25 +16644,11 @@ }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "# Generate a random CNOT, H, T circuit\n", - "random.seed(1330)\n", - "c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=40)\n", - "# for g in c.gates: print(g)\n", - "zx.draw(c)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ + }, { "data": { "text/html": [ - "
\n", + "
\n", "" ], "text/plain": [ @@ -17034,81 +17026,88 @@ } ], "source": [ - "# Convert to a ZX diagram and call the full_reduce procedure on it (PyZX's main ZX diagram optimisation pass)\n", - "g = c.to_graph()\n", - "zx.full_reduce(g)\n", - "zx.to_rg(g)\n", - "\n", - "# Normalise compacts the circuit visually and ensures every input/output is connected to a Z spider\n", - "g.normalize()\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, zwebs, xwebs = compute_pauli_webs(g)\n", + "random.seed(1337)\n", + "g = zx.generate.CNOT_HAD_PHASE_circuit(qubits=4, depth=10, clifford=True).to_graph(compress_rows=False)\n", + "zx.draw(g, labels=True)\n", "\n", - "# Draw the simplified ZX diagram. Note blue edges correspond to edges with Hadamard gates\n", - "zx.draw(g, labels=True)" + "# introduce random Pauli errors on edges and draw them\n", + "errors = PauliWeb(g)\n", + "for e in g.edges():\n", + " s,t = g.edge_st(e)\n", + " if random.random() > 0.75:\n", + " errors.add_half_edge((s,t), random.choice(['X','Y','Z']))\n", + " if random.random() > 0.75:\n", + " errors.add_half_edge((t,s), random.choice(['X','Y','Z']))\n", + "zx.draw(g, pauli_web=errors)" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 32, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "{(3, 1): 'Y',\n", - " (1, 3): 'Y',\n", - " (3, 43): 'Z',\n", - " (43, 3): 'Z',\n", - " (3, 5): 'Y',\n", - " (5, 3): 'Y',\n", - " (3, 58): 'Z',\n", - " (58, 3): 'X',\n", - " (3, 54): 'Z',\n", - " (54, 3): 'Z',\n", - " (3, 14): 'Y',\n", - " (14, 3): 'Y',\n", - " (5, 16): 'Y',\n", - " (16, 5): 'Y',\n", - " (5, 2): 'Y',\n", - " (2, 5): 'Y',\n", - " (5, 43): 'X',\n", - " (43, 5): 'Z',\n", - " (5, 58): 'X',\n", - " (58, 5): 'X',\n", - " (5, 54): 'X',\n", - " (54, 5): 'Z',\n", - " (14, 58): 'X',\n", - " (58, 14): 'X',\n", - " (14, 16): 'Y',\n", - " (16, 14): 'Y',\n", - " (16, 43): 'Z',\n", - " (43, 16): 'Z',\n", - " (16, 58): 'Z',\n", - " (58, 16): 'X'}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Z-web 20: commutes\n", + "X-web 20: anti-commutes, corrected\n", + "Z-web 21: commutes\n", + "X-web 21: commutes\n", + "Z-web 22: anti-commutes, corrected\n", + "X-web 22: commutes\n", + "Z-web 23: commutes\n", + "X-web 23: commutes\n" + ] } ], "source": [ - "pw = zwebs[43]\n", - "pw.half_edges()" + "# To find the appropriate corrections, we should introduce Pauli X and Z gates at the outputs\n", + "# to make all 8 output webs commute with `errors`.\n", + "\n", + "order, zwebs, xwebs = compute_pauli_webs(g)\n", + "output_es = [(o, next(iter(g.neighbors(o)))) for o in g.outputs()]\n", + "\n", + "\n", + "errors1 = errors.copy()\n", + "\n", + "# for each of the outputs, if the errors anti-commute with the associated Z-web, introduce\n", + "# an X-error at that output to make it commute, and do similar for X-webs.\n", + "for o,n in output_es:\n", + " print(f'Z-web {o}: ', end='')\n", + " if zwebs[o].commutes_with(errors):\n", + " print('commutes')\n", + " else:\n", + " print('anti-commutes', end='')\n", + " errors1.add_half_edge((o,n), 'X')\n", + " if zwebs[o].commutes_with(errors1):\n", + " print(', corrected')\n", + " print(f'X-web {o}: ', end='')\n", + " if xwebs[o].commutes_with(errors):\n", + " print('commutes')\n", + " else:\n", + " print('anti-commutes', end='')\n", + " errors1.add_half_edge((o,n), 'Z')\n", + " if xwebs[o].commutes_with(errors1):\n", + " print(', corrected')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Clifford+T Examples" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", "" ], "text/plain": [ @@ -17486,29 +17485,22 @@ } ], "source": [ - "# Once the Pauli webs have been computed, a specific web can be highlighted by `zx.draw` by passing it in as\n", - "# an optional argument. Note that webs change color when they cross Hadamard edges.\n", - "zx.draw(g, labels=True, pauli_web=zwebs[43])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now show how this works in some simpler cases. The first is a single T gate.\n", - "\n", - "The T gate becomes a single, 1-legged phase gadget, connected to the input. This can be implemented by Z-merging a T magic state, then doing either an X or a Y measurement, depending on the parity of the Pauli web." + "# Generate a random CNOT, H, T circuit\n", + "random.seed(1330)\n", + "c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=40)\n", + "# for g in c.gates: print(g)\n", + "zx.draw(c)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", "" ], "text/plain": [ @@ -17883,11 +17875,84 @@ }, "metadata": {}, "output_type": "display_data" - }, + } + ], + "source": [ + "# Convert to a ZX diagram and call the full_reduce procedure on it (PyZX's main ZX diagram optimisation pass)\n", + "g = c.to_graph()\n", + "zx.full_reduce(g)\n", + "zx.to_rg(g)\n", + "\n", + "# Normalise compacts the circuit visually and ensures every input/output is connected to a Z spider\n", + "g.normalize()\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, 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)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{(3, 1): 'Y',\n", + " (1, 3): 'Y',\n", + " (3, 43): 'Z',\n", + " (43, 3): 'Z',\n", + " (3, 5): 'Y',\n", + " (5, 3): 'Y',\n", + " (3, 58): 'Z',\n", + " (58, 3): 'X',\n", + " (3, 54): 'Z',\n", + " (54, 3): 'Z',\n", + " (3, 14): 'Y',\n", + " (14, 3): 'Y',\n", + " (5, 16): 'Y',\n", + " (16, 5): 'Y',\n", + " (5, 2): 'Y',\n", + " (2, 5): 'Y',\n", + " (5, 43): 'X',\n", + " (43, 5): 'Z',\n", + " (5, 58): 'X',\n", + " (58, 5): 'X',\n", + " (5, 54): 'X',\n", + " (54, 5): 'Z',\n", + " (14, 58): 'X',\n", + " (58, 14): 'X',\n", + " (14, 16): 'Y',\n", + " (16, 14): 'Y',\n", + " (16, 43): 'Z',\n", + " (43, 16): 'Z',\n", + " (16, 58): 'Z',\n", + " (58, 16): 'X'}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pw = zwebs[43]\n", + "pw.half_edges()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", "" ], @@ -18265,36 +18330,815 @@ } ], "source": [ - "c = zx.qasm(\"\"\"\n", - "qreg q[1];\n", - "t q[0];\n", - "\"\"\")\n", - "zx.draw(c)\n", - "\n", - "g = c.to_graph()\n", - "zx.full_reduce(g)\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=zwebs[1])" + "# Once the Pauli webs have been computed, a specific web can be highlighted by `zx.draw` by passing it in as\n", + "# an optional argument. Note that webs change color when they cross Hadamard edges.\n", + "zx.draw(g, labels=True, pauli_web=zwebs[43])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The next example is a circuit with some CNOT gates and a T gate, which can be simplified to a single phase gadget. I'm doing this manually here, since the automated simplifier comes up with a different answer (which is equivalent to this one, up to local Cliffords, but less clear what is going on)." + "We now show how this works in some simpler cases. The first is a single T gate.\n", + "\n", + "The T gate becomes a single, 1-legged phase gadget, connected to the input. This can be implemented by Z-merging a T magic state, then doing either an X or a Y measurement, depending on the parity of the Pauli web." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "c = zx.qasm(\"\"\"\n", + "qreg q[1];\n", + "t q[0];\n", + "\"\"\")\n", + "zx.draw(c)\n", + "\n", + "g = c.to_graph()\n", + "zx.full_reduce(g)\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=zwebs[1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next example is a circuit with some CNOT gates and a T gate, which can be simplified to a single phase gadget. I'm doing this manually here, since the automated simplifier comes up with a different answer (which is equivalent to this one, up to local Cliffords, but less clear what is going on)." + ] + }, + { + "cell_type": "code", + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -18673,7 +19517,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -19052,7 +19896,7 @@ { "data": { "text/html": [ - "
\n", + "
\n", "" @@ -19470,7 +20314,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -19484,7 +20328,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.12.0" } }, "nbformat": 4, diff --git a/pyzx/pauliweb.py b/pyzx/pauliweb.py index abb5f687..ba865e27 100644 --- a/pyzx/pauliweb.py +++ b/pyzx/pauliweb.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from .gflow import gflow from .utils import EdgeType, VertexType, vertex_is_zx from .graph.base import BaseGraph, VT, ET @@ -39,74 +41,64 @@ def h_pauli(p: str) -> str: class PauliWeb(Generic[VT, ET]): """A Pauli web - This class stores a Pauli web in "correction set" format. That is, the edges in the web are - all the edges incident to the vertices in `c`. To account for Hadamard edges, edge typing is - assigned to "half-edges". + This is a labelling of edges of a given graph by Paulis from the set {'X', 'Y', 'Z'}. In order + to deal properly with Hadamard edges, we actually label all "half-edges", where a half-edge named + (v, w) is the end of the edge closest to `v`, and (w, v) is the end closest to `w`. + + A Pauli web is "closed" if + (i) every spider has adjacent half-edges labelled by a stabiliser or anti-stabiliser of that spider + (ii) for every half-edge (v,w) labelled by a Pauli P, the other half is labelled by P (for a normal edge) + and labelled HPH (for a Hadamard edge). + + If these conditions are violated, the Pauli web is called "open", and a spider violating (i) or edge violating + (ii) is called a boundary of the web. """ def __init__(self, g: BaseGraph[VT,ET]): self.g = g self.es: Dict[Tuple[VT,VT], str] = dict() - self.vs: Set[VT] = set() + + def copy(self) -> PauliWeb: + pw = PauliWeb(self.g) + pw.es = self.es.copy() + return pw def add_half_edge(self, v_pair: Tuple[VT, VT], pauli: str): s, t = v_pair - self.vs.add(s) - p = self.es.get((s,t), 'I') - self.es[(s,t)] = multiply_paulis(p, pauli) + p = multiply_paulis(self.es.get((s,t), 'I'), pauli) + if p == 'I': + self.es.pop((s,t),'') + else: + self.es[(s,t)] = p def add_edge(self, v_pair: Tuple[VT, VT], pauli: str): s, t = v_pair et = self.g.edge_type(self.g.edge(s, t)) self.add_half_edge((s,t), pauli) self.add_half_edge((t,s), pauli if et == EdgeType.SIMPLE else h_pauli(pauli)) - - # if spread_to_input: - # inp = self.g.inputs() - # if ('Z' if self.g.type(s) == VertexType.Z else 'X') == pauli: - # for v2 in self.g.neighbors(s): - # if v2 in inp: - # self.add_edge((s, v2), pauli, spread_to_input=False) - # break - - # if ('Z' if self.g.type(t) == VertexType.Z else 'X') == pauli: - # for v2 in self.g.neighbors(t): - # if v2 in inp: - # self.add_edge((t, v2), pauli, spread_to_input=False) - # break - - # def spread_to_boundary(self, inputs=True, outputs=True): - # bnd = [] - # if inputs: bnd += self.g.inputs() - # if outputs: bnd += self.g.outputs() - - # for i in bnd: - # v = next(iter(self.g.neighbors(i))) - # vt = self.g.type(v) - # if vt == VertexType.Z: - # p = 'Z' - # elif vt == VertexType.X: - # p = 'X' - # else: - # continue - # adj = sum(1 for v1 in self.g.neighbors(v) - # if (v,v1) in self.es and self.es[(v,v1)] in [p, 'Y']) - # if adj % 2 == 1: - # self.add_edge((v, i), p) - - - # def add_vertex(self, v: VT, spread_to_input: bool=False): - # p = 'X' if self.g.type(v) == VertexType.Z else 'Z' - # for v1 in self.g.neighbors(v): - # self.add_edge((v, v1), p, spread_to_input=spread_to_input) def vertices(self): - return self.vs + return set(v for (v,_) in self.es) def half_edges(self) -> Dict[Tuple[VT,VT],str]: return self.es def __repr__(self): - return 'PauliWeb' + str(self.vs) + return 'PauliWeb' + str(self.vertices()) + + def __mul__(self, other: PauliWeb): + pw = self.copy() + for e,p in other.es.items(): + pw.add_half_edge(e, p) + return pw + + def commutes_with(self, other: PauliWeb): + comm = True + for e,p1 in self.es.items(): + p2 = other.es.get(e, 'I') + if p1 != 'I' and p2 != 'I' and p1 != p2: + comm = not comm + return comm + def transpose_corrections(c: Dict[VT, Set[VT]]) -> Dict[VT, Set[VT]]: ct: Dict[VT, Set[VT]] = dict()