Beispiel #1
0
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
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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]
Beispiel #7
0
def _eigh_free(
        matrix: BlockSparseTensor,
        which: Optional[Text] = 'LM',
        full_sort: Optional[bool] = True,
        threshold: Optional[float] = None,
        max_kept: Optional[int] = None,
        UPLO: Optional[Text] = 'L') -> Tuple[ChargeArray, BlockSparseTensor]:
    """
  Compute eigenvectors and eigenvalues of a hermitian matrix where the output
  charge order is free.
  """
    # reshape into matrix if needed
    pivot = matrix.ndim // 2
    m_shape = matrix.shape
    matrix = matrix.reshape(
        [np.prod(m_shape[:pivot]),
         np.prod(m_shape[pivot:])])

    if max_kept is None:
        max_kept = matrix.shape[0]
    max_kept = min(max_kept, matrix.shape[0])

    if threshold is None:
        if which == 'LM' or which == 'LA':
            threshold = -float('inf')
        elif which == 'SM' or which == 'SA':
            threshold = float('inf')

    # compute info about each block
    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)
    num_blocks = len(blocks)

    # diagonalize each block
    eigvals = [0] * num_blocks
    v_blocks = [0] * num_blocks
    for n, block in enumerate(blocks):
        etemp, vtemp = np.linalg.eigh(
            np.reshape(matrix.data[block], shapes[:, n]), UPLO)
        # sort within each block
        if which == 'LM':
            ord_temp = np.flip(np.argsort(abs(etemp)))
        elif which == 'LA':
            ord_temp = np.flip(np.argsort(etemp))
        elif which == 'SM':
            ord_temp = np.argsort(abs(etemp))
        elif which == 'SA':
            ord_temp = np.argsort(etemp)

        eigvals[n] = etemp[ord_temp]
        v_blocks[n] = (vtemp[:, ord_temp].T)

    # combine and sort eigenvalues from all symmetry blocks
    tmp_labels = [
        np.full(len(eigvals[n]), fill_value=n, dtype=np.int16)
        for n in range(len(eigvals))
    ]
    tmp_degens = [
        np.arange(len(eigvals[n]), dtype=np.int16) for n in range(len(eigvals))
    ]
    all_eigvals = np.concatenate(eigvals)
    all_labels = np.concatenate(tmp_labels)

    if which == 'LM':
        eig_ord = np.flip(np.argsort(np.abs(all_eigvals)))
        num_kept = min(sum(np.abs(all_eigvals) >= threshold), max_kept)
    elif which == 'LA':
        eig_ord = np.flip(np.argsort(all_eigvals))
        num_kept = min(sum(all_eigvals >= threshold), max_kept)
    elif which == 'SM':
        eig_ord = np.argsort(np.abs(all_eigvals))
        num_kept = min(sum(np.abs(all_eigvals) <= threshold), max_kept)
    elif which == 'SA':
        eig_ord = np.argsort(all_eigvals)
        num_kept = min(sum(all_eigvals <= threshold), max_kept)

    if full_sort:
        e_labels = np.concatenate(tmp_labels)[eig_ord[:num_kept]]
        e_degens = np.concatenate(tmp_degens)[eig_ord[:num_kept]]
        e_charge = charges[e_labels]
        E = ChargeArray(all_eigvals[eig_ord[:num_kept]], [e_charge], [False])
    else:
        num_per_block = [
            sum(all_labels[eig_ord[:num_kept]] == n) for n in range(num_blocks)
        ]
        e_labels = np.concatenate([
            np.full(num_per_block[n], fill_value=n, dtype=np.int16)
            for n in range(num_blocks)
        ])
        e_degens = np.concatenate([
            np.arange(num_per_block[n], dtype=np.int16)
            for n in range(num_blocks)
        ])
        e_charge = charges[e_labels]
        new_eigvals = np.concatenate(
            [eigvals[n][:num_per_block[n]] for n in range(num_blocks)])
        E = ChargeArray(new_eigvals, [e_charge], [False])

    charges_v = [e_charge] + [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]]

    all_v_blocks = np.concatenate(
        [v_blocks[e_labels[n]][e_degens[n], :] for n in range(num_kept)])
    fin_shape = [*m_shape[:pivot], num_kept]

    V = BlockSparseTensor(
        all_v_blocks,
        charges=charges_v,
        flows=flows_v,
        order=order_v,
        check_consistency=False).transpose().reshape(fin_shape)

    return E, V
Beispiel #8
0
def _eigh_fixed(
        matrix: BlockSparseTensor,
        link_charges: ChargeArray,
        which: Optional[Text] = 'LM',
        UPLO: Optional[Text] = 'L') -> Tuple[ChargeArray, BlockSparseTensor]:
    """
  Compute eigenvectors and eigenvalues of a hermitian matrix where the output
  charge order is fixed to match that of `link_charges`.
  """

    # reshape into matrix if needed
    pivot = matrix.ndim // 2
    m_shape = matrix.shape
    matrix = matrix.reshape(
        [np.prod(m_shape[:pivot]),
         np.prod(m_shape[pivot:])])

    # compute info about each block
    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)

    # intersect between link charges and block charges
    link_uni, link_pos, link_counts = link_charges.unique(return_inverse=True,
                                                          return_counts=True)
    _, blk_common, link_common = charges.intersect(link_uni,
                                                   return_indices=True)

    # diagonalize each block
    eigvals = []
    v_blocks = []
    for n, m in enumerate(blk_common):
        e, v = np.linalg.eigh(np.reshape(matrix.data[blocks[m]], shapes[:, m]),
                              UPLO)

        # sort within each block
        if which == 'SA':
            blk_sort = np.argsort(e)
        elif which == 'LA':
            blk_sort = np.flip(np.argsort(e))
        elif which == 'SM':
            blk_sort = np.argsort(np.abs(e))
        elif which == 'LM':
            blk_sort = np.flip(np.argsort(np.abs(e)))

        eigvals.append(e[blk_sort])
        v_blocks.append(v[:, blk_sort].T)

    link_degens = np.zeros(np.size(link_pos), dtype=np.int64)
    for n, count in enumerate(link_counts):
        link_degens[link_pos == n] = np.arange(count, dtype=np.int64)

    all_eigvals = np.zeros(link_pos.size, dtype=matrix.dtype)
    for n in range(len(link_pos)):
        all_eigvals[n] = eigvals[link_pos[n]][link_degens[n]]

    e_charge = charges[blk_common[link_pos]]
    E = ChargeArray(all_eigvals, [e_charge], [False])

    charges_v = [e_charge] + [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([
            v_blocks[link_pos[n]][link_degens[n], :]
            for n in range(len(all_eigvals))
        ])
    else:
        all_v_blocks = np.empty(0, dtype=matrix.dtype)

    fin_shape = [*m_shape[:pivot], len(all_eigvals)]
    V = BlockSparseTensor(
        all_v_blocks,
        charges=charges_v,
        flows=flows_v,
        order=order_v,
        check_consistency=False).transpose().reshape(fin_shape)

    return E, V