def _base_nodes( nodes: Iterable[BaseNode], algorithm: utils.Algorithm, output_edge_order: Optional[Sequence[Edge]] = None) -> BaseNode: """Base method for all `opt_einsum` contractors. Args: nodes: A collection of connected nodes. algorithm: `opt_einsum` contraction method to use. output_edge_order: An optional list of edges. Edges of the final node in `nodes_set` are reordered into `output_edge_order`; if final node has more than one edge, `output_edge_order` must be pronvided. Returns: Final node after full contraction. """ nodes_set = set(nodes) check_connected(nodes_set) edges = get_all_edges(nodes_set) #output edge order has to be determinded before any contraction #(edges are refreshed after contractions) if output_edge_order is None: output_edge_order = list(get_subgraph_dangling(nodes)) if len(output_edge_order) > 1: raise ValueError( "The final node after contraction has more than " "one remaining edge. In this case `output_edge_order` " "has to be provided.") if set(output_edge_order) != get_subgraph_dangling(nodes): raise ValueError("output edges are not equal to the remaining " "non-contracted edges of the final node.") for edge in edges: if not edge.is_disabled: #if its disabled we already contracted it if edge.is_trace(): nodes_set.remove(edge.node1) nodes_set.add(contract_parallel(edge)) if len(nodes_set) == 1: # There's nothing to contract. return list(nodes_set)[0].reorder_edges(output_edge_order) # Then apply `opt_einsum`'s algorithm path, nodes = utils.get_path(nodes_set, algorithm) for a, b in path: new_node = nodes[a] @ nodes[b] nodes.append(new_node) nodes = utils.multi_remove(nodes, [a, b]) # if the final node has more than one edge, # output_edge_order has to be specified final_node = nodes[0] # nodes were connected, we checked this final_node.reorder_edges(output_edge_order) return final_node
def check_network(self) -> None: """Check that the network has the expected dimensionality. This checks that all input and output edges are dangling and that there are no other dangling edges (except any specified in `ignore_edges`). If not, an exception is raised. """ for (i, e) in enumerate(self.out_edges): if not e.is_dangling(): raise ValueError("Output edge {} is not dangling!".format(i)) for (i, e) in enumerate(self.in_edges): if not e.is_dangling(): raise ValueError("Input edge {} is not dangling!".format(i)) for e in self.ignore_edges: if not e.is_dangling(): raise ValueError( "ignore_edges contains non-dangling edge: {}".format( str(e))) known_edges = set(self.in_edges) | set( self.out_edges) | self.ignore_edges all_dangling_edges = get_subgraph_dangling(self.nodes) if known_edges != all_dangling_edges: raise ValueError( "The network includes unexpected dangling edges (that " "are not members of ignore_edges).")
def _get_path_nodes(nodes: Iterable[BaseNode], algorithm: Algorithm ) -> Tuple[List[Tuple[int, int]], List[BaseNode]]: """Calculates the contraction paths using `opt_einsum` methods. Args: nodes: An iterable of nodes. algorithm: `opt_einsum` method to use for calculating the contraction path. Returns: The optimal contraction path as returned by `opt_einsum`. """ sorted_nodes = sorted(nodes, key=lambda n: n.signature) input_sets = [set(node.edges) for node in sorted_nodes] output_set = get_subgraph_dangling(nodes) size_dict = {edge: edge.dimension for edge in get_all_edges(nodes)} return algorithm(input_sets, output_set, size_dict), sorted_nodes
def get_path( nodes: Iterable[AbstractNode], algorithm: Algorithm ) -> Tuple[List[Tuple[int, int]], List[AbstractNode]]: """Calculates the contraction paths using `opt_einsum` methods. Args: nodes: An iterable of nodes. algorithm: `opt_einsum` method to use for calculating the contraction path. Returns: The optimal contraction path as returned by `opt_einsum`. """ nodes = list(nodes) input_sets = [set(node.edges) for node in nodes] output_set = get_subgraph_dangling(nodes) size_dict = {edge: edge.dimension for edge in get_all_edges(nodes)} return algorithm(input_sets, output_set, size_dict), nodes