//  [python]
def method_ttoi_as_matlab(
        Y, gt=None, ret_X_hat=False, ret_gt_metrics=True, tt_rank_max=None, max_iter=3, tol=None, **kwargs
):
    """
    Follows closely https://github.com/Lili-Zheng-stat/TTOI/blob/master/TTOI.m
    Performs Tensor-Train Orthogonal Iteration (TTOI) algorithm for noisy observed order-d tensor Y.
    :param Y: noisy tensor observation
    :param ranks: TT-ranks of Y
    :param max_iter: maximum number of iterations
    :param tol: tolerance
    :return: a list of the estimated tensors in all iterations
    """
    if tt_rank_max is None:
        raise ValueError(f'tt_rank_max not specified')
    if not torch.is_tensor(Y):
        raise ValueError(f'Invalid input tensor: {type(Y)}')
    if type(max_iter) != int or tol is not None and type(tol) not in (int, float):
        raise ValueError(f'Invalid method settings: {max_iter=} {tol=}')

    ranks = get_tt_ranks(list(Y.shape), max_rank=tt_rank_max, tt_rank_equal=False)
    ranks = ranks[1: -1]

    def prod(x):
        return torch.prod(torch.tensor(x)).item()

    def svds(A, k):
        U, S, Vt = torch.linalg.svd(A, full_matrices=False)
        return U[:, :k], torch.diag(S[:k]), Vt[:k, :].T, flops_svd(A.shape)

    def mm(A, B):
        assert A.dim() == 2
        assert B.dim() == 2
        return A @ B, flops_mm(A.shape[0], A.shape[1], B.shape[1])

    def eye(k):
        return torch.eye(k)

    def kron(A, B):
        # torch.kron does not like SVD outputs as inputs:
        # https://github.com/pytorch/pytorch/issues/74442#issuecomment-1111445351
        out = (A[:, None, :, None] * B[None, :, None, :]).reshape(A.shape[0] * B.shape[0], A.shape[1] * B.shape[1])
        flops = A.shape[0] * A.shape[1] * B.shape[0] * B.shape[1]
        return out, flops

    def reshape_fortran(A, *args):
        # Fortran order reshape
        # https://stackoverflow.com/a/63964246/411907
        if len(A.shape) > 0:
            A = A.permute(*reversed(range(len(A.shape))))
        A = A.reshape(*list(reversed(args)))
        A = A.permute(*reversed(range(A.dim())))
        return A

    dim_vec = Y.shape
    d = len(dim_vec)
    X_hat_arr = [None] * max_iter
    V_prod_arr = [None] * (max_iter // 2 + 1)
    U_prod_arr = [None] * ((max_iter + 1) // 2)
    Y_arr = [None] * (d-1)

    V_prod_arr[0] = [None] * (d-1)
    for i in range(d-1):
        Y_arr[i] = reshape_fortran(Y, prod(dim_vec[:i+1]), prod(dim_vec[i+1:]))

    n = 0
    chg = float('inf')
    Y_tilde_arr = [None] * (d-2)
    X_hat = None
    flops = []
    while n < max_iter and (not tol or chg > tol):
        if n & 1 == 0:
            idx_prod = n // 2
            # U_prod_arr{n/2+1}=cell(d-1,1);
            U_prod_arr[idx_prod] = [None] * (d-1)
            # Y_tilde_arr=cell(d-2,1);
            Y_tilde_arr = [None] * (d-2)
            # [U_temp,~,~]=svds(Y_arr{1} * V_prod_arr{n/2+1}{d-1},r_vec(1));
            V_prod_temp = V_prod_arr[idx_prod][d-2]
            if V_prod_temp is not None:
                YV_prod_temp, f = mm(Y_arr[0], V_prod_temp)
                flops.append(f)
            else:
                YV_prod_temp = Y_arr[0]
            U_temp, _, _, f = svds(YV_prod_temp, ranks[0])
            flops.append(f)
            # U_prod_arr{n/2+1}{1}=U_temp;
            U_prod_arr[idx_prod][0] = U_temp
            for k in range(1, d-1):
                # Y_temp=kron( eye(dim_vec(k)) , U_prod_arr{n/2+1}{k-1} ) ' * Y_arr{k};
                Y_temp, f = kron(eye(dim_vec[k]), U_prod_arr[idx_prod][k-1])
                flops.append(f)
                Y_temp, f = mm(Y_temp.T, Y_arr[k])
                flops.append(f)
                # Y_tilde_arr{k-1}=reshape(Y_temp,r_vec(k-1),prod(dim_vec(k:d)));
                Y_tilde_arr[k-1] = reshape_fortran(Y_temp, ranks[k-1], prod(dim_vec[k:]))
                # [U_temp,~,~]=svds(Y_temp * V_prod_arr{n/2+1}{d-k},r_vec(k));
                V_prod_temp = V_prod_arr[idx_prod][d-k-2]
                if V_prod_temp is not None:
                    YV_prod_temp, f = mm(Y_temp, V_prod_temp)
                    flops.append(f)
                else:
                    YV_prod_temp = Y_temp
                U_temp, _, _, f = svds(YV_prod_temp, ranks[k])
                flops.append(f)
                # U_prod_arr{n/2+1}{k}=kron(eye(dim_vec(k)),U_prod_arr{n/2+1}{k-1})*U_temp;
                U_prod_temp, f = kron(eye(dim_vec[k]), U_prod_arr[idx_prod][k-1])
                flops.append(f)
                U_prod_arr[idx_prod][k], f = mm(U_prod_temp, U_temp)
                flops.append(f)
            # X_hat_temp=U_prod_arr{n/2+1}{d-1}'*Y_arr{d-1};
            X_hat_temp, f = mm(U_prod_arr[idx_prod][d-2].T, Y_arr[d-2])
            flops.append(f)
            # X_hat_arr{n+1}=reshape(U_prod_arr{n/2+1}{d-1}*X_hat_temp,dim_vec);
            U_prod_temp, f = mm(U_prod_arr[idx_prod][d-2], X_hat_temp)
            flops.append(f)
            X_hat = reshape_fortran(U_prod_temp, dim_vec)
            X_hat_arr[n] = X_hat
        else:
            idx_prod = (n + 1) // 2
            # V_prod_arr{(n+1)/2+1}=cell(d-1,1);
            V_prod_arr[idx_prod] = [None] * (d-1)
            # [~,~,V_temp]=svds(U_prod_arr{(n+1)/2}{d-1}'*Y_arr{d-1},r_vec(d-1));
            U_prod_temp, f = mm(U_prod_arr[idx_prod-1][d-2].T, Y_arr[d-2])
            flops.append(f)
            _, _, V_temp, f = svds(U_prod_temp, ranks[d-2])
            flops.append(f)
            # V_prod_arr{(n+1)/2+1}{1}=V_temp;
            V_prod_arr[idx_prod][0] = V_temp
            for k in range(1, d-1):
                # [~,~,V_temp]=svds(Y_tilde_arr{d-k}*kron(V_prod_arr{(n+1)/2+1}{k-1},eye(dim_vec(d-k+1))),r_vec(d-k));
                V_temp, f = kron(V_prod_arr[idx_prod][k-1], eye(dim_vec[d-k-1]))
                flops.append(f)
                V_temp, f = mm(Y_tilde_arr[d-k-2], V_temp)
                flops.append(f)
                _, _, V_temp, f = svds(V_temp, ranks[d-k-2])
                flops.append(f)
                # V_prod_arr{(n+1)/2+1}{k}=kron(V_prod_arr{(n+1)/2+1}{k-1},eye(dim_vec(d-k+1)))*V_temp;
                V_prod_temp, f = kron(V_prod_arr[idx_prod][k-1], eye(dim_vec[d-k-1]))
                flops.append(f)
                V_prod_temp, f = mm(V_prod_temp, V_temp)
                flops.append(f)
                V_prod_arr[idx_prod][k] = V_prod_temp
            # X_hat_temp=Y_arr{1}*V_prod_arr{(n+1)/2+1}{d-1};
            X_hat_temp, f = mm(Y_arr[0], V_prod_arr[idx_prod][d-2])
            flops.append(f)
            # X_hat_arr{n+1}=reshape(X_hat_temp*V_prod_arr{(n+1)/2+1}{d-1}',dim_vec);
            X_hat_temp, f = mm(X_hat_temp, V_prod_arr[idx_prod][d - 2].T)
            flops.append(f)
            X_hat = reshape_fortran(X_hat_temp, dim_vec)
            X_hat_arr[n] = X_hat
        if n > 1:
            # chg=sum(X_hat_arr{n}(:).^2)-sum(X_hat_arr{n-1}(:).^2);
            chg = (X_hat_arr[n] ** 2).sum() - (X_hat_arr[n-1] ** 2).sum()
        n += 1

    flops_min = int(sum(a[0] if type(a) is tuple else a for a in flops))
    flops_max = int(sum(a[1] if type(a) is tuple else a for a in flops))

    out = {
        'max_iter': max_iter,
        'flops_total_min': flops_min,
        'flops_total_max': flops_max,
    }
    out.update(analyze_tensor(X_hat, gt, ret_X_hat, ret_gt_metrics))
    return out