Skip to content

Commit

Permalink
Merge pull request #271 from alexkoziell/catsdecomp
Browse files Browse the repository at this point in the history
Implement cats when Clifford has phase pi
  • Loading branch information
jvdwetering authored Sep 10, 2024
2 parents c9dd7a8 + 3796a79 commit e07fc26
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 74 deletions.
281 changes: 210 additions & 71 deletions pyzx/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

import numpy as np

from .utils import EdgeType, VertexType, toggle_vertex, toggle_edge, ave_pos
from .utils import EdgeType, FractionLike, VertexType, toggle_vertex, toggle_edge, ave_pos
from . import simplify
from .circuit import Circuit
from .graph.base import BaseGraph,VT,ET
Expand Down Expand Up @@ -575,46 +575,124 @@ def cut_wishbone(g,v,neighs,ph):

return (gLeft,gRight)

def gen_catlike_term(gInit: BaseGraph[VT,ET], verts: List[VT], ph_base, ph_central, ph_appendix, eType_base, eType_appendix, scal_positive, scal_power, scal_phase):
"""
Used for constructing graph terms with local structure of the form of the right-hand side terms of the cat (and magic5) decompositions seen in: https://arxiv.org/pdf/2202.09202.
For example, consider the magic5 decomposition in this above paper. Here, we exchange 5 T-spiders (verts) of our graph (gInit) for three catlike terms.

def gen_catlike_term(g_initial: BaseGraph[VT, ET],
vertices: List[VT],
ph_base: FractionLike,
ph_central: FractionLike,
ph_appendix: FractionLike,
eType_base: EdgeType,
eType_appendix: EdgeType,
scal_positive: bool,
scal_power: int,
scal_phase: FractionLike,
pi_case: bool = False) -> BaseGraph[VT, ET]:
"""Insert a term from a cat or magic5 decomposition into a graph.
Used for constructing graph terms with local structure of the form of the
right-hand side terms of the cat (and magic5) decompositions seen in
https://arxiv.org/pdf/2202.09202.
For example, consider the magic5 decomposition in this above paper. Here,
we exchange 5 T-spiders (verts) of our graph (gInit) for three catlike
terms.
The third and final such term has:
Phase of pi/2 on each of the 5 outgoing edges, i.e. ph_base=Fraction(1,2);
Phase of 0 on the inner central spider to which the others are connected, i.e. ph_central=0;
Phase of pi/4 on the outstanding one-legged 'appendix' spider, i.e. ph_appendix=Fraction(1,4);
Hadamard edges connecting each of the outgoing spiders to the inner central spider, i.e. eType_base=EdgeType.HADAMARD;
Hadamard edge connecting the appendix spider to the central spider, i.e. eType_appendix=EdgeType.HADAMARD;
Negative sign on the scalar factor, i.e. scal_positive=False;
sqrt(2)^3 factor in its scalar, i.e. scal_power=3;
e^{i*pi/4} factor in its scalar, i.e. scal_phase=Fraction(1,4).
- Phase pi/2 on each of the 5 outgoing edges,
i.e. ph_base=Fraction(1, 2)
- Phase 0 on the inner central spider to which the others are
connected, i.e. ph_central=0
- Phase pi/4 on the outstanding one-legged 'appendix' spider,
i.e. ph_appendix=Fraction(1, 4)
- Hadamard edges connecting each of the outgoing spiders to the inner
central spider, i.e. eType_base=EdgeType.HADAMARD
- Hadamard edge connecting the appendix spider to the central spider,
i.e. eType_appendix=EdgeType.HADAMARD.
- A negative sign on the scalar factor, i.e. scal_positive=False
- A sqrt(2)^3 factor in its scalar, i.e. scal_power=3
- An e^{i*pi/4} factor in its scalar, i.e. scal_phase=Fraction(1, 4)
The `pi_case` parameter is used to handle the case where the phase of the
Clifford spider in the state being decomposed is pi.
"""
g = gInit.clone()
g = g_initial.clone()

# Handle central phase for pi case
if pi_case:
ph_central += 1

# Add the scalar factor
g.scalar.add_power(scal_power)
if not scal_positive: g.scalar.add_power(1)
g.scalar.add_phase(scal_phase)

new_v1 = g.add_vertex(qubit=-1,row=-1,ty=VertexType.Z,phase=ph_central)
new_v2 = g.add_vertex(qubit=-2,row=-1,ty=VertexType.Z,phase=ph_appendix)
g.add_edge((new_v1,new_v2),eType_appendix)
for v in verts:
g.add_to_phase(v,ph_base-Fraction(1,4))
g.add_edge((v,new_v1),eType_base)

g.scalar.add_phase(scal_phase if scal_positive else scal_phase + 1)

# Add the central and appendix vertices
new_v1 = g.add_vertex(qubit=-1, row=-1, ty=VertexType.Z, phase=ph_central)
new_v2 = g.add_vertex(qubit=-2, row=-1, ty=VertexType.Z, phase=ph_appendix)

# Connect the appendix vertex to the central vertex
g.add_edge((new_v1, new_v2), eType_appendix)

# Connect the central vertex to the outgoing vertices
# and give each outgoing vertex the appropriate phase
for i, v in enumerate(vertices):
phase_change = ph_base
# Handle pi case. We make an arbitrary choice of the last vertex
# as where to apply the correction.
if pi_case and i == len(vertices) - 1:
phase_change -= Fraction(1, 2)
phase_change *= -1
# Subtracting pi/4 and adding ph_base is equivalent to
# unfusing, decomposing, then refusing.
phase_change -= Fraction(1, 4)

g.add_to_phase(v, phase_change)
g.add_edge((v, new_v1), eType_base)

return g

def check_catn(g: BaseGraph[VT,ET], vert: VT, n):
"""Runs assertion checks to ensure the cat_n decomposition is applicable to vertex v of graph g. See: https://arxiv.org/pdf/2202.09202."""
assert n in [3,4,5,6], "Cat"+str(n)+" decomposition not implemented. Try cat3, cat4, cat5, or cat6 instead."
assert vert in g.vertices(), "Vertex "+str(vert)+" does not exist in graph"
assert g.phase(vert) == 0, "The cat"+str(n)+" decomposition acts on a phaseless spider but specified vertex has phase "+str(g.phase(vert)) # TODO: the cat decomps should be updated to work even if the central phase is pi (i.e. not just zero)
assert len(g.neighbors(vert)) == n, "The cat"+str(n)+" decomposition acts on a "+str(n)+"-degree spider but specified vertex has degree "+str(len(g.neighbors(vert)))
for v in g.neighbors(vert):
assert (g.edge_type(g.edge(vert,v)) == EdgeType.HADAMARD and g.type(v) == g.type(vert)) or \
(g.edge_type(g.edge(vert,v)) == EdgeType.SIMPLE and g.type(v) == toggle_vertex(g.type(vert))), \
"The cat"+str(n)+" decomposition must act on a spider with "+str(n)+" opposite colour neighbours (or like-coloured with Hadamard edges)"

