"""Provides a class for open graphs."""from__future__importannotationsfromdataclassesimportdataclassfromtypingimportTYPE_CHECKINGimportnetworkxasnxfromgraphix.generatorimportgenerate_from_graphfromgraphix.measurementsimportMeasurementifTYPE_CHECKING:fromgraphix.patternimportPattern
[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:"""Validate the open graph."""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:"""Return `True` if two open graphs implement approximately the same unitary operator. Ensures 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:"""Initialise 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:"""Convert 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)