"""Provides a class for open graphs."""from__future__importannotationsimportmathfromdataclassesimportdataclassfromtypingimportTYPE_CHECKINGimportnetworkxasnxfromgraphix.generatorimportgenerate_from_graphifTYPE_CHECKING:fromgraphix.patternimportPatternfromgraphix.pauliimportPlane
[docs]@dataclass(frozen=True)classMeasurement:"""An MBQC measurement. :param angle: the angle of the measurement. Should be between [0, 2) :param plane: the measurement plane """angle:floatplane:Planedefisclose(self,other:Measurement,rel_tol:float=1e-09,abs_tol:float=0.0)->bool:"""Compares if two measurements have the same plane and their angles are close. Example ------- >>> from graphix.opengraph import Measurement >>> from graphix.pauli import Plane >>> Measurement(0.0, Plane.XY).isclose(Measurement(0.0, Plane.XY)) True >>> Measurement(0.0, Plane.XY).isclose(Measurement(0.0, Plane.YZ)) False >>> Measurement(0.1, Plane.XY).isclose(Measurement(0.0, Plane.XY)) False """returnmath.isclose(self.angle,other.angle,rel_tol=rel_tol,abs_tol=abs_tol)andself.plane==other.plane
[docs]@dataclass(frozen=True)classOpenGraph:"""Open graph contains the graph, measurement, and input and output nodes. This is the graph we wish to implement deterministically :param inside: the underlying graph state :param measurements: a dictionary whose key is the ID of a node and the value is the measurement at that node :param inputs: an ordered list of node IDs that are inputs to the graph :param outputs: an ordered list of node IDs that are outputs of the graph Example ------- >>> import networkx as nx >>> from graphix.opengraph import OpenGraph, Measurement >>> >>> inside_graph = nx.Graph([(0, 1), (1, 2), (2, 0)]) >>> >>> measurements = {i: Measurement(0.5 * i, Plane.XY) for i in range(2)} >>> inputs = [0] >>> outputs = [2] >>> og = OpenGraph(inside_graph, measurements, inputs, outputs) """inside:nx.Graphmeasurements:dict[int,Measurement]inputs:list[int]# Inputs are orderedoutputs:list[int]# Outputs are ordereddef__post_init__(self)->None:ifnotall(nodeinself.inside.nodesfornodeinself.measurements):raiseValueError("All measured nodes must be part of the graph's nodes.")ifnotall(nodeinself.inside.nodesfornodeinself.inputs):raiseValueError("All input nodes must be part of the graph's nodes.")ifnotall(nodeinself.inside.nodesfornodeinself.outputs):raiseValueError("All output nodes must be part of the graph's nodes.")ifany(nodeinself.outputsfornodeinself.measurements):raiseValueError("Output node cannot be measured.")iflen(set(self.inputs))!=len(self.inputs):raiseValueError("Input nodes contain duplicates.")iflen(set(self.outputs))!=len(self.outputs):raiseValueError("Output nodes contain duplicates.")defisclose(self,other:OpenGraph,rel_tol:float=1e-09,abs_tol:float=0.0)->bool:"""Compared two open graphs implement approximately the same unitary operator by ensuring the structure of the graphs are the same and all measurement angles are sufficiently close. This doesn't check they are equal up to an isomorphism"""ifnotnx.utils.graphs_equal(self.inside,other.inside):returnFalseifself.inputs!=other.inputsorself.outputs!=other.outputs:returnFalseifset(self.measurements.keys())!=set(other.measurements.keys()):returnFalsereturnall(m.isclose(other.measurements[node])fornode,minself.measurements.items())@classmethoddeffrom_pattern(cls,pattern:Pattern)->OpenGraph:"""Initialises an `OpenGraph` object based on the resource-state graph associated with the measurement pattern."""g=nx.Graph()nodes,edges=pattern.get_graph()g.add_nodes_from(nodes)g.add_edges_from(edges)inputs=pattern.input_nodesoutputs=pattern.output_nodesmeas_planes=pattern.get_meas_plane()meas_angles=pattern.get_angles()meas={node:Measurement(meas_angles[node],meas_planes[node])fornodeinmeas_angles}returncls(g,meas,inputs,outputs)defto_pattern(self)->Pattern:"""Converts the `OpenGraph` into a `Pattern`. Will raise an exception if the open graph does not have flow, gflow, or Pauli flow. The pattern will be generated using maximally-delayed flow. """g=self.inside.copy()inputs=self.inputsoutputs=self.outputsmeas=self.measurementsangles={node:m.anglefornode,minmeas.items()}planes={node:m.planefornode,minmeas.items()}returngenerate_from_graph(g,angles,inputs,outputs,planes)