Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multigraph rules fixes: bialgebra #248

Merged
merged 18 commits into from
Jul 9, 2024
Merged
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
32 changes: 6 additions & 26 deletions pyzx/basicrules.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
'remove_id']

from typing import Tuple, List
from .editor_actions import bialgebra
from .graph.base import BaseGraph, VT, ET
from .rules import apply_rule, w_fusion, z_to_z_box
from .utils import (EdgeType, VertexType, get_w_io, get_z_box_label, is_pauli,
Expand Down Expand Up @@ -84,38 +85,17 @@ def check_strong_comp(g: BaseGraph[VT,ET], v1: VT, v2: VT) -> bool:
is_pauli(g.phase(v1)) and
is_pauli(g.phase(v2)) and
g.connected(v1,v2) and
g.edge_type(g.edge(v1,v2)) == EdgeType.SIMPLE):
EdgeType.SIMPLE in [g.edge_type(edge) for edge in g.edges(v1,v2)]):
return False
return True

def strong_comp(g: BaseGraph[VT,ET], v1: VT, v2: VT) -> bool:
if not check_strong_comp(g, v1, v2): return False

nhd: Tuple[List[VT],List[VT]] = ([],[])
v = (v1,v2)

for i in range(2):
j = (i + 1) % 2
for vn in g.neighbors(v[i]):
if vn != v[j]:
q = 0.4*g.qubit(vn) + 0.6*g.qubit(v[i])
r = 0.4*g.row(vn) + 0.6*g.row(v[i])
newv = g.add_vertex(g.type(v[j]), qubit=q, row=r)
g.add_edge((newv,vn), edgetype=g.edge_type(g.edge(v[i],vn)))
g.set_phase(newv, g.phase(v[j]))
nhd[i].append(newv)

for n1 in nhd[0]:
for n2 in nhd[1]:
g.add_edge((n1,n2))

g.scalar.add_power((len(nhd[0]) - 1) * (len(nhd[1]) - 1))
if g.phase(v1) == 1 and g.phase(v2) == 1:
g.scalar.add_phase(1)

g.remove_vertex(v1)
g.remove_vertex(v2)

etab, rem_verts, rem_edges, check_isolated_vertices = bialgebra(g, [(v1, v2)])
g.remove_edges(rem_edges)
g.remove_vertices(rem_verts)
g.add_edge_table(etab)
return True

def check_copy_X(g: BaseGraph[VT,ET], v: VT) -> bool:
Expand Down
65 changes: 40 additions & 25 deletions pyzx/editor_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,35 +255,50 @@ def bialgebra(g: BaseGraph[VT,ET],
) -> rules.RewriteOutputType[VT,ET]:
rem_verts = []
etab = {}
for v,w in matches:
rem_verts.append(v)
rem_verts.append(w)
new_verts = []
# v is an X-spider, but w is either a Z-spider or an H-box
t = g.type(w)
for n in g.neighbors(v):
if n == w: continue
r = 0.6*g.row(v) + 0.4*g.row(n)
q = 0.6*g.qubit(v) + 0.4*g.qubit(n)
v2 = g.add_vertex(t,q,r)
etab[upair(n,v2)] = [1,0] if g.edge_type(g.edge(n,v)) == EdgeType.SIMPLE else [0,1]
new_verts.append(v2)
if g.type(w) == VertexType.Z:
for v1, v2 in matches:
rem_verts.append(v1)
rem_verts.append(v2)
v = (v1,v2)
new_verts: Tuple[List[VT],List[VT]] = ([],[]) # new vertices for v1 and v2

for i, j in [(0, 1), (1, 0)]:
multi_edge_found = False
for e in g.incident_edges(v[i]):
source, target = g.edge_st(e)
other_vertex = source if source != v[i] else target
if other_vertex != v[j] or multi_edge_found:
q = 0.4*g.qubit(other_vertex) + 0.6*g.qubit(v[i])
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]))
new_verts[i].append(newv)
if other_vertex == v[j]:
q = 0.4*g.qubit(v[i]) + 0.6*g.qubit(other_vertex)
r = 0.4*g.row(v[i]) + 0.6*g.row(other_vertex)
newv2 = g.add_vertex(g.type(v[i]), qubit=q, row=r)
new_verts[j].append(newv2)
other_vertex = newv2
if upair(newv, other_vertex) not in etab:
etab[upair(newv, other_vertex)] = [0, 0]
type_index = 0 if g.edge_type(e) == EdgeType.SIMPLE else 1
etab[upair(newv, other_vertex)][type_index] += 1
elif i == 0: # only add new vertex once
multi_edge_found = True

for n1 in new_verts[0]:
for n2 in new_verts[1]:
if upair(n1,n2) not in etab:
etab[upair(n1,n2)] = [0, 0]
etab[upair(n1,n2)][0] += 1

