Example #1
0
    def split_node(
        self,
        node: network_components.Node,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        max_singular_values: Optional[int] = None,
        max_truncation_err: Optional[float] = None
    ) -> Tuple[network_components.Node, network_components.Node, Tensor]:
        """Split a network_components.Node using Singular Value Decomposition.

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

    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.

    Returns:
      left_node: A new node created that connects to all of the `left_edges`.
      right_node: A new node created that connects to all of the `right_edges`.
      truncated_singular_values: A vector of the dropped smallest 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)
        sqrt_s = self.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 = self.backend.concat(
            [self.backend.shape(sqrt_s), [1] * (len(vh.shape) - 1)], axis=-1)
        vh_s = vh * self.backend.reshape(sqrt_s, sqrt_s_broadcast_shape)
        left_node = self.add_node(u_s)
        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(vh_s)
        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, trun_vals
Example #2
0
    def split_node_full_svd(
        self,
        node: network_components.Node,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        max_singular_values: Optional[int] = None,
        max_truncation_err: Optional[float] = None
    ) -> Tuple[network_components.Node, network_components.Node,
               network_components.Node, 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 U S V* = M be the Singular Value Decomposition of M.
    The left most node will be 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 V* tensor of the SVD.

    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.

    Returns:
      left_node: The new left node created. Its underlying tensor is the same
        as the U tensor from the SVD.
      singular_values_node: The new node representing the diagonal matrix of
        singular values.
      right_node: The new right node created. Its underlying tensor is the same
        as the V* tensor from the SVD.
      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)
        singular_values_node = self.add_node(self.backend.diag(s))
        right_node = self.add_node(vh)
        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
def test_transpose(backend):
    a = Node(np.random.rand(1, 2, 3, 4, 5), backend=backend)
    order = [a[n] for n in reversed(range(5))]
    transpa = node_linalg.transpose(a, [4, 3, 2, 1, 0])
    a.reorder_edges(order)
    np.testing.assert_allclose(a.tensor, transpa.tensor)
Example #4
0
    def split_node_full_svd(
        self,
        node: network_components.Node,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        max_singular_values: Optional[int] = None,
        max_truncation_err: Optional[float] = None
    ) -> Tuple[network_components.Node, network_components.Node,
               network_components.Node, 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.

    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)
        singular_values_node = self.add_node(self.backend.diag(s))
        right_node = self.add_node(vh)
        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
Example #5
0
    def split_node(
        self,
        node: network_components.Node,
        left_edges: List[network_components.Edge],
        right_edges: List[network_components.Edge],
        max_singular_values: Optional[int] = None,
        max_truncation_err: Optional[float] = None
    ) -> Tuple[network_components.Node, network_components.Node, Tensor]:
        """Split a network_components.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.

    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.
    """
        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)
        sqrt_s = self.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 = self.backend.concat(
            [self.backend.shape(sqrt_s), [1] * (len(vh.shape) - 1)], axis=-1)
        vh_s = vh * self.backend.reshape(sqrt_s, sqrt_s_broadcast_shape)
        left_node = self.add_node(u_s)
        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(vh_s)
        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, trun_vals