def eig(matrix: BlockSparseTensor) -> Tuple[ChargeArray, BlockSparseTensor]: """ Compute the eigen decomposition of an `M` by `M` matrix `matrix`. Args: matrix: A matrix (i.e. a rank-2 tensor) of type `BlockSparseTensor` Returns: (ChargeArray,BlockSparseTensor): The eigenvalues and eigenvectors """ if matrix.ndim != 2: raise NotImplementedError( "eig currently supports only rank-2 tensors.") flat_charges = matrix._charges flat_flows = matrix._flows flat_order = matrix.flat_order tr_partition = len(matrix._order[0]) blocks, charges, shapes = _find_transposed_diagonal_sparse_blocks( flat_charges, flat_flows, tr_partition, flat_order) eigvals = [] v_blocks = [] for n, block in enumerate(blocks): e, v = np.linalg.eig(np.reshape(matrix.data[block], shapes[:, n])) eigvals.append(e) v_blocks.append(v) tmp_labels = [ np.full(len(eigvals[n]), fill_value=n, dtype=np.int16) for n in range(len(eigvals)) ] if len(tmp_labels) > 0: eigvalscharge_labels = np.concatenate(tmp_labels) else: eigvalscharge_labels = np.empty(0, dtype=np.int16) eigvalscharge = charges[eigvalscharge_labels] if len(eigvals) > 0: all_eigvals = np.concatenate(eigvals) else: all_eigvals = np.empty(0, dtype=get_real_dtype(matrix.dtype)) E = ChargeArray(all_eigvals, [eigvalscharge], [False]) charges_v = [eigvalscharge ] + [matrix._charges[o] for o in matrix._order[0]] order_v = [[0]] + [list(np.arange(1, len(matrix._order[0]) + 1))] flows_v = [True] + [matrix._flows[o] for o in matrix._order[0]] if len(v_blocks) > 0: all_v_blocks = np.concatenate([np.ravel(v.T) for v in v_blocks]) else: all_v_blocks = np.empty(0, dtype=matrix.dtype) V = BlockSparseTensor(all_v_blocks, charges=charges_v, flows=flows_v, order=order_v, check_consistency=False).transpose() return E, V #pytype: disable=bad-return-type
def svd(matrix: BlockSparseTensor, full_matrices: Optional[bool] = True, compute_uv: Optional[bool] = True, hermitian: Optional[bool] = False) -> Any: """ Compute the singular value decomposition of `matrix`. The matrix if factorized into `u * s * vh`, with `u` and `vh` the left and right singular vectors of `matrix`, and `s` its singular values. Args: matrix: A matrix (i.e. an order-2 tensor) of type `BlockSparseTensor` full_matrices: If `True`, expand `u` and `v` to square matrices If `False` return the "economic" svd, i.e. `u.shape[1]=s.shape[0]` and `v.shape[0]=s.shape[1]` compute_uv: If `True`, return `u` and `v`. hermitian: If `True`, assume hermiticity of `matrix`. Returns: If `compute_uv` is `True`: Three BlockSparseTensors `U,S,V`. If `compute_uv` is `False`: A BlockSparseTensors `S` containing the singular values. """ if matrix.ndim != 2: raise NotImplementedError("svd currently supports only tensors of order 2.") flat_charges = matrix._charges flat_flows = matrix._flows flat_order = matrix.flat_order tr_partition = len(matrix._order[0]) blocks, charges, shapes = _find_transposed_diagonal_sparse_blocks( flat_charges, flat_flows, tr_partition, flat_order) u_blocks = [] singvals = [] v_blocks = [] for n, block in enumerate(blocks): out = np.linalg.svd( np.reshape(matrix.data[block], shapes[:, n]), full_matrices, compute_uv, hermitian) if compute_uv: u_blocks.append(out[0]) singvals.append(out[1]) v_blocks.append(out[2]) else: singvals.append(out) tmp_labels = [ np.full(len(singvals[n]), fill_value=n, dtype=np.int16) for n in range(len(singvals)) ] if len(tmp_labels) > 0: left_singval_charge_labels = np.concatenate(tmp_labels) else: left_singval_charge_labels = np.empty(0, dtype=np.int16) left_singval_charge = charges[left_singval_charge_labels] if len(singvals) > 0: all_singvals = np.concatenate(singvals) else: all_singvals = np.empty(0, dtype=get_real_dtype(matrix.dtype)) S = ChargeArray(all_singvals, [left_singval_charge], [False]) if compute_uv: #define the new charges on the two central bonds tmp_left_labels = [ np.full(u_blocks[n].shape[1], fill_value=n, dtype=np.int16) for n in range(len(u_blocks)) ] if len(tmp_left_labels) > 0: left_charge_labels = np.concatenate(tmp_left_labels) else: left_charge_labels = np.empty(0, dtype=np.int16) tmp_right_labels = [ np.full(v_blocks[n].shape[0], fill_value=n, dtype=np.int16) for n in range(len(v_blocks)) ] if len(tmp_right_labels) > 0: right_charge_labels = np.concatenate(tmp_right_labels) else: right_charge_labels = np.empty(0, dtype=np.int16) new_left_charge = charges[left_charge_labels] new_right_charge = charges[right_charge_labels] charges_u = [new_left_charge ] + [matrix._charges[o] for o in matrix._order[0]] order_u = [[0]] + [list(np.arange(1, len(matrix._order[0]) + 1))] flows_u = [True] + [matrix._flows[o] for o in matrix._order[0]] charges_v = [new_right_charge ] + [matrix._charges[o] for o in matrix._order[1]] flows_v = [False] + [matrix._flows[o] for o in matrix._order[1]] order_v = [[0]] + [list(np.arange(1, len(matrix._order[1]) + 1))] # We fill in data into the transposed U # note that transposing is essentially free if len(u_blocks) > 0: all_u_blocks = np.concatenate([np.ravel(u.T) for u in u_blocks]) all_v_blocks = np.concatenate([np.ravel(v) for v in v_blocks]) else: all_u_blocks = np.empty(0, dtype=matrix.dtype) all_v_blocks = np.empty(0, dtype=matrix.dtype) return BlockSparseTensor( all_u_blocks, charges=charges_u, flows=flows_u, order=order_u, check_consistency=False).transpose((1, 0)), S, BlockSparseTensor( all_v_blocks, charges=charges_v, flows=flows_v, order=order_v, check_consistency=False) return S
def svd(bt, tensor: BlockSparseTensor, pivot_axis: int, max_singular_values: Optional[int] = None, max_truncation_error: Optional[float] = None, relative: Optional[bool] = False ) -> Tuple[Tensor, Tensor, Tensor, Tensor]: """ Computes the singular value decomposition (SVD) of a tensor. See tensornetwork.backends.tensorflow.decompositions for details. """ left_dims = tensor.shape[:pivot_axis] right_dims = tensor.shape[pivot_axis:] matrix = bt.reshape(tensor, [np.prod(left_dims), np.prod(right_dims)]) flat_charges = matrix._charges flat_flows = matrix._flows flat_order = matrix.flat_order tr_partition = len(matrix._order[0]) blocks, charges, shapes = _find_transposed_diagonal_sparse_blocks( flat_charges, flat_flows, tr_partition, flat_order) u_blocks = [] singvals = [] v_blocks = [] for n, b in enumerate(blocks): out = np.linalg.svd(np.reshape(matrix.data[b], shapes[:, n]), full_matrices=False, compute_uv=True) u_blocks.append(out[0]) singvals.append(out[1]) v_blocks.append(out[2]) orig_num_singvals = np.int64(np.sum([len(s) for s in singvals])) discarded_singvals = np.zeros(0, dtype=get_real_dtype(tensor.dtype)) if (max_singular_values is not None) and (max_singular_values >= orig_num_singvals): max_singular_values = None if (max_truncation_error is not None) or (max_singular_values is not None): max_D = np.max([len(s) for s in singvals]) if len(singvals) > 0 else 0 #extend singvals of all blocks into a matrix by padding each block with 0 if len(singvals) > 0: extended_singvals = np.stack([ np.append(s, np.zeros(max_D - len(s), dtype=s.dtype)) for s in singvals ], axis=1) else: extended_singvals = np.empty((0, 0), dtype=get_real_dtype(tensor.dtype)) extended_flat_singvals = np.ravel(extended_singvals) #sort singular values inds = np.argsort(extended_flat_singvals, kind='stable') discarded_inds = np.zeros(0, dtype=SIZE_T) if inds.shape[0] > 0: maxind = inds[-1] else: maxind = 0 if max_truncation_error is not None: if relative and (len(singvals) > 0): max_truncation_error = max_truncation_error * np.max( [s[0] for s in singvals]) kept_inds_mask = np.sqrt( np.cumsum(np.square( extended_flat_singvals[inds]))) > max_truncation_error trunc_inds_mask = np.logical_not(kept_inds_mask) discarded_inds = inds[trunc_inds_mask] inds = inds[kept_inds_mask] if max_singular_values is not None: #if the original number of non-zero singular values #is smaller than `max_singular_values` we need to reset #`max_singular_values` (we were filling in 0.0 into singular #value blocks to facilitate trunction steps, thus we could end up #with more singular values than originally there). if max_singular_values > orig_num_singvals: max_singular_values = orig_num_singvals if max_singular_values < len(inds): discarded_inds = np.append(discarded_inds, inds[:(-1) * max_singular_values]) inds = inds[(-1) * max_singular_values::] if len(inds) == 0: #special case of truncation to 0 dimension; warnings.warn("svd_decomposition truncated to 0 dimensions. " "Adjusting to `max_singular_values = 1`") inds = np.asarray([maxind]) if extended_singvals.shape[1] > 0: #pylint: disable=no-member keep = np.divmod(inds, extended_singvals.shape[1]) else: keep = (np.zeros(1, dtype=SIZE_T), np.zeros(1, dtype=SIZE_T)) newsingvals = [ extended_singvals[keep[0][keep[1] == n], keep[1][keep[1] == n]][::-1] for n in range(extended_singvals.shape[1]) ] discarded_singvals = extended_flat_singvals[discarded_inds] singvals = newsingvals if len(singvals) > 0: left_singval_charge_labels = np.concatenate([ np.full(singvals[n].shape[0], fill_value=n, dtype=np.int16) for n in range(len(singvals)) ]) all_singvals = np.concatenate(singvals) #define the new charges on the two central bonds left_charge_labels = np.concatenate([ np.full(len(singvals[n]), fill_value=n, dtype=np.int16) for n in range(len(u_blocks)) ]) right_charge_labels = np.concatenate([ np.full(len(singvals[n]), fill_value=n, dtype=np.int16) for n in range(len(v_blocks)) ]) all_ublocks = np.concatenate([ np.ravel(np.transpose(u_blocks[n][:, 0:len(singvals[n])])) for n in range(len(u_blocks)) ]) all_vblocks = np.concatenate([ np.ravel(v_blocks[n][0:len(singvals[n]), :]) for n in range(len(v_blocks)) ]) else: left_singval_charge_labels = np.empty(0, dtype=np.int16) all_singvals = np.empty(0, dtype=get_real_dtype(tensor.dtype)) left_charge_labels = np.empty(0, dtype=np.int16) right_charge_labels = np.empty(0, dtype=np.int16) all_ublocks = np.empty(0, dtype=get_real_dtype(tensor.dtype)) all_vblocks = np.empty(0, dtype=get_real_dtype(tensor.dtype)) left_singval_charge = charges[left_singval_charge_labels] S = ChargeArray(all_singvals, [left_singval_charge], [False]) new_left_charge = charges[left_charge_labels] new_right_charge = charges[right_charge_labels] #get the indices of the new tensors U,S and V charges_u = [new_left_charge ] + [matrix._charges[o] for o in matrix._order[0]] order_u = [[0]] + [list(np.arange(1, len(matrix._order[0]) + 1))] flows_u = [True] + [matrix._flows[o] for o in matrix._order[0]] charges_v = [new_right_charge ] + [matrix._charges[o] for o in matrix._order[1]] flows_v = [False] + [matrix._flows[o] for o in matrix._order[1]] order_v = [[0]] + [list(np.arange(1, len(matrix._order[1]) + 1))] #We fill in data into the transposed U U = BlockSparseTensor(all_ublocks, charges=charges_u, flows=flows_u, order=order_u, check_consistency=False).transpose((1, 0)) V = BlockSparseTensor(all_vblocks, charges=charges_v, flows=flows_v, order=order_v, check_consistency=False) left_shape = left_dims + (S.shape[0], ) right_shape = (S.shape[0], ) + right_dims return U.reshape(left_shape), S, V.reshape( right_shape), discarded_singvals[discarded_singvals > 0.0]
def test_get_real_dtype(): assert get_real_dtype(np.complex128) == np.float64 assert get_real_dtype(np.complex64) == np.float32 assert get_real_dtype(np.float64) == np.float64 assert get_real_dtype(np.float32) == np.float32