Beispiel #1
0
    def remove_node(
        self, node: network_components.BaseNode
    ) -> Tuple[Dict[Text, network_components.Edge], Dict[
            int, network_components.Edge]]:
        """Remove a node from the network.

    Args:
      node: The node to be removed.

    Returns:
      broken_edges_by_name: A Dictionary mapping `node`'s axis names to
        the newly broken edges.
      broken_edges_by_axis: A Dictionary mapping `node`'s axis numbers
        to the newly broken edges.

    Raises:
      ValueError: If the node isn't in the network.
    """
        if node not in self:
            raise ValueError("Node '{}' is not in the network.".format(node))
        broken_edges_by_name = {}
        broken_edges_by_axis = {}
        print(len(node.axis_names))
        for i, name in enumerate(node.axis_names):
            print(i, name)
            if not node[i].is_dangling() and not node[i].is_trace():
                edge1, edge2 = self.disconnect(node[i])
                new_broken_edge = edge1 if edge1.node1 is not node else edge2
                broken_edges_by_axis[i] = new_broken_edge
                broken_edges_by_name[name] = new_broken_edge

        self.nodes_set.remove(node)
        node.disable()
        return broken_edges_by_name, broken_edges_by_axis
Beispiel #2
0
    def _remove_edges(self, edges: Set[network_components.Edge],
                      node1: network_components.BaseNode,
                      node2: network_components.BaseNode,
                      new_node: network_components.BaseNode) -> None:
        """Collapse a list of edges shared by two nodes in the network.

    Collapses the edges and updates the rest of the network.
    The nodes that currently share the edges in `edges` must be supplied as
    `node1` and `node2`. The ordering of `node1` and `node2` must match the
    axis ordering of `new_node` (as determined by the contraction procedure).

    Args:
      edges: The edges to contract.
      node1: The old node that supplies the first edges of `new_node`.
      node2: The old node that supplies the last edges of `new_node`.
      new_node: The new node that represents the contraction of the two old
        nodes.

    Raises:
      Value Error: If edge isn't in the network.
    """
        network_components._remove_edges(edges, node1, node2, new_node)

        if node1 in self.nodes_set:
            self.nodes_set.remove(node1)
        if node2 in self.nodes_set:
            self.nodes_set.remove(node2)
        if not node1.is_disabled:
            node1.disable()
        if not node1.is_disabled:
            node2.disable()
Beispiel #3
0
  def outer_product(self,
                    node1: network_components.BaseNode,
                    node2: network_components.BaseNode,
                    name: Optional[Text] = None) -> network_components.BaseNode:
    """Calculates an outer product of the two nodes.

    This causes the nodes to combine their edges and axes, so the shapes are
    combined. For example, if `a` had a shape (2, 3) and `b` had a shape
    (4, 5, 6), then the node `net.outer_product(a, b) will have shape
    (2, 3, 4, 5, 6).

    Args:
      node1: The first node. The axes on this node will be on the left side of
        the new node.
      node2: The second node. The axes on this node will be on the right side of
        the new node.
      name: Optional name to give the new node created.

    Returns:
      A new node. Its shape will be node1.shape + node2.shape
    """
    new_node = self.add_node(
        network_components.outer_product(node1, node2, name, axis_names=None))
    # Remove the nodes from the set.
    if node1 in self.nodes_set:
      self.nodes_set.remove(node1)
    if node2 in self.nodes_set:
      self.nodes_set.remove(node2)
    if not node1.is_disabled:
      node1.disable()
    if not node2.is_disabled:
      node2.disable()

    return new_node
Beispiel #4
0
  def split_node_rq(
      self,
      node: network_components.BaseNode,
      left_edges: List[network_components.Edge],
      right_edges: List[network_components.Edge],
      left_name: Optional[Text] = None,
      right_name: Optional[Text] = None,      
  ) -> Tuple[network_components.BaseNode, network_components.BaseNode]:
    """Split a `Node` using RQ (reversed QR) decomposition

    Let M be the matrix created by flattening left_edges and right_edges into
    2 axes. Let :math:`QR = M^*` be the QR Decomposition of 
    :math:`M^*`. This will split the network into 2 nodes. The left node's 
    tensor will be :math:`R^*` (a lower triangular matrix) and the right node's tensor will be 
    :math:`Q^*` (an orthonormal matrix)

    Args:
      node: The node you want to split.
      left_edges: The edges you want connected to the new left node.
      right_edges: The edges you want connected to the new right node.
      left_name: The name of the new left node. If `None`, a name will be generated
        automatically.
      right_name: The name of the new right node. If `None`, a name will be generated
        automatically.

    Returns:
      A tuple containing:
        left_node: 
          A new node created that connects to all of the `left_edges`.
          Its underlying tensor is :math:`Q`
        right_node: 
          A new node created that connects to all of the `right_edges`.
          Its underlying tensor is :math:`R`
    """
    node.reorder_edges(left_edges + right_edges)
    q, r = self.backend.qr_decomposition(node.tensor, len(left_edges))
    left_node = self.add_node(q, name=left_name)
    for i, edge in enumerate(left_edges):
      left_node.add_edge(edge, i)
      edge.update_axis(i, node, i, left_node)
    right_node = self.add_node(r, name=right_name)
    for i, edge in enumerate(right_edges):
      # i + 1 to account for the new edge.
      right_node.add_edge(edge, i + 1)
      edge.update_axis(i + len(left_edges), node, i + 1, right_node)
    self.connect(left_node[-1], right_node[0])
    self.nodes_set.remove(node)
    return left_node, right_node
def transpose(node: BaseNode,
              permutation: Sequence[Union[Text, int]],
              name: Optional[Text] = None,
              axis_names: Optional[List[Text]] = None) -> BaseNode:
    """Transpose `node`
  Args:
    node: A `BaseNode`. 
    permutation: A list of int ro str. The permutation of the axis
    name: Optional name to give the new node.
    axis_names: Optional list of names for the axis.
  Returns:
    A new node. The transpose of `node`.
  Raises:
    TypeError: If `node` has no `backend` attribute.
    ValueError: If either `permutation` is not the same as expected or
      if you try to permute with a trace edge.
    AttributeError: If `node` has no tensor.
  """

    if not hasattr(node, 'backend'):
        raise TypeError('Node {} of type {} has no `backend`'.format(
            node, type(node)))

    perm = [node.get_axis_number(p) for p in permutation]
    if not axis_names:
        axis_names = node.axis_names

    new_node = Node(node.tensor,
                    name=name,
                    axis_names=node.axis_names,
                    backend=node.backend.name)
    return new_node.reorder_axes(perm)
Beispiel #6
0
  def contract_between(
      self,
      node1: network_components.BaseNode,
      node2: network_components.BaseNode,
      name: Optional[Text] = None,
      allow_outer_product: bool = False,
      output_edge_order: Optional[Sequence[network_components.Edge]] = None,
  ) -> network_components.BaseNode:
    """Contract all of the edges between the two given nodes.

    Args:
      node1: The first node.
      node2: The second node.
      name: Name to give to the new node created.
      allow_outer_product: Optional boolean. If two nodes do not share any edges
        and `allow_outer_product` is set to `True`, then we return the outer
        product of the two nodes. Else, we raise a `ValueError`.
      output_edge_order: Optional sequence of Edges. When not `None`, must 
        contain all edges belonging to, but not shared by `node1` and `node2`.
        The axes of the new node will be permuted (if necessary) to match this
        ordering of Edges.

    Returns:
      The new node created.

    Raises:
      ValueError: If no edges are found between node1 and node2 and
        `allow_outer_product` is set to `False`.
    """
    new_node = self.add_node(
        network_components.contract_between(
            node1,
            node2,
            name,
            allow_outer_product,
            output_edge_order,
            axis_names=None))
    if node1 in self.nodes_set:
      self.nodes_set.remove(node1)
    if node2 in self.nodes_set:
      self.nodes_set.remove(node2)
    if not node1.is_disabled:
      node1.disable()
    if not node2.is_disabled:
      node2.disable()

    return new_node
Beispiel #7
0
    def split_node_rq(
        self,
        node: network_components.BaseNode,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        left_name: Optional[Text] = None,
        right_name: Optional[Text] = None,
        edge_name: Optional[Text] = None,
    ) -> Tuple[network_components.BaseNode, network_components.BaseNode]:
        """Split a `Node` using RQ (reversed QR) decomposition

    Let M be the matrix created by flattening left_edges and right_edges into
    2 axes. Let :math:`QR = M^*` be the QR Decomposition of 
    :math:`M^*`. This will split the network into 2 nodes. The left node's 
    tensor will be :math:`R^*` (a lower triangular matrix) and the right node's tensor will be 
    :math:`Q^*` (an orthonormal matrix)

    Args:
      node: The node you want to split.
      left_edges: The edges you want connected to the new left node.
      right_edges: The edges you want connected to the new right node.
      left_name: The name of the new left node. If `None`, a name will be generated
        automatically.
      right_name: The name of the new right node. If `None`, a name will be generated
        automatically.
      edge_name: The name of the new `Edge` connecting the new left and right node. 
        If `None`, a name will be generated automatically.

    Returns:
      A tuple containing:
        left_node: 
          A new node created that connects to all of the `left_edges`.
          Its underlying tensor is :math:`Q`
        right_node: 
          A new node created that connects to all of the `right_edges`.
          Its underlying tensor is :math:`R`
    """
        r, q = network_operations.split_node_rq(node, left_edges, right_edges,
                                                left_name, right_name,
                                                edge_name)
        left_node = self.add_node(r)
        right_node = self.add_node(q)

        self.nodes_set.remove(node)
        node.disable()
        return left_node, right_node
Beispiel #8
0
  def _remove_trace_edge(self, edge: network_components.Edge,
                         new_node: network_components.BaseNode) -> None:
    """Collapse a trace edge.

    Collapses a trace edge and updates the network.

    Args:
      edge: The edge to contract.
      new_node: The new node created after contraction.

    Returns:
      The node that had the contracted edge.

    Raises:
      ValueError: If edge is not a trace edge.
    """
    if edge.is_dangling():
      raise ValueError("Attempted to remove dangling edge '{}'.".format(edge))
    if edge.node1 is not edge.node2:
      raise ValueError("Edge '{}' is not a trace edge.".format(edge))
    axes = sorted([edge.axis1, edge.axis2])
    node_edges = edge.node1.edges[:]
    node_edges.pop(axes[0])
    node_edges.pop(axes[1] - 1)
    seen_edges = set()
    for tmp_edge in node_edges:
      if tmp_edge in seen_edges:
        continue
      else:
        seen_edges.add(tmp_edge)
      if tmp_edge.node1 is edge.node1:
        to_reduce = 0
        to_reduce += 1 if tmp_edge.axis1 > axes[0] else 0
        to_reduce += 1 if tmp_edge.axis1 > axes[1] else 0
        tmp_edge.axis1 -= to_reduce
        tmp_edge.node1 = new_node
      if tmp_edge.node2 is edge.node1:
        to_reduce = 0
        to_reduce += 1 if tmp_edge.axis2 > axes[0] else 0
        to_reduce += 1 if tmp_edge.axis2 > axes[1] else 0
        tmp_edge.axis2 -= to_reduce
        tmp_edge.node2 = new_node
    # Update edges for the new node.
    for i, e in enumerate(node_edges):
      new_node.add_edge(e, i)
    self.nodes_set.remove(edge.node1)
Beispiel #9
0
    def split_node(
        self,
        node: network_components.BaseNode,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        max_singular_values: Optional[int] = None,
        max_truncation_err: Optional[float] = None,
        left_name: Optional[Text] = None,
        right_name: Optional[Text] = None,
        edge_name: Optional[Text] = None,
    ) -> Tuple[network_components.BaseNode, network_components.BaseNode,
               Tensor]:
        """Split a `Node` using Singular Value Decomposition.

    Let M be the matrix created by flattening left_edges and right_edges into
    2 axes. Let :math:`U S V^* = M` be the Singular Value Decomposition of 
    :math:`M`. This will split the network into 2 nodes. The left node's 
    tensor will be :math:`U \\sqrt{S}` and the right node's tensor will be 
    :math:`\\sqrt{S} V^*` where :math:`V^*` is
    the adjoint of :math:`V`.

    The singular value decomposition is truncated if `max_singular_values` or
    `max_truncation_err` is not `None`.

    The truncation error is the 2-norm of the vector of truncated singular
    values. If only `max_truncation_err` is set, as many singular values will
    be truncated as possible while maintaining:
    `norm(truncated_singular_values) <= max_truncation_err`.

    If only `max_singular_values` is set, the number of singular values kept
    will be `min(max_singular_values, number_of_singular_values)`, so that
    `max(0, number_of_singular_values - max_singular_values)` are truncated.

    If both `max_truncation_err` and `max_singular_values` are set,
    `max_singular_values` takes priority: The truncation error may be larger
    than `max_truncation_err` if required to satisfy `max_singular_values`.

    Args:
      node: The node you want to split.
      left_edges: The edges you want connected to the new left node.
      right_edges: The edges you want connected to the new right node.
      max_singular_values: The maximum number of singular values to keep.
      max_truncation_err: The maximum allowed truncation error.
      left_name: The name of the new left node. If `None`, a name will be generated
        automatically.
      right_name: The name of the new right node. If `None`, a name will be generated
        automatically.
      edge_name: The name of the new `Edge` connecting the new left and right node. 
        If `None`, a name will be generated automatically.

    Returns:
      A tuple containing:
        left_node: 
          A new node created that connects to all of the `left_edges`.
          Its underlying tensor is :math:`U \\sqrt{S}`
        right_node: 
          A new node created that connects to all of the `right_edges`.
          Its underlying tensor is :math:`\\sqrt{S} V^*`
        truncated_singular_values: 
          The vector of truncated singular values.
    """
        left, right, trun_vals = network_operations.split_node(
            node, left_edges, right_edges, max_singular_values,
            max_truncation_err, left_name, right_name, edge_name)
        left_node = self.add_node(left)
        right_node = self.add_node(right)

        self.nodes_set.remove(node)
        node.disable()
        return left_node, right_node, trun_vals
def split_node_full_svd(
    node: BaseNode,
    left_edges: List[Edge],
    right_edges: List[Edge],
    max_singular_values: Optional[int] = None,
    max_truncation_err: Optional[float] = None,
    left_name: Optional[Text] = None,
    middle_name: Optional[Text] = None,
    right_name: Optional[Text] = None,
    left_edge_name: Optional[Text] = None,
    right_edge_name: Optional[Text] = None,
) -> Tuple[BaseNode, BaseNode, BaseNode, Tensor]:
    """Split a node by doing a full singular value decomposition.

  Let M be the matrix created by flattening left_edges and right_edges into
  2 axes. Let :math:`U S V^* = M` be the Singular Value Decomposition of
  :math:`M`.

  The left most node will be :math:`U` tensor of the SVD, the middle node is
  the diagonal matrix of the singular values, ordered largest to smallest,
  and the right most node will be the :math:`V*` tensor of the SVD.

  The singular value decomposition is truncated if `max_singular_values` or
  `max_truncation_err` is not `None`.

  The truncation error is the 2-norm of the vector of truncated singular
  values. If only `max_truncation_err` is set, as many singular values will
  be truncated as possible while maintaining:
  `norm(truncated_singular_values) <= max_truncation_err`.

  If only `max_singular_values` is set, the number of singular values kept
  will be `min(max_singular_values, number_of_singular_values)`, so that
  `max(0, number_of_singular_values - max_singular_values)` are truncated.

  If both `max_truncation_err` and `max_singular_values` are set,
  `max_singular_values` takes priority: The truncation error may be larger
  than `max_truncation_err` if required to satisfy `max_singular_values`.

  Args:
    node: The node you want to split.
    left_edges: The edges you want connected to the new left node.
    right_edges: The edges you want connected to the new right node.
    max_singular_values: The maximum number of singular values to keep.
    max_truncation_err: The maximum allowed truncation error.
    left_name: The name of the new left node. If None, a name will be 
      generated automatically.
    middle_name: The name of the new center node. If None, a name will be 
      generated automatically.
    right_name: The name of the new right node. If None, a name will be 
      generated automatically.
    left_edge_name: The name of the new left `Edge` connecting
      the new left node (`U`) and the new central node (`S`).
      If `None`, a name will be generated automatically.
    right_edge_name: The name of the new right `Edge` connecting
      the new central node (`S`) and the new right node (`V*`).
      If `None`, a name will be generated automatically.

  Returns:
    A tuple containing:
      left_node:
        A new node created that connects to all of the `left_edges`.
        Its underlying tensor is :math:`U`
      singular_values_node:
        A new node that has 2 edges connecting `left_node` and `right_node`.
        Its underlying tensor is :math:`S`
      right_node:
        A new node created that connects to all of the `right_edges`.
        Its underlying tensor is :math:`V^*`
      truncated_singular_values:
        The vector of truncated singular values.
  """
    if not hasattr(node, 'backend'):
        raise TypeError('Node {} of type {} has no `backend`'.format(
            node, type(node)))

    if node.axis_names and left_edge_name and right_edge_name:
        left_axis_names = []
        right_axis_names = [right_edge_name]
        for edge in left_edges:
            left_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is
                                   node else node.axis_names[edge.axis2])
        for edge in right_edges:
            right_axis_names.append(node.axis_names[edge.axis1] if edge.node1
                                    is node else node.axis_names[edge.axis2])
        left_axis_names.append(left_edge_name)
        center_axis_names = [left_edge_name, right_edge_name]
    else:
        left_axis_names = None
        center_axis_names = None
        right_axis_names = None

    backend = node.backend

    node.reorder_edges(left_edges + right_edges)
    u, s, vh, trun_vals = backend.svd_decomposition(node.tensor,
                                                    len(left_edges),
                                                    max_singular_values,
                                                    max_truncation_err)
    left_node = Node(u,
                     name=left_name,
                     axis_names=left_axis_names,
                     backend=backend.name)
    singular_values_node = Node(backend.diag(s),
                                name=middle_name,
                                axis_names=center_axis_names,
                                backend=backend.name)

    right_node = Node(vh,
                      name=right_name,
                      axis_names=right_axis_names,
                      backend=backend.name)

    for i, edge in enumerate(left_edges):
        left_node.add_edge(edge, i)
        edge.update_axis(i, node, i, left_node)
    for i, edge in enumerate(right_edges):
        # i + 1 to account for the new edge.
        right_node.add_edge(edge, i + 1)
        edge.update_axis(i + len(left_edges), node, i + 1, right_node)
    connect(left_node.edges[-1],
            singular_values_node.edges[0],
            name=left_edge_name)
    connect(singular_values_node.edges[1],
            right_node.edges[0],
            name=right_edge_name)
    return left_node, singular_values_node, right_node, trun_vals
def split_node_rq(
    node: BaseNode,
    left_edges: List[Edge],
    right_edges: List[Edge],
    left_name: Optional[Text] = None,
    right_name: Optional[Text] = None,
    edge_name: Optional[Text] = None,
) -> Tuple[BaseNode, BaseNode]:
    """Split a `Node` using RQ (reversed QR) decomposition

  Let M be the matrix created by flattening left_edges and right_edges into
  2 axes. Let :math:`QR = M^*` be the QR Decomposition of
  :math:`M^*`. This will split the network into 2 nodes. The left node's
  tensor will be :math:`R^*` (a lower triangular matrix) and the right 
    node's tensor will be :math:`Q^*` (an orthonormal matrix)

  Args:
    node: The node you want to split.
    left_edges: The edges you want connected to the new left node.
    right_edges: The edges you want connected to the new right node.
    left_name: The name of the new left node. If `None`, a name will be 
      generated automatically.
    right_name: The name of the new right node. If `None`, a name will be 
      generated automatically.
    edge_name: The name of the new `Edge` connecting the new left and 
      right node. If `None`, a name will be generated automatically.

  Returns:
    A tuple containing:
      left_node:
        A new node created that connects to all of the `left_edges`.
        Its underlying tensor is :math:`Q`
      right_node:
        A new node created that connects to all of the `right_edges`.
        Its underlying tensor is :math:`R`
  """
    if not hasattr(node, 'backend'):
        raise TypeError('Node {} of type {} has no `backend`'.format(
            node, type(node)))

    if node.axis_names and edge_name:
        left_axis_names = []
        right_axis_names = [edge_name]
        for edge in left_edges:
            left_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is
                                   node else node.axis_names[edge.axis2])
        for edge in right_edges:
            right_axis_names.append(node.axis_names[edge.axis1] if edge.node1
                                    is node else node.axis_names[edge.axis2])
        left_axis_names.append(edge_name)
    else:
        left_axis_names = None
        right_axis_names = None
    backend = node.backend
    node.reorder_edges(left_edges + right_edges)
    r, q = backend.rq_decomposition(node.tensor, len(left_edges))
    left_node = Node(r,
                     name=left_name,
                     axis_names=left_axis_names,
                     backend=backend.name)
    for i, edge in enumerate(left_edges):
        left_node.add_edge(edge, i)
        edge.update_axis(i, node, i, left_node)
    right_node = Node(q,
                      name=right_name,
                      axis_names=right_axis_names,
                      backend=backend.name)
    for i, edge in enumerate(right_edges):
        # i + 1 to account for the new edge.
        right_node.add_edge(edge, i + 1)
        edge.update_axis(i + len(left_edges), node, i + 1, right_node)
    connect(left_node.edges[-1], right_node.edges[0], name=edge_name)
    return left_node, right_node
def split_node(
    node: BaseNode,
    left_edges: List[Edge],
    right_edges: List[Edge],
    max_singular_values: Optional[int] = None,
    max_truncation_err: Optional[float] = None,
    left_name: Optional[Text] = None,
    right_name: Optional[Text] = None,
    edge_name: Optional[Text] = None,
) -> Tuple[BaseNode, BaseNode, Tensor]:
    """Split a `Node` using Singular Value Decomposition.

  Let M be the matrix created by flattening left_edges and right_edges into
  2 axes. Let :math:`U S V^* = M` be the Singular Value Decomposition of 
  :math:`M`. This will split the network into 2 nodes. The left node's 
  tensor will be :math:`U \\sqrt{S}` and the right node's tensor will be 
  :math:`\\sqrt{S} V^*` where :math:`V^*` is
  the adjoint of :math:`V`.

  The singular value decomposition is truncated if `max_singular_values` or
  `max_truncation_err` is not `None`.

  The truncation error is the 2-norm of the vector of truncated singular
  values. If only `max_truncation_err` is set, as many singular values will
  be truncated as possible while maintaining:
  `norm(truncated_singular_values) <= max_truncation_err`.

  If only `max_singular_values` is set, the number of singular values kept
  will be `min(max_singular_values, number_of_singular_values)`, so that
  `max(0, number_of_singular_values - max_singular_values)` are truncated.

  If both `max_truncation_err` and `max_singular_values` are set,
  `max_singular_values` takes priority: The truncation error may be larger
  than `max_truncation_err` if required to satisfy `max_singular_values`.

  Args:
    node: The node you want to split.
    left_edges: The edges you want connected to the new left node.
    right_edges: The edges you want connected to the new right node.
    max_singular_values: The maximum number of singular values to keep.
    max_truncation_err: The maximum allowed truncation error.
    left_name: The name of the new left node. If `None`, a name will be 
      generated automatically.
    right_name: The name of the new right node. If `None`, a name will be 
      genenerated automatically.
    edge_name: The name of the new `Edge` connecting the new left and 
      right node. If `None`, a name will be generated automatically. 
      The new axis will get the same name as the edge.

  Returns:
    A tuple containing:
      left_node: 
        A new node created that connects to all of the `left_edges`.
        Its underlying tensor is :math:`U \\sqrt{S}`
      right_node: 
        A new node created that connects to all of the `right_edges`.
        Its underlying tensor is :math:`\\sqrt{S} V^*`
      truncated_singular_values: 
        The vector of truncated singular values.
  """

    if not hasattr(node, 'backend'):
        raise TypeError('Node {} of type {} has no `backend`'.format(
            node, type(node)))

    if node.axis_names and edge_name:
        left_axis_names = []
        right_axis_names = [edge_name]
        for edge in left_edges:
            left_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is
                                   node else node.axis_names[edge.axis2])
        for edge in right_edges:
            right_axis_names.append(node.axis_names[edge.axis1] if edge.node1
                                    is node else node.axis_names[edge.axis2])
        left_axis_names.append(edge_name)
    else:
        left_axis_names = None
        right_axis_names = None

    backend = node.backend
    node.reorder_edges(left_edges + right_edges)

    u, s, vh, trun_vals = backend.svd_decomposition(node.tensor,
                                                    len(left_edges),
                                                    max_singular_values,
                                                    max_truncation_err)
    sqrt_s = backend.sqrt(s)
    u_s = u * sqrt_s
    # We have to do this since we are doing element-wise multiplication against
    # the first axis of vh. If we don't, it's possible one of the other axes of
    # vh will be the same size as sqrt_s and would multiply across that axis
    # instead, which is bad.
    sqrt_s_broadcast_shape = backend.concat(
        [backend.shape(sqrt_s), [1] * (len(vh.shape) - 1)], axis=-1)
    vh_s = vh * backend.reshape(sqrt_s, sqrt_s_broadcast_shape)
    left_node = Node(u_s,
                     name=left_name,
                     axis_names=left_axis_names,
                     backend=backend.name)
    for i, edge in enumerate(left_edges):
        left_node.add_edge(edge, i)
        edge.update_axis(i, node, i, left_node)
    right_node = Node(vh_s,
                      name=right_name,
                      axis_names=right_axis_names,
                      backend=backend.name)
    for i, edge in enumerate(right_edges):
        # i + 1 to account for the new edge.
        right_node.add_edge(edge, i + 1)
        edge.update_axis(i + len(left_edges), node, i + 1, right_node)
    connect(left_node.edges[-1], right_node.edges[0], name=edge_name)
    node.fresh_edges(node.axis_names)
    return left_node, right_node, trun_vals
