//  [python]
def _smi(
    X,
    Y,
    n_components: Optional[int] = 10,
    projection: str = "orthogonal",
    significance: bool = False,
    B: int = 10_000,
    center: bool = True,
):
    """
    Similarity of Matrices Index (SMI)

    A similarity index for comparing coupled data matrices.
    A two-step process starts with extraction of stable subspaces using Principal Component Analysis or some other
    method yielding two orthonormal bases. These bases are compared using Orthogonal Projection (OP / ordinary least
    squares) or Procrustes Rotation (PR). The result is a similarity measure that can be adjusted to various data sets
    and contexts and which includes explorative plotting and permutation based testing of matrix subspace equality.

    Reference: `A similarity index for comparing coupled matrices`_
    .. _A similarity index for comparing coupled matrices: https://onlinelibrary.wiley.com/doi/abs/10.1002/cem.3049
    .. _Hoggorm implementation: https://github.com/olivertomic/hoggorm

    significance=True:
        Significance estimation for Similarity of Matrices Index (SMI)

        For each combination of components significance is estimated by sampling from a null distribution of no
        similarity, i.e. when the rows of one matrix is permuted B times and corresponding SMI values are computed. If
        the vector replicates is included, replicates will be kept together through permutations.
    """
    if center:
        X = X - X.mean(0)
        Y = Y - Y.mean(0)

    rankX = _matrix_rank(X) if n_components is None else n_components
    rankY = _matrix_rank(Y) if n_components is None else n_components

    UX, _, _ = torch.svd(X)
    UY, _, _ = torch.svd(Y)

    m = torch.empty(())  # please torch.jit

    # Compute SMI values
    if projection == "orthogonal":
        m = (
            torch.arange(rankX, device=X.device)[:, None]
            .tile(1, rankX)
            .min(torch.arange(rankY, device=X.device)[None, :].tile(rankY, 1))
            .add(1)
            .reshape(rankX, rankY)
        )
        smi = (UX[:, :rankX].T @ UY[:, :rankY]).square().cumsum(1).cumsum(0) / m

    else:  # procrustes
        smi = torch.zeros((rankX, rankY), device=X.device)
        TU = UX[:, :rankX].T @ UY[:, :rankY]
        for p in range(rankX):
            for q in range(rankY):
                smi[p, q] = torch.svd(TU[: p + 1, : q + 1]).S.mean().square()

    # Recover wrong calculations (due to numerics)
    smi[smi > 1] = 1
    smi[smi < 0] = 0

    P = torch.zeros((rankX, rankY), device=X.device)

    if significance:
        BUX = UX.clone()

        if projection == "orthogonal":
            for __ in range(B):
                BUX = BUX[torch.randperm(len(BUX), device=X.device)]
                smiB = (BUX[:, :rankX].T @ UY[:, :rankY]).square().cumsum(1).cumsum(0) / m
                P[smi > torch.maximum(smiB, 1 - smiB)] += 1  # Increase P-value if non-significant permutation

        else:  # procrustes
            for __ in range(B):
                BUX = BUX[torch.randperm(len(BUX), device=X.device)]
                smiB = torch.zeros((rankX, rankY), device=X.device)
                TU = BUX[:, :rankX].T @ UY[:, :rankY]
                for p in range(rankX):
                    for q in range(rankY):
                        smiB[p, q] = torch.svd(TU[: p + 1, : q + 1]).S.mean().square()
                P[smi > torch.maximum(smiB, 1 - smiB)] += 1  # Increase P-value if non-significant permutation

        return smi, P / B

    return smi, -torch.ones(())