예제 #1
0
    def _flatten_trace_edges(
            self, edges: List[network_components.Edge],
            new_edge_name: Optional[Text]) -> network_components.Edge:
        """Flatten trace edges into single edge.

    Args:
      edges: List of trace edges to flatten
      new_edge_name: Optional name of the new edge created.

    Returns:
      new_edge: The new edge that represents the flattening of the given edges.
    """
        node = edges[
            0].node1  # We are in the trace case, so this is the only node.
        # Flatten all of the edge's axes into a a single list.
        perm_back = [min(e.axis1, e.axis2) for e in edges]
        perm_back += [max(e.axis1, e.axis2) for e in edges]
        perm_front = set(range(len(node.edges))) - set(perm_back)
        perm_front = sorted(perm_front)
        perm = perm_front + perm_back
        new_dim = self.backend.prod(
            [self.backend.shape(node.tensor)[e.axis1] for e in edges])
        node.reorder_axes(perm)
        unaffected_shape = self.backend.shape(node.tensor)[:len(perm_front)]
        new_shape = self.backend.concat([unaffected_shape, [new_dim, new_dim]],
                                        axis=-1)
        node.tensor = self.backend.reshape(node.tensor, new_shape)
        edge1 = network_components.Edge("TraceFront", node, len(perm_front))
        edge2 = network_components.Edge("TraceBack", node, len(perm_front) + 1)
        node.edges = node.edges[:len(perm_front)] + [edge1, edge2]
        new_edge = self.connect(edge1, edge2, new_edge_name)
        node.axis_names = None
        return new_edge
예제 #2
0
    def disconnect(
        self,
        edge: network_components.Edge,
        dangling_edge_name_1: Optional[Text] = None,
        dangling_edge_name_2: Optional[Text] = None
    ) -> List[network_components.Edge]:
        """Break a edge into two dangling edges.

    Args:
      edge: An edge to break.
      dangling_edge_name_1: Optional name to give the new dangling edge 1.
      dangling_edge_name_2: Optional name to give the new dangling edge 2.

    Returns:
      dangling_edge_1: A new dangling edge.
      dangling_edge_2: A new dangling edge.

    Raises:
      ValueError: If input edge is a dangling one.
    """
        if edge.is_dangling():
            raise ValueError(
                "Attempted to break a dangling edge '{}'.".format(edge))
        node1 = edge.node1
        node2 = edge.node2
        dangling_edge_name_1 = self._new_edge_name(dangling_edge_name_1)
        dangling_edge_name_2 = self._new_edge_name(dangling_edge_name_2)
        dangling_edge_1 = network_components.Edge(dangling_edge_name_1, node1,
                                                  edge.axis1)
        dangling_edge_2 = network_components.Edge(dangling_edge_name_2, node2,
                                                  edge.axis2)
        node1.add_edge(dangling_edge_1, edge.axis1, True)
        node2.add_edge(dangling_edge_2, edge.axis2, True)
        self.edge_order.remove(edge)
        return [dangling_edge_1, dangling_edge_2]
예제 #3
0
    def copy(self, conj: bool = False) -> Tuple["TensorNetwork", dict, dict]:
        """

    Return a copy of the TensorNetwork.
    Args:
      conj: Boolean. Whether to conjugate all of the nodes in the
        `TensorNetwork` (useful for calculating norms and reduced density
        matrices).
    Returns:
      A tuple containing:
        TensorNetwork: A copy of the network.
        node_dict: A dictionary mapping the nodes of the original
                   network to the nodes of the copy.
        edge_dict: A dictionary mapping the edges of the original
                   network to the edges of the copy.
    """
        new_net = TensorNetwork(backend=self.backend.name)
        #TODO: add support for copying CopyTensor
        if conj:
            node_dict = {
                node: new_net.add_node(self.backend.conj(node.tensor),
                                       name=node.name,
                                       axis_names=node.axis_names)
                for node in self.nodes_set
            }
        else:
            node_dict = {
                node: new_net.add_node(node.tensor,
                                       name=node.name,
                                       axis_names=node.axis_names)
                for node in self.nodes_set
            }
        edge_dict = {}
        for edge in self.get_all_edges():
            node1 = edge.node1
            axis1 = edge.node1.get_axis_number(edge.axis1)

            if not edge.is_dangling():
                node2 = edge.node2
                axis2 = edge.node2.get_axis_number(edge.axis2)
                new_edge = network_components.Edge(node_dict[node1], axis1,
                                                   edge.name, node_dict[node2],
                                                   axis2)
                new_edge.set_signature(edge.signature)
            else:
                new_edge = network_components.Edge(node_dict[node1], axis1,
                                                   edge.name)

            node_dict[node1].add_edge(new_edge, axis1)
            if not edge.is_dangling():
                node_dict[node2].add_edge(new_edge, axis2)
            edge_dict[edge] = new_edge
        return new_net, node_dict, edge_dict
예제 #4
0
    def connect(self,
                edge1: network_components.Edge,
                edge2: network_components.Edge,
                name: Optional[Text] = None) -> network_components.Edge:
        """Join two dangling edges into a new edge.

    Args:
      edge1: The first dangling edge.
      edge2: The second dangling edge.
      name: Optional name to give the new edge.

    Returns:
      new_edge: A new edge created by joining the two dangling edges together.
    Raises:
      ValueError: If either edge1 or edge2 is not a dangling edge.
    """
        for edge in [edge1, edge2]:
            if not edge.is_dangling():
                raise ValueError(
                    "Edge '{}' is not a dangling edge. "
                    "This edge points to nodes: '{}' and '{}'".format(
                        edge, edge.node1, edge.node2))
        node1 = edge1.node1
        node2 = edge2.node1
        axis1_num = node1.get_axis_number(edge1.axis1)
        axis2_num = node2.get_axis_number(edge2.axis1)
        name = self._new_edge_name(name)
        new_edge = network_components.Edge(name, node1, axis1_num, node2,
                                           axis2_num)
        node1.add_edge(new_edge, axis1_num)
        node2.add_edge(new_edge, axis2_num)
        self.edge_order.append(new_edge)
        return new_edge