def check_catn(g: BaseGraph[VT, ET], vertex: VT, n: int) -> bool:
"""Ensure cat `n` decomposition is applicable to a vertex of graph `g`.
See https://arxiv.org/pdf/2202.09202.
"""
if n not in (3, 4, 5, 6):
raise ValueError(f'Cat {n} decomposition not implemented. '
+ 'Try cat3, cat4, cat5, or cat6 instead.')

if vertex not in g.vertices():
raise ValueError(f'Vertex {vertex} does not exist in graph')

phase = g.phase(vertex)
if phase % 1 != 0:
raise ValueError(f'The cat {n} decomposition function can only be '
+ 'applied to a phaseless or pi-phase spider, but '
+ f'specified vertex has phase {phase}')
neighbors = g.neighbors(vertex)
if len(neighbors) != n:
raise ValueError(f'The cat {n} decomposition acts on a degree {n} '
+ 'spider, but specified vertex has degree '
+ str(len(neighbors)))

vertex_type = g.type(vertex)
for neighbor in neighbors:
neighbor_type = g.type(neighbor)
edge_type = g.edge_type(g.edge(vertex, neighbor))
if (
(edge_type != EdgeType.HADAMARD
or neighbor_type is not vertex_type)
and
(edge_type != EdgeType.SIMPLE
or neighbor_type is not toggle_vertex(vertex_type))
):
raise ValueError(
f'The cat {n} decomposition must act on a spider with {n} '
+ 'opposite colour neighbours '
+ '(or like-coloured with Hadamard edges).')

return True


def apply_magic5(g: BaseGraph[VT,ET], verts: List[VT]) -> SumGraph:
"""Apply the magic5 decomposition to vertices verts of graph g. See: https://arxiv.org/pdf/2202.09202."""
assert len(verts) == 5, "The magic5 decomposition acts on 5 spiders but you specified "+str(len(verts))
Expand All @@ -626,47 +704,108 @@ def apply_magic5(g: BaseGraph[VT,ET], verts: List[VT]) -> SumGraph:

return SumGraph([g_A, g_B, g_C])