Beispiel #13
0
def split_node_qr(
    node: BaseNode,
    left_edges: List[Edge],
    right_edges: List[Edge],
    left_name: Optional[Text] = None,
    right_name: Optional[Text] = None,
    edge_name: Optional[Text] = None,
) -> Tuple[BaseNode, BaseNode]:
  """Split a `node` using QR decomposition.

  Let :math:`M` be the matrix created by 
  flattening `left_edges` and `right_edges` into 2 axes. 
  Let :math:`QR = M` be the QR Decomposition of :math:`M`.
  This will split the network into 2 nodes.
  The `left node`'s tensor will be :math:`Q` (an orthonormal matrix)
  and the `right node`'s tensor will be :math:`R` (an upper triangular matrix)

  Args:
    node: The node you want to split.
    left_edges: The edges you want connected to the new left node.
    right_edges: The edges you want connected to the new right node.
    left_name: The name of the new left node. If `None`, a name will be
      generated automatically.
    right_name: The name of the new right node. If `None`, a name will be
      generated automatically.
    edge_name: The name of the new `Edge` connecting the new left and right
      node. If `None`, a name will be generated automatically.

  Returns:
    A tuple containing:
      left_node:
        A new node created that connects to all of the `left_edges`.
        Its underlying tensor is :math:`Q`
      right_node:
        A new node created that connects to all of the `right_edges`.
        Its underlying tensor is :math:`R`
  Raises:
    AttributeError: If `node` has no backend attribute
  """
  if not hasattr(node, 'backend'):
    raise AttributeError('Node {} of type {} has no `backend`'.format(
        node, type(node)))

  if node.axis_names and edge_name:
    left_axis_names = []
    right_axis_names = [edge_name]
    for edge in left_edges:
      left_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is node
                             else node.axis_names[edge.axis2])
    for edge in right_edges:
      right_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is node
                              else node.axis_names[edge.axis2])
    left_axis_names.append(edge_name)
  else:
    left_axis_names = None
    right_axis_names = None

  backend = node.backend
  transp_tensor = node.tensor_from_edge_order(left_edges + right_edges)

  q, r = backend.qr_decomposition(transp_tensor, len(left_edges))
  left_node = Node(
      q, name=left_name, axis_names=left_axis_names, backend=backend)

  left_axes_order = [
      edge.axis1 if edge.node1 is node else edge.axis2 for edge in left_edges
  ]
  for i, edge in enumerate(left_edges):
    left_node.add_edge(edge, i)
    edge.update_axis(left_axes_order[i], node, i, left_node)

  right_node = Node(
      r, name=right_name, axis_names=right_axis_names, backend=backend)

  right_axes_order = [
      edge.axis1 if edge.node1 is node else edge.axis2 for edge in right_edges
  ]
  for i, edge in enumerate(right_edges):
    # i + 1 to account for the new edge.
    right_node.add_edge(edge, i + 1)
    edge.update_axis(right_axes_order[i], node, i + 1, right_node)

  connect(left_node.edges[-1], right_node.edges[0], name=edge_name)
  node.fresh_edges(node.axis_names)

  return left_node, right_node
Beispiel #14
0
def split_node(
    node: BaseNode,
    left_edges: List[Edge],
    right_edges: List[Edge],
    max_singular_values: Optional[int] = None,
    max_truncation_err: Optional[float] = None,
    relative: Optional[bool] = False,
    left_name: Optional[Text] = None,
    right_name: Optional[Text] = None,
    edge_name: Optional[Text] = None,
) -> Tuple[BaseNode, BaseNode, Tensor]:
  """Split a `node` using Singular Value Decomposition.

  Let :math:`M` be the matrix created by flattening `left_edges` and 
  `right_edges` into 2 axes. 
  Let :math:`U S V^* = M` be the SVD of :math:`M`. 
  This will split the network into 2 nodes. 
  The left node's tensor will be :math:`U \\sqrt{S}` 
  and the right node's tensor will be
  :math:`\\sqrt{S} V^*` where :math:`V^*` is the adjoint of :math:`V`.

  The singular value decomposition is truncated if `max_singular_values` or
  `max_truncation_err` is not `None`.

  The truncation error is the 2-norm of the vector of truncated singular
  values. If only `max_truncation_err` is set, as many singular values will
  be truncated as possible while maintaining:
  `norm(truncated_singular_values) <= max_truncation_err`.
  If `relative` is set `True` then `max_truncation_err` is understood
  relative to the largest singular value.

  If only `max_singular_values` is set, the number of singular values kept
  will be `min(max_singular_values, number_of_singular_values)`, so that
  `max(0, number_of_singular_values - max_singular_values)` are truncated.

  If both `max_truncation_err` and `max_singular_values` are set,
  `max_singular_values` takes priority: The truncation error may be larger
  than `max_truncation_err` if required to satisfy `max_singular_values`.

  Args:
    node: The node you want to split.
    left_edges: The edges you want connected to the new left node.
    right_edges: The edges you want connected to the new right node.
    max_singular_values: The maximum number of singular values to keep.
    max_truncation_err: The maximum allowed truncation error.
    relative: Multiply `max_truncation_err` with the largest singular value.
    left_name: The name of the new left node. If `None`, a name will be 
      generated automatically.
    right_name: The name of the new right node. If `None`, a name will be
      generated automatically.
    edge_name: The name of the new `Edge` connecting the new left and
      right node. If `None`, a name will be generated automatically.
      The new axis will get the same name as the edge.

  Returns:
    A tuple containing:
      left_node:
        A new node created that connects to all of the `left_edges`.
        Its underlying tensor is :math:`U \\sqrt{S}`
      right_node:
        A new node created that connects to all of the `right_edges`.
        Its underlying tensor is :math:`\\sqrt{S} V^*`
      truncated_singular_values:
        The vector of truncated singular values.
  Raises:
    AttributeError: If `node` has no backend attribute
  """

  if not hasattr(node, 'backend'):
    raise AttributeError('Node {} of type {} has no `backend`'.format(
        node, type(node)))

  if node.axis_names and edge_name:
    left_axis_names = []
    right_axis_names = [edge_name]
    for edge in left_edges:
      left_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is node
                             else node.axis_names[edge.axis2])
    for edge in right_edges:
      right_axis_names.append(node.axis_names[edge.axis1] if edge.node1 is node
                              else node.axis_names[edge.axis2])
    left_axis_names.append(edge_name)
  else:
    left_axis_names = None
    right_axis_names = None

  backend = node.backend
  transp_tensor = node.tensor_from_edge_order(left_edges + right_edges)

  u, s, vh, trun_vals = backend.svd_decomposition(
      transp_tensor,
      len(left_edges),
      max_singular_values,
      max_truncation_err,
      relative=relative)
  sqrt_s = backend.sqrt(s)
  u_s = backend.broadcast_right_multiplication(u, sqrt_s)
  vh_s = backend.broadcast_left_multiplication(sqrt_s, vh)

  left_node = Node(
      u_s, name=left_name, axis_names=left_axis_names, backend=backend)

  left_axes_order = [
      edge.axis1 if edge.node1 is node else edge.axis2 for edge in left_edges
  ]
  for i, edge in enumerate(left_edges):
    left_node.add_edge(edge, i)
    edge.update_axis(left_axes_order[i], node, i, left_node)

  right_node = Node(
      vh_s, name=right_name, axis_names=right_axis_names, backend=backend)

  right_axes_order = [
      edge.axis1 if edge.node1 is node else edge.axis2 for edge in right_edges
  ]
  for i, edge in enumerate(right_edges):
    # i + 1 to account for the new edge.
    right_node.add_edge(edge, i + 1)
    edge.update_axis(right_axes_order[i], node, i + 1, right_node)

  connect(left_node.edges[-1], right_node.edges[0], name=edge_name)
  node.fresh_edges(node.axis_names)
  return left_node, right_node, trun_vals