예제 #5
0
    def connect(self,
                edge1: network_components.Edge,
                edge2: network_components.Edge,
                name: Optional[Text] = None) -> network_components.Edge:
        """Join two dangling edges into a new edge.

    Args:
      edge1: The first dangling edge.
      edge2: The second dangling edge.
      name: Optional name to give the new edge.

    Returns:
      A new edge created by joining the two dangling edges together.

    Raises:
      ValueError: If either edge1 or edge2 is not a dangling edge or if edge1
        and edge2 are the same edge.
    """
        if edge1 is edge2:
            raise ValueError(
                "Cannot connect and edge '{}' to itself.".format(edge1))
        if edge1.dimension != edge2.dimension:
            raise ValueError("Cannot connect edges of unequal dimension. "
                             "Dimension of edge '{}': {}, "
                             "Dimension of edge '{}': {}.".format(
                                 edge1, edge1.dimension, edge2,
                                 edge2.dimension))
        for edge in [edge1, edge2]:
            if not edge.is_dangling():
                raise ValueError(
                    "Edge '{}' is not a dangling edge. "
                    "This edge points to nodes: '{}' and '{}'".format(
                        edge, edge.node1, edge.node2))
        node1 = edge1.node1
        node2 = edge2.node1
        axis1_num = node1.get_axis_number(edge1.axis1)
        axis2_num = node2.get_axis_number(edge2.axis1)
        name = self._new_edge_name(name)
        new_edge = network_components.Edge(name, node1, axis1_num, node2,
                                           axis2_num)
        new_edge.set_signature(self.edge_increment)
        node1.add_edge(new_edge, axis1_num)
        node2.add_edge(new_edge, axis2_num)
        return new_edge
예제 #6
0
    def flatten_edges(
            self,
            edges: List[network_components.Edge],
            new_edge_name: Optional[Text] = None) -> network_components.Edge:
        """Flatten edges into single edge.

    If two nodes have multiple edges connecting them, it may be
    benifitial to flatten these edges into a single edge to avoid having several
    unnecessary trace edges. This can speed up computation time and reduce
    memory cost.

    Warning: This will remove all axes names.

    Args:
      edges: A list of edges to flatten.
      new_edge_name: Optional name to give to the newly created edge.

    Returns:
      new_edge: The new flattened edge.

    Raises:
      ValueError: If edges is an empty list.
      ValueError: If not all of the edges connect to the same node(s).
      ValueError: If one of the nodes connecting to these edges does not have
        edge definitions for all of its axes.
    """
        if not edges:
            raise ValueError("At least 1 edge must be given.")
        if len(edges) == 1:
            return edges[0]  # Don't bother with reshaping.
        # Set equality is transitive (a=b, b=c, therefore a=c) so it is only
        # necessary to compare the first edge against the rest.
        expected_nodes = set(edges[0].get_nodes())
        for edge in edges:
            if expected_nodes != set(edge.get_nodes()):
                raise ValueError(
                    "Two edges do not share the same nodes. "
                    "'{}'s nodes: '{}', '{}'. '{}'s nodes: '{}', '{}'".format(
                        edges[0], edges[0].node1, edges[0].node2, edge,
                        edge.node1, edge.node2))
        if len(expected_nodes) == 1:
            return self._flatten_trace_edges(edges, new_edge_name)
        # Flatten standard or dangling edges.
        new_dangling_edges = []
        for node in expected_nodes:
            # Required for dangling case.
            if node is None:
                continue
            perm_back = []
            for edge in edges:
                # There will only be 1 edge since we are in the standard edge case.
                perm_back.append(node.edges.index(edge))
            perm_front = sorted(set(range(len(node.edges))) - set(perm_back))
            node.reorder_axes(perm_front + perm_back)
            old_tensor_shape = self.backend.shape(node.tensor)
            # Calculate the new axis dimension as a product of the other
            # axes dimensions.
            flattened_axis_dim = self.backend.prod(
                old_tensor_shape[len(perm_front):])
            new_tensor_shape = self.backend.concat(
                [old_tensor_shape[:len(perm_front)], [flattened_axis_dim]],
                axis=-1)
            new_tensor = self.backend.reshape(node.tensor, new_tensor_shape)
            # Modify the node in place. Currently, this is they only method that
            # modifies a node's tensor.
            node.tensor = new_tensor
            # This Edge is required for the connect call later.
            edge = network_components.Edge(new_edge_name, node,
                                           len(perm_front))
            node.edges = node.edges[:len(perm_front)] + [edge]
            new_dangling_edges.append(edge)
            # TODO: Allow renaming of the new axis.
            node.axis_names = None
        node1, node2 = tuple(expected_nodes)
        # Sets are returned in a random order, so this is how we deal with
        # dangling edges.
        if node1 is None or node2 is None:
            return new_dangling_edges[0]
        return self.connect(new_dangling_edges[0], new_dangling_edges[1],
                            new_edge_name)