diff --git a/demos/PauliWebs.ipynb b/demos/PauliWebs.ipynb
new file mode 100644
index 00000000..c2cab353
--- /dev/null
+++ b/demos/PauliWebs.ipynb
@@ -0,0 +1,1240 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import sys, os, random\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 preprocess, compute_pauli_webs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook demonstrates using PyZX's simplifier to construct the reduced ZX diagram of a random Clifford+T circuit, then using PyZX's gflow-finding algorithm to automatically associate each node `n` in the diagram to an integer `order[n]` and a Pauli web (a.k.a. _correlation surface_) `web[n]` with the following properties:\n",
+ "\n",
+ "1. the boundary of `web[n]` consists of `n` and inputs\n",
+ "2. `web[n]` connects to `n` by a single edge, which is the same colour as `n`\n",
+ "3. for non-Clifford nodes `m, n`, if `m` is in `web[n].vertices()` then `order[m] < order[n]`\n",
+ "\n",
+ "The values of `web[n]` at non-Clifford nodes and at outputs should be enough data to deterministically implement the diagram via lattice surgery and either measure at the end or compute the updated Pauli frame."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "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=50)\n",
+ "zx.draw(c)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "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",
+ "\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",
+ "\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": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "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=webs[87])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/pyzx/drawing.py b/pyzx/drawing.py
index 656e86b4..d1d335f2 100644
--- a/pyzx/drawing.py
+++ b/pyzx/drawing.py
@@ -29,6 +29,8 @@
from typing_extensions import Literal
import numpy as np
+from .pauliweb import PauliWeb
+
# matplotlib is lazy-imported on the first call to draw_matplotlib
plt: Any = None
path: Any = None
@@ -328,6 +330,7 @@ def draw_d3(
auto_hbox:Optional[bool]=None,
show_scalar:bool=False,
vdata: List[str]=[],
+ pauli_web:Optional[PauliWeb[VT,ET]]=None,
auto_layout = False
) -> Any:
"""If auto_layout is checked, will automatically space vertices of graph
@@ -397,7 +400,13 @@ def draw_d3(
for link in links:
s,t = (str(link['source']), str(link['target']))
link['num_parallel'] = counts[(s,t)]
- graphj = json.dumps({'nodes': nodes, 'links': links})
+
+ if pauli_web:
+ pw_edges = [{ 'source': vs[0], 'target': vs[1], 't': t } for vs,t in pauli_web.half_edges().items() ]
+ else:
+ pw_edges = []
+
+ graphj = json.dumps({'nodes': nodes, 'links': links, 'pauli_web': pw_edges})
with open(os.path.join(settings.javascript_location, 'zx_viewer.inline.js'), 'r') as f:
library_code = f.read() + '\n'
diff --git a/pyzx/js/zx_viewer.inline.js b/pyzx/js/zx_viewer.inline.js
index 1d46effb..9636d632 100644
--- a/pyzx/js/zx_viewer.inline.js
+++ b/pyzx/js/zx_viewer.inline.js
@@ -31,6 +31,13 @@ function edgeColor(t) {
else if (t == 3) return "gray";
}
+function webColor(t) {
+ if (t == 'X') return "#ff8888";
+ else if (t == 'Y') return "#88ff88";
+ else if (t == 'Z') return "#44aa44";
+ else if (t == 'I') return "#eee";
+}
+
function nodeStyle(selected) {
return selected ? "stroke-width: 2px; stroke: #00f" : "stroke-width: 1.5px";
}
@@ -78,6 +85,13 @@ function showGraph(tag, graph, width, height, scale, node_size, auto_hbox, show_
t.nhd.push(s);
});
+ graph.pauli_web.forEach(function(d) {
+ var s = ntab[d.source];
+ var t = ntab[d.target];
+ d.source = s;
+ d.target = t;
+ });
+
var shiftKey;
// SETUP SVG ITEMS
@@ -92,6 +106,15 @@ function showGraph(tag, graph, width, height, scale, node_size, auto_hbox, show_
.attr("width", width)
.attr("height", height);
+ var web = svg.append("g")
+ .attr("class", "web")
+ .selectAll("line")
+ .data(graph.pauli_web)
+ .enter().append("path")
+ .attr("stroke", function(d) { return webColor(d.t); })
+ .attr("fill", "transparent")
+ .attr("style", "stroke-width: 7px");
+
var link = svg.append("g")
.attr("class", "link")
.selectAll("line")
@@ -265,6 +288,13 @@ function showGraph(tag, graph, width, height, scale, node_size, auto_hbox, show_
};
link.attr("d", link_curve);
+
+ var web_curve = function(d) {
+ var x1 = d.source.x, x2 = (x1 + d.target.x)/2, y1 = d.source.y, y2 = (y1 + d.target.y)/2;
+ return `M ${x1} ${y1} L ${x2} ${y2}`;
+ }
+ web.attr("d", web_curve);
+
// EVENTS FOR DRAGGING AND SELECTION
node.on("mousedown", function(d) {
@@ -293,6 +323,8 @@ function showGraph(tag, graph, width, height, scale, node_size, auto_hbox, show_
link.filter(function(d) { return d.source.selected || d.target.selected ||
(auto_hbox && d.source.t == 3); })
.attr("d", link_curve);
+ web.filter(function(d) { return d.source.selected || d.target.selected; })
+ .attr("d", web_curve);
}));
brush.call(d3.brush().keyModifiers(false)
diff --git a/scratchpads/ak.ipynb b/scratchpads/ak.ipynb
index 2339d0e5..286acd83 100644
--- a/scratchpads/ak.ipynb
+++ b/scratchpads/ak.ipynb
@@ -17,7 +17,7 @@
"from fractions import Fraction\n",
"import random\n",
"from pyzx.gflow import gflow\n",
- "from pyzx.pauliwebs import preprocess\n",
+ "from pyzx.pauliweb import preprocess, compute_pauli_webs\n",
"from pyzx.utils import vertex_is_zx\n",
"from pyzx.graph.base import BaseGraph\n",
"from pyzx import VertexType, EdgeType\n",
@@ -34,57 +34,11 @@
"cell_type": "code",
"execution_count": 2,
"metadata": {},
- "outputs": [],
- "source": [
- "def transpose(p):\n",
- " pt = dict()\n",
- " for k,s in p.items():\n",
- " for v in s:\n",
- " if v not in pt: pt[v] = set()\n",
- " pt[v].add(k)\n",
- " return pt\n",
- "def extend(g, s):\n",
- " s1 = s.copy()\n",
- " for v in s:\n",
- " s1 |= {v for v in g.neighbors(v) if vertex_is_zx(g.type(v))}\n",
- " return s1\n",
- "def extend_dict(g, p):\n",
- " pe = dict()\n",
- " for k,s in p.items():\n",
- " pe[k] = extend(g, s)\n",
- " return pe\n",
- "def is_dag(p):\n",
- " pt = transpose(p)\n",
- " for k,s in p.items():\n",
- " for v in s:\n",
- " if k != v and k in pt and v in pt[k]:\n",
- " return False\n",
- " return True\n",
- "\n",
- "def is_clifford(p):\n",
- " return p in (0, 1, Fraction(1, 2), Fraction(3,2))\n",
- "\n",
- "def gadgetize(g: BaseGraph):\n",
- " for v in list(g.vertices()):\n",
- " p = g.phase(v)\n",
- " if not is_clifford(p) and g.vertex_degree(v) > 1:\n",
- " x = g.add_vertex(VertexType.Z, -1, g.row(v))\n",
- " y = g.add_vertex(VertexType.Z, -2, g.row(v))\n",
- " g.add_edge((x, y), EdgeType.HADAMARD)\n",
- " g.add_edge((v, x), EdgeType.HADAMARD)\n",
- " g.set_phase(y, p)\n",
- " g.set_phase(v, 0)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
@@ -433,7 +419,7 @@
"Graph(94 vertices, 113 edges)"
]
},
- "execution_count": 3,
+ "execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
@@ -447,13 +433,13 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- "\n",
+ "\n",
""
],
@@ -800,65 +818,29 @@
"source": [
"g1 = g.copy()\n",
"zx.full_reduce(g1)\n",
- "gadgetize(g1)\n",
- "g1.normalize()\n",
- "g1 = g1.copy()\n",
"preprocess(g1)\n",
- "#zx.to_rg(g1)\n",
- "# g2 = g1.copy()\n",
- "# for e in list(g2.edges()): zx.hrules.had_edge_to_hbox(g2, e)\n",
- "zx.draw(g1, labels=True)"
+ "ord, webs = compute_pauli_webs(g1)\n",
+ "zx.draw(g1, labels=True, pauli_web=webs[111])"
]
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 5,
"metadata": {},
"outputs": [
{
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "15 <- {11, 28, 7} + {8, 10, 15, 29, 30}\n",
- "23 <- {11, 22} + {8, 30, 23}\n",
- "25 <- {24, 31} + {32, 9, 25}\n",
- "27 <- {26, 28} + {10, 27, 29}\n",
- "17 <- {10, 11, 15, 16, 27, 29, 31} + {32, 3, 7, 8, 9, 17, 26, 28, 30}\n",
- "21 <- {8, 10, 11, 20, 23, 27, 28, 29, 30, 31} + {32, 3, 5, 7, 9, 21, 22, 26}\n",
- "19 <- {32, 5, 8, 9, 11, 15, 18, 21, 23, 25, 30, 31} + {3, 4, 6, 7, 10, 19, 20, 22, 24}\n"
- ]
- }
- ],
- "source": [
- "gf = gflow(g1, focus=True, pauli=True)\n",
- "if gf:\n",
- " o,p = gf\n",
- " vs = [v for v in g1.vertices() if not is_clifford(g1.phase(v))]\n",
- " list.sort(vs, key=lambda v: o[v], reverse=True)\n",
- " for v in vs: print(f\"{v} <- {p[v]} + {extend(g1, p[v]).difference(p[v])}\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "{32: 4, 29: 4, 30: 4, 8: 3, 9: 3, 10: 3, 11: 3, 15: 3, 23: 3, 25: 3, 27: 3, 28: 3, 31: 3, 3: 2, 5: 2, 7: 2, 17: 2, 21: 2, 22: 2, 24: 2, 26: 2, 4: 1, 6: 1, 16: 1, 19: 1, 20: 1, 18: 0}\n",
- "{11: {3, 4, 5, 8, 15, 17, 19, 21, 23}, 31: {3, 4, 5, 9, 17, 19, 21, 25}, 28: {5, 10, 15, 21, 27}, 30: {4, 5, 11, 19, 21}, 7: {15}, 22: {23}, 24: {25}, 26: {27}, 29: {3, 5, 6, 17, 21, 28}, 32: {19, 4, 31}, 10: {3, 5, 6, 17, 21}, 15: {3, 4, 6, 7, 17, 19}, 27: {3, 5, 6, 17, 21, 26}, 8: {21, 19, 4, 5}, 23: {4, 5, 19, 21, 22}, 16: {17}, 20: {21}, 25: {24, 19, 4}, 5: {19, 4, 6}, 9: {19, 4}, 21: {19, 4, 20, 6}, 17: {16}, 18: {19}, 19: {18}}\n"
- ]
+ "data": {
+ "text/plain": [
+ "{2, 4, 27, 110}"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
}
],
"source": [
- "gf = gflow(g1, focus=True, reverse=False, pauli=True)\n",
- "if gf:\n",
- " o,p = gf\n",
- " pt = transpose(p)\n",
- " print(o)\n",
- " print(pt)"
+ "webs[110].boundary()"
]
},
{