Beispiel #15
0
    def split_node_full_svd(
        self,
        node: network_components.BaseNode,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        max_singular_values: Optional[int] = None,
        max_truncation_err: Optional[float] = None,
        left_name: Optional[Text] = None,
        middle_name: Optional[Text] = None,
        right_name: Optional[Text] = None,
        left_edge_name: Optional[Text] = None,
        right_edge_name: Optional[Text] = None,
    ) -> Tuple[network_components.BaseNode, network_components.BaseNode,
               network_components.BaseNode, Tensor]:
        """Split a node by doing a full singular value decomposition.

    Let M be the matrix created by flattening left_edges and right_edges into
    2 axes. Let :math:`U S V^* = M` be the Singular Value Decomposition of 
    :math:`M`.

    The left most node will be :math:`U` tensor of the SVD, the middle node is
    the diagonal matrix of the singular values, ordered largest to smallest,
    and the right most node will be the :math:`V*` tensor of the SVD.

    The singular value decomposition is truncated if `max_singular_values` or
    `max_truncation_err` is not `None`.

    The truncation error is the 2-norm of the vector of truncated singular
    values. If only `max_truncation_err` is set, as many singular values will
    be truncated as possible while maintaining:
    `norm(truncated_singular_values) <= max_truncation_err`.

    If only `max_singular_values` is set, the number of singular values kept
    will be `min(max_singular_values, number_of_singular_values)`, so that
    `max(0, number_of_singular_values - max_singular_values)` are truncated.

    If both `max_truncation_err` and `max_singular_values` are set,
    `max_singular_values` takes priority: The truncation error may be larger
    than `max_truncation_err` if required to satisfy `max_singular_values`.

    Args:
      node: The node you want to split.
      left_edges: The edges you want connected to the new left node.
      right_edges: The edges you want connected to the new right node.
      max_singular_values: The maximum number of singular values to keep.
      max_truncation_err: The maximum allowed truncation error.
      left_name: The name of the new left node. If None, a name will be generated
        automatically.
      middle_name: The name of the new center node. If None, a name will be generated
        automatically.
      right_name: The name of the new right node. If None, a name will be generated
        automatically.
      left_edge_name: The name of the new left `Edge` connecting 
        the new left node (`U`) and the new central node (`S`). 
        If `None`, a name will be generated automatically.
      right_edge_name: The name of the new right `Edge` connecting 
        the new central node (`S`) and the new right node (`V*`). 
        If `None`, a name will be generated automatically.

    Returns:
      A tuple containing:
        left_node: 
          A new node created that connects to all of the `left_edges`.
          Its underlying tensor is :math:`U`
        singular_values_node: 
          A new node that has 2 edges connecting `left_node` and `right_node`.
          Its underlying tensor is :math:`S`
        right_node: 
          A new node created that connects to all of the `right_edges`.
          Its underlying tensor is :math:`V^*`
        truncated_singular_values: 
          The vector of truncated singular values.
    """
        U, S, V, trun_vals = network_operations.split_node_full_svd(
            node, left_edges, right_edges, max_singular_values,
            max_truncation_err, left_name, middle_name, right_name,
            left_edge_name, right_edge_name)
        left_node = self.add_node(U)
        singular_values_node = self.add_node(S)
        right_node = self.add_node(V)

        self.nodes_set.remove(node)
        node.disable()
        return left_node, singular_values_node, right_node, trun_vals