# TODO: the cat decomps should be updated to work even if the central phase is pi (i.e. not just zero)
def apply_cat3(g: BaseGraph[VT,ET], vert: VT) -> SumGraph:
"""Apply the cat3 decomposition to vertex verts of graph g. See: https://arxiv.org/pdf/2202.09202."""
check_catn(g,vert,3)
verts = list(g.neighbors(vert))
g_A = gen_catlike_term(g, verts, 0, Fraction(-1,2), 0, EdgeType.SIMPLE, EdgeType.HADAMARD, True, -1, Fraction(-1,4))
g_B = gen_catlike_term(g, verts, 0, 0, 0, EdgeType.HADAMARD, EdgeType.SIMPLE, True, 0, Fraction(1,2))
g_A.remove_vertex(vert)
g_B.remove_vertex(vert)

def apply_cat3(g: BaseGraph[VT, ET], vertex: VT) -> SumGraph:
"""Apply the cat3 decomposition to a vertex of graph `g`.
See https://arxiv.org/pdf/2202.09202."""
# Check the decomposition is applicable
check_catn(g, vertex, 3)
# Generate the terms of the decomposition
neighbors = list(g.neighbors(vertex))
pi_case = g.phase(vertex) == 1
g_A = gen_catlike_term(g, neighbors,
0, Fraction(-1, 2), 0,
EdgeType.SIMPLE, EdgeType.HADAMARD,
True, -1, Fraction(-1, 4), pi_case=pi_case)
g_B = gen_catlike_term(g, neighbors,
0, 0, 0,
EdgeType.HADAMARD, EdgeType.SIMPLE,
True, 0, Fraction(1, 2), pi_case=pi_case)
# Remove the decomposed vertices from the terms
g_A.remove_vertex(vertex)
g_B.remove_vertex(vertex)

return SumGraph([g_A, g_B])

def apply_cat4(g: BaseGraph[VT,ET], vert: VT) -> SumGraph:
"""Apply the cat4 decomposition to vertex verts of graph g. See: https://arxiv.org/pdf/2202.09202."""
check_catn(g,vert,4)
verts = list(g.neighbors(vert))
g_A = gen_catlike_term(g, verts, 0, Fraction(-1,2), 0, EdgeType.SIMPLE, EdgeType.SIMPLE, True, -1, Fraction(-1,4))
g_B = gen_catlike_term(g, verts, 0, 0, 0, EdgeType.HADAMARD, EdgeType.SIMPLE, True, 0, Fraction(1,2))
g_A.remove_vertex(vert)
g_B.remove_vertex(vert)

def apply_cat4(g: BaseGraph[VT, ET], vertex: VT) -> SumGraph:
"""Apply the cat4 decomposition to a vertex of graph `g`.
See https://arxiv.org/pdf/2202.09202."""
# Check the decomposition is applicable
check_catn(g, vertex, 4)
# Generate the terms of the decomposition
neighbors = list(g.neighbors(vertex))
pi_case = g.phase(vertex) == 1
g_A = gen_catlike_term(g, neighbors,
0, Fraction(-1, 2), 0,
EdgeType.SIMPLE, EdgeType.SIMPLE,
True, -1, Fraction(-1, 4), pi_case=pi_case)
g_B = gen_catlike_term(g, neighbors,
0, 0, 0,
EdgeType.HADAMARD, EdgeType.SIMPLE,
True, 0, Fraction(1, 2), pi_case=pi_case)
# Remove the decomposed vertices from the terms
g_A.remove_vertex(vertex)
g_B.remove_vertex(vertex)

return SumGraph([g_A, g_B])

def apply_cat5(g: BaseGraph[VT,ET], vert: VT) -> SumGraph:
"""Apply the cat5 decomposition to vertex verts of graph g. See: https://arxiv.org/pdf/2202.09202."""
check_catn(g,vert,5)
verts = list(g.neighbors(vert))
g_A = gen_catlike_term(g, verts, 0, Fraction(-1,2), 0, EdgeType.SIMPLE, EdgeType.HADAMARD, True, -2, 0)
g_B = gen_catlike_term(g, verts, 0, 0, 0, EdgeType.HADAMARD, EdgeType.SIMPLE, True, -1, Fraction(3,4))
g_C = gen_catlike_term(g, verts, Fraction(1,2), 0, 0, EdgeType.HADAMARD, EdgeType.SIMPLE, False, -1, Fraction(1,4))
g_A.remove_vertex(vert)
g_B.remove_vertex(vert)
g_C.remove_vertex(vert)

