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
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)
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
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