if g.type(v2) == VertexType.Z:
t = VertexType.X
g.scalar.add_power((g.vertex_degree(v)-2)*(g.vertex_degree(w)-2))
g.scalar.add_power((g.vertex_degree(v1)-2)*(g.vertex_degree(v2)-2))
else: #g.type(w) == VertexType.H_BOX
t = VertexType.Z
g.scalar.add_power(g.vertex_degree(v)-2)
for n in g.neighbors(w):
if n == v: continue
r = 0.6*g.row(w) + 0.4*g.row(n)
q = 0.6*g.qubit(w) + 0.4*g.qubit(n)
w2 = g.add_vertex(t,q,r)
etab[upair(n,w2)] = [1,0] if g.edge_type(g.edge(n,w)) == EdgeType.SIMPLE else [0,1]
for v2 in new_verts:
etab[upair(w2,v2)] = [1,0]
g.scalar.add_power(g.vertex_degree(v1)-2)
return (etab, rem_verts, [], False)


MATCHES_VERTICES = 1
MATCHES_EDGES = 2
Expand Down
49 changes: 31 additions & 18 deletions pyzx/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from typing import Any, Callable, TypeVar, Optional, Union
from typing_extensions import Literal

from collections import Counter
from fractions import Fraction
import itertools

Expand Down Expand Up @@ -99,8 +100,9 @@ def match_bialg_parallel(
tries to find as many as possible.
:rtype: List of 4-tuples ``(v1, v2, neighbors_of_v1,neighbors_of_v2)``
"""
if matchf is not None: candidates = set([e for e in g.edges() if matchf(e)])
else: candidates = g.edge_set()
if matchf is not None: candidates_set = set([e for e in g.edges() if matchf(e)])
else: candidates_set = g.edge_set()
candidates = list(Counter(candidates_set).elements())
phases = g.phases()
types = g.types()

Expand All @@ -118,14 +120,17 @@ def match_bialg_parallel(
((v0t == VertexType.Z and v1t == VertexType.X) or (v0t == VertexType.X and v1t == VertexType.Z))):
v0n = [n for n in g.neighbors(v0) if not n == v1]
v1n = [n for n in g.neighbors(v1) if not n == v0]
if (
all([types[n] == v1t and phases[n] == 0 for n in v0n]) and
all([types[n] == v0t and phases[n] == 0 for n in v1n])):
if (all([types[n] == v1t and phases[n] == 0 for n in v0n]) and # all neighbors of v0 are of the same type as v1
all([types[n] == v0t and phases[n] == 0 for n in v1n]) and # all neighbors of v1 are of the same type as v0
len(g.edges(v0, v1)) == 1 and # there is exactly one edge between v0 and v1
len(g.edges(v0, v0)) == 0 and # there are no self-loops on v0
len(g.edges(v1, v1)) == 0): # there are no self-loops on v1
i += 1
for v in v0n:
for c in g.incident_edges(v): candidates.discard(c)
for v in v1n:
for c in g.incident_edges(v): candidates.discard(c)
for vn in [v0n, v1n]:
for v in vn:
for c in g.incident_edges(v):
if c in candidates:
candidates.remove(c)
m.append((v0,v1,v0n,v1n))
return m

Expand Down Expand Up @@ -401,8 +406,9 @@ def match_pivot_parallel(
consider all edges.
:rtype: List of 4-tuples. See :func:`pivot` for the details.
"""
if matchf is not None: candidates = set([e for e in g.edges() if matchf(e)])
else: candidates = g.edge_set()
if matchf is not None: candidates_set = set([e for e in g.edges() if matchf(e)])
else: candidates_set = g.edge_set()
candidates = list(Counter(candidates_set).elements())
types = g.types()
phases = g.phases()

Expand All @@ -426,6 +432,9 @@ def match_pivot_parallel(
v0n = list(g.neighbors(v0))
v0b: List[VT] = []
for n in v0n:
if len(list(g.edges(v0,n))) != 1:
invalid_edge = True
break
et = g.edge_type(g.edge(v0,n))
if types[n] == VertexType.Z and et == EdgeType.HADAMARD: pass
elif types[n] == VertexType.BOUNDARY: v0b.append(n)
Expand All @@ -449,10 +458,11 @@ def match_pivot_parallel(
if len(v0b) + len(v1b) > 1: continue

i += 1
for v in v0n:
for c in g.incident_edges(v): candidates.discard(c)
for v in v1n:
for c in g.incident_edges(v): candidates.discard(c)
for vn in [v0n, v1n]:
for v in vn:
for c in g.incident_edges(v):
if c in candidates:
candidates.remove(c)
b0 = list(v0b)
b1 = list(v1b)
m.append(((v0,v1),(b0,b1)))
Expand All @@ -465,8 +475,9 @@ def match_pivot_gadget(
"""Like :func:`match_pivot_parallel`, but except for pairings of
Pauli vertices, it looks for a pair of an interior Pauli vertex and an
interior non-Clifford vertex in order to gadgetize the non-Clifford vertex."""
if matchf is not None: candidates = set([e for e in g.edges() if matchf(e)])
else: candidates = g.edge_set()
if matchf is not None: candidates_set = set([e for e in g.edges() if matchf(e)])
else: candidates_set = g.edge_set()
candidates = list(Counter(candidates_set).elements())
types = g.types()
phases = g.phases()
rs = g.rows()
Expand Down Expand Up @@ -524,7 +535,9 @@ def match_pivot_gadget(

m.append(((v0,v1),([],[v])))
i += 1
for c in discard_edges: candidates.discard(c)
for c in discard_edges:
if c in candidates:
candidates.remove(c)
g.add_edges(edge_list,EdgeType.SIMPLE)
return m

Expand Down
Loading