Skip to content

Commit

Permalink
🔀 Merge branch 'master' into fix-invalid-escapes
Browse files Browse the repository at this point in the history
  • Loading branch information
EarlMilktea committed Oct 29, 2024
2 parents a000a74 + 9628d64 commit ec623e2
Show file tree
Hide file tree
Showing 16 changed files with 1,085 additions and 4,206 deletions.
2 changes: 1 addition & 1 deletion demos/Time Benchmark.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,7 @@
"plt.scatter(x, y,color='grey') # Plot all the datapoints\n",
"c = np.polyfit(np.log(x),np.log(y),1, w=np.sqrt(y)) # Generate power law fit\n",
"a = np.linspace(0,max(x),100); b = (math.e**c[1])*a**c[0]\n",
"plt.plot(a,b,'-',color='red',label=\"$y = a\\cdot x^{{{:.2f}}}$\".format(c[0])) # Plot the fit\n",
"plt.plot(a,b,'-',color='red',label=r\"$y = a\\cdot x^{{{:.2f}}}$\".format(c[0])) # Plot the fit\n",
"plt.ylim(ymin=0); plt.xlim(xmin=0,xmax=max(x)+50); \n",
"plt.xlabel(\"Gate-count\"); plt.ylabel(\"Time [seconds]\"); plt.legend(loc='upper left');"
]
Expand Down
2 changes: 1 addition & 1 deletion pyzx/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def CNOT_HAD_PHASE_circuit(
clifford:bool=False
) -> Circuit:
"""Construct a Circuit consisting of CNOT, HAD and phase gates.
The default phase gate is the T gate, but if ``clifford=True``, then
The default phase gate is the T gate, but if ``clifford=True``\\ , then
this is replaced by the S gate.
Args:
Expand Down
4 changes: 2 additions & 2 deletions pyzx/gflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
gFlowaux (V,Gamma,In,Out,k) =
begin
C := {}
for all u in V \ Out do
Solve in F2 : Gamma[V \ Out, Out \ In] * I[X] = I[{u}]
for all u in V \\ Out do
Solve in F2 : Gamma[V \\ Out, Out \\ In] * I[X] = I[{u}]
if there is a solution X0 then
C := C union {u}
g(u) := X0
Expand Down
32 changes: 31 additions & 1 deletion pyzx/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,12 @@ def to_matrix(self,preserve_scalar:bool=True) -> np.ndarray:
"""Returns a representation of the graph as a matrix using :func:`~pyzx.tensor.tensorfy`"""
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`."""
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.
Convert back into a graph using :meth:`from_json`."""
Expand All @@ -497,7 +503,7 @@ def to_tikz(self,draw_scalar:bool=False) -> str:
return to_tikz(self,draw_scalar)

@classmethod
def from_json(cls, js) -> 'BaseGraph':
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
Expand Down Expand Up @@ -990,3 +996,27 @@ def is_well_formed(self) -> bool:
if self.vertex_degree(v) < 2:
return False
return True

def get_auto_simplify(self) -> bool:
"""Returns whether this graph auto-simplifies parallel edges
For multigraphs, this parameter might change, but simple graphs should always return True."""
return True

def set_auto_simplify(self, s: bool) -> None:
"""Set whether this graph auto-simplifies parallel edges
Simple graphs should always auto-simplify, so this method is a no-op."""
pass

def is_phase_gadget(self, v: VT) -> bool:
"""Returns True if the vertex is the 'hub' of a phase gadget"""
if self.phase(v) != 0 or self.vertex_degree(v) < 2:
return False
for w in self.neighbors(v):
if self.vertex_degree(w) == 1:
return True
return False



283 changes: 143 additions & 140 deletions pyzx/graph/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,147 +22,150 @@
from ..utils import VertexType, EdgeType, FractionLike, FloatInt, phase_to_s
from .base import BaseGraph, VT, ET
from .graph_s import GraphS
from .jsonparser import ComplexDecoder, ComplexEncoder, string_to_phase
from .jsonparser import string_to_phase

class GraphDiff(Generic[VT, ET]):
removed_verts: List[VT]
new_verts: List[VT]
removed_edges: List[ET]
new_edges: List[Tuple[Tuple[VT,VT],EdgeType]]
changed_vertex_types: Dict[VT,VertexType]
changed_edge_types: Dict[ET, EdgeType]
changed_phases: Dict[VT, FractionLike]
changed_pos: Dict[VT, Tuple[FloatInt,FloatInt]]
changed_vdata: Dict[VT, Any]
variable_types: Dict[str,bool]

def __init__(self, g1: BaseGraph[VT,ET], g2: BaseGraph[VT,ET]) -> None:
self.calculate_diff(g1,g2)

def calculate_diff(self, g1: BaseGraph[VT,ET], g2: BaseGraph[VT,ET]) -> None:
self.changed_vertex_types = {}
self.changed_edge_types = {}
self.changed_phases = {}
self.changed_pos = {}
self.changed_vdata = {}
self.variable_types = g1.variable_types.copy()
self.variable_types.update(g2.variable_types)

old_verts = g1.vertex_set()
new_verts = g2.vertex_set()
self.removed_verts = list(old_verts - new_verts)
self.new_verts = list(new_verts - old_verts)
old_edges = g1.edge_set()
new_edges = g2.edge_set()
self.new_edges = []
self.removed_edges = []

for e in Counter(new_edges - old_edges).elements():
self.new_edges.append((g2.edge_st(e), g2.edge_type(e)))

for e in Counter(old_edges - new_edges).elements():
s,t = g1.edge_st(e)
self.removed_edges.append(e)

for v in new_verts:
if v in old_verts:
if g1.type(v) != g2.type(v):
self.changed_vertex_types[v] = g2.type(v)
if g1.phase(v) != g2.phase(v):
self.changed_phases[v] = g2.phase(v)
if g1._vdata.get(v, None) != g2._vdata.get(v, None):
self.changed_vdata[v] = g2._vdata.get(v, None)
pos1 = g1.qubit(v), g1.row(v)
pos2 = g2.qubit(v), g2.row(v)
if pos1 != pos2:
self.changed_pos[v] = pos2
else: # It is a new vertex
if g2.type(v) != VertexType.Z: # We are taking the Z type to be the default
self.changed_vertex_types[v] = g2.type(v)
if g2.phase(v) != 0:
self.changed_phases[v] = g2.phase(v)
pos2 = g2.qubit(v), g2.row(v)
self.changed_pos[v] = pos2

for e in new_edges:
if e in old_edges:
if g1.edge_type(e) != g2.edge_type(e):
self.changed_edge_types[e] = g2.edge_type(e)
else:
if g2.edge_type(e) != EdgeType.HADAMARD: # We take Hadamard edges to be the default
self.changed_edge_types[e] = g2.edge_type(e)

def apply_diff(self,g: BaseGraph[VT,ET]) -> BaseGraph[VT,ET]:
g = copy.deepcopy(g)
g.remove_edges(self.removed_edges)
g.remove_vertices(self.removed_verts)
for v in self.new_verts:
g.add_vertex_indexed(v)
g.set_position(v,*self.changed_pos[v])
if v in self.changed_vertex_types:
g.set_type(v,self.changed_vertex_types[v])
else:
g.set_type(v,VertexType.Z)
if v in self.changed_phases:
g.set_phase(v,self.changed_phases[v])
if v in self.changed_vdata:
g._vdata[v] = self.changed_vdata[v]
for st, ty in self.new_edges:
g.add_edge(st,ty)

for v in self.changed_pos:
if v in self.new_verts: continue
g.set_position(v,*self.changed_pos[v])

for v in self.changed_vertex_types:
if v in self.new_verts: continue
g.set_type(v,self.changed_vertex_types[v])

for v in self.changed_phases:
if v in self.new_verts: continue
g.set_phase(v,self.changed_phases[v])

for v in self.changed_vdata:
if v in self.new_verts: continue
g._vdata[v] = self.changed_vdata[v]

for e in self.changed_edge_types:
g.set_edge_type(e,self.changed_edge_types[e])

return g

def to_json(self) -> str:
changed_edge_types_str_dict = {}
for key, value in self.changed_edge_types.items():
changed_edge_types_str_dict[f"{key[0]},{key[1]}"] = value # type: ignore
changed_phases_str = {k: phase_to_s(v) for k, v in self.changed_phases.items()}
return json.dumps({
"removed_verts": self.removed_verts,
"new_verts": self.new_verts,
"removed_edges": self.removed_edges,
"new_edges": self.new_edges,
"changed_vertex_types": self.changed_vertex_types,
"changed_edge_types": changed_edge_types_str_dict,
"changed_phases": changed_phases_str,
"changed_pos": self.changed_pos,
"changed_vdata": self.changed_vdata,
"variable_types": self.variable_types
}, cls=ComplexEncoder)

@staticmethod
def from_json(json_str: str) -> "GraphDiff":
d = json.loads(json_str, cls=ComplexDecoder)
gd = GraphDiff(GraphS(),GraphS())
gd.removed_verts = d["removed_verts"]
gd.new_verts = d["new_verts"]
gd.removed_edges = list(map(tuple, d["removed_edges"])) # type: ignore
gd.new_edges = list(map(tuple, d["new_edges"])) # type: ignore
gd.changed_vertex_types = map_dict_keys(d["changed_vertex_types"], int)
gd.changed_edge_types = map_dict_keys(d["changed_edge_types"], lambda x: tuple(map(int, x.split(","))))
gd.changed_phases = {int(k): string_to_phase(v,gd) for k, v in d["changed_phases"].items()}
gd.changed_pos = map_dict_keys(d["changed_pos"], int)
gd.changed_vdata = map_dict_keys(d["changed_vdata"], int)
return gd
removed_verts: List[VT]
new_verts: List[VT]
removed_edges: List[ET]
new_edges: List[Tuple[Tuple[VT,VT],EdgeType]]
changed_vertex_types: Dict[VT,VertexType]
changed_edge_types: Dict[ET, EdgeType]
changed_phases: Dict[VT, FractionLike]
changed_pos: Dict[VT, Tuple[FloatInt,FloatInt]]
changed_vdata: Dict[VT, Any]
variable_types: Dict[str,bool]

def __init__(self, g1: BaseGraph[VT,ET], g2: BaseGraph[VT,ET]) -> None:
self.calculate_diff(g1,g2)

def calculate_diff(self, g1: BaseGraph[VT,ET], g2: BaseGraph[VT,ET]) -> None:
self.changed_vertex_types = {}
self.changed_edge_types = {}
self.changed_phases = {}
self.changed_pos = {}
self.changed_vdata = {}
self.variable_types = g1.variable_types.copy()
self.variable_types.update(g2.variable_types)

old_verts = g1.vertex_set()
new_verts = g2.vertex_set()
self.removed_verts = list(old_verts - new_verts)
self.new_verts = list(new_verts - old_verts)
old_edges = g1.edge_set()
new_edges = g2.edge_set()
self.new_edges = []
self.removed_edges = []

for e in Counter(new_edges - old_edges).elements():
self.new_edges.append((g2.edge_st(e), g2.edge_type(e)))

for e in Counter(old_edges - new_edges).elements():
s,t = g1.edge_st(e)
self.removed_edges.append(e)

for v in new_verts:
if v in old_verts:
if g1.type(v) != g2.type(v):
self.changed_vertex_types[v] = g2.type(v)
if g1.phase(v) != g2.phase(v):
self.changed_phases[v] = g2.phase(v)
if g1._vdata.get(v, None) != g2._vdata.get(v, None):
self.changed_vdata[v] = g2._vdata.get(v, None)
pos1 = g1.qubit(v), g1.row(v)
pos2 = g2.qubit(v), g2.row(v)
if pos1 != pos2:
self.changed_pos[v] = pos2
else: # It is a new vertex
if g2.type(v) != VertexType.Z: # We are taking the Z type to be the default
self.changed_vertex_types[v] = g2.type(v)
if g2.phase(v) != 0:
self.changed_phases[v] = g2.phase(v)
pos2 = g2.qubit(v), g2.row(v)
self.changed_pos[v] = pos2

for e in new_edges:
if e in old_edges:
if g1.edge_type(e) != g2.edge_type(e):
self.changed_edge_types[e] = g2.edge_type(e)
else:
if g2.edge_type(e) != EdgeType.HADAMARD: # We take Hadamard edges to be the default
self.changed_edge_types[e] = g2.edge_type(e)

def apply_diff(self,g: BaseGraph[VT,ET]) -> BaseGraph[VT,ET]:
g = copy.deepcopy(g)
g.remove_edges(self.removed_edges)
g.remove_vertices(self.removed_verts)
for v in self.new_verts:
g.add_vertex_indexed(v)
g.set_position(v,*self.changed_pos[v])
if v in self.changed_vertex_types:
g.set_type(v,self.changed_vertex_types[v])
else:
g.set_type(v,VertexType.Z)
if v in self.changed_phases:
g.set_phase(v,self.changed_phases[v])
if v in self.changed_vdata:
g._vdata[v] = self.changed_vdata[v]
for st, ty in self.new_edges:
g.add_edge(st,ty)

for v in self.changed_pos:
if v in self.new_verts: continue
g.set_position(v,*self.changed_pos[v])

for v in self.changed_vertex_types:
if v in self.new_verts: continue
g.set_type(v,self.changed_vertex_types[v])

for v in self.changed_phases:
if v in self.new_verts: continue
g.set_phase(v,self.changed_phases[v])

for v in self.changed_vdata:
if v in self.new_verts: continue
g._vdata[v] = self.changed_vdata[v]

for e in self.changed_edge_types:
g.set_edge_type(e,self.changed_edge_types[e])

return g

def to_dict(self) -> Dict[str, Any]:
changed_edge_types_str_dict = {}
for key, value in self.changed_edge_types.items():
changed_edge_types_str_dict[f"{key[0]},{key[1]}"] = value # type: ignore
changed_phases_str = {k: phase_to_s(v) for k, v in self.changed_phases.items()}
return {
"removed_verts": self.removed_verts,
"new_verts": self.new_verts,
"removed_edges": self.removed_edges,
"new_edges": self.new_edges,
"changed_vertex_types": self.changed_vertex_types,
"changed_edge_types": changed_edge_types_str_dict,
"changed_phases": changed_phases_str,
"changed_pos": self.changed_pos,
"changed_vdata": self.changed_vdata,
"variable_types": self.variable_types
}

def to_json(self) -> str:
return json.dumps(self.to_dict())

@staticmethod
def from_json(json_str: str) -> "GraphDiff":
d = json.loads(json_str)
gd = GraphDiff(GraphS(),GraphS())
gd.removed_verts = d["removed_verts"]
gd.new_verts = d["new_verts"]
gd.removed_edges = list(map(tuple, d["removed_edges"])) # type: ignore
gd.new_edges = list(map(tuple, d["new_edges"])) # type: ignore
gd.changed_vertex_types = map_dict_keys(d["changed_vertex_types"], int)
gd.changed_edge_types = map_dict_keys(d["changed_edge_types"], lambda x: tuple(map(int, x.split(","))))
gd.changed_phases = {int(k): string_to_phase(v,gd) for k, v in d["changed_phases"].items()}
gd.changed_pos = map_dict_keys(d["changed_pos"], int)
gd.changed_vdata = map_dict_keys(d["changed_vdata"], int)
return gd

def map_dict_keys(d: Dict[str, Any], f: Callable[[str], Any]) -> Dict[Any, Any]:
return {f(k): v for k, v in d.items()}
return {f(k): v for k, v in d.items()}
Loading

0 comments on commit ec623e2

Please sign in to comment.