Source code for tqec.interop.pyzx.positioned

"""ZX graph with 3D positions."""

from fractions import Fraction
from typing import Mapping

import pyzx as zx
from pyzx.graph.graph_s import GraphS

from tqec.interop.pyzx.utils import is_boundary
from tqec.utils.exceptions import TQECException
from tqec.utils.position import Direction3D, Position3D


[docs] class PositionedZX:
[docs] def __init__(self, g: GraphS, positions: Mapping[int, Position3D]) -> None: """A ZX graph with 3D positions and additional constraints. The constraints are: 0. All the Boundary vertices are labeled as inputs or outputs. 1. The vertex IDs in the graph match the position keys exactly. 2. The neighbors are all shifted by 1 in the 3D positions. 3. All the spiders are Z(0) or X(0) or Z(1/2) or Boundary spiders. 4. Boundary and Z(1/2) spiders are dangling, and Z(1/2) connects to the time direction. 5. There are no 3D corners. Args: g: The ZX graph. positions: A dictionary mapping vertex IDs to their 3D positions. Raises: TQECException: If the constraints are not satisfied. """ self.check_preconditions(g, positions) self._g = g self._positions: dict[int, Position3D] = dict(positions)
[docs] @staticmethod def check_preconditions(g: GraphS, positions: Mapping[int, Position3D]) -> None: """Check the preconditions for the ZX graph with 3D positions.""" # 0. Check all the Boundary vertices are labeled as inputs or outputs iset, oset = set(g.inputs()), set(g.outputs()) boundaries = {v for v in g.vertices() if is_boundary(g, v)} if len(iset) != len(g.inputs()) or len(oset) != len(g.outputs()): raise TQECException("Duplicate vertices are labeled as inputs or outputs.") if boundaries != iset | oset: raise TQECException( "Inputs + Outputs must be equal to all the boundary vertices." ) # 1. Check the vertex IDs in the graph match the positions if g.vertex_set() != set(positions.keys()): raise TQECException( "The vertex IDs in the ZX graph and the positions do not match." ) # 2. Check the neighbors are all shifted by 1 in the 3D positions for s, t in g.edge_set(): ps, pt = positions[s], positions[t] if not ps.is_neighbour(pt): raise TQECException( f"The 3D positions of the endpoints of the edge {s}--{t} " f"must be neighbors, but got {ps} and {pt}." ) # 3. Check all the spiders are Z(0) or X(0) or Z(1/2) or Boundary spiders for v in g.vertices(): vt = g.type(v) phase = g.phase(v) if (vt, phase) not in [ (zx.VertexType.Z, 0), (zx.VertexType.X, 0), (zx.VertexType.Z, Fraction(1, 2)), (zx.VertexType.BOUNDARY, 0), ]: raise TQECException( f"Unsupported vertex type and phase: {vt} and {phase}." ) # 4. Check Boundary and Z(1/2) spiders are dangling, additionally # Z(1/2) connects to time direction if vt == zx.VertexType.BOUNDARY or phase == Fraction(1, 2): if g.vertex_degree(v) != 1: raise TQECException( "Boundary or Z(1/2) spider must be dangling, but got " f"{len(g.neighbors(v))} neighbors." ) if phase == Fraction(1, 2): nb = next(iter(g.neighbors(v))) vp, nbp = positions[v], positions[nb] if abs(nbp.z - vp.z) != 1: raise TQECException( f"Z(1/2) spider must connect to the time direction, " f"but Z(1/2) at {vp} connects to {nbp}." ) # 5. Check there are no 3D corners for v in g.vertices(): vp = positions[v] if len({_get_direction(vp, positions[u]) for u in g.neighbors(v)}) == 3: raise TQECException(f"ZX graph has a 3D corner at node {v}.")
def __getitem__(self, v: int) -> Position3D: return self._positions[v] @property def g(self) -> GraphS: """Return the internal ZX graph.""" return self._g @property def positions(self) -> dict[int, Position3D]: """Return the 3D positions of the vertices.""" return self._positions
[docs] def get_direction(self, v1: int, v2: int) -> Direction3D: """Return the direction connecting two vertices.""" p1, p2 = self[v1], self[v2] return _get_direction(p1, p2)
def _get_direction(p1: Position3D, p2: Position3D) -> Direction3D: """Return the direction connecting two 3D positions.""" if p1.x != p2.x: return Direction3D.X if p1.y != p2.y: return Direction3D.Y return Direction3D.Z