diff --git a/demos/PauliWebs.ipynb b/demos/PauliWebs.ipynb
index ba6738c8..10f0ea2f 100644
--- a/demos/PauliWebs.ipynb
+++ b/demos/PauliWebs.ipynb
@@ -7,10 +7,12 @@
"outputs": [],
"source": [
"import sys, os, random\n",
+ "from fractions import Fraction\n",
"sys.path.insert(0,os.path.expanduser('~/git/pyzx')) # git version\n",
"sys.path.insert(0,'/workspaces/pyzx')\n",
"import pyzx as zx\n",
"from pyzx.pauliweb import PauliWeb, compute_pauli_webs\n",
+ "from pyzx import VertexType\n",
"zx.settings.colors = zx.original_colors"
]
},
@@ -54,7 +56,7 @@
{
"data": {
"text/html": [
- "
\n",
+ "\n",
""
@@ -451,7 +453,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -830,7 +832,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -1209,7 +1211,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -1588,7 +1590,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -1981,7 +1983,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -2360,7 +2362,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -2739,7 +2741,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -3118,7 +3120,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -3515,7 +3517,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -3917,7 +3919,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -4296,7 +4298,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -4675,7 +4677,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -5054,7 +5056,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -5433,7 +5435,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -5812,7 +5814,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -6207,7 +6209,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -6599,7 +6601,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -6978,7 +6980,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -7357,7 +7359,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -7736,7 +7738,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -8115,7 +8117,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -8494,7 +8496,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -8898,7 +8900,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -9277,7 +9279,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -9680,7 +9682,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -10059,7 +10061,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -10463,7 +10465,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -10870,7 +10872,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -11249,7 +11251,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -11628,7 +11630,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -12007,7 +12009,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -12386,7 +12388,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -12765,7 +12767,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -13161,7 +13163,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -13540,7 +13542,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -13938,7 +13940,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -14317,7 +14319,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -14696,7 +14698,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -15095,7 +15097,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -15474,7 +15476,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -15853,7 +15855,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -16256,20 +16258,24 @@
"source": [
"# 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",
+ "Before moving on to Clifford+T examples, we'll show how to introduce errors into ZX-diagrams, correct them, and check the corrected diagram is equal to the original, error-free diagram, up to a global phase.\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."
+ "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. At some point, it might be worth adding an `is_valid` method to `PauliWeb` to check the Pauli web conditions are satisfied.\n",
+ "\n",
+ "The kinds of errors we are interested in here are logical flips coming from lattice surgery operations. Normally, these will non-deterministically introduce X errors next to Z nodes and Z errors next to X nodes.\n",
+ "\n",
+ "Since we know at runtime where these errors are, they are always correctable. Hence, the error rate and which subset of errors _actually_ occur is irrelevant. To demonstrate the corrections for arbitrary errors, we will call `PauliWeb.random` with its default arguments, which introduces a uniformly random Pauli X, Y, or Z with 30% probability on every half-edge."
]
},
{
"cell_type": "code",
- "execution_count": 26,
+ "execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -16648,7 +16654,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
@@ -17031,83 +17037,19 @@
"zx.draw(g, labels=True)\n",
"\n",
"# 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",
+ "errors = PauliWeb.random(g)\n",
"zx.draw(g, pauli_web=errors)"
]
},
{
"cell_type": "code",
- "execution_count": 32,
- "metadata": {},
- "outputs": [
- {
- "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": [
- "# 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": 18,
+ "execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -17485,11 +17427,60 @@
}
],
"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)"
+ "# The `PauliWeb.graph_with_errors()` method returns a copy of the original graph, where\n",
+ "# Pauli errors are inserted as pi-spiders on the edges colored by the web.\n",
+ "zx.draw(errors.graph_with_errors())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Z-web for output 20: commutes\n",
+ "X-web for output 20: anti-commutes, corrected\n",
+ "Z-web for output 21: anti-commutes, corrected\n",
+ "X-web for output 21: anti-commutes, corrected\n",
+ "Z-web for output 22: commutes\n",
+ "X-web for output 22: commutes\n",
+ "Z-web for output 23: anti-commutes, corrected\n",
+ "X-web for output 23: anti-commutes, corrected\n"
+ ]
+ }
+ ],
+ "source": [
+ "# 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 for output {o}: ', end='')\n",
+ " if zwebs[o].commutes_with(errors1):\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 for output {o}: ', end='')\n",
+ " if xwebs[o].commutes_with(errors1):\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')"
]
},
{
@@ -17500,7 +17491,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -17875,84 +17866,11 @@
},
"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",
""
],
"text/plain": [
@@ -18330,29 +18248,45 @@
}
],
"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])"
+ "# All the errors have now been corrected, so the graph with Pauli errors given by `errors1` should\n",
+ "# be equal, up to a global phase to the origin graph\n",
+ "ge = errors1.graph_with_errors()\n",
+ "zx.draw(ge)\n",
+ "\n",
+ "# ...so if we compose `ge` with the adjoint of `g`, we should get the identity\n",
+ "h = g.adjoint() * ge\n",
+ "zx.full_reduce(h)\n",
+ "h.pack_circuit_rows()\n",
+ "zx.draw(h)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Clifford+T Examples"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "We now show how this works in some simpler cases. The first is a single T gate.\n",
+ "Now, we'll move to some Clifford+T examples. By definition, spiders with phases that are not multiples of $\\pi/2$ must necessarily be on the boundary of a Pauli web.\n",
+ "\n",
+ "For our purposes, we will treat non-Clifford spiders as additional outputs that have been measured in the basis $\\{|T\\rangle, Z|T\\rangle \\}$, where $|T\\rangle := T|{+}\\rangle$ is a $T$ magic state.\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."
+ "There are many equivalent ways to implement this fault-tolerantly in lattice surgery. One way to do this is to first perform a Z-merge between a data qubit and a $|T\\rangle$ magic state, then depending on the outcome of the merge, measure the remaining qubit in the X or Y basis."
]
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -18731,7 +18665,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -19109,36 +19043,29 @@
}
],
"source": [
- "c = zx.qasm(\"\"\"\n",
- "qreg q[1];\n",
- "t q[0];\n",
- "\"\"\")\n",
- "zx.draw(c)\n",
+ "# Merge with parity 0, followed by an X measurement\n",
+ "g = zx.Graph()\n",
+ "g.add_vertex(qubit=0, row=0)\n",
+ "g.add_vertex(VertexType.Z, qubit=2, row=0, phase=Fraction(1/4))\n",
+ "g.add_vertex(VertexType.Z, qubit=1, row=1)\n",
+ "g.add_vertex(VertexType.Z, qubit=1, row=2)\n",
+ "for e in [(0,2), (1,2), (2,3)]: g.add_edge(e)\n",
+ "zx.draw(g, labels=True)\n",
"\n",
- "g = c.to_graph()\n",
+ "# ...is equivalent to a |T> measurement\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)."
+ "zx.draw(g)"
]
},
{
"cell_type": "code",
- "execution_count": 23,
+ "execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -19517,7 +19444,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -19892,11 +19819,42 @@
},
"metadata": {},
"output_type": "display_data"
- },
+ }
+ ],
+ "source": [
+ "# Merge with parity 1, followed by an Y measurement\n",
+ "h = zx.Graph()\n",
+ "h.add_vertex(qubit=0, row=0)\n",
+ "h.add_vertex(VertexType.Z, qubit=2, row=0, phase=Fraction(1/4))\n",
+ "h.add_vertex(VertexType.Z, qubit=1, row=1)\n",
+ "h.add_vertex(VertexType.Z, qubit=1, row=2, phase=Fraction(1/2))\n",
+ "h.add_vertex(VertexType.X, qubit=1.5, row=0.5, phase=1)\n",
+ "for e in [(0,2), (1,4), (4,2), (2,3)]: h.add_edge(e)\n",
+ "zx.draw(h, labels=True)\n",
+ "\n",
+ "# ...is also equivalent to a |T> measurement\n",
+ "zx.full_reduce(h)\n",
+ "zx.draw(h)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In both cases above, we show the +1 outcome of the X/Y measurement. Getting the other outcome yields the same effect, up to a Pauli Z error, which can be corrected later.\n",
+ "\n",
+ "Using this primitive, we now look at implementing a single T gate."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -20271,37 +20229,9408 @@
},
"metadata": {},
"output_type": "display_data"
- }
- ],
- "source": [
- "c = zx.qasm(\"\"\"\n",
- "qreg q[3];\n",
- "cx q[0], q[1];\n",
- "cx q[1], q[2];\n",
- "t q[2];\n",
- "cx q[1], q[2];\n",
- "cx q[0], q[1];\n",
- "\"\"\")\n",
- "zx.draw(c)\n",
- "\n",
- "# manual ZX simplification to get a single phase gadget\n",
- "g = c.to_graph()\n",
- "zx.simplify.gadgetize(g, graphlike=False)\n",
- "zx.basicrules.strong_comp(g, 5, 7)\n",
- "zx.simplify.spider_simp(g, quiet=True)\n",
- "zx.basicrules.strong_comp(g, 3, 6)\n",
- "zx.simplify.spider_simp(g, quiet=True)\n",
- "zx.simplify.id_simp(g, quiet=True)\n",
- "\n",
- "zx.draw(g, labels=True)\n",
- "\n",
- "d = dict()\n",
- "\n",
- "# pauli web calculation\n",
- "order, zwebs, xwebs = compute_pauli_webs(g, debug=d)\n",
- "\n",
- "# highlight the web associated to the T spider\n",
- "zx.draw(g, labels=True, pauli_web=zwebs[15])"
+ },
+ {
+ "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",
+ "g = c.to_graph()\n",
+ "zx.draw(g)\n",
+ "\n",
+ "# Unfuse the non-Clifford spiders, so they look like outputs measured in {|T>, Z|T>}\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.draw(g, labels=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# The relevant Pauli webs for this example are the Z and X webs of the output 2\n",
+ "# as well as the Z-web of the non-Clifford spider 3.\n",
+ "\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[2])\n",
+ "zx.draw(g, labels=True, pauli_web=xwebs[2])\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "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": [
+ "# As we did in the Clifford examples, we can introduce random Pauli errors on all the edges\n",
+ "random.seed(1330)\n",
+ "errors = PauliWeb.random(g)\n",
+ "zx.draw(g, pauli_web=errors)\n",
+ "\n",
+ "errors1 = errors.copy()\n",
+ "\n",
+ "# To implement the T-measurement at node 3, we need to look at the associated Z-web of 3\n",
+ "# If it anti-commutes with the errors, we should apply an X correction before we measure, or equivalently,\n",
+ "# flip whether we are measuring X or Y in the fault-tolerant gadget that injects a magic state.\n",
+ "if not zwebs[3].commutes_with(errors):\n",
+ " errors1.add_half_edge((3,1), 'X')\n",
+ "\n",
+ "# a single Y error indeed anti-commutes with zweb[3], so a correction appears\n",
+ "zx.draw(g, pauli_web=errors1)\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "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": [
+ "errors2 = errors1.copy()\n",
+ "\n",
+ "# The T-measurement itself will either apply 0.5:\n",
+ " errors2.add_half_edge((v, n), 'Z')\n",
+ "\n",
+ "# Finally, we apply corrections on the output just like we did in the Clifford case\n",
+ "if not zwebs[2].commutes_with(errors2): errors2.add_half_edge((2,1), 'X')\n",
+ "if not xwebs[2].commutes_with(errors2): errors2.add_half_edge((2,1), 'Z')\n",
+ "\n",
+ "zx.draw(g, pauli_web=errors2)\n",
+ "\n",
+ "# The result, after we have applied corrections, should indeed be a single T gate\n",
+ "ge = errors2.graph_with_errors()\n",
+ "zx.full_reduce(ge)\n",
+ "zx.draw(ge)"
+ ]
+ },
+ {
+ "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": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "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[3];\n",
+ "cx q[0], q[1];\n",
+ "cx q[1], q[2];\n",
+ "t q[2];\n",
+ "cx q[1], q[2];\n",
+ "cx q[0], q[1];\n",
+ "\"\"\")\n",
+ "zx.draw(c)\n",
+ "\n",
+ "# manual ZX simplification to get a single phase gadget\n",
+ "g = c.to_graph()\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.basicrules.strong_comp(g, 5, 7)\n",
+ "zx.simplify.spider_simp(g, quiet=True)\n",
+ "zx.basicrules.strong_comp(g, 3, 6)\n",
+ "zx.simplify.spider_simp(g, quiet=True)\n",
+ "zx.simplify.id_simp(g, quiet=True)\n",
+ "\n",
+ "zx.draw(g, labels=True)\n",
+ "\n",
+ "d = dict()\n",
+ "\n",
+ "# pauli web calculation\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g, debug=d)\n",
+ "\n",
+ "# highlight the web associated to the T spider\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[15])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This and the output Pauli webs is enough data to simulate running this computation and computing corrections. Rather than doing that explicitly, lets go straight to a random Clifford+T circuit."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "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": [
+ "# Generate a random CNOT, H, T circuit\n",
+ "random.seed(1330)\n",
+ "c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=30)\n",
+ "g = c.to_graph(compress_rows=False)\n",
+ "zx.draw(g)\n",
+ "\n",
+ "# Unfuse the non-Clifford spiders, so they look like outputs measured in {|T>, Z|T>}\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.draw(g, labels=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# as before, we can introduce random Pauli errors into the graph\n",
+ "random.seed(1337)\n",
+ "errors = PauliWeb.random(g)\n",
+ "zx.draw(g, pauli_web=errors)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{51: 1, 52: 1, 53: 1, 54: 1, 55: 2, 56: 2, 57: 2, 58: 2}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# however, the correction strategy is more elaborate, since we have internal non-Clifford\n",
+ "# gadgets to handle. First, compute Pauli webs:\n",
+ "\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "\n",
+ "# `order` tells us the order in which we should measure non-Clifford spiders. For Clifford diagrams,\n",
+ "# it was empty, since there were no non-Clifford spiders. In this example, it shows that we need to\n",
+ "# split the non-Clifford spiders into 2 time steps, where the last 4 measurements might depend on the\n",
+ "# outcomes of the first 4.\n",
+ "print(order)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# For example, if we look at the Z-web of spider 55, it touches 51 and 52, which means we need\n",
+ "# to be done with those spiders first.\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[55])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "errors1 = errors.copy()\n",
+ "\n",
+ "# traverse through the T spiders in the order given by `order`\n",
+ "for v in sorted(order.keys(), key=lambda v: order[v]):\n",
+ " n = next(iter(g.neighbors(v)))\n",
+ "\n",
+ " # if the Z-web associated with that spider anti-commutes with previous errors,\n",
+ " # introduce an X error before the measurement. This has the same effect, up to Paulis\n",
+ " # as choosing to measure Y rather than X in the fault-tolerant gadget\n",
+ " if not zwebs[v].commutes_with(errors1):\n",
+ " errors1.add_half_edge((v, n), 'X')\n",
+ " \n",
+ " # \"do\" the measurement, i.e. plug in |T> or Z|T> with uniform probability\n",
+ " if random.random() > 0.5:\n",
+ " errors1.add_half_edge((v, n), 'Z')\n",
+ "\n",
+ "# finally, use the output webs to correct outputs, as before\n",
+ "for o in g.outputs():\n",
+ " n = next(iter(g.neighbors(o)))\n",
+ " if not zwebs[o].commutes_with(errors1):\n",
+ " errors1.add_half_edge((o,n), 'X')\n",
+ " if not xwebs[o].commutes_with(errors1):\n",
+ " errors1.add_half_edge((o,n), 'Z')\n",
+ "\n",
+ "# here is the original web\n",
+ "zx.draw(g, pauli_web=errors)\n",
+ "\n",
+ "# here is the corrected web\n",
+ "zx.draw(g, pauli_web=errors1)\n",
+ "\n",
+ "# n.b. we can multiply these two to just show the corrections\n",
+ "zx.draw(g, pauli_web=errors*errors1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "h = g.adjoint() * errors1.graph_with_errors()\n",
+ "zx.full_reduce(h)\n",
+ "h.pack_circuit_rows()\n",
+ "zx.draw(h)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "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": [
+ "# The same procedure works with a reduced ZX-diagram, rather than a circuit, as input\n",
+ "random.seed(1337)\n",
+ "g = zx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=40).to_graph()\n",
+ "zx.draw(g)\n",
+ "zx.full_reduce(g)\n",
+ "g.normalize()\n",
+ "\n",
+ "# for simplicity, we'll make sure all the non-Clifford spiders are Z\n",
+ "zx.to_rg(g, init_z=set(v for v in g.vertices() if not zx.utils.phase_is_clifford(g.phase(v))))\n",
+ "\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.draw(g, labels=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "random.seed(1337)\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "errors = PauliWeb.random(g)\n",
+ "errors1 = errors.copy()\n",
+ "\n",
+ "# \"simulate\" the diagram and compute corrections, as before\n",
+ "for v in sorted(order.keys(), key=lambda v: order[v]):\n",
+ " n = next(iter(g.neighbors(v)))\n",
+ " if not zwebs[v].commutes_with(errors1): errors1.add_half_edge((v, n), 'X')\n",
+ " if random.random() > 0.5: errors1.add_half_edge((v, n), 'Z')\n",
+ "for o in g.outputs():\n",
+ " n = next(iter(g.neighbors(o)))\n",
+ " if not zwebs[o].commutes_with(errors1): errors1.add_half_edge((o,n), 'X')\n",
+ " if not xwebs[o].commutes_with(errors1): errors1.add_half_edge((o,n), 'Z')\n",
+ "\n",
+ "zx.draw(g, pauli_web=errors)\n",
+ "zx.draw(g, pauli_web=errors*errors1)\n",
+ "\n",
+ "h = g.adjoint() * errors1.graph_with_errors()\n",
+ "zx.full_reduce(h)\n",
+ "h.pack_circuit_rows()\n",
+ "zx.draw(h)"
]
},
{
diff --git a/demos/PauliWebsRGB.ipynb b/demos/PauliWebsRGB.ipynb
index bd9f8831..a9f9cfd4 100644
--- a/demos/PauliWebsRGB.ipynb
+++ b/demos/PauliWebsRGB.ipynb
@@ -7,10 +7,12 @@
"outputs": [],
"source": [
"import sys, os, random\n",
+ "from fractions import Fraction\n",
"sys.path.insert(0,os.path.expanduser('~/git/pyzx')) # git version\n",
"sys.path.insert(0,'/workspaces/pyzx')\n",
"import pyzx as zx\n",
"from pyzx.pauliweb import PauliWeb, compute_pauli_webs\n",
+ "from pyzx import VertexType\n",
"zx.settings.colors = zx.rgb_colors"
]
},
@@ -18,21 +20,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 +56,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -449,7 +453,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -828,7 +832,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -1207,7 +1211,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -1586,7 +1590,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -1979,7 +1983,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -2358,7 +2362,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -2737,7 +2741,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -3116,7 +3120,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -3513,7 +3517,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -3915,7 +3919,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -4294,7 +4298,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -4673,7 +4677,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -5052,7 +5056,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -5431,7 +5435,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -5810,7 +5814,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -6205,7 +6209,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -6597,7 +6601,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -6976,7 +6980,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -7355,7 +7359,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -7734,7 +7738,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -8113,7 +8117,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -8492,7 +8496,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -8896,7 +8900,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -9275,7 +9279,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -9678,7 +9682,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -10057,7 +10061,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -10461,7 +10465,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -10868,7 +10872,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -11247,7 +11251,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -11626,7 +11630,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -12005,7 +12009,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -12384,7 +12388,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -12763,7 +12767,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -13159,7 +13163,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -13538,7 +13542,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -13936,7 +13940,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -14315,7 +14319,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -14694,7 +14698,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -15093,7 +15097,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -15472,7 +15476,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
@@ -15851,7 +15855,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
@@ -16243,6 +16247,7 @@
"zx.draw(g1, labels=True)\n",
"\n",
"order, zwebs, xwebs = compute_pauli_webs(g1)\n",
+ "\n",
"zx.draw(g1, pauli_web=zwebs[41])\n",
"zx.draw(g1, pauli_web=xwebs[41])"
]
@@ -16251,7 +16256,15 @@
"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 check the corrected diagram is equal to the original, error-free diagram, up to a global phase.\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. At some point, it might be worth adding an `is_valid` method to `PauliWeb` to check the Pauli web conditions are satisfied.\n",
+ "\n",
+ "The kinds of errors we are interested in here are logical flips coming from lattice surgery operations. Normally, these will non-deterministically introduce X errors next to Z nodes and Z errors next to X nodes.\n",
+ "\n",
+ "Since we know at runtime where these errors are, they are always correctable. Hence, the error rate and which subset of errors _actually_ occur is irrelevant. To demonstrate the corrections for arbitrary errors, we will call `PauliWeb.random` with its default arguments, which introduces a uniformly random Pauli X, Y, or Z with 30% probability on every half-edge."
]
},
{
@@ -16262,7 +16275,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -16637,25 +16650,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": [
@@ -17033,81 +17032,24 @@
}
],
"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.random(g)\n",
+ "zx.draw(g, pauli_web=errors)"
]
},
{
"cell_type": "code",
- "execution_count": 18,
- "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"
- }
- ],
- "source": [
- "pw = zwebs[43]\n",
- "pw.half_edges()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
+ "execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -17485,29 +17427,71 @@
}
],
"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])"
+ "# The `PauliWeb.graph_with_errors()` method returns a copy of the original graph, where\n",
+ "# Pauli errors are inserted as pi-spiders on the edges colored by the web.\n",
+ "zx.draw(errors.graph_with_errors())"
]
},
{
- "cell_type": "markdown",
+ "cell_type": "code",
+ "execution_count": 18,
"metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Z-web for output 20: commutes\n",
+ "X-web for output 20: anti-commutes, corrected\n",
+ "Z-web for output 21: anti-commutes, corrected\n",
+ "X-web for output 21: anti-commutes, corrected\n",
+ "Z-web for output 22: commutes\n",
+ "X-web for output 22: commutes\n",
+ "Z-web for output 23: anti-commutes, corrected\n",
+ "X-web for output 23: anti-commutes, corrected\n"
+ ]
+ }
+ ],
"source": [
- "We now show how this works in some simpler cases. The first is a single T gate.\n",
+ "# 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",
- "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."
+ "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 for output {o}: ', end='')\n",
+ " if zwebs[o].commutes_with(errors1):\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 for output {o}: ', end='')\n",
+ " if xwebs[o].commutes_with(errors1):\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": "code",
- "execution_count": 20,
+ "execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
@@ -17886,7 +17870,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -18264,36 +18248,45 @@
}
],
"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",
+ "# All the errors have now been corrected, so the graph with Pauli errors given by `errors1` should\n",
+ "# be equal, up to a global phase to the origin graph\n",
+ "ge = errors1.graph_with_errors()\n",
+ "zx.draw(ge)\n",
"\n",
- "# highlight the web associated to the T spider\n",
- "zx.draw(g, labels=True, pauli_web=zwebs[1])"
+ "# ...so if we compose `ge` with the adjoint of `g`, we should get the identity\n",
+ "h = g.adjoint() * ge\n",
+ "zx.full_reduce(h)\n",
+ "h.pack_circuit_rows()\n",
+ "zx.draw(h)"
]
},
{
"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)."
+ "# Clifford+T Examples"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, we'll move to some Clifford+T examples. By definition, spiders with phases that are not multiples of $\\pi/2$ must necessarily be on the boundary of a Pauli web.\n",
+ "\n",
+ "For our purposes, we will treat non-Clifford spiders as additional outputs that have been measured in the basis $\\{|T\\rangle, Z|T\\rangle \\}$, where $|T\\rangle := T|{+}\\rangle$ is a $T$ magic state.\n",
+ "\n",
+ "There are many equivalent ways to implement this fault-tolerantly in lattice surgery. One way to do this is to first perform a Z-merge between a data qubit and a $|T\\rangle$ magic state, then depending on the outcome of the merge, measure the remaining qubit in the X or Y basis."
]
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -18672,7 +18665,7 @@
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
"text/plain": [
@@ -19050,38 +19043,10595 @@
}
],
"source": [
- "c = zx.qasm(\"\"\"\n",
- "qreg q[3];\n",
- "cx q[0], q[1];\n",
- "cx q[1], q[2];\n",
- "t q[2];\n",
- "cx q[1], q[2];\n",
- "cx q[0], q[1];\n",
- "\"\"\")\n",
- "zx.draw(c)\n",
- "\n",
- "# manual ZX simplification to get a single phase gadget\n",
- "g = c.to_graph()\n",
- "zx.simplify.gadgetize(g, graphlike=False)\n",
- "zx.basicrules.strong_comp(g, 5, 7)\n",
- "zx.simplify.spider_simp(g, quiet=True)\n",
- "zx.basicrules.strong_comp(g, 3, 6)\n",
- "zx.simplify.spider_simp(g, quiet=True)\n",
- "zx.simplify.id_simp(g, quiet=True)\n",
- "\n",
- "# pauli web calculation\n",
- "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "# Merge with parity 0, followed by an X measurement\n",
+ "g = zx.Graph()\n",
+ "g.add_vertex(qubit=0, row=0)\n",
+ "g.add_vertex(VertexType.Z, qubit=2, row=0, phase=Fraction(1/4))\n",
+ "g.add_vertex(VertexType.Z, qubit=1, row=1)\n",
+ "g.add_vertex(VertexType.Z, qubit=1, row=2)\n",
+ "for e in [(0,2), (1,2), (2,3)]: g.add_edge(e)\n",
+ "zx.draw(g, labels=True)\n",
"\n",
- "# highlight the web associated to the T spider\n",
- "zx.draw(g, labels=True, pauli_web=zwebs[15])"
+ "# ...is equivalent to a |T> measurement\n",
+ "zx.full_reduce(g)\n",
+ "zx.draw(g)"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 21,
"metadata": {},
- "outputs": [],
- "source": []
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Merge with parity 1, followed by an Y measurement\n",
+ "h = zx.Graph()\n",
+ "h.add_vertex(qubit=0, row=0)\n",
+ "h.add_vertex(VertexType.Z, qubit=2, row=0, phase=Fraction(1/4))\n",
+ "h.add_vertex(VertexType.Z, qubit=1, row=1)\n",
+ "h.add_vertex(VertexType.Z, qubit=1, row=2, phase=Fraction(1/2))\n",
+ "h.add_vertex(VertexType.X, qubit=1.5, row=0.5, phase=1)\n",
+ "for e in [(0,2), (1,4), (4,2), (2,3)]: h.add_edge(e)\n",
+ "zx.draw(h, labels=True)\n",
+ "\n",
+ "# ...is also equivalent to a |T> measurement\n",
+ "zx.full_reduce(h)\n",
+ "zx.draw(h)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In both cases above, we show the +1 outcome of the X/Y measurement. Getting the other outcome yields the same effect, up to a Pauli Z error, which can be corrected later.\n",
+ "\n",
+ "Using this primitive, we now look at implementing a single T gate."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "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",
+ "g = c.to_graph()\n",
+ "zx.draw(g)\n",
+ "\n",
+ "# Unfuse the non-Clifford spiders, so they look like outputs measured in {|T>, Z|T>}\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.draw(g, labels=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# The relevant Pauli webs for this example are the Z and X webs of the output 2\n",
+ "# as well as the Z-web of the non-Clifford spider 3.\n",
+ "\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[2])\n",
+ "zx.draw(g, labels=True, pauli_web=xwebs[2])\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "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": [
+ "# As we did in the Clifford examples, we can introduce random Pauli errors on all the edges\n",
+ "random.seed(1330)\n",
+ "errors = PauliWeb.random(g)\n",
+ "zx.draw(g, pauli_web=errors)\n",
+ "\n",
+ "errors1 = errors.copy()\n",
+ "\n",
+ "# To implement the T-measurement at node 3, we need to look at the associated Z-web of 3\n",
+ "# If it anti-commutes with the errors, we should apply an X correction before we measure, or equivalently,\n",
+ "# flip whether we are measuring X or Y in the fault-tolerant gadget that injects a magic state.\n",
+ "if not zwebs[3].commutes_with(errors):\n",
+ " errors1.add_half_edge((3,1), 'X')\n",
+ "\n",
+ "# a single Y error indeed anti-commutes with zweb[3], so a correction appears\n",
+ "zx.draw(g, pauli_web=errors1)\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "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": [
+ "errors2 = errors1.copy()\n",
+ "\n",
+ "# The T-measurement itself will either apply 0.5:\n",
+ " errors2.add_half_edge((v, n), 'Z')\n",
+ "\n",
+ "# Finally, we apply corrections on the output just like we did in the Clifford case\n",
+ "if not zwebs[2].commutes_with(errors2): errors2.add_half_edge((2,1), 'X')\n",
+ "if not xwebs[2].commutes_with(errors2): errors2.add_half_edge((2,1), 'Z')\n",
+ "\n",
+ "zx.draw(g, pauli_web=errors2)\n",
+ "\n",
+ "# The result, after we have applied corrections, should indeed be a single T gate\n",
+ "ge = errors2.graph_with_errors()\n",
+ "zx.full_reduce(ge)\n",
+ "zx.draw(ge)"
+ ]
+ },
+ {
+ "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": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "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[3];\n",
+ "cx q[0], q[1];\n",
+ "cx q[1], q[2];\n",
+ "t q[2];\n",
+ "cx q[1], q[2];\n",
+ "cx q[0], q[1];\n",
+ "\"\"\")\n",
+ "zx.draw(c)\n",
+ "\n",
+ "# manual ZX simplification to get a single phase gadget\n",
+ "g = c.to_graph()\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.basicrules.strong_comp(g, 5, 7)\n",
+ "zx.simplify.spider_simp(g, quiet=True)\n",
+ "zx.basicrules.strong_comp(g, 3, 6)\n",
+ "zx.simplify.spider_simp(g, quiet=True)\n",
+ "zx.simplify.id_simp(g, quiet=True)\n",
+ "\n",
+ "zx.draw(g, labels=True)\n",
+ "\n",
+ "d = dict()\n",
+ "\n",
+ "# pauli web calculation\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g, debug=d)\n",
+ "\n",
+ "# highlight the web associated to the T spider\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[15])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This and the output Pauli webs is enough data to simulate running this computation and computing corrections. Rather than doing that explicitly, lets go straight to a random Clifford+T circuit."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "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": [
+ "# Generate a random CNOT, H, T circuit\n",
+ "random.seed(1330)\n",
+ "c = zx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=30)\n",
+ "g = c.to_graph(compress_rows=False)\n",
+ "zx.draw(g)\n",
+ "\n",
+ "# Unfuse the non-Clifford spiders, so they look like outputs measured in {|T>, Z|T>}\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.draw(g, labels=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# as before, we can introduce random Pauli errors into the graph\n",
+ "random.seed(1337)\n",
+ "errors = PauliWeb.random(g)\n",
+ "zx.draw(g, pauli_web=errors)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{51: 1, 52: 1, 53: 1, 54: 1, 55: 2, 56: 2, 57: 2, 58: 2}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# however, the correction strategy is more elaborate, since we have internal non-Clifford\n",
+ "# gadgets to handle. First, compute Pauli webs:\n",
+ "\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "\n",
+ "# `order` tells us the order in which we should measure non-Clifford spiders. For Clifford diagrams,\n",
+ "# it was empty, since there were no non-Clifford spiders. In this example, it shows that we need to\n",
+ "# split the non-Clifford spiders into 2 time steps, where the last 4 measurements might depend on the\n",
+ "# outcomes of the first 4.\n",
+ "print(order)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# For example, if we look at the Z-web of spider 55, it touches 51 and 52, which means we need\n",
+ "# to be done with those spiders first.\n",
+ "zx.draw(g, labels=True, pauli_web=zwebs[55])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "errors1 = errors.copy()\n",
+ "\n",
+ "# traverse through the T spiders in the order given by `order`\n",
+ "for v in sorted(order.keys(), key=lambda v: order[v]):\n",
+ " n = next(iter(g.neighbors(v)))\n",
+ "\n",
+ " # if the Z-web associated with that spider anti-commutes with previous errors,\n",
+ " # introduce an X error before the measurement. This has the same effect, up to Paulis\n",
+ " # as choosing to measure Y rather than X in the fault-tolerant gadget\n",
+ " if not zwebs[v].commutes_with(errors1):\n",
+ " errors1.add_half_edge((v, n), 'X')\n",
+ " \n",
+ " # \"do\" the measurement, i.e. plug in |T> or Z|T> with uniform probability\n",
+ " if random.random() > 0.5:\n",
+ " errors1.add_half_edge((v, n), 'Z')\n",
+ "\n",
+ "# finally, use the output webs to correct outputs, as before\n",
+ "for o in g.outputs():\n",
+ " n = next(iter(g.neighbors(o)))\n",
+ " if not zwebs[o].commutes_with(errors1):\n",
+ " errors1.add_half_edge((o,n), 'X')\n",
+ " if not xwebs[o].commutes_with(errors1):\n",
+ " errors1.add_half_edge((o,n), 'Z')\n",
+ "\n",
+ "# here is the original web\n",
+ "zx.draw(g, pauli_web=errors)\n",
+ "\n",
+ "# here is the corrected web\n",
+ "zx.draw(g, pauli_web=errors1)\n",
+ "\n",
+ "# n.b. we can multiply these two to just show the corrections\n",
+ "zx.draw(g, pauli_web=errors*errors1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "h = g.adjoint() * errors1.graph_with_errors()\n",
+ "zx.full_reduce(h)\n",
+ "h.pack_circuit_rows()\n",
+ "zx.draw(h)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "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": [
+ "# The same procedure works with a reduced ZX-diagram, rather than a circuit, as input\n",
+ "random.seed(1337)\n",
+ "g = zx.generate.CNOT_HAD_PHASE_circuit(qubits=3, depth=40).to_graph()\n",
+ "zx.draw(g)\n",
+ "zx.full_reduce(g)\n",
+ "g.normalize()\n",
+ "\n",
+ "# for simplicity, we'll make sure all the non-Clifford spiders are Z\n",
+ "zx.to_rg(g, init_z=set(v for v in g.vertices() if not zx.utils.phase_is_clifford(g.phase(v))))\n",
+ "\n",
+ "zx.simplify.unfuse_non_cliffords(g)\n",
+ "zx.draw(g, labels=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "random.seed(1337)\n",
+ "order, zwebs, xwebs = compute_pauli_webs(g)\n",
+ "errors = PauliWeb.random(g)\n",
+ "errors1 = errors.copy()\n",
+ "\n",
+ "# \"simulate\" the diagram and compute corrections, as before\n",
+ "for v in sorted(order.keys(), key=lambda v: order[v]):\n",
+ " n = next(iter(g.neighbors(v)))\n",
+ " if not zwebs[v].commutes_with(errors1): errors1.add_half_edge((v, n), 'X')\n",
+ " if random.random() > 0.5: errors1.add_half_edge((v, n), 'Z')\n",
+ "for o in g.outputs():\n",
+ " n = next(iter(g.neighbors(o)))\n",
+ " if not zwebs[o].commutes_with(errors1): errors1.add_half_edge((o,n), 'X')\n",
+ " if not xwebs[o].commutes_with(errors1): errors1.add_half_edge((o,n), 'Z')\n",
+ "\n",
+ "zx.draw(g, pauli_web=errors)\n",
+ "zx.draw(g, pauli_web=errors*errors1)\n",
+ "\n",
+ "h = g.adjoint() * errors1.graph_with_errors()\n",
+ "zx.full_reduce(h)\n",
+ "h.pack_circuit_rows()\n",
+ "zx.draw(h)"
+ ]
},
{
"cell_type": "code",
@@ -19093,7 +29643,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3 (ipykernel)",
+ "display_name": "Python 3",
"language": "python",
"name": "python3"
},
@@ -19107,7 +29657,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.12"
+ "version": "3.12.0"
}
},
"nbformat": 4,
diff --git a/pyzx/gflow.py b/pyzx/gflow.py
index bf432cff..b106021c 100644
--- a/pyzx/gflow.py
+++ b/pyzx/gflow.py
@@ -137,7 +137,7 @@ def gflow(
if not correct:
if len(vertices) == len(processed):
- return {v: k - i - 1 for v,i in l.items()}, gflow
+ return {v: i if reverse else k - i - 1 for v,i in l.items()}, gflow
return None
else:
processed.update(correct)
diff --git a/pyzx/pauliweb.py b/pyzx/pauliweb.py
index ba865e27..dcae3f48 100644
--- a/pyzx/pauliweb.py
+++ b/pyzx/pauliweb.py
@@ -17,10 +17,11 @@
from __future__ import annotations
from .gflow import gflow
-from .utils import EdgeType, VertexType, vertex_is_zx
+from .utils import EdgeType, VertexType, vertex_is_zx, phase_is_clifford
from .graph.base import BaseGraph, VT, ET
from typing import Any, Optional, Set, Dict, Tuple, Generic
+import random
def multiply_paulis(p1: str, p2: str) -> str:
if p1 == 'I': return p2
@@ -57,6 +58,22 @@ def __init__(self, g: BaseGraph[VT,ET]):
self.g = g
self.es: Dict[Tuple[VT,VT], str] = dict()
+ @staticmethod
+ def random(g: BaseGraph[VT,ET], pX=0.1, pY=0.1, pZ=0.1):
+ w = PauliWeb(g)
+ for e in g.edges():
+ s,t = g.edge_st(e)
+ for _ in range(2):
+ p = random.random()
+ if p < pX:
+ w.add_half_edge((s,t), 'X')
+ elif p < pX + pY:
+ w.add_half_edge((s,t), 'Y')
+ elif p < pX + pY + pZ:
+ w.add_half_edge((s,t), 'Z')
+ s,t = (t,s)
+ return w
+
def copy(self) -> PauliWeb:
pw = PauliWeb(self.g)
pw.es = self.es.copy()
@@ -98,15 +115,73 @@ def commutes_with(self, other: PauliWeb):
if p1 != 'I' and p2 != 'I' and p1 != p2:
comm = not comm
return comm
+
+ def graph_with_errors(self) -> BaseGraph[VT,ET]:
+ g = self.g.clone()
+ edges = set((s,t) if s < t else (t,s) for s,t in self.es)
+ for s,t in edges:
+ p0 = self.es.get((s,t), 'I')
+ p1 = self.es.get((t,s), 'I')
+
+ e = g.edge(s, t)
+ et = g.edge_type(e)
+ g.remove_edge(e)
+
+ spots = 1
+ for p in [p0,p1]:
+ if p == 'Y': spots += 2
+ elif p == 'X' or p == 'Z': spots += 1
+
+ q, r = (g.qubit(s), g.row(s))
+ dq, dr = ((g.qubit(t) - q)/spots, (g.row(t) - r)/spots)
+
+ v0 = s
+ v1 = t
+ if p0 == 'X':
+ q += dq; r += dr
+ v0 = g.add_vertex(VertexType.X, q, r, phase=1)
+ g.add_edge((s, v0))
+ elif p0 == 'Y':
+ q += dq; r += dr
+ v = g.add_vertex(VertexType.X, q, r, phase=1)
+ q += dq; r += dr
+ v0 = g.add_vertex(VertexType.Z, q, r, phase=1)
+ g.add_edge((s, v))
+ g.add_edge((v, v0))
+ elif p0 == 'Z':
+ q += dq; r += dr
+ v0 = g.add_vertex(VertexType.Z, q, r, phase=1)
+ g.add_edge((s, v0))
+ if p1 == 'X':
+ q += dq; r += dr
+ v1 = g.add_vertex(VertexType.X, q, r, phase=1)
+ g.add_edge((t, v1))
+ elif p1 == 'Y':
+ q += dq; r += dr
+ v1 = g.add_vertex(VertexType.Z, q, r, phase=1)
+ q += dq; r += dr
+ v = g.add_vertex(VertexType.X, q, r, phase=1)
+ g.add_edge((v1, v))
+ g.add_edge((v, t))
+ elif p1 == 'Z':
+ q += dq; r += dr
+ v1 = g.add_vertex(VertexType.Z, q, r, phase=1)
+ g.add_edge((t, v1))
+
+ g.add_edge((v0,v1), et)
+ return g
-def transpose_corrections(c: Dict[VT, Set[VT]]) -> Dict[VT, Set[VT]]:
- ct: Dict[VT, Set[VT]] = dict()
- for k,s in c.items():
- for v in s:
- if v not in ct: ct[v] = set()
- ct[v].add(k)
- return ct
+
+
+
+# def transpose_corrections(c: Dict[VT, Set[VT]]) -> Dict[VT, Set[VT]]:
+# ct: Dict[VT, Set[VT]] = dict()
+# for k,s in c.items():
+# for v in s:
+# if v not in ct: ct[v] = set()
+# ct[v].add(k)
+# return ct
def compute_pauli_webs(g: BaseGraph[VT,ET], backwards:bool=True, debug:Optional[Dict[str,Any]]=None) -> Tuple[Dict[VT, int], Dict[VT, PauliWeb[VT,ET]], Dict[VT, PauliWeb[VT,ET]]]:
g1 = g.clone()
@@ -155,12 +230,11 @@ def compute_pauli_webs(g: BaseGraph[VT,ET], backwards:bool=True, debug:Optional[
if not gf:
raise ValueError("Graph must have gFlow")
order, corr = gf
+ vset = g.vertex_set()
+ order = { v: i for v,i in order.items() if v in vset and not phase_is_clifford(g.phase(v)) }
zwebs: Dict[VT, PauliWeb[VT,ET]] = dict()
xwebs: Dict[VT, PauliWeb[VT,ET]] = dict()
- vset = g.vertex_set()
- # corr_t = transpose_corrections(corr)
-
for v,c in corr.items():
pw = PauliWeb(g)
for v1 in c:
diff --git a/pyzx/simplify.py b/pyzx/simplify.py
index 1d4cdbe7..9823d77c 100644
--- a/pyzx/simplify.py
+++ b/pyzx/simplify.py
@@ -599,6 +599,20 @@ def to_graph_like(g: BaseGraph[VT,ET]) -> None:
assert(is_graph_like(g,strict=True))
+def unfuse_non_cliffords(g: BaseGraph[VT,ET]) -> None:
+ """Unfuses any non-Clifford spider into a magic state connected to a phase-free spider
+
+ Replaces any spider with a non-Clifford phase p and degree n > 1 with a degree 1 spider with phase p connected to
+ a degree n+1 spider with phase 0.
+ """
+ for v in list(g.vertices()):
+ ty = g.type(v)
+ p = g.phase(v)
+ if vertex_is_zx(ty) and not phase_is_clifford(p) and g.vertex_degree(v) > 1:
+ v1 = g.add_vertex(ty, qubit=-1, row=g.row(v), phase=p)
+ g.set_phase(v, 0)
+ g.add_edge((v, v1))
+
def to_clifford_normal_form_graph(g: BaseGraph[VT,ET]) -> None:
"""Converts a graph that is Clifford into the form described by the right-hand side of eq. (11) of
*Graph-theoretic Simplification of Quantum Circuits with the ZX-calculus* (https://arxiv.org/abs/1902.03178).