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

Implement cats when Clifford has phase pi #271

Merged
merged 7 commits into from
Sep 10, 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
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()
Loading