def test_ChargeArray_reshape_raises(): Ds = [8, 9, 10, 11] indices = [ Index(U1Charge.random(dimension=Ds[n], minval=-5, maxval=5), False) for n in range(4) ] arr = ChargeArray.random(indices) with pytest.raises(ValueError, match=r"The shape \(2, 4, 9, 2, 5, 11\)"): arr.reshape([2, 4, 9, 2, 5, 11]) with pytest.raises(ValueError, match="A tensor with"): arr.reshape([64, 65]) arr2 = arr.reshape([72, 110]) with pytest.raises( ValueError, match=r"The shape \(9, 8, 10, 11\) is incompatible with the" r" elementary shape \(8, 9, 10, 11\) of the tensor."): arr2.reshape([9, 8, 10, 11]) Ds = [8, 9, 0, 11] indices = [ Index(U1Charge.random(dimension=Ds[n], minval=-5, maxval=5), False) for n in range(4) ] arr3 = ChargeArray.random(indices) with pytest.raises(ValueError): arr3.reshape([72, 0])
def test_ChargeArray_reshape_raises(): Ds = [8, 9, 10, 11] indices = [Index(U1Charge.random(-5, 5, Ds[n]), False) for n in range(4)] arr = ChargeArray.random(indices) with pytest.raises(ValueError): arr.reshape([64, 65]) arr2 = arr.reshape([72, 110]) with pytest.raises(ValueError): arr2.reshape([9, 8, 10, 11]) Ds = [8, 9, 0, 11] indices = [Index(U1Charge.random(-5, 5, Ds[n]), False) for n in range(4)] arr3 = ChargeArray.random(indices) with pytest.raises(ValueError): arr3.reshape([72, 0])
def test_ChargeArray_transpose_reshape_contiguous(num_charges, chargetype): Ds = np.array([8, 9, 10, 11]) flows = [True, False, True, False] indices = [ Index(get_charge(chargetype, num_charges, Ds[n]), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices) nparr = np.reshape(arr.data, Ds) arr2 = arr.transpose([2, 0, 1, 3]) nparr2 = nparr.transpose([2, 0, 1, 3]) arr3 = arr2.reshape([80, 99]) nparr3 = nparr2.reshape([80, 99]) arr4 = arr3.transpose([1, 0]) nparr4 = nparr3.transpose([1, 0]) arr5 = arr4.reshape([9, 11, 10, 8]) nparr5 = nparr4.reshape([9, 11, 10, 8]) np.testing.assert_allclose(arr3.contiguous().data, np.ascontiguousarray(nparr3).flat) np.testing.assert_allclose(arr4.contiguous().data, np.ascontiguousarray(nparr4).flat) np.testing.assert_allclose(arr5.contiguous().data, np.ascontiguousarray(nparr5).flat)
def test_ChargeArray_transpose_reshape_transpose_data(num_charges): Ds = np.array([8, 9, 10, 11]) flows = [True, False, True, False] indices = [ Index( BaseCharge(np.random.randint(-5, 6, (num_charges, Ds[n])), charge_types=[U1Charge] * num_charges), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices) nparr = np.reshape(arr.data, Ds) arr2 = arr.transpose([2, 0, 1, 3]) nparr2 = nparr.transpose([2, 0, 1, 3]) arr3 = arr2.reshape([80, 99]) nparr3 = nparr2.reshape([80, 99]) arr4 = arr3.transpose([1, 0]) nparr4 = nparr3.transpose([1, 0]) arr5 = arr4.reshape([9, 11, 10, 8]) nparr5 = nparr4.reshape([9, 11, 10, 8]) np.testing.assert_allclose(arr3.transpose_data().data, np.ascontiguousarray(nparr3).flat) np.testing.assert_allclose(arr4.transpose_data().data, np.ascontiguousarray(nparr4).flat) np.testing.assert_allclose(arr5.transpose_data().data, np.ascontiguousarray(nparr5).flat)
def test_ChargeArray_todense(dtype, num_charges, chargetype): Ds = [8, 9, 10, 11] indices = [ Index(get_charge(chargetype, num_charges, Ds[n]), False) for n in range(4) ] arr = ChargeArray.random(indices, dtype=dtype) np.testing.assert_allclose(arr.todense(), np.reshape(arr.data, Ds))
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 conj(tensor: ChargeArray) -> ChargeArray: """ Return the complex conjugate of `tensor` in a new `ChargeArray`. Args: tensor: A `ChargeArray` object. Returns: ChargeArray """ return tensor.conj()
def test_herm_raises(chargetype, dtype): np.random.seed(10) D = 10 rank = 3 charges = [get_charge(chargetype, 1, D) for _ in range(rank)] flows = np.random.choice([True, False], size=rank, replace=True) inds = [Index(c, f) for c, f in zip(charges, flows)] T = ChargeArray.random(inds, dtype=dtype) with pytest.raises(ValueError, match="hermitian"): T.H
def test_herm(chargetype, dtype): np.random.seed(10) D = 10 rank = 2 charges = [get_charge(chargetype, 1, D) for _ in range(rank)] flows = np.random.choice([True, False], size=rank, replace=True) inds = [Index(c, f) for c, f in zip(charges, flows)] T = ChargeArray.random(inds, dtype=dtype) TH = T.H np.testing.assert_allclose(TH.todense(), T.todense().T.conj())
def test_ChargeArray_conj(dtype): Ds = np.array([8, 9, 10, 11]) flows = [True, False, True, False] indices = [ Index(U1Charge.random(dimension=Ds[n], minval=-5, maxval=5), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices, dtype=dtype) conj = arr.conj() np.testing.assert_allclose(conj.data, np.conj(arr.data))
def test_ChargeArray_init_raises(chargetype): np.random.seed(10) D = 10 rank = 4 charges = [get_charge(chargetype, 1, D) for _ in range(rank)] data = np.random.uniform(0, 1, size=D**rank) flows = np.random.choice([True, False], size=rank, replace=True) order = [[n + 10] for n in range(rank)] with pytest.raises(ValueError): ChargeArray(data, charges, flows, order=order)
def test_ChargeArray_todense(dtype, num_charges): Ds = [8, 9, 10, 11] indices = [ Index( BaseCharge(np.random.randint(-5, 6, (num_charges, Ds[n])), charge_types=[U1Charge] * num_charges), False) for n in range(4) ] arr = ChargeArray.random(indices, dtype=dtype) np.testing.assert_allclose(arr.todense(), np.reshape(arr.data, Ds))
def test_ChargeArray_transpose_raises(): Ds = np.array([8, 9, 10, 11]) flows = [True, False, True, False] indices = [ Index(U1Charge.random(-5, 5, Ds[n]), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices) order = [2, 1, 0] with pytest.raises(ValueError): arr.transpose(order)
def test_ChargeArray_generic(dtype, chargetype): Ds = [8, 9, 10, 11] indices = [Index(get_charge(chargetype, 1, Ds[n]), False) for n in range(4)] arr = ChargeArray.random(indices, dtype=dtype) assert arr.ndim == 4 assert arr.dtype == dtype np.testing.assert_allclose(arr.shape, Ds) np.testing.assert_allclose(arr.flat_flows, [False, False, False, False]) for n in range(4): assert charge_equal(indices[n]._charges[0], arr.flat_charges[n]) assert arr.sparse_shape[n] == indices[n]
def test_ChargeArray_transpose(chargetype): Ds = np.array([8, 9, 10, 11]) flows = [True, False, True, False] indices = [ Index(get_charge(chargetype, 1, Ds[n]), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices) order = [2, 1, 0, 3] arr2 = arr.transpose(order) np.testing.assert_allclose(Ds[order], arr2.shape) np.testing.assert_allclose(arr2._order, [[2], [1], [0], [3]]) np.testing.assert_allclose(arr2.flows, [[True], [False], [True], [False]])
def transpose(tensor: ChargeArray, order: Sequence[int] = np.asarray([1, 0]), shuffle: Optional[bool] = False) -> ChargeArray: """ Transpose the tensor into the new order `order`. If `shuffle=False` no data-reshuffling is done. Args: order: The new order of indices. shuffle: If `True`, reshuffle data. Returns: ChargeArray: The transposed tensor. """ return tensor.transpose(order, shuffle)
def test_ChargeArray_init(chargetype): np.random.seed(10) D = 10 rank = 4 charges = [get_charge(chargetype, 1, D) for _ in range(rank)] data = np.random.uniform(0, 1, size=D**rank) flows = np.random.choice([True, False], size=rank, replace=True) order = [[n] for n in range(rank)] arr = ChargeArray(data, charges, flows, order=order) np.testing.assert_allclose(data, arr.data) for c1, c2 in zip(charges, arr.charges): assert charge_equal(c1, c2[0]) for c1, c2 in zip(charges, arr._charges): assert charge_equal(c1, c2)
def test_diag_raises(): np.random.seed(10) Ds = [8, 9, 10] rank = len(Ds) indices = [ Index( BaseCharge(np.random.randint(-2, 3, (1, Ds[n])), charge_types=[U1Charge]), False) for n in range(rank) ] arr = BlockSparseTensor.random(indices) chargearr = ChargeArray.random([indices[0], indices[1]]) with pytest.raises(ValueError): diag(arr) with pytest.raises(ValueError): diag(chargearr)
def test_ChargeArray_reshape_with_index(dtype, chargetype): Ds = [8, 9, 10, 11] R = len(Ds) indices = [ Index(get_charge(chargetype, 1, Ds[n]), False) for n in range(R) ] arr = ChargeArray.random(indices, dtype=dtype) arr2 = arr.reshape([indices[0] * indices[1], indices[2] * indices[3]]) cnt = 0 for n in range(arr2.ndim): for m in range(len(arr2.charges[n])): assert charge_equal(arr2.charges[n][m], indices[cnt].charges) cnt += 1 np.testing.assert_allclose(arr2.shape, [72, 110]) assert arr2.ndim == 2
def test_repr(): np.random.seed(10) dtype = np.float64 D = 10 rank = 3 charges = [U1Charge.random(D, -2, 2) for _ in range(rank)] flows = np.random.choice([True, False], size=rank, replace=True) inds = [Index(c, f) for c, f in zip(charges, flows)] T = ChargeArray.random(inds, dtype=dtype) actual = T.__repr__() expected = "ChargeArray\n shape: (10, 10, 10)\n " +\ " charge types: ['U1Charge']\n dtype: " +\ repr(T.dtype.name) + "\n flat flows: " + \ repr(list(flows)) + "\n order: " + repr(T._order) assert actual == expected
def test_ChargeArray_contiguous(num_charges, chargetype): Ds = np.array([8, 9, 10, 11]) order = [2, 0, 1, 3] flows = [True, False, True, False] indices = [ Index(get_charge(chargetype, num_charges, Ds[n]), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices) data = np.ascontiguousarray(np.transpose(np.reshape(arr.data, Ds), order)) arr2 = arr.transpose(order).contiguous() data3 = np.reshape(arr2.data, Ds[order]) np.testing.assert_allclose(data, data3) np.testing.assert_allclose(arr2.shape, Ds[order]) np.testing.assert_allclose(arr2._order, [[0], [1], [2], [3]]) np.testing.assert_allclose(arr2.flows, [[True], [True], [False], [False]])
def test_create_diag(dtype, num_charges): np.random.seed(10) D = 200 index = Index( BaseCharge(np.random.randint(-2, 3, (num_charges, D)), charge_types=[U1Charge] * num_charges), False) arr = ChargeArray.random([index], dtype=dtype) diagarr = diag(arr) dense = np.ravel(diagarr.todense()) np.testing.assert_allclose(np.sort(dense[dense != 0.0]), np.sort(diagarr.data[diagarr.data != 0.0])) sparse_blocks, charges, block_shapes = _find_diagonal_sparse_blocks( diagarr.flat_charges, diagarr.flat_flows, 1) #in range(index._charges[0].unique_charges.shape[1]): for n, block in enumerate(sparse_blocks): shape = block_shapes[:, n] block_diag = np.diag(np.reshape(diagarr.data[block], shape)) np.testing.assert_allclose( arr.data[np.squeeze(index._charges[0] == charges[n])], block_diag)
def test_ChargeArray_arithmetic_raises(): np.random.seed(10) dtype = np.float64 D = 10 rank = 3 charges = [U1Charge.random(D, -2, 2) for _ in range(rank)] flows = np.random.choice([True, False], size=rank, replace=True) inds = [Index(c, f) for c, f in zip(charges, flows)] T = ChargeArray.random(inds, dtype=dtype) with pytest.raises(NotImplementedError): T - T with pytest.raises(NotImplementedError): T + T with pytest.raises(NotImplementedError): -T with pytest.raises(NotImplementedError): T * 5 with pytest.raises(NotImplementedError): 5 * T with pytest.raises(NotImplementedError): T / 5
def reshape( tensor: ChargeArray, shape: Union[List[Index], Tuple[Index, ...], List[int], Tuple[int, ...]] ) -> ChargeArray: """ Reshape `tensor` into `shape. `ChargeArray.reshape` works the same as the dense version, with the notable exception that the tensor can only be reshaped into a form compatible with its elementary shape. The elementary shape is the shape determined by ChargeArray._charges. For example, while the following reshaping is possible for regular dense numpy tensor, ``` A = np.random.rand(6,6,6) np.reshape(A, (2,3,6,6)) ``` the same code for ChargeArray ``` q1 = U1Charge(np.random.randint(0,10,6)) q2 = U1Charge(np.random.randint(0,10,6)) q3 = U1Charge(np.random.randint(0,10,6)) i1 = Index(charges=q1,flow=False) i2 = Index(charges=q2,flow=True) i3 = Index(charges=q3,flow=False) A = ChargeArray.randn(indices=[i1,i2,i3]) print(A.shape) #prints (6,6,6) A.reshape((2,3,6,6)) #raises ValueError ``` raises a `ValueError` since (2,3,6,6) is incompatible with the elementary shape (6,6,6) of the tensor. Args: tensor: A symmetric tensor. shape: The new shape. Can either be a list of `Index` or a list of `int`. Returns: ChargeArray: A new tensor reshaped into `shape` """ return tensor.reshape(shape)
def test_ChargeArray_transpose_reshape(chargetype): Ds = np.array([8, 9, 10, 11]) flows = [True, False, True, False] indices = [ Index(get_charge(chargetype, 1, Ds[n]), flows[n]) for n in range(4) ] arr = ChargeArray.random(indices) arr2 = arr.transpose([2, 0, 1, 3]) arr3 = arr2.reshape([80, 99]) np.testing.assert_allclose(arr3.shape, [80, 99]) np.testing.assert_allclose(arr3._order, [[2, 0], [1, 3]]) np.testing.assert_allclose(arr3.flows, [[True, True], [False, False]]) arr4 = arr3.transpose([1, 0]) np.testing.assert_allclose(arr4.shape, [99, 80]) np.testing.assert_allclose(arr4._order, [[1, 3], [2, 0]]) np.testing.assert_allclose(arr4.flows, [[False, False], [True, True]]) arr5 = arr4.reshape([9, 11, 10, 8]) np.testing.assert_allclose(arr5.shape, [9, 11, 10, 8]) np.testing.assert_allclose(arr5._order, [[1], [3], [2], [0]]) np.testing.assert_allclose(arr5.flows, [[False], [False], [True], [True]])
def test_repr(): np.random.seed(10) dtype = np.float64 D = 10 rank = 3 charges = [U1Charge.random(D, -2, 2) for _ in range(rank)] flows = np.random.choice([True, False], size=rank, replace=True) inds = [Index(c, f) for c, f in zip(charges, flows)] T = ChargeArray.random(inds, dtype=dtype) actual = T.__repr__() expected = "ChargeArray\n shape: (10, 10, 10)\n " +\ " charge types: ['U1Charge']\n dtype: " +\ repr(T.dtype.name) + "\n flat flows: " + \ repr(list(flows)) + "\n order: " + repr(T._order) assert actual == expected res = tensordot(T, T.conj(), ([0, 1, 2], [0, 1, 2])) actual = res.__repr__() expected = "BlockSparseTensor\n shape: ()\n " +\ " charge types: no charge types (scalar)\n dtype: " +\ repr(res.dtype.name) + "\n flat flows: " + \ repr(list(res.flat_flows)) + "\n order: " + repr(res._order) assert actual == expected
def test_ChargeArray_reshape(dtype, Ds, chargetype): flat_Ds = sum(Ds, []) R = len(flat_Ds) indices = [ Index(get_charge(chargetype, 1, flat_Ds[n]), False) for n in range(R) ] arr = ChargeArray.random(indices, dtype=dtype) ds = [np.prod(D) for D in Ds] arr2 = arr.reshape(ds) cnt = 0 for n in range(arr2.ndim): for m in range(len(arr2.charges[n])): assert charge_equal(arr2.charges[n][m], indices[cnt].charges) cnt += 1 order = [] flows = [] start = 0 for D in Ds: order.append(list(range(start, start + len(D)))) start += len(D) flows.append([False] * len(D)) np.testing.assert_allclose(arr2.shape, ds) for n in range(len(arr2._order)): np.testing.assert_allclose(arr2._order[n], order[n]) np.testing.assert_allclose(arr2.flows[n], flows[n]) assert arr2.ndim == len(Ds) arr3 = arr.reshape(flat_Ds) for n in range(len(Ds)): assert charge_equal(arr3.charges[n][0], indices[n].charges) np.testing.assert_allclose(arr3.shape, flat_Ds) np.testing.assert_allclose(arr3._order, [[n] for n in range(len(flat_Ds))]) np.testing.assert_allclose(arr3.flows, [[False] for n in range(len(flat_Ds))]) assert arr3.ndim == len(flat_Ds)
def diag(tensor: ChargeArray) -> Any: """ Return a diagonal `BlockSparseTensor` from a `ChargeArray`, or return the diagonal of a `BlockSparseTensor` as a `ChargeArray`. For input of type `BlockSparseTensor`: The full diagonal is obtained from finding the diagonal blocks of the `BlockSparseTensor`, taking the diagonal elements of those and packing the result into a ChargeArray. Note that the computed diagonal elements are usually different from the diagonal elements obtained from converting the `BlockSparseTensor` to dense storage and taking the diagonal. Note that the flow of the resulting 1d `ChargeArray` object is `False`. Args: tensor: A `ChargeArray`. Returns: ChargeArray: A 1d `CharggeArray` containing the diagonal of `tensor`, or a diagonal matrix of type `BlockSparseTensor` containing `tensor` on its diagonal. """ if tensor.ndim > 2: raise ValueError("`diag` currently only implemented for matrices, " "found `ndim={}".format(tensor.ndim)) if not isinstance(tensor, BlockSparseTensor): if tensor.ndim > 1: raise ValueError( "`diag` currently only implemented for `ChargeArray` with ndim=1, " "found `ndim={}`".format(tensor.ndim)) flat_charges = tensor._charges + tensor._charges flat_flows = list(tensor._flows) + list(np.logical_not(tensor._flows)) flat_order = list(tensor.flat_order) + list( np.asarray(tensor.flat_order) + len(tensor._charges)) tr_partition = len(tensor._order[0]) blocks, charges, shapes = _find_transposed_diagonal_sparse_blocks( flat_charges, flat_flows, tr_partition, flat_order) data = np.zeros( np.int64(np.sum(np.prod(shapes, axis=0))), dtype=tensor.dtype) lookup, unique, labels = compute_sparse_lookup(tensor._charges, tensor._flows, charges) for n, block in enumerate(blocks): label = labels[np.nonzero(unique == charges[n])[0][0]] data[block] = np.ravel( np.diag(tensor.data[np.nonzero(lookup == label)[0]])) order = [ tensor._order[0], list(np.asarray(tensor._order[0]) + len(tensor._charges)) ] new_charges = [tensor._charges[0].copy(), tensor._charges[0].copy()] return BlockSparseTensor( data, charges=new_charges, flows=list(tensor._flows) + list(np.logical_not(tensor._flows)), order=order, check_consistency=False) flat_charges = tensor._charges flat_flows = tensor._flows flat_order = tensor.flat_order tr_partition = len(tensor._order[0]) sparse_blocks, charges, block_shapes = _find_transposed_diagonal_sparse_blocks(#pylint: disable=line-too-long flat_charges, flat_flows, tr_partition, flat_order) shapes = np.min(block_shapes, axis=0) if len(sparse_blocks) > 0: data = np.concatenate([ np.diag(np.reshape(tensor.data[sparse_blocks[n]], block_shapes[:, n])) for n in range(len(sparse_blocks)) ]) charge_labels = np.concatenate([ np.full(shapes[n], fill_value=n, dtype=np.int16) for n in range(len(sparse_blocks)) ]) else: data = np.empty(0, dtype=tensor.dtype) charge_labels = np.empty(0, dtype=np.int16) newcharges = [charges[charge_labels]] flows = [False] return ChargeArray(data, newcharges, flows)
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]