From c6c5ee24b1ea8c105e0faa69019cfcb8b013fdfa Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Tue, 21 Jan 2025 17:19:38 +0100 Subject: [PATCH 1/6] Implementation of new file format for graphs that also supports multigraphs --- pyzx/graph/base.py | 2 +- pyzx/graph/jsonparser.py | 104 ++++++++++++++++++++++++++++++++++++++- pyzx/graph/multigraph.py | 23 ++++++--- tests/test_graph.py | 10 ++-- 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/pyzx/graph/base.py b/pyzx/graph/base.py index f966c303..f7ea93eb 100644 --- a/pyzx/graph/base.py +++ b/pyzx/graph/base.py @@ -507,7 +507,7 @@ def from_json(cls, js:Union[str,Dict[str,Any]]) -> 'BaseGraph': """Converts the given .qgraph json string into a Graph. Works with the output of :meth:`to_json`.""" from .jsonparser import json_to_graph - return json_to_graph(js,cls.backend) + return json_to_graph(js) @classmethod def from_tikz(cls, tikz: str, warn_overlap:bool= True, fuse_overlap:bool = True, ignore_nonzx:bool = False) -> 'BaseGraph': diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index 79c0fe95..31308384 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -19,6 +19,8 @@ from fractions import Fraction from typing import List, Dict, Any, Optional, Callable, Union, TYPE_CHECKING +from pyzx.graph.multigraph import Multigraph + from ..utils import FractionLike, EdgeType, VertexType, phase_to_s from .graph import Graph from .scalar import Scalar @@ -27,6 +29,7 @@ from ..symbolic import parse, Poly, new_var if TYPE_CHECKING: from .diff import GraphDiff + from .multigraph import Multigraph #def _phase_to_quanto_value(p: FractionLike) -> str: @@ -71,9 +74,11 @@ def _new_var(name: str) -> Poly: except Exception as e: raise ValueError(e) -def json_to_graph(js: Union[str,Dict[str,Any]], backend:Optional[str]=None) -> BaseGraph: +def json_to_graph_old(js: Union[str,Dict[str,Any]], backend:Optional[str]=None) -> BaseGraph: """Converts the json representation of a .qgraph Quantomatic graph into - a pyzx graph. If JSON is given as a string, parse it first.""" + a pyzx graph. If JSON is given as a string, parse it first. + + This method is deprecated. Use `json_to_graph` or `dict_to_graph` instead.""" if isinstance(js, str): j = json.loads(js) else: @@ -182,6 +187,55 @@ def json_to_graph(js: Union[str,Dict[str,Any]], backend:Optional[str]=None) -> B return g def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, Any]: + """Converts a PyZX graph into Python dict for JSON output. + If include_scalar is set to True (the default), then this includes the value + of g.scalar with the json, which will also be loaded by the ``from_json`` method.""" + d = { + "version": 2, + "backend": g.backend, + "variable_types": g.variable_types, + } + if hasattr(g,'name'): + d['name'] = g.name + if include_scalar: + d["scalar"] = g.scalar.to_dict() + d['inputs'] = g.inputs() + d['outputs'] = g.outputs() + + verts = [] + for v in g.vertices(): + d_v = { + 'id': v, + 't': g.type(v), + 'pos': (round(g.row(v),3),round(g.qubit(v),3)), + } + if g.phase(v): + d_v['phase'] = phase_to_s(g.phase(v)) + t = g.type(v) + pos = [round(g.row(v),3),round(-g.qubit(v),3)] + vdata_keys = g.vdata_keys(v) + if vdata_keys: + d_v['data'] = {k: g.vdata(v,k) for k in vdata_keys} + if g.is_ground(v): + d_v['is_ground'] = True + verts.append(d_v) + + edges: list[tuple[VT,VT,EdgeType]] = [] + if g.backend == 'multigraph': + for e in g.edges(): + edges.append(e) # type: ignore # We know what we are doing, for multigraphs this has the right type. + else: + for e in g.edges(): + src,tgt = g.edge_st(e) + et = g.edge_type(e) + edges.append((src,tgt,et)) + + d['vertices'] = verts + d['edges'] = edges + return d + + +def graph_to_dict_old(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, Any]: """Converts a PyZX graph into Python dict for JSON output. If include_scalar is set to True (the default), then this includes the value of g.scalar with the json, which will also be loaded by the ``from_json`` method.""" @@ -288,6 +342,52 @@ def graph_to_json(g: BaseGraph[VT,ET], include_scalar: bool=True) -> str: of g.scalar with the json, which will also be loaded by the ``from_json`` method.""" return json.dumps(graph_to_dict(g, include_scalar)) +def dict_to_graph(d: dict[str,Any], backend: Optional[str]=None) -> BaseGraph: + """Converts a Python dict representation a graph produced by `graph_to_dict` into + a pyzx Graph. + If backend is given, it will be used as the backend for the graph, + otherwise the backend will be read from the dict description.""" + if not 'version' in d: + # "Version is not specified in dictionary, will try to parse it as an older format") + return json_to_graph_old(d, backend) + else: + if d['version'] != 2: + raise ValueError("Unsupported version "+str(d['version'])) + if backend == None: + backend = d.get('backend', None) + if backend is None: raise ValueError("No backend specified in dictionary") + + g = Graph(backend) + g.variable_types = d.get('variable_types',{}) + for v_d in d['vertices']: + pos = v_d['pos'] + v = v_d['id'] + g.add_vertex_indexed(v) + g.set_type(v,v_d['t']) + g.set_row(v,pos[0]) + g.set_qubit(v,pos[1]) + if 'phase' in v_d: + g.set_phase(v,string_to_phase(v_d['phase'],g)) + if 'is_ground' in v_d and v_d['is_ground'] == True: + g.set_ground(v) + if 'data' in v_d: + for k,val in v_d['data'].items(): + g.set_vdata(v,k,val) + + for (s,t,et) in d['edges']: + g.add_edge((s,t),et) + + return g + +def json_to_graph(js: Union[str,Dict[str,Any]], backend:Optional[str]=None) -> BaseGraph: + """Converts the json representation of a pyzx graph (as a string or dict) into + a `Graph`. If JSON is given as a string, parse it first.""" + if isinstance(js, str): + d = json.loads(js) + else: + d = js + return dict_to_graph(d, backend) + def to_graphml(g: BaseGraph[VT,ET]) -> str: gml = """ diff --git a/pyzx/graph/multigraph.py b/pyzx/graph/multigraph.py index b8e17179..6cf83cff 100644 --- a/pyzx/graph/multigraph.py +++ b/pyzx/graph/multigraph.py @@ -177,7 +177,7 @@ def add_edge(self, edge_pair, edgetype=EdgeType.SIMPLE): elif edgetype == EdgeType.HADAMARD: e.add(h=1) else: e.add(w_io=1) - if self._auto_simplify: + if self._auto_simplify: # This currently can keep a parallel regular and Hadamard edge t1 = self.ty[s] t2 = self.ty[t] if (vertex_is_zx_like(t1) and vertex_is_zx_like(t2)): @@ -189,16 +189,25 @@ def add_edge(self, edge_pair, edgetype=EdgeType.SIMPLE): else: self.add_to_phase(s, 1) self.scalar.add_power(-e.h) + self.nedges = self.nedges - e.h e.h = 0 else: # apply spider and hopf to merge/cancel parallel edges if t1 == t2: - e.s = 1 if e.s > 0 else 0 - self.scalar.add_power(-2 * (e.h - (e.h % 2))) - e.h = e.h % 2 + if e.s > 0: + self.nedges = self.nedges - e.s + 1 + e.s = 1 + if e.h > 0: + self.nedges = self.nedges - (e.h - e.h % 2) + self.scalar.add_power(-2 * (e.h - (e.h % 2))) + e.h = e.h % 2 else: - e.h = 1 if e.h > 0 else 0 - self.scalar.add_power(-2 * (e.s - (e.s % 2))) - e.s = e.s % 2 + if e.h > 0: + self.nedges = self.nedges - e.h + 1 + e.h = 1 + if e.s > 0: + self.nedges = self.nedges - (e.s - e.s % 2) + self.scalar.add_power(-2 * (e.s - (e.s % 2))) + e.s = e.s % 2 if e.is_empty(): del self.graph[s][t] del self.graph[t][s] diff --git a/tests/test_graph.py b/tests/test_graph.py index 56a2a937..f487e1dd 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -220,8 +220,8 @@ def test_compose_unitary(self): g2.compose(g) self.assertTrue(compare_tensors(g2,identity(2), False)) - -test_graph = {'node_vertices': { +# A graph in the old format (a Quantomatic .qgraph file) +test_graph_old_format = {'node_vertices': { 'v0': {'annotation': {'coord': [1.0, -1.0]}, 'data': {'type': 'X', 'value': '\\pi'}}, 'v1': {'annotation': {'coord': [2.0, -1.0]}, @@ -482,13 +482,13 @@ def test_compose_unitary(self): class TestGraphIO(unittest.TestCase): - def test_load_json(self): - js = json.dumps(test_graph) + def test_load_json_old_format(self): + js = json.dumps(test_graph_old_format) g = Graph.from_json(js) js2 = g.to_json() def test_load_tikz(self): - js = json.dumps(test_graph) + js = json.dumps(test_graph_old_format) g = Graph.from_json(js) tikz = g.to_tikz() g2 = Graph.from_tikz(tikz, warn_overlap=False) From b02d26f411eecbde63bb43d494834b55ac5fa4b5 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Tue, 21 Jan 2025 17:57:22 +0100 Subject: [PATCH 2/6] Jsonparser now remembers whether to remove parallel edges, and added a test. --- pyzx/graph/jsonparser.py | 35 ++--- tests/test_graph.py | 275 --------------------------------- tests/test_jsonparser.py | 319 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+), 296 deletions(-) create mode 100644 tests/test_jsonparser.py diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index 31308384..e753e05b 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -32,18 +32,6 @@ from .multigraph import Multigraph -#def _phase_to_quanto_value(p: FractionLike) -> str: -# if not p: return "" -# try: -# 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) -# except TypeError: - - def string_to_phase(string: str, g: Union[BaseGraph,'GraphDiff']) -> Union[Fraction, Poly]: if not string: return Fraction(0) @@ -75,10 +63,10 @@ def _new_var(name: str) -> Poly: raise ValueError(e) def json_to_graph_old(js: Union[str,Dict[str,Any]], backend:Optional[str]=None) -> BaseGraph: - """Converts the json representation of a .qgraph Quantomatic graph into - a pyzx graph. If JSON is given as a string, parse it first. - - This method is deprecated. Use `json_to_graph` or `dict_to_graph` instead.""" + """This method is deprecated. Use `json_to_graph` or `dict_to_graph` instead. + + Converts the json representation of a .qgraph Quantomatic graph into + a pyzx graph. If JSON is given as a string, parse it first.""" if isinstance(js, str): j = json.loads(js) else: @@ -193,7 +181,7 @@ def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, A d = { "version": 2, "backend": g.backend, - "variable_types": g.variable_types, + "variable_types": g.variable_types, # Potential source of error: this dictionary is mutable } if hasattr(g,'name'): d['name'] = g.name @@ -201,6 +189,8 @@ def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, A d["scalar"] = g.scalar.to_dict() d['inputs'] = g.inputs() d['outputs'] = g.outputs() + if g.backend == 'multigraph': + d['auto_simplify'] = g.get_auto_simplify() verts = [] for v in g.vertices(): @@ -211,8 +201,6 @@ def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, A } if g.phase(v): d_v['phase'] = phase_to_s(g.phase(v)) - t = g.type(v) - pos = [round(g.row(v),3),round(-g.qubit(v),3)] vdata_keys = g.vdata_keys(v) if vdata_keys: d_v['data'] = {k: g.vdata(v,k) for k in vdata_keys} @@ -223,7 +211,7 @@ def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, A edges: list[tuple[VT,VT,EdgeType]] = [] if g.backend == 'multigraph': for e in g.edges(): - edges.append(e) # type: ignore # We know what we are doing, for multigraphs this has the right type. + edges.append(e) # type: ignore # We know what we are doing, for multigraphs this has the right type. else: for e in g.edges(): src,tgt = g.edge_st(e) @@ -236,7 +224,8 @@ def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, A def graph_to_dict_old(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, Any]: - """Converts a PyZX graph into Python dict for JSON output. + """This method is deprecated, and replaced by `graph_to_dict`. + Converts a PyZX graph into Python dict for JSON output that is compatible with the Quantomatic format. If include_scalar is set to True (the default), then this includes the value of g.scalar with the json, which will also be loaded by the ``from_json`` method.""" node_vs: Dict[str, Dict[str, Any]] = {} @@ -359,6 +348,10 @@ def dict_to_graph(d: dict[str,Any], backend: Optional[str]=None) -> BaseGraph: g = Graph(backend) g.variable_types = d.get('variable_types',{}) + if g.backend == 'multigraph': + if TYPE_CHECKING: + assert isinstance(g, Multigraph) + g.set_auto_simplify(d.get('auto_simplify', True)) for v_d in d['vertices']: pos = v_d['pos'] v = v_d['id'] diff --git a/tests/test_graph.py b/tests/test_graph.py index f487e1dd..e796408b 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -220,278 +220,3 @@ def test_compose_unitary(self): g2.compose(g) self.assertTrue(compare_tensors(g2,identity(2), False)) -# A graph in the old format (a Quantomatic .qgraph file) -test_graph_old_format = {'node_vertices': { - 'v0': {'annotation': {'coord': [1.0, -1.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v1': {'annotation': {'coord': [2.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v10': {'annotation': {'coord': [6.0, -2.0]}, - 'data': {'type': 'X'}}, - 'v11': {'annotation': {'coord': [7.0, -2.0]}, - 'data': {'type': 'Z'}}, - 'v12': {'annotation': {'coord': [2.0, -2.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v13': {'annotation': {'coord': [3.0, -2.0]}, - 'data': {'type': 'Z'}}, - 'v14': {'annotation': {'coord': [4.0, -2.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v15': {'annotation': {'coord': [11.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v16': {'annotation': {'coord': [2.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi/2'}}, - 'v17': {'annotation': {'coord': [3.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi/2'}}, - 'v18': {'annotation': {'coord': [4.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi/2'}}, - 'v19': {'annotation': {'coord': [6.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi/2'}}, - 'v2': {'annotation': {'coord': [3.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v20': {'annotation': {'coord': [7.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi/2'}}, - 'v21': {'annotation': {'coord': [8.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi/2'}}, - 'v22': {'annotation': {'coord': [9.0, -4.15]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v23': {'annotation': {'coord': [10.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v24': {'annotation': {'coord': [11.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v25': {'annotation': {'coord': [13.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v26': {'annotation': {'coord': [16.0, -3.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v27': {'annotation': {'coord': [3.0, -4.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v28': {'annotation': {'coord': [6.0, -4.0]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v29': {'annotation': {'coord': [6.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v3': {'annotation': {'coord': [4.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v30': {'annotation': {'coord': [7.0, -5.0]}, - 'data': {'type': 'X'}}, - 'v31': {'annotation': {'coord': [8.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v32': {'annotation': {'coord': [9.75, -4.975]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v33': {'annotation': {'coord': [2.0, -5.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v34': {'annotation': {'coord': [3.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v35': {'annotation': {'coord': [4.0, -5.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v36': {'annotation': {'coord': [1.0, -6.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v37': {'annotation': {'coord': [6.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v38': {'annotation': {'coord': [7.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v39': {'annotation': {'coord': [8.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v4': {'annotation': {'coord': [6.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v40': {'annotation': {'coord': [1.0, -6.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v41': {'annotation': {'coord': [2.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v42': {'annotation': {'coord': [3.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v43': {'annotation': {'coord': [4.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v44': {'annotation': {'coord': [9.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v45': {'annotation': {'coord': [10.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v46': {'annotation': {'coord': [11.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v47': {'annotation': {'coord': [12.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v48': {'annotation': {'coord': [12.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v49': {'annotation': {'coord': [13.0, -2.0]}, - 'data': {'type': 'Z'}}, - 'v5': {'annotation': {'coord': [7.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v50': {'annotation': {'coord': [13.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v51': {'annotation': {'coord': [14.0, -2.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v52': {'annotation': {'coord': [15.0, -2.0]}, - 'data': {'type': 'Z'}}, - 'v53': {'annotation': {'coord': [16.0, -2.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v54': {'annotation': {'coord': [17.0, -2.0]}, - 'data': {'type': 'Z'}}, - 'v55': {'annotation': {'coord': [14.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v56': {'annotation': {'coord': [15.0, -5.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v57': {'annotation': {'coord': [16.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v58': {'annotation': {'coord': [17.0, -5.0]}, - 'data': {'type': 'X', 'value': '\\pi'}}, - 'v59': {'annotation': {'coord': [1.0, -2.0]}, - 'data': {'type': 'Z'}}, - 'v6': {'annotation': {'coord': [8.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v60': {'annotation': {'coord': [1.0, -5.0]}, - 'data': {'type': 'Z'}}, - 'v61': {'annotation': {'coord': [16.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v62': {'annotation': {'coord': [17.0, -6.0]}, - 'data': {'type': 'Z'}}, - 'v63': {'annotation': {'coord': [9.75374984741211, - -4.166666793823242]}, - 'data': {'is_edge': 'false', - 'type': 'hadamard', - 'value': '\\pi'}}, - 'v7': {'annotation': {'coord': [9.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v8': {'annotation': {'coord': [10.0, -1.0]}, - 'data': {'type': 'Z'}}, - 'v9': {'annotation': {'coord': [5.0, -2.0]}, - 'data': {'type': 'Z'}}}, - 'undir_edges': {'e0': {'src': 'v0', 'tgt': 'v59'}, - 'e1': {'src': 'v0', 'tgt': 'v1'}, - 'e10': {'src': 'v5', 'tgt': 'v6'}, - 'e11': {'src': 'v5', 'tgt': 'v20'}, - 'e12': {'src': 'v6', 'tgt': 'v7'}, - 'e13': {'src': 'v6', 'tgt': 'v21'}, - 'e14': {'src': 'v7', 'tgt': 'v8'}, - 'e15': {'src': 'v7', 'tgt': 'v22'}, - 'e16': {'src': 'v8', 'tgt': 'v15'}, - 'e17': {'src': 'v8', 'tgt': 'v23'}, - 'e18': {'src': 'v9', 'tgt': 'v14'}, - 'e19': {'src': 'v9', 'tgt': 'v10'}, - 'e2': {'src': 'v1', 'tgt': 'v2'}, - 'e20': {'src': 'v9', 'tgt': 'v16'}, - 'e21': {'src': 'v10', 'tgt': 'v11'}, - 'e22': {'src': 'v10', 'tgt': 'v27'}, - 'e23': {'src': 'v11', 'tgt': 'v49'}, - 'e24': {'src': 'v11', 'tgt': 'v18'}, - 'e25': {'src': 'v12', 'tgt': 'v59'}, - 'e26': {'src': 'v12', 'tgt': 'v13'}, - 'e27': {'src': 'v13', 'tgt': 'v14'}, - 'e28': {'src': 'v13', 'tgt': 'v23'}, - 'e29': {'src': 'v15', 'tgt': 'v47'}, - 'e3': {'src': 'v1', 'tgt': 'v16'}, - 'e30': {'src': 'v15', 'tgt': 'v25'}, - 'e31': {'src': 'v16', 'tgt': 'v41'}, - 'e32': {'src': 'v17', 'tgt': 'v42'}, - 'e33': {'src': 'v17', 'tgt': 'v27'}, - 'e34': {'src': 'v18', 'tgt': 'v43'}, - 'e35': {'src': 'v19', 'tgt': 'v37'}, - 'e36': {'src': 'v19', 'tgt': 'v29'}, - 'e37': {'src': 'v20', 'tgt': 'v28'}, - 'e38': {'src': 'v20', 'tgt': 'v38'}, - 'e39': {'src': 'v21', 'tgt': 'v31'}, - 'e4': {'src': 'v2', 'tgt': 'v3'}, - 'e40': {'src': 'v21', 'tgt': 'v39'}, - 'e41': {'src': 'v22', 'tgt': 'v44'}, - 'e42': {'src': 'v22', 'tgt': 'v63'}, - 'e43': {'src': 'v24', 'tgt': 'v34'}, - 'e44': {'src': 'v24', 'tgt': 'v45'}, - 'e45': {'src': 'v25', 'tgt': 'v55'}, - 'e46': {'src': 'v25', 'tgt': 'v61'}, - 'e47': {'src': 'v25', 'tgt': 'v52'}, - 'e48': {'src': 'v26', 'tgt': 'v57'}, - 'e49': {'src': 'v26', 'tgt': 'v62'}, - 'e5': {'src': 'v2', 'tgt': 'v17'}, - 'e50': {'src': 'v26', 'tgt': 'v54'}, - 'e51': {'src': 'v26', 'tgt': 'v46'}, - 'e52': {'src': 'v28', 'tgt': 'v30'}, - 'e53': {'src': 'v29', 'tgt': 'v35'}, - 'e54': {'src': 'v29', 'tgt': 'v30'}, - 'e55': {'src': 'v30', 'tgt': 'v31'}, - 'e56': {'src': 'v31', 'tgt': 'v32'}, - 'e57': {'src': 'v32', 'tgt': 'v50'}, - 'e58': {'src': 'v32', 'tgt': 'v63'}, - 'e59': {'src': 'v33', 'tgt': 'v60'}, - 'e6': {'src': 'v3', 'tgt': 'v4'}, - 'e60': {'src': 'v33', 'tgt': 'v34'}, - 'e61': {'src': 'v34', 'tgt': 'v35'}, - 'e62': {'src': 'v37', 'tgt': 'v43'}, - 'e63': {'src': 'v37', 'tgt': 'v38'}, - 'e64': {'src': 'v38', 'tgt': 'v39'}, - 'e65': {'src': 'v39', 'tgt': 'v44'}, - 'e66': {'src': 'v40', 'tgt': 'v60'}, - 'e67': {'src': 'v40', 'tgt': 'v41'}, - 'e68': {'src': 'v41', 'tgt': 'v42'}, - 'e69': {'src': 'v42', 'tgt': 'v43'}, - 'e7': {'src': 'v3', 'tgt': 'v18'}, - 'e70': {'src': 'v44', 'tgt': 'v45'}, - 'e71': {'src': 'v45', 'tgt': 'v46'}, - 'e72': {'src': 'v46', 'tgt': 'v48'}, - 'e73': {'src': 'v47', 'tgt': 'v49'}, - 'e74': {'src': 'v48', 'tgt': 'v50'}, - 'e75': {'src': 'v49', 'tgt': 'v51'}, - 'e76': {'src': 'v50', 'tgt': 'v55'}, - 'e77': {'src': 'v51', 'tgt': 'v52'}, - 'e78': {'src': 'v52', 'tgt': 'v53'}, - 'e79': {'src': 'v53', 'tgt': 'v54'}, - 'e8': {'src': 'v4', 'tgt': 'v5'}, - 'e80': {'src': 'v54', 'tgt': 'b1'}, - 'e81': {'src': 'v55', 'tgt': 'v56'}, - 'e82': {'src': 'v56', 'tgt': 'v57'}, - 'e83': {'src': 'v57', 'tgt': 'v58'}, - 'e84': {'src': 'v58', 'tgt': 'b3'}, - 'e85': {'src': 'v59', 'tgt': 'b0'}, - 'e86': {'src': 'v60', 'tgt': 'b2'}, - 'e9': {'src': 'v4', 'tgt': 'v19'}}, - 'wire_vertices': {'b0': {'annotation': {'boundary': True, - 'coord': [0.0, -2.0], - 'input': 0}}, - 'b1': {'annotation': {'boundary': True, - 'coord': [18.0, -2.0], - 'output': 0}}, - 'b2': {'annotation': {'boundary': True, - 'coord': [0.0, -5.0], - 'input': 1}}, - 'b3': {'annotation': {'boundary': True, - 'coord': [18.0, -5.0], - 'output': 1}}}} - - -class TestGraphIO(unittest.TestCase): - - def test_load_json_old_format(self): - js = json.dumps(test_graph_old_format) - g = Graph.from_json(js) - js2 = g.to_json() - - def test_load_tikz(self): - js = json.dumps(test_graph_old_format) - g = Graph.from_json(js) - tikz = g.to_tikz() - g2 = Graph.from_tikz(tikz, warn_overlap=False) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_jsonparser.py b/tests/test_jsonparser.py new file mode 100644 index 00000000..a7595808 --- /dev/null +++ b/tests/test_jsonparser.py @@ -0,0 +1,319 @@ +# 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 unittest +from fractions import Fraction +import json +import sys +if __name__ == '__main__': + sys.path.append('..') + sys.path.append('.') + +from pyzx.graph import Graph +from pyzx.utils import EdgeType, VertexType + + +# A graph in the old format (a Quantomatic .qgraph file) +test_graph_old_format = {'node_vertices': { + 'v0': {'annotation': {'coord': [1.0, -1.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v1': {'annotation': {'coord': [2.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v10': {'annotation': {'coord': [6.0, -2.0]}, + 'data': {'type': 'X'}}, + 'v11': {'annotation': {'coord': [7.0, -2.0]}, + 'data': {'type': 'Z'}}, + 'v12': {'annotation': {'coord': [2.0, -2.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v13': {'annotation': {'coord': [3.0, -2.0]}, + 'data': {'type': 'Z'}}, + 'v14': {'annotation': {'coord': [4.0, -2.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v15': {'annotation': {'coord': [11.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v16': {'annotation': {'coord': [2.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi/2'}}, + 'v17': {'annotation': {'coord': [3.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi/2'}}, + 'v18': {'annotation': {'coord': [4.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi/2'}}, + 'v19': {'annotation': {'coord': [6.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi/2'}}, + 'v2': {'annotation': {'coord': [3.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v20': {'annotation': {'coord': [7.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi/2'}}, + 'v21': {'annotation': {'coord': [8.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi/2'}}, + 'v22': {'annotation': {'coord': [9.0, -4.15]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v23': {'annotation': {'coord': [10.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v24': {'annotation': {'coord': [11.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v25': {'annotation': {'coord': [13.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v26': {'annotation': {'coord': [16.0, -3.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v27': {'annotation': {'coord': [3.0, -4.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v28': {'annotation': {'coord': [6.0, -4.0]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v29': {'annotation': {'coord': [6.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v3': {'annotation': {'coord': [4.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v30': {'annotation': {'coord': [7.0, -5.0]}, + 'data': {'type': 'X'}}, + 'v31': {'annotation': {'coord': [8.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v32': {'annotation': {'coord': [9.75, -4.975]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v33': {'annotation': {'coord': [2.0, -5.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v34': {'annotation': {'coord': [3.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v35': {'annotation': {'coord': [4.0, -5.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v36': {'annotation': {'coord': [1.0, -6.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v37': {'annotation': {'coord': [6.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v38': {'annotation': {'coord': [7.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v39': {'annotation': {'coord': [8.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v4': {'annotation': {'coord': [6.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v40': {'annotation': {'coord': [1.0, -6.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v41': {'annotation': {'coord': [2.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v42': {'annotation': {'coord': [3.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v43': {'annotation': {'coord': [4.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v44': {'annotation': {'coord': [9.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v45': {'annotation': {'coord': [10.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v46': {'annotation': {'coord': [11.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v47': {'annotation': {'coord': [12.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v48': {'annotation': {'coord': [12.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v49': {'annotation': {'coord': [13.0, -2.0]}, + 'data': {'type': 'Z'}}, + 'v5': {'annotation': {'coord': [7.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v50': {'annotation': {'coord': [13.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v51': {'annotation': {'coord': [14.0, -2.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v52': {'annotation': {'coord': [15.0, -2.0]}, + 'data': {'type': 'Z'}}, + 'v53': {'annotation': {'coord': [16.0, -2.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v54': {'annotation': {'coord': [17.0, -2.0]}, + 'data': {'type': 'Z'}}, + 'v55': {'annotation': {'coord': [14.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v56': {'annotation': {'coord': [15.0, -5.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v57': {'annotation': {'coord': [16.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v58': {'annotation': {'coord': [17.0, -5.0]}, + 'data': {'type': 'X', 'value': '\\pi'}}, + 'v59': {'annotation': {'coord': [1.0, -2.0]}, + 'data': {'type': 'Z'}}, + 'v6': {'annotation': {'coord': [8.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v60': {'annotation': {'coord': [1.0, -5.0]}, + 'data': {'type': 'Z'}}, + 'v61': {'annotation': {'coord': [16.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v62': {'annotation': {'coord': [17.0, -6.0]}, + 'data': {'type': 'Z'}}, + 'v63': {'annotation': {'coord': [9.75374984741211, + -4.166666793823242]}, + 'data': {'is_edge': 'false', + 'type': 'hadamard', + 'value': '\\pi'}}, + 'v7': {'annotation': {'coord': [9.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v8': {'annotation': {'coord': [10.0, -1.0]}, + 'data': {'type': 'Z'}}, + 'v9': {'annotation': {'coord': [5.0, -2.0]}, + 'data': {'type': 'Z'}}}, + 'undir_edges': {'e0': {'src': 'v0', 'tgt': 'v59'}, + 'e1': {'src': 'v0', 'tgt': 'v1'}, + 'e10': {'src': 'v5', 'tgt': 'v6'}, + 'e11': {'src': 'v5', 'tgt': 'v20'}, + 'e12': {'src': 'v6', 'tgt': 'v7'}, + 'e13': {'src': 'v6', 'tgt': 'v21'}, + 'e14': {'src': 'v7', 'tgt': 'v8'}, + 'e15': {'src': 'v7', 'tgt': 'v22'}, + 'e16': {'src': 'v8', 'tgt': 'v15'}, + 'e17': {'src': 'v8', 'tgt': 'v23'}, + 'e18': {'src': 'v9', 'tgt': 'v14'}, + 'e19': {'src': 'v9', 'tgt': 'v10'}, + 'e2': {'src': 'v1', 'tgt': 'v2'}, + 'e20': {'src': 'v9', 'tgt': 'v16'}, + 'e21': {'src': 'v10', 'tgt': 'v11'}, + 'e22': {'src': 'v10', 'tgt': 'v27'}, + 'e23': {'src': 'v11', 'tgt': 'v49'}, + 'e24': {'src': 'v11', 'tgt': 'v18'}, + 'e25': {'src': 'v12', 'tgt': 'v59'}, + 'e26': {'src': 'v12', 'tgt': 'v13'}, + 'e27': {'src': 'v13', 'tgt': 'v14'}, + 'e28': {'src': 'v13', 'tgt': 'v23'}, + 'e29': {'src': 'v15', 'tgt': 'v47'}, + 'e3': {'src': 'v1', 'tgt': 'v16'}, + 'e30': {'src': 'v15', 'tgt': 'v25'}, + 'e31': {'src': 'v16', 'tgt': 'v41'}, + 'e32': {'src': 'v17', 'tgt': 'v42'}, + 'e33': {'src': 'v17', 'tgt': 'v27'}, + 'e34': {'src': 'v18', 'tgt': 'v43'}, + 'e35': {'src': 'v19', 'tgt': 'v37'}, + 'e36': {'src': 'v19', 'tgt': 'v29'}, + 'e37': {'src': 'v20', 'tgt': 'v28'}, + 'e38': {'src': 'v20', 'tgt': 'v38'}, + 'e39': {'src': 'v21', 'tgt': 'v31'}, + 'e4': {'src': 'v2', 'tgt': 'v3'}, + 'e40': {'src': 'v21', 'tgt': 'v39'}, + 'e41': {'src': 'v22', 'tgt': 'v44'}, + 'e42': {'src': 'v22', 'tgt': 'v63'}, + 'e43': {'src': 'v24', 'tgt': 'v34'}, + 'e44': {'src': 'v24', 'tgt': 'v45'}, + 'e45': {'src': 'v25', 'tgt': 'v55'}, + 'e46': {'src': 'v25', 'tgt': 'v61'}, + 'e47': {'src': 'v25', 'tgt': 'v52'}, + 'e48': {'src': 'v26', 'tgt': 'v57'}, + 'e49': {'src': 'v26', 'tgt': 'v62'}, + 'e5': {'src': 'v2', 'tgt': 'v17'}, + 'e50': {'src': 'v26', 'tgt': 'v54'}, + 'e51': {'src': 'v26', 'tgt': 'v46'}, + 'e52': {'src': 'v28', 'tgt': 'v30'}, + 'e53': {'src': 'v29', 'tgt': 'v35'}, + 'e54': {'src': 'v29', 'tgt': 'v30'}, + 'e55': {'src': 'v30', 'tgt': 'v31'}, + 'e56': {'src': 'v31', 'tgt': 'v32'}, + 'e57': {'src': 'v32', 'tgt': 'v50'}, + 'e58': {'src': 'v32', 'tgt': 'v63'}, + 'e59': {'src': 'v33', 'tgt': 'v60'}, + 'e6': {'src': 'v3', 'tgt': 'v4'}, + 'e60': {'src': 'v33', 'tgt': 'v34'}, + 'e61': {'src': 'v34', 'tgt': 'v35'}, + 'e62': {'src': 'v37', 'tgt': 'v43'}, + 'e63': {'src': 'v37', 'tgt': 'v38'}, + 'e64': {'src': 'v38', 'tgt': 'v39'}, + 'e65': {'src': 'v39', 'tgt': 'v44'}, + 'e66': {'src': 'v40', 'tgt': 'v60'}, + 'e67': {'src': 'v40', 'tgt': 'v41'}, + 'e68': {'src': 'v41', 'tgt': 'v42'}, + 'e69': {'src': 'v42', 'tgt': 'v43'}, + 'e7': {'src': 'v3', 'tgt': 'v18'}, + 'e70': {'src': 'v44', 'tgt': 'v45'}, + 'e71': {'src': 'v45', 'tgt': 'v46'}, + 'e72': {'src': 'v46', 'tgt': 'v48'}, + 'e73': {'src': 'v47', 'tgt': 'v49'}, + 'e74': {'src': 'v48', 'tgt': 'v50'}, + 'e75': {'src': 'v49', 'tgt': 'v51'}, + 'e76': {'src': 'v50', 'tgt': 'v55'}, + 'e77': {'src': 'v51', 'tgt': 'v52'}, + 'e78': {'src': 'v52', 'tgt': 'v53'}, + 'e79': {'src': 'v53', 'tgt': 'v54'}, + 'e8': {'src': 'v4', 'tgt': 'v5'}, + 'e80': {'src': 'v54', 'tgt': 'b1'}, + 'e81': {'src': 'v55', 'tgt': 'v56'}, + 'e82': {'src': 'v56', 'tgt': 'v57'}, + 'e83': {'src': 'v57', 'tgt': 'v58'}, + 'e84': {'src': 'v58', 'tgt': 'b3'}, + 'e85': {'src': 'v59', 'tgt': 'b0'}, + 'e86': {'src': 'v60', 'tgt': 'b2'}, + 'e9': {'src': 'v4', 'tgt': 'v19'}}, + 'wire_vertices': {'b0': {'annotation': {'boundary': True, + 'coord': [0.0, -2.0], + 'input': 0}}, + 'b1': {'annotation': {'boundary': True, + 'coord': [18.0, -2.0], + 'output': 0}}, + 'b2': {'annotation': {'boundary': True, + 'coord': [0.0, -5.0], + 'input': 1}}, + 'b3': {'annotation': {'boundary': True, + 'coord': [18.0, -5.0], + 'output': 1}}}} + + +class TestGraphIO(unittest.TestCase): + + def test_load_multigraph_preserve_parallel_edges(self): + g = Graph('multigraph') + g.set_auto_simplify(False) + v = g.add_vertex(VertexType.Z, 0, 0) + w = g.add_vertex(VertexType.X, 0, 1) + x = g.add_vertex(VertexType.X, 0, 2) + g.add_edge((v,w)) + g.add_edge((v,w)) + g.add_edge((w,x)) + g.add_edge((w,x),EdgeType.HADAMARD) + + d = g.to_dict() + g2 = Graph.from_json(d) + self.assertEqual(g.num_edges(), g2.num_edges()) + + def test_load_json_old_format(self): + js = json.dumps(test_graph_old_format) + g = Graph.from_json(js) + js2 = g.to_json() + + def test_load_tikz(self): + js = json.dumps(test_graph_old_format) + g = Graph.from_json(js) + tikz = g.to_tikz() + g2 = Graph.from_tikz(tikz, warn_overlap=False) + +if __name__ == '__main__': + unittest.main() From f36cbc5d26e917e59512df72a94b6ad5c46a2511 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Tue, 21 Jan 2025 18:46:13 +0100 Subject: [PATCH 3/6] Docstrings --- pyzx/graph/base.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyzx/graph/base.py b/pyzx/graph/base.py index fea40b46..36e3354e 100644 --- a/pyzx/graph/base.py +++ b/pyzx/graph/base.py @@ -729,13 +729,12 @@ def to_matrix(self,preserve_scalar:bool=True) -> np.ndarray: return tensor_to_matrix(tensorfy(self, preserve_scalar), self.num_inputs(), self.num_outputs()) def to_dict(self, include_scalar:bool=True) -> Dict[str, Any]: - """Returns a json representation of the graph that follows the Quantomatic .qgraph format. - Convert back into a graph using :meth:`from_json`.""" + """Returns a dictionary representation of the graph, which can then be converted into json.""" from .jsonparser import graph_to_dict return graph_to_dict(self, include_scalar) def to_json(self, include_scalar:bool=True) -> str: - """Returns a json representation of the graph that follows the Quantomatic .qgraph format. + """Returns a json representation of the graph. Convert back into a graph using :meth:`from_json`.""" from .jsonparser import graph_to_json return graph_to_json(self, include_scalar) @@ -755,7 +754,7 @@ def from_json(cls, js:Union[str,Dict[str,Any]]) -> BaseGraph[VT,ET]: """Converts the given .qgraph json string into a Graph. Works with the output of :meth:`to_json`.""" from .jsonparser import json_to_graph - return json_to_graph(js) + return json_to_graph(js, backend=cls.backend) @classmethod def from_tikz(cls, tikz: str, warn_overlap:bool= True, fuse_overlap:bool = True, ignore_nonzx:bool = False) -> BaseGraph[VT,ET]: From 06eb82fa2c2a352e3efd24df11939288bb8b5b86 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Tue, 21 Jan 2025 19:02:08 +0100 Subject: [PATCH 4/6] mypy complaints on python 3.8 --- pyzx/graph/jsonparser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index e753e05b..9240f081 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -17,7 +17,7 @@ import json import re from fractions import Fraction -from typing import List, Dict, Any, Optional, Callable, Union, TYPE_CHECKING +from typing import List, Dict, Tuple, Any, Optional, Callable, Union, TYPE_CHECKING from pyzx.graph.multigraph import Multigraph @@ -208,7 +208,7 @@ def graph_to_dict(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[str, A d_v['is_ground'] = True verts.append(d_v) - edges: list[tuple[VT,VT,EdgeType]] = [] + edges: List[Tuple[VT,VT,EdgeType]] = [] if g.backend == 'multigraph': for e in g.edges(): edges.append(e) # type: ignore # We know what we are doing, for multigraphs this has the right type. @@ -331,7 +331,7 @@ def graph_to_json(g: BaseGraph[VT,ET], include_scalar: bool=True) -> str: of g.scalar with the json, which will also be loaded by the ``from_json`` method.""" return json.dumps(graph_to_dict(g, include_scalar)) -def dict_to_graph(d: dict[str,Any], backend: Optional[str]=None) -> BaseGraph: +def dict_to_graph(d: Dict[str,Any], backend: Optional[str]=None) -> BaseGraph: """Converts a Python dict representation a graph produced by `graph_to_dict` into a pyzx Graph. If backend is given, it will be used as the backend for the graph, From 5cc673fc4b19810f330c6791ffb1a6cb1690f6f4 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Tue, 21 Jan 2025 19:28:45 +0100 Subject: [PATCH 5/6] Fixed wrongly loading auto_simplify value --- pyzx/graph/base.py | 2 +- pyzx/graph/jsonparser.py | 3 ++- tests/test_jsonparser.py | 8 ++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pyzx/graph/base.py b/pyzx/graph/base.py index 36e3354e..c59e9a04 100644 --- a/pyzx/graph/base.py +++ b/pyzx/graph/base.py @@ -754,7 +754,7 @@ def from_json(cls, js:Union[str,Dict[str,Any]]) -> BaseGraph[VT,ET]: """Converts the given .qgraph json string into a Graph. Works with the output of :meth:`to_json`.""" from .jsonparser import json_to_graph - return json_to_graph(js, backend=cls.backend) + return json_to_graph(js) @classmethod def from_tikz(cls, tikz: str, warn_overlap:bool= True, fuse_overlap:bool = True, ignore_nonzx:bool = False) -> BaseGraph[VT,ET]: diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index 9240f081..a4c62069 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -351,7 +351,8 @@ def dict_to_graph(d: Dict[str,Any], backend: Optional[str]=None) -> BaseGraph: if g.backend == 'multigraph': if TYPE_CHECKING: assert isinstance(g, Multigraph) - g.set_auto_simplify(d.get('auto_simplify', True)) + b = True if d.get('auto_simplify', True) in ('true', True) else False + g.set_auto_simplify(b) for v_d in d['vertices']: pos = v_d['pos'] v = v_d['id'] diff --git a/tests/test_jsonparser.py b/tests/test_jsonparser.py index a7595808..0ea961e6 100644 --- a/tests/test_jsonparser.py +++ b/tests/test_jsonparser.py @@ -302,8 +302,16 @@ def test_load_multigraph_preserve_parallel_edges(self): d = g.to_dict() g2 = Graph.from_json(d) + self.assertTrue(g2.backend,'multigraph') + self.assertFalse(g2.get_auto_simplify()) self.assertEqual(g.num_edges(), g2.num_edges()) + js = json.dumps(d) + g3 = Graph.from_json(js) + self.assertTrue(g3.backend,'multigraph') + self.assertFalse(g3.get_auto_simplify()) + self.assertEqual(g.num_edges(), g3.num_edges()) + def test_load_json_old_format(self): js = json.dumps(test_graph_old_format) g = Graph.from_json(js) From dc3ea31b257883e6c101822834141c2bfca1c3c1 Mon Sep 17 00:00:00 2001 From: John van de Wetering Date: Wed, 22 Jan 2025 16:22:44 +0100 Subject: [PATCH 6/6] No longer let __init__ refer to io.py which only contains deprecated functions, but instead use jsonparser.py --- pyzx/__init__.py | 2 +- pyzx/graph/jsonparser.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyzx/__init__.py b/pyzx/__init__.py index f320dcfe..53e142bb 100644 --- a/pyzx/__init__.py +++ b/pyzx/__init__.py @@ -25,7 +25,7 @@ from .simplify import * from .optimize import * from .extract import * -from .io import * +from .graph.jsonparser import * from .tensor import * from .local_search.simulated_annealing import anneal from .local_search.genetic import GeneticOptimizer diff --git a/pyzx/graph/jsonparser.py b/pyzx/graph/jsonparser.py index a4c62069..8286fb69 100644 --- a/pyzx/graph/jsonparser.py +++ b/pyzx/graph/jsonparser.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +__all__ = ['string_to_phase','to_graphml'] + import json import re from fractions import Fraction