Beispiel #16
0
  def split_node_full_svd(self,
                          node: network_components.BaseNode,
                          left_edges: List[network_components.Edge],
                          right_edges: List[network_components.Edge],
                          max_singular_values: Optional[int] = None,
                          max_truncation_err: Optional[float] = None,
                          left_name: Optional[Text] = None,
                          middle_name: Optional[Text] = None,      
                          right_name: Optional[Text] = None
                         ) -> Tuple[network_components.BaseNode,
                                    network_components.BaseNode,
                                    network_components.BaseNode, Tensor]:
    """Split a node by doing a full singular value decomposition.

    Let M be the matrix created by flattening left_edges and right_edges into
    2 axes. Let :math:`U S V^* = M` be the Singular Value Decomposition of 
    :math:`M`.

    The left most node will be :math:`U` tensor of the SVD, the middle node is
    the diagonal matrix of the singular values, ordered largest to smallest,
    and the right most node will be the :math:`V*` tensor of the SVD.

    The singular value decomposition is truncated if `max_singular_values` or
    `max_truncation_err` is not `None`.

    The truncation error is the 2-norm of the vector of truncated singular
    values. If only `max_truncation_err` is set, as many singular values will
    be truncated as possible while maintaining:
    `norm(truncated_singular_values) <= max_truncation_err`.

    If only `max_singular_values` is set, the number of singular values kept
    will be `min(max_singular_values, number_of_singular_values)`, so that
    `max(0, number_of_singular_values - max_singular_values)` are truncated.

    If both `max_truncation_err` and `max_singular_values` are set,
    `max_singular_values` takes priority: The truncation error may be larger
    than `max_truncation_err` if required to satisfy `max_singular_values`.

    Args:
      node: The node you want to split.
      left_edges: The edges you want connected to the new left node.
      right_edges: The edges you want connected to the new right node.
      max_singular_values: The maximum number of singular values to keep.
      max_truncation_err: The maximum allowed truncation error.
      left_name: The name of the new left node. If None, a name will be generated
        automatically.
      middle_name: The name of the new center node. If None, a name will be generated
        automatically.
      right_name: The name of the new right node. If None, a name will be generated
        automatically.

    Returns:
      A tuple containing:
        left_node: 
          A new node created that connects to all of the `left_edges`.
          Its underlying tensor is :math:`U`
        singular_values_node: 
          A new node that has 2 edges connecting `left_node` and `right_node`.
          Its underlying tensor is :math:`S`
        right_node: 
          A new node created that connects to all of the `right_edges`.
          Its underlying tensor is :math:`V^*`
        truncated_singular_values: 
          The vector of truncated singular values.
    """
    node.reorder_edges(left_edges + right_edges)
    u, s, vh, trun_vals = self.backend.svd_decomposition(
        node.tensor, len(left_edges), max_singular_values, max_truncation_err)
    left_node = self.add_node(u, name=left_name)
    singular_values_node = self.add_node(self.backend.diag(s), name=middle_name)
    right_node = self.add_node(vh, name=right_name)
    for i, edge in enumerate(left_edges):
      left_node.add_edge(edge, i)
      edge.update_axis(i, node, i, left_node)
    for i, edge in enumerate(right_edges):
      # i + 1 to account for the new edge.
      right_node.add_edge(edge, i + 1)
      edge.update_axis(i + len(left_edges), node, i + 1, right_node)
    self.connect(left_node[-1], singular_values_node[0])
    self.connect(singular_values_node[1], right_node[0])
    self.nodes_set.remove(node)
    return left_node, singular_values_node, right_node, trun_vals
Beispiel #17
0
  def _remove_edges(self, edges: Set[network_components.Edge],
                    node1: network_components.BaseNode,
                    node2: network_components.BaseNode,
                    new_node: network_components.BaseNode) -> None:
    """Collapse a list of edges shared by two nodes in the network.

    Collapses the edges and updates the rest of the network.
    The nodes that currently share the edges in `edges` must be supplied as
    `node1` and `node2`. The ordering of `node1` and `node2` must match the
    axis ordering of `new_node` (as determined by the contraction procedure).

    Args:
      edges: The edges to contract.
      node1: The old node that supplies the first edges of `new_node`.
      node2: The old node that supplies the last edges of `new_node`.
      new_node: The new node that represents the contraction of the two old
        nodes.

    Raises:
      Value Error: If edge isn't in the network.
    """
    if node1 is node2:
      raise ValueError(
          "node1 and node2 are the same ('{}' == '{}'), but trace edges cannot "
          "be removed by _remove_edges.".format(node1, node2))

    node1_edges = node1.edges[:]
    node2_edges = node2.edges[:]

    nodes_set = set([node1, node2])
    for edge in edges:
      if edge.is_dangling():
        raise ValueError("Attempted to remove dangling edge '{}'.".format(edge))
      if set([edge.node1, edge.node2]) != nodes_set:
        raise ValueError(
            "Attempted to remove edges belonging to different node pairs: "
            "'{}' != '{}'.".format(nodes_set, set([edge.node1, edge.node2])))

    remaining_edges = []
    for (i, edge) in enumerate(node1_edges):
      if edge not in edges:  # NOTE: Makes the cost quadratic in # edges
        edge.update_axis(
            old_node=node1,
            old_axis=i,
            new_axis=len(remaining_edges),
            new_node=new_node)
        remaining_edges.append(edge)

    for (i, edge) in enumerate(node2_edges):
      if edge not in edges:
        edge.update_axis(
            old_node=node2,
            old_axis=i,
            new_axis=len(remaining_edges),
            new_node=new_node)
        remaining_edges.append(edge)

    for (i, edge) in enumerate(remaining_edges):
      new_node.add_edge(edge, i)

    # Remove nodes
    self.nodes_set.remove(node1)
    self.nodes_set.remove(node2)