down_svd_pair#

t3toolbox.backend.linalg.down_svd_pair(G_i_a_j, B_a_o, min_rank=None, max_rank=None, rtol=None, atol=None)#
def down_svd_pair(
        G_i_a_j: NDArray, # shape=(..., ni, na, nj)
        B_a_o: NDArray, # shape=(..., na, N)
        min_rank: int = None, # 1 <= min_rank <= max_rank <= minimum(ni*na, nj)
        max_rank: int = None, # 1 <= min_rank <= max_rank <= minimum(ni*na, nj)
        rtol: float = None, # removes singular values satisfying sigma < maximum(atol, rtol*sigma1)
        atol: float = None, # removes singular values satisfying sigma < maximum(atol, rtol*sigma1)
) -> typ.Tuple[
    NDArray, # new_G, shape=(..., ni, nx, nj),
    NDArray, # new_B, shape=(..., nx, N)
    NDArray, # ss, shape=(..., nx)
]:

Compute (truncated) singular value decomposition of B, pushing non-orthogonal remainder onto G.

Mirror of up_svd_pair(): orthogonalizes the matrix B (so new_B has orthonormal rows) and absorbs the U @ ss remainder into G’s shared middle index, leaving the product over the shared index unchanged. Truncation args behave as in truncated_svd().

Examples

>>> import numpy as np
>>> import t3toolbox.backend.linalg as linalg
>>> np.random.seed(0)
>>> G = np.random.randn(2, 3, 4)                      # (ni, na, nj)
>>> B = np.random.randn(3, 7)                         # (na, N) -- shared index na=3
>>> new_G, new_B, ss = linalg.down_svd_pair(G, B)
>>> print(new_G.shape, new_B.shape, ss.shape)
(2, 3, 4) (3, 7) (3,)
>>> before = np.einsum('iaj,ao->ijo', G, B)
>>> after  = np.einsum('ixj,xo->ijo', new_G, new_B)
>>> print(np.allclose(before, after))                # product across the shared index preserved
True
>>> print(np.allclose(new_B @ new_B.T, np.eye(new_B.shape[0])))   # new_B has orthonormal rows
True
Parameters:
  • G_i_a_j (NDArray)

  • B_a_o (NDArray)

  • min_rank (int)

  • max_rank (int)

  • rtol (float)

  • atol (float)

Return type:

t3toolbox.backend.common.typ.Tuple[NDArray, NDArray, NDArray]