def apply_cat5(g: BaseGraph[VT, ET], vertex: VT) -> SumGraph:
"""Apply the cat5 decomposition to a vertex of graph `g`.
See https://arxiv.org/pdf/2202.09202."""
# Check the decomposition is applicable
check_catn(g, vertex, 5)
# Generate the terms of the decomposition
neighbors = list(g.neighbors(vertex))
pi_case = g.phase(vertex) == 1
g_A = gen_catlike_term(g, neighbors,
0, Fraction(-1, 2), 0,
EdgeType.SIMPLE, EdgeType.HADAMARD,
True, -2, 0, pi_case=pi_case)
g_B = gen_catlike_term(g, neighbors,
0, 0, 0,
EdgeType.HADAMARD, EdgeType.SIMPLE,
True, -1, Fraction(3, 4), pi_case=pi_case)
g_C = gen_catlike_term(g, neighbors,
Fraction(1, 2), 0, 0,
EdgeType.HADAMARD, EdgeType.SIMPLE,
False, -1, Fraction(1, 4), pi_case=pi_case)
# Remove the decomposed vertices from the terms
g_A.remove_vertex(vertex)
g_B.remove_vertex(vertex)
g_C.remove_vertex(vertex)

return SumGraph([g_A, g_B, g_C])

def apply_cat6(g: BaseGraph[VT,ET], vert: VT) -> SumGraph:
"""Apply the cat6 decomposition to vertex verts of graph g. See: https://arxiv.org/pdf/2202.09202."""
check_catn(g,vert,6)
verts = list(g.neighbors(vert))
g_A = gen_catlike_term(g, verts, 0, Fraction(-1,2), 0, EdgeType.SIMPLE, EdgeType.SIMPLE, True, -2, 0)
g_B = gen_catlike_term(g, verts, 0, 0, 0, EdgeType.HADAMARD, EdgeType.SIMPLE, True, -1, Fraction(3,4))
g_C = gen_catlike_term(g, verts, Fraction(1,2), 0, 0, EdgeType.HADAMARD, EdgeType.SIMPLE, False, -1, Fraction(1,4))
g_A.remove_vertex(vert)
g_B.remove_vertex(vert)
g_C.remove_vertex(vert)

def apply_cat6(g: BaseGraph[VT, ET], vertex: VT) -> SumGraph:
"""Apply the cat6 decomposition to a vertex of graph `g`.
See https://arxiv.org/pdf/2202.09202."""
# Check the decomposition is applicable
check_catn(g, vertex, 6)
# Generate the terms of the decomposition
verts = list(g.neighbors(vertex))
pi_case = g.phase(vertex) == 1
g_A = gen_catlike_term(g, verts,
0, Fraction(-1, 2), 0,
EdgeType.SIMPLE, EdgeType.SIMPLE,
True, -2, 0, pi_case=pi_case)
g_B = gen_catlike_term(g, verts,
0, 0, 0,
EdgeType.HADAMARD, EdgeType.SIMPLE,
True, -1, Fraction(3, 4), pi_case=pi_case)
g_C = gen_catlike_term(g, verts,
Fraction(1, 2), 0, 0,
EdgeType.HADAMARD, EdgeType.SIMPLE,
False, -1, Fraction(1, 4), pi_case=pi_case)
# Remove the decomposed vertices from the terms
g_A.remove_vertex(vertex)
g_B.remove_vertex(vertex)
g_C.remove_vertex(vertex)

return SumGraph([g_A, g_B, g_C])
42 changes: 39 additions & 3 deletions tests/test_simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
# 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.


from fractions import Fraction
import unittest
import sys
import random
Expand All @@ -25,7 +24,13 @@
sys.path.append('..')
sys.path.append('.')
from pyzx.circuit import Circuit
from pyzx.simulate import replace_magic_states, cut_vertex, cut_edge
from pyzx.graph import Graph, EdgeType, Scalar
from pyzx.simulate import (
replace_magic_states,
cut_vertex,
cut_edge,
gen_catlike_term
)
from pyzx.generate import cliffords
from pyzx.simplify import full_reduce

Expand Down Expand Up @@ -81,6 +86,37 @@ def test_edge_cut(self,repeats=20):
scalCut = round_complex(g0.scalar.to_number()+g1.scalar.to_number(),3) # the sum of scalars from the cut graph
assert(scal == scalCut)

def test_cat_decomp_scalar(self) -> None:
"""Test the scalars of generated cat-like terms are correct."""
g = Graph()

# Generate random scalar parameters
phase = Fraction(random.randint(1, 10), random.randint(1, 10))
power = random.randint(1, 10)
positive = bool(random.randint(0, 1))

# Generate cat-like term with random scalar parameters
G = gen_catlike_term(g, [],
Fraction(1, 1),
Fraction(1, 1),
Fraction(1, 1),
EdgeType.SIMPLE,
EdgeType.SIMPLE,
positive,
power,
phase,
False)

# Create expected scalar
s = Scalar()
s.add_power(power)
s.add_phase(phase)
if not positive:
s.add_phase(1)

# Check if the scalar from generated term is correct
self.assertTrue(G.scalar.to_number() == s.to_number())


if __name__ == '__main__':
unittest.main()

0 comments on commit e07fc26

Please sign in to comment.