Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions pyzx/drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
lines: Any = None


from .utils import settings, get_mode, phase_to_s, EdgeType, VertexType, FloatInt, get_z_box_label
from .utils import settings, get_mode, phase_to_s, VertexType, FloatInt, get_z_box_label, get_h_box_label, hbox_has_complex_label
from .graph.base import BaseGraph, VT, ET
from .circuit import Circuit

Expand Down Expand Up @@ -241,7 +241,16 @@ def draw_matplotlib(
t = g.type(v)
a = g.phase(v)
a_offset = 0.5
phase_str = phase_to_s(a, t)
# Handle H-boxes with complex labels.
if t == VertexType.H_BOX and hbox_has_complex_label(g, v):
label = get_h_box_label(g, v)
# Standard Hadamard (-1) is displayed as empty.
if cmath.isclose(label, -1):
phase_str = ''
else:
phase_str = str(label)
else:
phase_str = phase_to_s(a, t)

if t == VertexType.Z:
ax.add_patch(patches.Circle(p, 0.2, facecolor='#ccffcc', edgecolor='black', zorder=1))
Expand Down Expand Up @@ -332,12 +341,25 @@ def graph_json(g: BaseGraph[VT, ET],
vdata: Optional[List[str]]=None,
pauli_web: Optional[PauliWeb[VT,ET]]=None) -> str:

def get_phase_str(v):
"""Get phase string for a vertex, handling complex labels."""
ty = g.type(v)
if ty == VertexType.Z_BOX:
return str(get_z_box_label(g, v))
elif ty == VertexType.H_BOX and hbox_has_complex_label(g, v):
label = get_h_box_label(g, v)
# Standard Hadamard (-1) is displayed as empty.
if cmath.isclose(label, -1):
return ''
return str(label)
return phase_to_s(g.phase(v), ty, poly_with_pi=True)

nodes = [{'name': str(v),
'x': float(coords[v][0]),
'y': float(coords[v][1]),
'z': float(coords[v][2]),
't': g.type(v),
'phase': phase_to_s(g.phase(v), g.type(v), poly_with_pi=True) if g.type(v) != VertexType.Z_BOX else str(get_z_box_label(g, v)),
'phase': get_phase_str(v),
'ground': g.is_ground(v),
'vdata': [(key, g.vdata(v, key))
for key in vdata or [] if g.vdata(v, key, None) is not None],
Expand Down
4 changes: 3 additions & 1 deletion pyzx/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,9 @@ def add_vertex(self,
) -> VT:
"""Add a single vertex to the graph and return its index.
The optional parameters allow you to respectively set
the type, qubit index, row index and phase of the vertex."""
the type, qubit index, row index and phase of the vertex.
For H-boxes and Z-boxes with complex labels, use set_h_box_label
or set_z_box_label after creating the vertex."""
if index is not None:
self.add_vertex_indexed(index)
v = index
Expand Down
8 changes: 7 additions & 1 deletion pyzx/graph/jsonparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,20 @@ def graph_to_dict_old(g: BaseGraph[VT,ET], include_scalar: bool=True) -> Dict[st
elif t==VertexType.H_BOX:
node_vs[name]["data"]["type"] = "hadamard"
node_vs[name]["data"]["is_edge"] = "false"
# Only export label if set; legacy H-boxes use phase field instead.
hbox_label = g.vdata(v, 'label', None)
if hbox_label is not None:
if isinstance(hbox_label, Fraction):
hbox_label = phase_to_s(hbox_label, limit_denominator=False)
node_vs[name]["annotation"]["label"] = hbox_label
elif t==VertexType.W_INPUT:
node_vs[name]["data"]["type"] = "W_input"
elif t==VertexType.W_OUTPUT:
node_vs[name]["data"]["type"] = "W_output"
elif t==VertexType.Z_BOX:
node_vs[name]["data"]["type"] = "Z_box"
zbox_label = g.vdata(v, 'label', 1)
if type(zbox_label) == Fraction:
if isinstance(zbox_label, Fraction):
zbox_label = phase_to_s(zbox_label, limit_denominator=False)
node_vs[name]["annotation"]["label"] = zbox_label
else: raise Exception("Unkown vertex type "+ str(t))
Expand Down
6 changes: 5 additions & 1 deletion pyzx/rewrite_rules/bialgebra_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@

from collections import defaultdict
from typing import Callable, Optional, List, Tuple, Dict
from pyzx.utils import EdgeType, VertexType, is_pauli
from pyzx.utils import (EdgeType, VertexType, is_pauli,
hbox_has_complex_label, get_h_box_label, set_h_box_label)
from pyzx.graph.base import BaseGraph, VT, ET, upair

RewriteOutputType = Tuple[Dict[Tuple[VT,VT],List[int]], List[VT], List[ET], bool]
Expand Down Expand Up @@ -94,6 +95,9 @@ def unsafe_bialgebra(g: BaseGraph[VT,ET], v1: VT, v2: VT ) -> bool:
r = 0.4*g.row(other_vertex) + 0.6*g.row(v[i])
newv = g.add_vertex(g.type(v[j]), qubit=q, row=r)
g.set_phase(newv, g.phase(v[j]))
# Copy complex label if H-box has one.
if g.type(v[j]) == VertexType.H_BOX and hbox_has_complex_label(g, v[j]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v[i] and v[j] are always Z and X spiders here, and there phase I believe is always a Pauli, so this case here never applies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the check on line 121 below? I think it has the same applicability as this check here, so if this one is removed, that one should as well, for consistency.

set_h_box_label(g, newv, get_h_box_label(g, v[j]))
new_verts[i].append(newv)
if other_vertex == v[j]:
q = 0.4*g.qubit(v[i]) + 0.6*g.qubit(other_vertex)
Expand Down
6 changes: 5 additions & 1 deletion pyzx/rewrite_rules/copy_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
'unsafe_copy',]

from typing import Optional
from pyzx.utils import EdgeType, VertexType, toggle_vertex, vertex_is_zx
from pyzx.utils import EdgeType, VertexType, toggle_vertex, vertex_is_zx, is_standard_hbox

from pyzx.graph.base import BaseGraph, ET, VT

Expand Down Expand Up @@ -95,6 +95,10 @@ def check_copy_h(
et = g.edge_type(g.edge(v, w))

if tw == VertexType.H_BOX:
# Only apply to standard H-boxes (label=-1 or phase=1).
# Non-standard H-boxes have different scalar factors.
if not is_standard_hbox(g, w):
return None
# X pi/0 can always copy through H-box
# But if v is Z, then it can only copy if the phase is 1
if et == EdgeType.HADAMARD:
Expand Down
6 changes: 3 additions & 3 deletions pyzx/rewrite_rules/fuse_hboxes_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@


from typing import Dict, List, Tuple, Set
from pyzx.utils import EdgeType, VertexType
from pyzx.utils import EdgeType, VertexType, is_standard_hbox
from pyzx.graph.base import BaseGraph, ET, VT, upair


Expand All @@ -50,7 +50,7 @@ def check_connected_hboxes(g: BaseGraph[VT ,ET], v: VT, w: VT) -> bool:
if g.edge_type(e) != EdgeType.HADAMARD: return False
v1 ,v2 = g.edge_st(e)
if ty[v1] != VertexType.H_BOX or ty[v2] != VertexType.H_BOX: return False
if g.phase(v1) != 1 and g.phase(v2) != 1: return False
if not is_standard_hbox(g, v1) and not is_standard_hbox(g, v2): return False
m.add(e)

return True
Expand All @@ -67,7 +67,7 @@ def unsafe_fuse_hboxes(g: BaseGraph[VT ,ET], v1: VT, v2: VT) -> bool:
rem_verts = []
etab: Dict[Tuple[VT ,VT], List[int]] = {}

if g.phase(v2) != 1: # at most one of v1 and v2 has a phase different from 1
if not is_standard_hbox(g, v2): # Ensure v2 is the standard one.
v1, v2 = v2, v1
rem_verts.append(v2)
g.scalar.add_power(1)
Expand Down
4 changes: 2 additions & 2 deletions pyzx/rewrite_rules/had_edge_hbox_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@
'had_edge_to_hbox',
'unsafe_had_edge_to_hbox']

from pyzx.utils import EdgeType, VertexType
from pyzx.utils import EdgeType, VertexType, is_standard_hbox
from pyzx.graph.base import BaseGraph, ET, VT
from pyzx.rewrite_rules.euler_rule import check_hadamard_edge

def check_hadamard(g: BaseGraph[VT ,ET], v: VT) -> bool:
"""Returns whether the vertex v in graph g is a Hadamard gate."""
if g.type(v) != VertexType.H_BOX: return False
if g.phase(v) != 1: return False
if not is_standard_hbox(g, v): return False
if g.vertex_degree(v) != 2: return False
return True

Expand Down
8 changes: 4 additions & 4 deletions pyzx/rewrite_rules/hbox_cancel_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
'unsafe_hbox_cancel']

from pyzx.graph.base import BaseGraph, VT, ET
from pyzx.utils import EdgeType, VertexType
from pyzx.utils import EdgeType, VertexType, is_standard_hbox


def check_hbox_cancel(g: BaseGraph[VT, ET], v: VT) -> bool:
Expand All @@ -40,7 +40,7 @@ def check_hbox_cancel(g: BaseGraph[VT, ET], v: VT) -> bool:
return False
if g.type(v) != VertexType.H_BOX:
return False
if g.phase(v) != 1:
if not is_standard_hbox(g, v):
return False
if g.vertex_degree(v) != 2:
return False
Expand All @@ -51,7 +51,7 @@ def check_hbox_cancel(g: BaseGraph[VT, ET], v: VT) -> bool:
e = g.edge(v, n)
if g.edge_type(e) == EdgeType.SIMPLE:
if (g.type(n) == VertexType.H_BOX and
g.phase(n) == 1 and
is_standard_hbox(g, n) and
g.vertex_degree(n) == 2):
return True

Expand Down Expand Up @@ -79,7 +79,7 @@ def unsafe_hbox_cancel(g: BaseGraph[VT, ET], v: VT) -> bool:
e = g.edge(v, n)
if g.edge_type(e) == EdgeType.SIMPLE:
if (g.type(n) == VertexType.H_BOX and
g.phase(n) == 1 and
is_standard_hbox(g, n) and
g.vertex_degree(n) == 2):

# Found two adjacent H-boxes connected by a simple edge.
Expand Down
4 changes: 2 additions & 2 deletions pyzx/rewrite_rules/hbox_not_remove_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


from typing import Dict, List, Tuple
from pyzx.utils import EdgeType, VertexType
from pyzx.utils import EdgeType, VertexType, is_standard_hbox
from pyzx.graph.base import BaseGraph, ET, VT, upair


Expand Down Expand Up @@ -63,7 +63,7 @@ def check_hbox_parallel_not(
types = g.types()

if not (h in g.vertices() and n in g.vertices()): return False
if types[h] != VertexType.H_BOX or phases[h] != 1: return False
if types[h] != VertexType.H_BOX or not is_standard_hbox(g, h): return False

if g.vertex_degree(n) != 2 or phases[n] != 1: return False # If it turns out to be useful, this rule can be generalised to allow spiders of arbitrary phase here

Expand Down
11 changes: 5 additions & 6 deletions pyzx/rewrite_rules/hpivot_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from fractions import Fraction
from itertools import combinations
from typing import List, Tuple, Optional
from pyzx.utils import VertexType, toggle_edge, FractionLike, FloatInt
from pyzx.utils import VertexType, toggle_edge, FractionLike, FloatInt, is_standard_hbox
from pyzx.graph.base import BaseGraph, ET, VT


Expand Down Expand Up @@ -79,7 +79,7 @@ def match_hpivot(
(vertices is None or (vertices[0] == h)) and
g.vertex_degree(h) == 2 and
types[h] == VertexType.H_BOX and
phases[h] == 1
is_standard_hbox(g, h)
): continue

v0, v1 = g.neighbors(h)
Expand All @@ -94,10 +94,9 @@ def match_hpivot(
v1b = [v for v in v1n if types[v] == VertexType.BOUNDARY]
v1h = [v for v in v1n if types[v] == VertexType.H_BOX and v != h]

# check that at least one of v0 or v1 has all pi phases on adjacent
# hboxes.
if not (all(phases[v] == 1 for v in v0h)):
if not (all(phases[v] == 1 for v in v1h)):
# check that at least one of v0 or v1 has all standard H-boxes adjacent.
if not (all(is_standard_hbox(g, v) for v in v0h)):
if not (all(is_standard_hbox(g, v) for v in v1h)):
continue
else:
# interchange the roles of v0 <-> v1
Expand Down
4 changes: 3 additions & 1 deletion pyzx/rewrite_rules/par_hbox_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


from typing import Dict, List, Tuple, Optional, Set, FrozenSet
from pyzx.utils import EdgeType, VertexType
from pyzx.utils import EdgeType, VertexType, hbox_has_complex_label
from pyzx.graph.base import BaseGraph, ET, VT


Expand Down Expand Up @@ -68,6 +68,7 @@ def match_par_hbox(
ty = g.types()
for h in candidates:
if ty[h] != VertexType.H_BOX: continue
if hbox_has_complex_label(g, h): continue
suitable = True
neighbors_regular = set()
neighbors_NOT = set()
Expand Down Expand Up @@ -164,6 +165,7 @@ def match_par_hbox_intro(g: BaseGraph[VT, ET], vertices: Optional[List[VT]]=None
ty = g.types()
for h in candidates:
if ty[h] != VertexType.H_BOX: continue
if hbox_has_complex_label(g, h): continue
suitable = True
neighbors_regular = set()
neighbors_NOT = set()
Expand Down
4 changes: 2 additions & 2 deletions pyzx/rewrite_rules/push_pauli_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

from typing import List, Dict, Tuple

from pyzx.utils import EdgeType, VertexType, FractionLike, phase_is_pauli, vertex_is_zx, toggle_vertex
from pyzx.utils import EdgeType, VertexType, FractionLike, phase_is_pauli, vertex_is_zx, toggle_vertex, is_standard_hbox
from pyzx.graph.base import BaseGraph, VT, ET, upair

def check_pauli(g: BaseGraph[VT,ET], v: VT, w: VT) -> bool:
Expand All @@ -58,7 +58,7 @@ def check_pauli(g: BaseGraph[VT,ET], v: VT, w: VT) -> bool:

if ((types[v] == types[w] and et == EdgeType.HADAMARD) or
(vertex_is_zx(types[v]) and types[v] != types[w] and et == EdgeType.SIMPLE) or
(types[v] == VertexType.H_BOX and phases[v] == 1 and (
(types[v] == VertexType.H_BOX and is_standard_hbox(g, v) and (
(et == EdgeType.SIMPLE and types[w] == VertexType.X) or
(et == EdgeType.HADAMARD and types[w] == VertexType.Z)))
):
Expand Down
13 changes: 8 additions & 5 deletions pyzx/rewrite_rules/zero_hbox_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@
'unsafe_zero_hbox']


from pyzx.utils import VertexType
import cmath
from pyzx.utils import VertexType, get_h_box_label, hbox_has_complex_label
from pyzx.graph.base import BaseGraph, ET, VT


def check_zero_hbox(g: BaseGraph[VT,ET], v:VT) -> bool:
"""Matches H-boxes that have a phase of 2pi==0."""
"""Matches H-boxes with label 1 (or phase 0)."""
types = g.types()
phases = g.phases()
if types[v] == VertexType.H_BOX and phases[v] == 0: return True
return False
if types[v] != VertexType.H_BOX:
return False
if hbox_has_complex_label(g, v):
return cmath.isclose(get_h_box_label(g, v), 1)
return g.phase(v) == 0


def zero_hbox(g: BaseGraph[VT,ET], v: VT) -> bool:
Expand Down
14 changes: 11 additions & 3 deletions pyzx/tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ def X_to_tensor(arity: int, phase: float) -> np.ndarray:
m[i] -= np.exp(1j*phase)
return np.power(np.sqrt(0.5),arity)*m.reshape([2]*arity)

def H_to_tensor(arity: int, phase: float) -> np.ndarray:
def H_to_tensor(arity: int, phase: float, label: Optional[complex] = None) -> np.ndarray:
m = np.ones(2**arity, dtype = complex)
if phase != 0: m[-1] = np.exp(1j*phase)
if label is not None:
m[-1] = label
elif phase != 0:
m[-1] = np.exp(1j*phase)
return m.reshape([2]*arity)

def W_to_tensor(arity: int) -> np.ndarray:
Expand Down Expand Up @@ -184,7 +187,12 @@ def tensorfy_naive(g: 'BaseGraph[VT,ET]', preserve_scalar: bool = True) -> NDArr
elif types[v] == VertexType.X:
t = X_to_tensor(d,phase)
elif types[v] == VertexType.H_BOX:
t = H_to_tensor(d,phase)
# Check if H-box has a complex label.
h_label = g.vdata(v, 'label', None)
if h_label is not None:
t = H_to_tensor(d, 0, label=complex(h_label))
else:
t = H_to_tensor(d, phase)
elif types[v] == VertexType.W_INPUT or types[v] == VertexType.W_OUTPUT:
if phase != 0: raise ValueError("Phase on W node")
t = W_to_tensor(d)
Expand Down
Loading