TuckerTensorTrain.rank_adjustment_sweep#
- t3toolbox.tucker_tensor_train.TuckerTensorTrain.rank_adjustment_sweep(direction='right_to_left', sharing=None)#
def rank_adjustment_sweep( self, direction: str = 'right_to_left', # 'right_to_left' | 'left_to_right' sharing: typ.Sequence = None, # len=d; group labels (None = unshared) ) -> 'TuckerTensorTrain':
A single lossless directional sweep that drops structurally-redundant ranks (the separate rank-minimization step;
t3svd()itself does not minimize). Returns the adjusted T3.'right_to_left'returns a right-orthogonal T3;'left_to_right'a left-orthogonal one – fully (Tucker cores included) when the input’s Tucker cores are already orthonormal (anyt3svd()result); the sweep preserves Tucker orthonormality but never creates it, so on a generic input only the TT cores come out orthogonal. A single sweep reaches minimal ranks only if the input is already orthogonal in the opposite direction – e.g. at3svd()result is left-orthogonal, soresult.rank_adjustment_sweep('right_to_left')minimizes it (check withhas_minimal_ranks). That precondition is not enforced: sweeping the wrong direction for the input’s gauge just under-minimizes (it stays lossless here – but the uniformrank_adjustment_sweep()is lossy in that case). Verify the gauge withis_left_orthogonal()/is_right_orthogonal()first, or compose both directions for guaranteed minimal ranks. The represented tensor is unchanged (when used correctly).Examples
>>> import numpy as np >>> import t3toolbox.tucker_tensor_train as t3 >>> np.random.seed(0) >>> x = t3.TuckerTensorTrain.randn((5, 6, 7), (4, 5, 6), (1, 3, 2, 1)) >>> x2, _, _ = x.t3svd(max_tt_ranks=2) # basic T3-SVD: left-orthogonal, NOT minimal >>> print(x2.has_minimal_ranks, x2.tucker_ranks) False (3, 4, 2) >>> x3 = x2.rank_adjustment_sweep('right_to_left') # x2 is left-orthogonal -> R->L minimizes >>> print(x3.has_minimal_ranks, x3.tucker_ranks) True (2, 4, 2) >>> print(np.allclose(x3.to_dense(), x2.to_dense())) # same tensor, redundant rank removed True
Wrong direction for the input’s gauge – the left-orthogonal
x2needs'right_to_left';'left_to_right'here just under-minimizes (lossless, but still non-minimal). Use a bond orphan to show it clearly:>>> np.random.seed(0) >>> y = t3.TuckerTensorTrain.randn((10, 10, 10), (9, 9, 9), (1, 9, 9, 1)) >>> y2, _, _ = y.t3svd(max_tucker_ranks=[9, 1, 9], max_tt_ranks=[1, 9, 2, 1]) # left-orth, non-minimal >>> print(y2.has_minimal_ranks) False >>> wrong = y2.rank_adjustment_sweep('left_to_right') # WRONG direction for a left-orth input >>> print(wrong.has_minimal_ranks, np.allclose(wrong.to_dense(), y2.to_dense())) # non-minimal, but lossless False True >>> print(y2.rank_adjustment_sweep('right_to_left').has_minimal_ranks) # correct direction True
sharing: a partition with a real group routes through the grouped lossless reduction (a per-mode Tucker step would untie the group). The group’s ceiling is the SUM of its modes’ local ceilings, so the shared rank may legitimately exceed an individual mode’srL_i*rR_i– here bond 1 is capped to 1, so mode 0’s local ceiling isrL_0*rR_0 = 1and the unshared reduction would clip it (untying the group), while the group ceiling1 + 2 = 3keeps the shared rank at 3. Tied factors are a safe-mode precondition; the directional/compose-both-directions contract is as above:>>> np.random.seed(0) >>> z = t3.TuckerTensorTrain.randn((6, 6, 5), (3, 3, 2), (1, 2, 2, 1)) >>> tkz, ttz = z.data >>> z = t3.TuckerTensorTrain((tkz[0], tkz[0], tkz[2]), ttz) # a tied point >>> z2, _, _ = z.t3svd(sharing=(0, 0, 1), max_tt_ranks=[1, 1, 2, 1]) # left-orth output >>> z3 = z2.rank_adjustment_sweep('right_to_left', sharing=(0, 0, 1)) >>> print(z3.tucker_ranks, z3.data[0][0] is z3.data[0][1]) # group ceiling, ONE group array (3, 3, 2) True >>> print(z3.rank_adjustment_sweep('right_to_left').tucker_ranks) # unshared clips mode 0 (unties!) (1, 2, 2) >>> print(np.allclose(z3.to_dense(), z2.to_dense())) # lossless True
- Parameters:
direction (str)
sharing (Sequence)
- Return type: