diff --git a/demos/AllFeatures.ipynb b/demos/AllFeatures.ipynb index af029675..e8862f16 100644 --- a/demos/AllFeatures.ipynb +++ b/demos/AllFeatures.ipynb @@ -8,7 +8,7 @@ "\n", "## Contents:\n", "* [Loading and saving circuits](#circuits)\n", - "* [Interacting with Quantomatic](#quantomatic)\n", + "* [Importing, exporting and editing diagrams](#diagram-io)\n", "* [Optimizing ZX-diagrams](#optimization-zx)\n", "* [Extracting and optimizing circuits](#optimization-circuits)\n", "* [Phase Teleportation](#phase-teleportation)" @@ -172,11 +172,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "# Interacting with Quantomatic\n", - "PyZX allows easy integration with quantomatic.\n", - "\n", - "First of all, Quantomatic graph files can be imported into PyZX:" + "\n", + "# Importing, exporting and editing diagrams\n", + "PyZX also has its own json format for exporting graphs and interacts with [ZXLive](https://github.com/zxcalc/zxlive) to make manually modifying diagrams easy. First, let's see that we can indeed load diagrams:" ] }, { @@ -195,24 +193,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "PyZX saves the names of the vertices:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(g.vdata(12,'name'))\n", - "print(g.vdata(1,'name'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Because this graph was originally exported from PyZX, it has automatically remembered what its inputs and outputs are:" + "Because this graph was originally exported from PyZX starting as a circuit, it has automatically remembered what its inputs and outputs are:" ] }, { @@ -228,7 +209,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For a graph that originated from Quantomatic we need to tell it what its inputs and outputs are.\n", + "For a graph that was built manually, we might need to tell it what its inputs and outputs are.\n", "\n", "This can be done either manually:\n", "\n", @@ -243,17 +224,17 @@ "metadata": {}, "outputs": [], "source": [ - "g.set_inputs(())\n", + "g.set_inputs(()) # Reset the inputs and outputs, so we can let PyZX auto-detect them\n", "g.set_outputs(())\n", "g.auto_detect_io()\n", "print(g.inputs(), g.outputs())" ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ - "We can also call Quantomatic from PyZX. To do this we first need to tell PyZX where the Quantomatic executable can be found:" + "we can export a diagram to a JSON format that can be loaded back into PyZX (or ZXLive):" ] }, { @@ -262,14 +243,14 @@ "metadata": {}, "outputs": [], "source": [ - "zx.quantomatic.quantomatic_location = os.path.join('path', 'to', 'Quantomatic.jar')" + "print(g.to_json())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now, we can load a PyZX graph into Quantomatic using the following line:" + "We can call ZXLive from within a Jupyter notebook, in order to modify diagrams on the fly:" ] }, { @@ -278,18 +259,39 @@ "metadata": {}, "outputs": [], "source": [ - "result = zx.quantomatic.edit_graph(g)" + "%gui qt6\n", + "\n", + "# First make sure zxlive is installed by `pip install zxlive`\n", + "from zxlive import app\n", + "\n", + "g = zx.Graph()\n", + "g.add_vertex(zx.VertexType.Z, 0, 0)\n", + "g.add_vertex(zx.VertexType.X, 0, 1)\n", + "g.add_edge((0, 1))\n", + "zx.draw(g)\n", + "\n", + "zxl = app.get_embedded_app()\n", + "zxl.edit_graph(g, 'g1')\n", + "zxl.edit_graph(g, 'g2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "This starts Quantomatic with the graph ``g`` loaded. When you are done editing the graph, you simply save the file in Quantomatic, and close it. The result is then loaded and returned.\n", - "\n", - "NOTE1: The Notebook will be blocked until the Quantomatic executable is closed.\n", - "\n", - "NOTE2: Currently this only works with a recent build of Quantomatic that is as of yet only available via the repository, so make sure you are working with an up-to-date branch of Quantomatic." + "After making some edits within ZXLive, we can get the diagram back into this window so we can continue to do further work with them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zx.draw(zxl.get_copy_of_graph('g1'))\n", + "zx.draw(zxl.get_copy_of_graph('g2'))\n", + "#Note that ZXLive only works with MultiGraph's, and hence zxl.get_copy_of_graph() always returns an instance of MultiGraph, \n", + "#and not of the default graph backend." ] }, { @@ -689,7 +691,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -703,7 +705,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/doc/api.rst b/doc/api.rst index 2139c27e..362286d6 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -150,7 +150,7 @@ Below is listed the content of ``drawing.py``. :undoc-members: -Tikz and Quantomatic functionality +Tikz functionality ---------------------------------- .. _tikz: @@ -165,11 +165,3 @@ Below is listed the content of ``tikz.py``. .. _quanto: -Below is listed the content of ``quantomatic.py``. - -.. module:: quantomatic - -.. automodule:: pyzx.quantomatic - :members: - :undoc-members: - diff --git a/pyzx/__init__.py b/pyzx/__init__.py index 53e142bb..197940fd 100644 --- a/pyzx/__init__.py +++ b/pyzx/__init__.py @@ -31,7 +31,6 @@ from .local_search.genetic import GeneticOptimizer from .circuit.qasmparser import qasm from .circuit.sqasm import sqasm -from . import quantomatic from . import generate from . import todd from . import linalg diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index 8286fb69..5015a8a8 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -431,22 +431,3 @@ def to_graphml(g: BaseGraph[VT,ET]) -> str: return gml -# class ComplexEncoder(json.JSONEncoder): -# def default(self, obj): -# if isinstance(obj, complex): -# return str(obj) -# return super().default(obj) - -# class ComplexDecoder(json.JSONDecoder): -# def __init__(self, *args, **kwargs): -# json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) - -# def object_hook(self, dct): -# for k, v in dct.items(): -# if isinstance(v, str): -# try: -# dct[k] = complex(v) -# except ValueError: -# pass -# return dct - diff --git a/pyzx/io.py b/pyzx/io.py deleted file mode 100644 index ffb4b344..00000000 --- a/pyzx/io.py +++ /dev/null @@ -1,262 +0,0 @@ -# PyZX - Python library for quantum circuit rewriting -# and optimization using the ZX-calculus -# Copyright (C) 2018 - Aleks Kissinger and John van de Wetering - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -from fractions import Fraction -from typing import List, Dict, Any - -from .utils import FractionLike -from .graph import Graph, EdgeType, VertexType -from .graph.base import BaseGraph, VT, ET -from .symbolic import Poly - -__all__ = ['json_to_graph', 'graph_to_json', 'to_graphml'] - -def _quanto_value_to_phase(s: str) -> Fraction: - if not s: return Fraction(0) - if r'\pi' in s: - try: - r = s.replace(r'\pi','').strip() - if r.startswith('-'): r = "-1"+r[1:] - if r.startswith('/'): r = "1"+r - return Fraction(str(r)) if r else Fraction(1) - except ValueError: - raise ValueError("Invalid phase '{}'".format(s)) - return Fraction(s) - -def _phase_to_quanto_value(p: FractionLike) -> str: - if not p: return "" - if isinstance(p, Poly): - raise ValueError("Symbolic phases not supported") - p = Fraction(p) - if p.numerator == -1: v = "-" - elif p.numerator == 1: v = "" - else: v = str(p.numerator) - d = "/"+str(p.denominator) if p.denominator!=1 else "" - return r"{}\pi{}".format(v,d) - - -def json_to_graph(js: str, force_deprecated_behavior=False) -> BaseGraph: - """Converts the json representation of a .qgraph Quantomatic graph into - a pyzx graph.""" - print("json_to_graph(js) is deprecated. Please use zx.Graph.from_json(js) instead.") - if not force_deprecated_behavior: - return Graph.from_json(js) # type: ignore - j = json.loads(js) - g = Graph() - - names: Dict[str, Any] = {} # TODO: Any = VT - hadamards: Dict[str, List[Any]] = {} - - inputs = [] - outputs = [] - for name,attr in j.get('node_vertices',{}).items(): - if ('data' in attr and 'type' in attr['data'] and attr['data']['type'] == "hadamard" - and 'is_edge' in attr['data'] and attr['data']['is_edge'] == 'true'): - hadamards[name] = [] - continue - c = attr['annotation']['coord'] - q, r = -c[1], c[0] - if q == int(q): q = int(q) - if r == int(r): r = int(r) - v = g.add_vertex(qubit=q, row=r) - g.set_vdata(v,'name',name) - names[name] = v - if 'data' in attr: - d = attr['data'] - if not 'type' in d or d['type'] == 'Z': g.set_type(v,VertexType.Z) - elif d['type'] == 'X': g.set_type(v,VertexType.X) - elif d['type'] == 'hadamard': g.set_type(v,VertexType.H_BOX) - else: raise TypeError("unsupported type '{}'".format(d['type'])) - if 'value' in d: - g.set_phase(v,_quanto_value_to_phase(d['value'])) - else: - g.set_phase(v,Fraction(0,1)) - else: - g.set_type(v,VertexType.Z) - g.set_phase(v,Fraction(0,1)) - - #g.set_vdata(v, 'x', c[0]) - #g.set_vdata(v, 'y', c[1]) - for name,attr in j.get('wire_vertices',{}).items(): - ann = attr['annotation'] - c = ann['coord'] - q, r = -c[1], c[0] - if q == int(q): q = int(q) - if r == int(r): r = int(r) - v = g.add_vertex(VertexType.BOUNDARY,q,r) - g.set_vdata(v,'name',name) - names[name] = v - if "input" in ann and ann["input"]: inputs.append(v) - if "output" in ann and ann["output"]: outputs.append(v) - #g.set_vdata(v, 'x', c[0]) - #g.set_vdata(v, 'y', c[1]) - - g.set_inputs(tuple(inputs)) - g.set_outputs(tuple(outputs)) - - edges: Dict[Any, List[int]] = {} # TODO: Any = ET - for edge in j.get('undir_edges',{}).values(): - n1, n2 = edge['src'], edge['tgt'] - if n1 in hadamards and n2 in hadamards: #Both - v = g.add_vertex(VertexType.Z) - name = "v"+str(len(names)) - g.set_vdata(v, 'name',name) - names[name] = v - hadamards[n1].append(v) - hadamards[n2].append(v) - continue - if n1 in hadamards: - hadamards[n1].append(names[n2]) - continue - if n2 in hadamards: - hadamards[n2].append(names[n1]) - continue - - amount = edges.get(g.edge(names[n1],names[n2]),[0,0]) - amount[0] += 1 - edges[g.edge(names[n1],names[n2])] = amount - - for l in hadamards.values(): - if len(l) != 2: raise TypeError("Can't parse graphs with irregular Hadamard nodes") - e = g.edge(*tuple(l)) - amount = edges.get(e,[0,0]) - amount[1] += 1 - edges[e] = amount - g.add_edge_table(edges) - - return g - -def graph_to_json(g: BaseGraph[VT,ET], force_deprecated_behavior=False) -> str: - """Converts a PyZX graph into JSON output compatible with Quantomatic.""" - print("graph_to_json(g) is deprecated. Please use g.to_json() instead (for a given graph g).") - if not force_deprecated_behavior: - return g.to_json() - node_vs: Dict[str, Dict[str, Any]] = {} - wire_vs: Dict[str, Dict[str, Any]] = {} - edges: Dict[str, Dict[str, str]] = {} - names: Dict[VT, str] = {} - freenamesv = ["v"+str(i) for i in range(g.num_vertices()+g.num_edges())] - freenamesb = ["b"+str(i) for i in range(g.num_vertices())] - - inputs = g.inputs() - outputs = g.outputs() - - for v in g.vertices(): - t = g.type(v) - coord = [g.row(v),-g.qubit(v)] - name = g.vdata(v, 'name') - if not name: - if t == VertexType.BOUNDARY: name = freenamesb.pop(0) - else: name = freenamesv.pop(0) - else: - try: - freenamesb.remove(name) if t==VertexType.BOUNDARY else freenamesv.remove(name) - except: - pass - #print("couldn't remove name '{}'".format(name)) - - names[v] = name - if t == VertexType.BOUNDARY: - wire_vs[name] = {"annotation":{"boundary":True,"coord":coord, - "input":(v in inputs), "output":(v in outputs)}} - else: - node_vs[name] = {"annotation": {"coord":coord},"data":{}} - if t==VertexType.Z: - node_vs[name]["data"]["type"] = "Z" - elif t==VertexType.X: - node_vs[name]["data"]["type"] = "X" - elif t==VertexType.H_BOX: - node_vs[name]["data"]["type"] = "hadamard" - node_vs[name]["data"]["is_edge"] = "false" - else: raise Exception("Unkown vertex type "+ str(t)) - phase = _phase_to_quanto_value(g.phase(v)) - if phase: node_vs[name]["data"]["value"] = phase - if not node_vs[name]["data"]: del node_vs[name]["data"] - - i = 0 - for e in g.edges(): - src,tgt = g.edge_st(e) - et = g.edge_type(e) - if et == EdgeType.SIMPLE: - edges["e"+ str(i)] = {"src": names[src],"tgt": names[tgt]} - i += 1 - elif et==EdgeType.HADAMARD: - x1,y1 = g.row(src), -g.qubit(src) - x2,y2 = g.row(tgt), -g.qubit(tgt) - hadname = freenamesv.pop(0) - node_vs[hadname] = {"annotation": {"coord":[(x1+x2)/2.0,(y1+y2)/2.0]}, - "data": {"type": "hadamard","is_edge": "true"}} - edges["e"+str(i)] = {"src": names[src],"tgt": hadname} - i += 1 - edges["e"+str(i)] = {"src": names[tgt],"tgt": hadname} - i += 1 - else: - raise TypeError("Edge of type 0") - - - return json.dumps({"wire_vertices": wire_vs, - "node_vertices": node_vs, - "undir_edges": edges}) - -def to_graphml(g: BaseGraph[VT,ET], force_deprecated_behavior=False) -> str: - gml = """ - - - 1 - - - 0 - - - 1 - - - 0 - - - 0 - - -""" - print("to_graphml(g) is deprecated. Please use g.to_graphml() instead (where g is a Graph instance).") - if not force_deprecated_behavior: - return g.to_graphml() - - for v in g.vertices(): - gml += ( - (8*" " + - """{!s}{!s}"""+ - """{!s}{!s}\n""" - ).format( - v, g.type(v), g.phase(v), g.row(v) * 100, g.qubit(v) * 100 - )) - - for e in g.edges(): - s,t = g.edge_st(e) - gml += ( - (8*" " + - """"""+ - """{!s}\n""" - ).format( - s, t, s, t, g.edge_type(e) - )) - gml += """ - - -""" - - return gml diff --git a/pyzx/quantomatic.py b/pyzx/quantomatic.py deleted file mode 100644 index 6e6968bf..00000000 --- a/pyzx/quantomatic.py +++ /dev/null @@ -1,79 +0,0 @@ -# PyZX - Python library for quantum circuit rewriting -# and optimization using the ZX-calculus -# Copyright (C) 2018 - Aleks Kissinger and John van de Wetering - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Implements methods for interacting with Quantomatic:: - - import pyzx as zx - zx.settings.quantomatic_location = "path/to/quantomatic/jar/file.jar" - g = zx.generate.cliffordT(3,10,0.2) - g2 = zx.quantomatic.edit_graph(g) # Opens Quantomatic with the graph g opened. Execution is blocked until Quantomatic is closed again. - # If you have saved the qgraph file in quantomatic, then g2 should now contain your changes. - -""" - -import tempfile -import os -import subprocess - -from .utils import settings -from .io import json_to_graph, graph_to_json -from .graph.base import BaseGraph - -def edit_graph(g: BaseGraph) -> BaseGraph: - """Opens Quantomatic with the graph ``g`` loaded. When you are done editing the graph, - you save it in Quantomatic and close the executable. The resulting graph is returned by this function. - Note that this function blocks until the Quantomatic executable is closed. For this function to work - you must first set ``zx.settings.quantomatic_location`` to point towards the Quantomatic .jar file.""" - if not settings.quantomatic_location or not os.path.exists(settings.quantomatic_location): - raise Exception("Please point towards the Quantomatic jar file with pyzx.settings.quantomatic_location") - - with tempfile.TemporaryDirectory() as tmpdirname: - projectname = os.path.join(tmpdirname, "main.qgraph") - with open(projectname,'w') as f: - f.write(pyzx_qproject) - js = graph_to_json(g) - fname = os.path.join(tmpdirname, "pyzxgraph.qgraph") - with open(fname,'w') as f: - f.write(js) - print("Opening Quantomatic...") - subprocess.check_call(["java", "-jar",settings.quantomatic_location, projectname, fname]) - print("Done") - with open(fname, 'r') as f: - js = f.read() - g = json_to_graph(js) - return g - - -pyzx_qproject = """ -{"name":"PyZX", -"theory":{"name":"Red/green theory","core_name":"red_green", -"vertex_types":{ - "X":{"value":{"type":"angle_expr","latex_constants":true,"validate_with_core":false}, - "style":{"label":{"position":"inside","fg_color":[1.0,1.0,1.0]},"stroke_color":[0.0,0.0,0.0],"fill_color":[1.0,0.0,0.0],"shape":"circle","stroke_width":1},"default_data":{"type":"X","value":""}}, - "Z":{"value":{"type":"angle_expr","latex_constants":true,"validate_with_core":false}, - "style":{"label":{"position":"inside","fg_color":[0.0,0.0,0.0]},"stroke_color":[0.0,0.0,0.0],"fill_color":[0.0,0.800000011920929,0.0],"shape":"circle","stroke_width":1},"default_data":{"type":"Z","value":""}}, - "hadamard":{"value":{"type":"string","latex_constants":false,"validate_with_core":false}, - "style":{"label":{"position":"inside","fg_color":[0.0,0.20000000298023224,0.0]},"stroke_color":[0.0,0.0,0.0],"fill_color":[1.0,1.0,0.0],"shape":"rectangle","stroke_width":1},"default_data":{"type":"hadamard","value":""}}, - "var":{"value":{"type":"string","latex_constants":false,"validate_with_core":false}, - "style":{"label":{"position":"inside","fg_color":[0.0,0.0,0.0]},"stroke_color":[0.0,0.0,0.0],"fill_color":[0.6000000238418579,1.0,0.800000011920929],"shape":"rectangle","stroke_width":1},"default_data":{"type":"var","value":""}} - }, -"default_vertex_type":"Z", -"default_edge_type":"string", -"edge_types":{ - "string":{"value":{"type":"string","latex_constants":false,"validate_with_core":false},"style":{"stroke_color":[0.0,0.0,0.0],"stroke_width":1,"label":{"position":"center","fg_color":[0.0,0.0,1.0],"bg_color":[0.800000011920929,0.800000011920929,1.0,0.699999988079071]}},"default_data":{"type":"string","value":""}}} - } -}""" \ No newline at end of file