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()" ] }, {