jiahuimbzuai/CoQuIR huggingface dataset:
//  [python]
def zca(content, style):
    '''
    Matches the mean and covariance of 'content' to those of 'style'
    content -- N x D pytorch tensor of content feature vectors
    style   -- N x D pytorch tensor of style feature vectors
    '''
    mu_c = content.mean(0, keepdim=True)
    mu_s = style.mean(0, keepdim=True)

    content = content - mu_c
    style = style - mu_s

    cov_c = torch.matmul(content.transpose(1,0), content) / float(content.size(0))
    cov_s = torch.matmul(style.transpose(1,0), style) / float(style.size(0))

    u_c, sig_c, _ = torch.linalg.svd(cov_c + torch.eye(cov_c.size(0)).cuda()*1e-4)
    u_s, sig_s, _ = torch.linalg.svd(cov_s + torch.eye(cov_s.size(0)).cuda()*1e-4)

    sig_c = sig_c.unsqueeze(1)
    sig_s = sig_s.unsqueeze(1)


    u_c_i = u_c.transpose(1,0)
    u_s_i = u_s.transpose(1,0)

    scl_c = torch.sqrt(torch.clamp(sig_c, 1e-8, 1e8))
    scl_s = torch.sqrt(torch.clamp(sig_s, 1e-8, 1e8))


    whiten_c = whiten(content, u_c_i, u_c, scl_c)
    color_c = colorize(whiten_c, u_s_i, u_s, scl_s) + mu_s

    return color_c, cov_s
//  [python]
def __init__(self, H):
        self._U, self._singulars, self._V = torch.svd(H, some=False)
        self._Vt = self._V.transpose(0, 1)
        self._Ut = self._U.transpose(0, 1)

        ZERO = 1e-3
        self._singulars[self._singulars < ZERO] = 0
        print(len([x.item() for x in self._singulars if x == 0]))
//  [python]
def __init__(self, H):
        self._U, self._singulars, self._V = torch.linalg.svd(H)
        self._Vt = self._V.transpose(0, 1)
        self._Ut = self._U.transpose(0, 1)

        ZERO = 1e-3
        self._singulars[self._singulars < ZERO] = 0
        print(len([x.item() for x in self._singulars if x == 0]))
//  [python]
def __init__(self, img_dim, device):
        self.channels = 3
        self.img_dim = img_dim
        #Do the SVD for the per-pixel matrix
        H = torch.Tensor([[0.3333, 0.3334, 0.3333]]).to(device)
        self.U_small, self.singulars_small, self.V_small = torch.svd(H, some=False)
        self.Vt_small = self.V_small.transpose(0, 1)
//  [python]
def __init__(self, img_dim, device):
        self.channels = 3
        self.img_dim = img_dim
        #Do the SVD for the per-pixel matrix
        H = torch.Tensor([[0.3333, 0.3334, 0.3333]]).to(device)
        self.U_small, self.singulars_small, self.V_small = torch.linalg.svd(H)
        self.Vt_small = self.V_small.transpose(0, 1)
//  [python]
def SV(self):
        """
        Singular values
        :return: Singular values as a vector
        """
        (U, S, V) = torch.svd(self.C)
        return S
//  [python]
def SV(self):
        """
        Singular values
        :return: Singular values as a vector
        """
        (U, S, V) = torch.linalg.svd(self.C)
        return S
//  [python]
def SVD(self):
        """
        Singular values decomposition on C
        :return: Singular values as a vector
        """
        return torch.svd(self.C)
//  [python]
def SVD(self):
        """
        Singular values decomposition on C
        :return: Singular values as a vector
        """
        return torch.linalg.svd(self.C)
//  [python]
def svd_orthonormal(self, w):
        shape = w.shape
        flat_shape = (shape[0], np.prod(shape[1:]))
        a = torch.rand(flat_shape, device=w.device)
        u, _, v = torch.svd(a, some=True)
        q = u if u.shape == flat_shape else v.t()
        q = q.view(shape)
        return q.to(torch.float)
//  [python]
def svd_orthonormal(self, w):
        shape = w.shape
        flat_shape = (shape[0], np.prod(shape[1:]))
        a = torch.rand(flat_shape, device=w.device)
        u, _, v = torch.linalg.svd(a, full_matrices=False)
        q = u if u.shape == flat_shape else v.t()
        q = q.view(shape)
        return q.to(torch.float)
//  [python]
def homography(X):

    dims = X.size()
    A_dims = tuple(list(dims)[:-2] + [9, 9])

    A = torch.zeros(A_dims, device=X.device)

    A[..., 0:8:2, 5] = -1
    A[..., 0:8:2, 3:5] = -X[..., :, 0:2]
    A[..., 0:8:2, 6:8] = X[..., :, 0:2]
    A[..., 0:8:2, 6] *= X[..., :, 3]
    A[..., 0:8:2, 7] *= X[..., :, 3]
    A[..., 0:8:2, 8] = X[..., :, 3]
    A[..., 1:8:2, 2] = 1
    A[..., 1:8:2, 0:2] = X[..., :, 0:2]
    A[..., 1:8:2, 6:8] = -X[..., :, 0:2]
    A[..., 1:8:2, 6] *= X[..., :, 2]
    A[..., 1:8:2, 7] *= X[..., :, 2]
    A[..., 1:8:2, 8] = -X[..., :, 2]

    try:
        u, s, v = torch.svd(A)
        h = v[..., -1].to(X.device)
    except:
        h = torch.zeros(list(dims)[:-2] + [9], device=X.device)

    return h
//  [python]
def homography(X):

    dims = X.size()
    A_dims = tuple(list(dims)[:-2] + [9, 9])

    A = torch.zeros(A_dims, device=X.device)

    A[..., 0:8:2, 5] = -1
    A[..., 0:8:2, 3:5] = -X[..., :, 0:2]
    A[..., 0:8:2, 6:8] = X[..., :, 0:2]
    A[..., 0:8:2, 6] *= X[..., :, 3]
    A[..., 0:8:2, 7] *= X[..., :, 3]
    A[..., 0:8:2, 8] = X[..., :, 3]
    A[..., 1:8:2, 2] = 1
    A[..., 1:8:2, 0:2] = X[..., :, 0:2]
    A[..., 1:8:2, 6:8] = -X[..., :, 0:2]
    A[..., 1:8:2, 6] *= X[..., :, 2]
    A[..., 1:8:2, 7] *= X[..., :, 2]
    A[..., 1:8:2, 8] = -X[..., :, 2]

    try:
        u, s, v = torch.linalg.svd(A)
        h = v[..., -1].to(X.device)
    except:
        h = torch.zeros(list(dims)[:-2] + [9], device=X.device)

    return h
//  [python]
def modify_SVs(self, svs_func):
        """
        Modify singular values with a function
        """
        # SVD on C
        Uc, Sc, Vc = torch.svd(self.C)

        # Modify singular values with the function
        new_Sc = svs_func(Sc)

        # Apply the change to C
        self.C = torch.mm(Uc, torch.mm(torch.diag(new_Sc), Vc.t()))
//  [python]
def modify_SVs(self, svs_func):
        """
        Modify singular values with a function
        """
        # SVD on C
        Uc, Sc, Vc = torch.linalg.svd(self.C)

        # Modify singular values with the function
        new_Sc = svs_func(Sc)

        # Apply the change to C
        self.C = torch.mm(Uc, torch.mm(torch.diag(new_Sc), Vc.t()))
//  [python]
def __init__(self, kernel1, kernel2, channels, img_dim, device):
        self.img_dim = img_dim
        self.channels = channels
        #build 1D conv matrix - kernel1
        H_small1 = torch.zeros(img_dim, img_dim, device=device)
        for i in range(img_dim):
            for j in range(i - kernel1.shape[0]//2, i + kernel1.shape[0]//2):
                if j < 0 or j >= img_dim: continue
                H_small1[i, j] = kernel1[j - i + kernel1.shape[0]//2]
        #build 1D conv matrix - kernel2
        H_small2 = torch.zeros(img_dim, img_dim, device=device)
        for i in range(img_dim):
            for j in range(i - kernel2.shape[0]//2, i + kernel2.shape[0]//2):
                if j < 0 or j >= img_dim: continue
                H_small2[i, j] = kernel2[j - i + kernel2.shape[0]//2]
        #get the svd of the 1D conv
        self.U_small1, self.singulars_small1, self.V_small1 = torch.svd(H_small1, some=False)
        self.U_small2, self.singulars_small2, self.V_small2 = torch.svd(H_small2, some=False)
        ZERO = 3e-2
        self.singulars_small1[self.singulars_small1 < ZERO] = 0
        self.singulars_small2[self.singulars_small2 < ZERO] = 0
        #calculate the singular values of the big matrix
        self._singulars = torch.matmul(self.singulars_small1.reshape(img_dim, 1), self.singulars_small2.reshape(1, img_dim)).reshape(img_dim**2)
        #sort the big matrix singulars and save the permutation
        self._singulars, self._perm = self._singulars.sort(descending=True)
//  [python]
def __init__(self, kernel1, kernel2, channels, img_dim, device):
        self.img_dim = img_dim
        self.channels = channels
        #build 1D conv matrix - kernel1
        H_small1 = torch.zeros(img_dim, img_dim, device=device)
        for i in range(img_dim):
            for j in range(i - kernel1.shape[0]//2, i + kernel1.shape[0]//2):
                if j < 0 or j >= img_dim: continue
                H_small1[i, j] = kernel1[j - i + kernel1.shape[0]//2]
        #build 1D conv matrix - kernel2
        H_small2 = torch.zeros(img_dim, img_dim, device=device)
        for i in range(img_dim):
            for j in range(i - kernel2.shape[0]//2, i + kernel2.shape[0]//2):
                if j < 0 or j >= img_dim: continue
                H_small2[i, j] = kernel2[j - i + kernel2.shape[0]//2]
        #get the svd of the 1D conv
        self.U_small1, self.singulars_small1, self.V_small1 = torch.linalg.svd(H_small1)
        self.U_small2, self.singulars_small2, self.V_small2 = torch.linalg.svd(H_small2)
        ZERO = 3e-2
        self.singulars_small1[self.singulars_small1 < ZERO] = 0
        self.singulars_small2[self.singulars_small2 < ZERO] = 0
        #calculate the singular values of the big matrix
        self._singulars = torch.matmul(self.singulars_small1.reshape(img_dim, 1), self.singulars_small2.reshape(1, img_dim)).reshape(img_dim**2)
        #sort the big matrix singulars and save the permutation
        self._singulars, self._perm = self._singulars.sort(descending=True)
//  [python]
def computeR(C, aperture, inv_algo=torch.inverse):
        """
        Compute R from conceptor matrix C
        :param C: Conceptor matrix C
        :param aperture: Aperture parameter
        :param inv_algo: Matrix inversion function (default: torch.inv)
        :return: R matrix (as torch tensor)
        """
        C_dim = C.size(0)

        # Compute R directory if C != I
        if torch.all(torch.eq(C, torch.eye(C_dim, dtype=C.dtype))):
            return None
        else:
            targetC = torch.eye(C_dim) - C
            # print("I - C: {}".format(targetC))
            U, S, V = torch.svd(targetC)
            # print("U of target: {}".format(U))
            return math.pow(aperture, -2) * torch.mm(C, inv_algo(torch.eye(C_dim) - C))
//  [python]
def computeR(C, aperture, inv_algo=torch.inverse):
        """
        Compute R from conceptor matrix C
        :param C: Conceptor matrix C
        :param aperture: Aperture parameter
        :param inv_algo: Matrix inversion function (default: torch.inv)
        :return: R matrix (as torch tensor)
        """
        C_dim = C.size(0)

        # Compute R directory if C != I
        if torch.all(torch.eq(C, torch.eye(C_dim, dtype=C.dtype))):
            return None
        else:
            targetC = torch.eye(C_dim) - C
            # print("I - C: {}".format(targetC))
            U, S, V = torch.linalg.svd(targetC)
            # print("U of target: {}".format(U))
            return math.pow(aperture, -2) * torch.mm(C, inv_algo(torch.eye(C_dim) - C))
//  [python]
def PHI(self, gamma):
        """
        Multiply aperture by a factor
        :param gamma: Multiply aperture by a factor.
        """
        # Dimension
        dim = self.dim

        # Multiply by 0
        if gamma == 0:
            (U, S, V) = torch.svd(self.C)
            Sdiag = S
            Sdiag[Sdiag < 1] = torch.zeros((sum(Sdiag < 1), 1))
            Cnew = torch.mm(U, torch.mm(torch.diag(Sdiag), U.t()))
        elif gamma == float("inf"):
            (U, S, V) = torch.svd(self.C)
            Sdiag = S
            Sdiag[Sdiag > 0] = torch.ones(sum(Sdiag > 0), 1)
            Cnew = torch.mm(U, torch.mm(torch.diag(Sdiag), U.t()))
        else:
            Cnew = torch.mm(self.C, torch.inverse(self.C + math.pow(gamma, -2) * (torch.eye(dim) - self.C)))
        # end

        # Set aperture and C
        self.set_C(Cnew, self._aperture * gamma)
//  [python]
def PHI(self, gamma):
        """
        Multiply aperture by a factor
        :param gamma: Multiply aperture by a factor.
        """
        # Dimension
        dim = self.dim

        # Multiply by 0
        if gamma == 0:
            (U, S, V) = torch.linalg.svd(self.C)
            Sdiag = S
            Sdiag[Sdiag < 1] = torch.zeros((sum(Sdiag < 1), 1))
            Cnew = torch.mm(U, torch.mm(torch.diag(Sdiag), U.t()))
        elif gamma == float("inf"):
            (U, S, V) = torch.linalg.svd(self.C)
            Sdiag = S
            Sdiag[Sdiag > 0] = torch.ones(sum(Sdiag > 0), 1)
            Cnew = torch.mm(U, torch.mm(torch.diag(Sdiag), U.t()))
        else:
            Cnew = torch.mm(self.C, torch.inverse(self.C + math.pow(gamma, -2) * (torch.eye(dim) - self.C)))
        # end

        # Set aperture and C
        self.set_C(Cnew, self._aperture * gamma)
//  [python]
def logeuclid(A, B):

    def log_mat(X):
        N, d = X.size()
        u,s,vh = torch.svd(X)
        eigXtX = (s**2) / (N - 1)
        logeig = torch.log(eigXtX)
        #logX =   torch.einsum('id,d,jd->ij', (vh,logeig,vh))
        logX = vh.mm(torch.diag(logeig)).mm(vh.transpose(1,0))
        return logX
    
    logA = log_mat(A)
    logB = log_mat(B)

    #logA = apply(A, torch.log)
    #logB = apply(B, torch.log)
    
    return euclid(logA, logB)
//  [python]
def logeuclid(A, B):

    def log_mat(X):
        N, d = X.size()
        u,s,vh = torch.linalg.svd(X)
        eigXtX = (s**2) / (N - 1)
        logeig = torch.log(eigXtX)
        #logX =   torch.einsum('id,d,jd->ij', (vh,logeig,vh))
        logX = vh.mm(torch.diag(logeig)).mm(vh.transpose(1,0))
        return logX
    
    logA = log_mat(A)
    logB = log_mat(B)

    #logA = apply(A, torch.log)
    #logB = apply(B, torch.log)
    
    return euclid(logA, logB)
//  [python]
def normalize_rotmat(mat: torch.Tensor) -> torch.Tensor:
    """Normalizes rotation matrix to a valid one"""
    u, _, v = torch.svd(mat)

    s = torch.zeros_like(mat)
    s[..., 0, 0] = 1.0
    s[..., 1, 1] = 1.0
    s[..., 2, 2] = torch.det(u) * torch.det(v)
    return u @ s @ v.transpose(-1, -2)
//  [python]
def normalize_rotmat(mat: torch.Tensor) -> torch.Tensor:
    """Normalizes rotation matrix to a valid one"""
    u, _, v = torch.linalg.svd(mat)

    s = torch.zeros_like(mat)
    s[..., 0, 0] = 1.0
    s[..., 1, 1] = 1.0
    s[..., 2, 2] = torch.det(u) * torch.det(v)
    return u @ s @ v.transpose(-1, -2)
//  [python]
def compute_rigid_transformation(src, src_corr, weight):
    """
        Compute rigid transforms between two point sets
        Args:
            src: Source point clouds. Size (B, 3, N)
            src_corr: Pseudo target point clouds. Size (B, 3, N)
            weight: Inlier confidence. (B, 1, N)
        Returns:
            R: Rotation. Size (B, 3, 3)
            t: translation. Size (B, 3, 1)
    """
    src2 = (src * weight).sum(dim=2, keepdim=True) / weight.sum(dim=2, keepdim=True)
    src_corr2 = (src_corr * weight).sum(dim=2, keepdim=True) / weight.sum(dim=2, keepdim=True)
    src_centered = src - src2
    src_corr_centered = src_corr - src_corr2
    bs, dim, _ = src.shape
    cov = torch.matmul(src_centered * weight, src_corr_centered.transpose(2, 1).contiguous())
    eye = torch.eye(dim).to(src).repeat(bs, 1, 1)
    try:
        cov = torch.nan_to_num(cov, nan=0.0) + 1e-5 * eye
        u, s, v = torch.svd(cov.cpu(), some=True, compute_uv=True)
    except Exception:
        cov = eye
        u, s, v = torch.svd(cov.cpu(), some=True, compute_uv=True)
    u, v = u.to(src), v.to(src_corr)
    rot_pos = v @ u.transpose(-1, -2)
    v_neg = v.clone()
    v_neg[:, :, 2] *= -1
    rot_neg = v_neg @ u.transpose(-1, -2)
    rot_mat = torch.where(torch.det(rot_pos)[:, None, None] > 0, rot_pos, rot_neg)
    # assert torch.all(torch.det(rot_mat) > 0)
    # Compute translation (uncenter centroid)
    translation = torch.matmul(-rot_mat, src2.mean(dim=2, keepdim=True)) + src_corr2.mean(dim=2, keepdim=True)
    return rot_mat, translation
//  [python]
def compute_rigid_transformation(src, src_corr, weight):
    """
        Compute rigid transforms between two point sets
        Args:
            src: Source point clouds. Size (B, 3, N)
            src_corr: Pseudo target point clouds. Size (B, 3, N)
            weight: Inlier confidence. (B, 1, N)
        Returns:
            R: Rotation. Size (B, 3, 3)
            t: translation. Size (B, 3, 1)
    """
    src2 = (src * weight).sum(dim=2, keepdim=True) / weight.sum(dim=2, keepdim=True)
    src_corr2 = (src_corr * weight).sum(dim=2, keepdim=True) / weight.sum(dim=2, keepdim=True)
    src_centered = src - src2
    src_corr_centered = src_corr - src_corr2
    bs, dim, _ = src.shape
    cov = torch.matmul(src_centered * weight, src_corr_centered.transpose(2, 1).contiguous())
    eye = torch.eye(dim).to(src).repeat(bs, 1, 1)
    try:
        cov = torch.nan_to_num(cov, nan=0.0) + 1e-5 * eye
        u, s, v = torch.linalg.svd(cov.cpu())
    except Exception:
        cov = eye
        u, s, v = torch.linalg.svd(cov.cpu())
    u, v = u.to(src), v.to(src_corr)
    rot_pos = v @ u.transpose(-1, -2)
    v_neg = v.clone()
    v_neg[:, :, 2] *= -1
    rot_neg = v_neg @ u.transpose(-1, -2)
    rot_mat = torch.where(torch.det(rot_pos)[:, None, None] > 0, rot_pos, rot_neg)
    # assert torch.all(torch.det(rot_mat) > 0)
    # Compute translation (uncenter centroid)
    translation = torch.matmul(-rot_mat, src2.mean(dim=2, keepdim=True)) + src_corr2.mean(dim=2, keepdim=True)
    return rot_mat, translation
//  [python]
def _sparsify(self, tensor, rank_j, k_split, scheme):
        """Sparsify to the desired number of features (rank_j)."""
        # get chunks from dim 1 (input dimension)
        tensor_chunks = torch.chunk(tensor, k_split, dim=1)

        # fold tensor chunks
        tensor_chunks = [scheme.fold(chunk) for chunk in tensor_chunks]

        # compute chunked SVD
        svd_chunks = [torch.svd(chunk) for chunk in tensor_chunks]

        # change from A = U * D * V^T convention to A = U * V convention
        svd_chunks = [
            (u @ torch.sqrt(torch.diag(d)), torch.sqrt(torch.diag(d)) @ v.t())
            for u, d, v in svd_chunks
        ]

        # remove undesired parts of approximation for lower singular values
        return [(u[:, :rank_j], v[:rank_j, :]) for u, v in svd_chunks]
//  [python]
def _sparsify(self, tensor, rank_j, k_split, scheme):
        """Sparsify to the desired number of features (rank_j)."""
        # get chunks from dim 1 (input dimension)
        tensor_chunks = torch.chunk(tensor, k_split, dim=1)

        # fold tensor chunks
        tensor_chunks = [scheme.fold(chunk) for chunk in tensor_chunks]

        # compute chunked SVD
        svd_chunks = [torch.linalg.svd(chunk) for chunk in tensor_chunks]

        # change from A = U * D * V^T convention to A = U * V convention
        svd_chunks = [
            (u @ torch.sqrt(torch.diag(d)), torch.sqrt(torch.diag(d)) @ v.t())
            for u, d, v in svd_chunks
        ]

        # remove undesired parts of approximation for lower singular values
        return [(u[:, :rank_j], v[:rank_j, :]) for u, v in svd_chunks]
//  [python]
def rank(A):
    """Rank of a matrix"""
    U, S, V = torch.svd(A)
    cond = get_condition(A.dtype)
    cutoff = torch.max(S) * cond
    return torch.sum(S > cutoff).type(torch.get_default_dtype())
//  [python]
def rank(A):
    """Rank of a matrix"""
    U, S, V = torch.linalg.svd(A)
    cond = get_condition(A.dtype)
    cutoff = torch.max(S) * cond
    return torch.sum(S > cutoff).type(torch.get_default_dtype())
//  [python]
def AND(self, B, tol=1e-14):
        """
        AND in Conceptor Logic
        :param B: Second conceptor operand (reservoir size x reservoir size)
        :return: Self AND B
        """
        # Dimension
        dim = self.input_dim

        # Conceptor matrices
        Cc = self.C
        Bc = B.C

        # Same conceptor ?
        same_conceptor = torch.all(torch.eq(Cc, Bc))

        # Apertures
        C_aperture = self.aperture
        B_aperture = B.aperture

        # Same aperture
        same_aperture = C_aperture == B_aperture

        # SV on both conceptor
        (UC, SC, UtC) = torch.svd(Cc)
        (UB, SB, UtB) = torch.svd(Bc)

        # Get singular values
        dSC = SC
        dSB = SB

        # How many non-zero singular values
        numRankC = int(torch.sum(1.0 * (dSC > tol)))
        numRankB = int(torch.sum(1.0 * (dSB > tol)))

        # Select zero singular vector
        UC0 = UC[:, numRankC:]
        UB0 = UB[:, numRankB:]

        # SVD on UC0 + UB0
        # (W, Sigma, Wt) = lin.svd(np.dot(UC0, UC0.T) + np.dot(UB0, UB0.T))
        (W, Sigma, Wt) = torch.svd(torch.mm(UC0, UC0.t()) + torch.mm(UB0, UB0.t()))

        # Number of non-zero SV
        numRankSigma = int(sum(1.0 * (Sigma > tol)))

        # Select zero singular vector
        Wgk = W[:, numRankSigma:]

        # C and B
        # Wgk * (Wgk^T * (C^-1 + B^-1 - I) * Wgk)^-1 * Wgk^T
        # CandB = Wgk @ torch.inverse(Wgk.t() @ (torch.pinverse(Cc, tol) + torch.pinverse(Bc, tol) - torch.eye(dim)) @ Wgk) @ Wgk.t()
        CandB = torch.mm(torch.mm(Wgk, torch.inverse(torch.mm(Wgk.t(), torch.mm((torch.pinverse(Cc, tol) + torch.pinverse(Bc, tol) - torch.eye(dim)), Wgk)))), Wgk.t())

        # New conceptor
        new_conceptor = Conceptor(
            input_dim=dim,
            aperture=1
        )

        # Cet C
        # TODO: Problem with aperture of a AND of two different conceptors
        if same_conceptor:
            new_conceptor.set_C(
                C=CandB,
                aperture=1.0 / math.sqrt(math.pow(C_aperture, -2) + math.pow(B_aperture, -2))
            )
        elif not same_conceptor and same_aperture:
            new_conceptor.set_C(
                C=CandB,
                aperture=C_aperture
            )
        else:
            # print("WARNING: Computing the AND of two different conceptors with different aperture is hazardous (aperture put to 1)")
            new_conceptor.set_C(
                C=CandB,
                aperture=1.0
            )
        # end if

        return new_conceptor
//  [python]
def AND(self, B, tol=1e-14):
        """
        AND in Conceptor Logic
        :param B: Second conceptor operand (reservoir size x reservoir size)
        :return: Self AND B
        """
        # Dimension
        dim = self.input_dim

        # Conceptor matrices
        Cc = self.C
        Bc = B.C

        # Same conceptor ?
        same_conceptor = torch.all(torch.eq(Cc, Bc))

        # Apertures
        C_aperture = self.aperture
        B_aperture = B.aperture

        # Same aperture
        same_aperture = C_aperture == B_aperture

        # SV on both conceptor
        (UC, SC, UtC) = torch.linalg.svd(Cc)
        (UB, SB, UtB) = torch.linalg.svd(Bc)

        # Get singular values
        dSC = SC
        dSB = SB

        # How many non-zero singular values
        numRankC = int(torch.sum(1.0 * (dSC > tol)))
        numRankB = int(torch.sum(1.0 * (dSB > tol)))

        # Select zero singular vector
        UC0 = UC[:, numRankC:]
        UB0 = UB[:, numRankB:]

        # SVD on UC0 + UB0
        # (W, Sigma, Wt) = lin.svd(np.dot(UC0, UC0.T) + np.dot(UB0, UB0.T))
        (W, Sigma, Wt) = torch.linalg.svd(torch.mm(UC0, UC0.t()) + torch.mm(UB0, UB0.t()))

        # Number of non-zero SV
        numRankSigma = int(sum(1.0 * (Sigma > tol)))

        # Select zero singular vector
        Wgk = W[:, numRankSigma:]

        # C and B
        # Wgk * (Wgk^T * (C^-1 + B^-1 - I) * Wgk)^-1 * Wgk^T
        # CandB = Wgk @ torch.inverse(Wgk.t() @ (torch.pinverse(Cc, tol) + torch.pinverse(Bc, tol) - torch.eye(dim)) @ Wgk) @ Wgk.t()
        CandB = torch.mm(torch.mm(Wgk, torch.inverse(torch.mm(Wgk.t(), torch.mm((torch.pinverse(Cc, tol) + torch.pinverse(Bc, tol) - torch.eye(dim)), Wgk)))), Wgk.t())

        # New conceptor
        new_conceptor = Conceptor(
            input_dim=dim,
            aperture=1
        )

        # Cet C
        # TODO: Problem with aperture of a AND of two different conceptors
        if same_conceptor:
            new_conceptor.set_C(
                C=CandB,
                aperture=1.0 / math.sqrt(math.pow(C_aperture, -2) + math.pow(B_aperture, -2))
            )
        elif not same_conceptor and same_aperture:
            new_conceptor.set_C(
                C=CandB,
                aperture=C_aperture
            )
        else:
            # print("WARNING: Computing the AND of two different conceptors with different aperture is hazardous (aperture put to 1)")
            new_conceptor.set_C(
                C=CandB,
                aperture=1.0
            )
        # end if

        return new_conceptor
//  [python]
def fundamental_7point(X):

    # adapted from https://imkaywu.github.io/blog/2017/06/fundamental-matrix/

    dims = X.size()
    A_dims = tuple(list(dims)[:-2] + [7, 9])

    A = torch.ones(A_dims, device=X.device)

    A[..., 0:2] = X[..., 0:2]
    A[..., 0:2] *= X[..., 2][..., None]
    A[..., 3:5] = X[..., 0:2]
    A[..., 3:5] *= X[..., 3][..., None]
    A[..., 6:8] = X[..., 0:2]

    B = A.cpu()
    u, s, v = torch.svd(B)

    degenerate = s[..., -3].to(X.device).abs() < 1e-5

    fs = v[..., -2:].transpose(-1, -2).to(X.device)
    fs = fs / torch.clamp(torch.norm(fs, dim=-1, keepdim=True), min=1e-9)
    Fs = fs.contiguous().view(list(dims)[:-2] + [2, 3, 3])

    D = torch.zeros(list(dims)[:-2] + [2, 2, 2], device=Fs.device)
    D_tmp = torch.zeros(list(dims)[:-2] + [3, 3], device=D.device)
    for i1 in range(2):
        for i2 in range(2):
            for i3 in range(2):
                D_tmp[..., 0] = Fs[..., i1, :, 0]
                D_tmp[..., 1] = Fs[..., i2, :, 1]
                D_tmp[..., 2] = Fs[..., i3, :, 2]
                D[..., i1, i2, i3] = torch.det(D_tmp)

    poly_coeffs = torch.zeros(list(dims)[:-2] + [4], device=X.device)
    poly_coeffs[..., 3] = -D[..., 1, 0, 0]+D[..., 0, 1, 1]+D[..., 0, 0, 0]+D[..., 1, 1, 0]+D[..., 1, 0, 1]-D[..., 0, 1, 0]-D[..., 0, 0, 1]-D[..., 1, 1, 1]
    poly_coeffs[..., 2] = D[..., 0, 0, 1]-2*D[..., 0, 1, 1]-2*D[..., 1, 0, 1]+D[..., 1, 0, 0]-2*D[..., 1, 1, 0]+D[..., 0, 1, 0]+3*D[..., 1, 1, 1]
    poly_coeffs[..., 1] = D[..., 1, 1, 0]+D[..., 0, 1, 1]+D[..., 1, 0, 1]-3*D[..., 1, 1, 1]
    poly_coeffs[..., 0] = D[..., 1, 1, 1]
    poly_coeffs = poly_coeffs / torch.clamp(torch.abs(poly_coeffs[..., 3][..., None]), min=1e-9)
    poly_coeffs = poly_coeffs * torch.sign(poly_coeffs[..., 3][..., None])

    # find roots of polynomial det(a*F1 + (1-a)*F2)
    # https://en.wikipedia.org/wiki/Companion_matrix
    companion = torch.diag_embed(torch.ones(2, dtype=torch.float32, device=X.device), offset=-1)
    companion = companion.view([1]*(len(dims)-2) + [3, 3]).expand(list(dims[:-2]) + [3, 3])
    companion = torch.clone(companion)
    companion[..., 0, -1] -= poly_coeffs[..., 0]
    companion[..., 1, -1] -= poly_coeffs[..., 1]
    companion[..., 2, -1] -= poly_coeffs[..., 2]

    eigvals_complex, _ = torch.linalg.eig(companion)
    eigvals = eigvals_complex.real.to(X.device)

    isnotreal = torch.logical_not(torch.isreal(eigvals_complex.to(X.device)))

    # compute residuals for all three solutions and select the best
    f = fs[..., 0, None, :] * eigvals[..., None] + fs[..., 1, None, :] * (1 - eigvals[..., None])
    X_ = X[..., None, :, :].expand(list(dims)[:-2] + [3] + list(dims)[-2:])
    res = utils.residual_functions.sampson_distance(X_, f)
    mean_res = res.mean(-1) + isnotreal.float() * res.max() * 10
    best_fs = torch.argmin(mean_res, dim=-1, keepdim=True)[..., None].expand(list(dims)[:-2] + [1, 9])
    h = torch.gather(f, -2, best_fs).squeeze(-2)

    h = h * torch.logical_not(degenerate).float()[..., None]

    return h
//  [python]
def fundamental_7point(X):

    dims = X.size()
    A_dims = tuple(list(dims)[:-2] + [7, 9])

    A = torch.ones(A_dims, device=X.device)

    A[..., 0:2] = X[..., 0:2]
    A[..., 0:2] *= X[..., 2][..., None]
    A[..., 3:5] = X[..., 0:2]
    A[..., 3:5] *= X[..., 3][..., None]
    A[..., 6:8] = X[..., 0:2]

    B = A.cpu()
    u, s, v = torch.linalg.svd(B)

    degenerate = s[..., -3].to(X.device).abs() < 1e-5

    fs = v[..., -2:].transpose(-1, -2).to(X.device)
    fs = fs / torch.clamp(torch.norm(fs, dim=-1, keepdim=True), min=1e-9)
    Fs = fs.contiguous().view(list(dims)[:-2] + [2, 3, 3])

    D = torch.zeros(list(dims)[:-2] + [2, 2, 2], device=Fs.device)
    D_tmp = torch.zeros(list(dims)[:-2] + [3, 3], device=D.device)
    for i1 in range(2):
        for i2 in range(2):
            for i3 in range(2):
                D_tmp[..., 0] = Fs[..., i1, :, 0]
                D_tmp[..., 1] = Fs[..., i2, :, 1]
                D_tmp[..., 2] = Fs[..., i3, :, 2]
                D[..., i1, i2, i3] = torch.det(D_tmp)

    poly_coeffs = torch.zeros(list(dims)[:-2] + [4], device=X.device)
    poly_coeffs[..., 3] = -D[..., 1, 0, 0]+D[..., 0, 1, 1]+D[..., 0, 0, 0]+D[..., 1, 1, 0]+D[..., 1, 0, 1]-D[..., 0, 1, 0]-D[..., 0, 0, 1]-D[..., 1, 1, 1]
    poly_coeffs[..., 2] = D[..., 0, 0, 1]-2*D[..., 0, 1, 1]-2*D[..., 1, 0, 1]+D[..., 1, 0, 0]-2*D[..., 1, 1, 0]+D[..., 0, 1, 0]+3*D[..., 1, 1, 1]
    poly_coeffs[..., 1] = D[..., 1, 1, 0]+D[..., 0, 1, 1]+D[..., 1, 0, 1]-3*D[..., 1, 1, 1]
    poly_coeffs[..., 0] = D[..., 1, 1, 1]
    poly_coeffs = poly_coeffs / torch.clamp(torch.abs(poly_coeffs[..., 3][..., None]), min=1e-9)
    poly_coeffs = poly_coeffs * torch.sign(poly_coeffs[..., 3][..., None])

    # find roots of polynomial det(a*F1 + (1-a)*F2)
    # https://en.wikipedia.org/wiki/Companion_matrix
    companion = torch.diag_embed(torch.ones(2, dtype=torch.float32, device=X.device), offset=-1)
    companion = companion.view([1]*(len(dims)-2) + [3, 3]).expand(list(dims[:-2]) + [3, 3])
    companion = torch.clone(companion)
    companion[..., 0, -1] -= poly_coeffs[..., 0]
    companion[..., 1, -1] -= poly_coeffs[..., 1]
    companion[..., 2, -1] -= poly_coeffs[..., 2]

    eigvals_complex, _ = torch.linalg.eig(companion)
    eigvals = eigvals_complex.real.to(X.device)

    isnotreal = torch.logical_not(torch.isreal(eigvals_complex.to(X.device)))

    # compute residuals for all three solutions and select the best
    f = fs[..., 0, None, :] * eigvals[..., None] + fs[..., 1, None, :] * (1 - eigvals[..., None])
    X_ = X[..., None, :, :].expand(list(dims)[:-2] + [3] + list(dims)[-2:])
    res = utils.residual_functions.sampson_distance(X_, f)
    mean_res = res.mean(-1) + isnotreal.float() * res.max() * 10
    best_fs = torch.argmin(mean_res, dim=-1, keepdim=True)[..., None].expand(list(dims)[:-2] + [1, 9])
    h = torch.gather(f, -2, best_fs).squeeze(-2)

    h = h * torch.logical_not(degenerate).float()[..., None]

    return h
//  [python]
def calc_crd_rmsd(pred, true, log=False, gamma=0.95):
    '''
    Calculate coordinate RMSD
    Input:
        - pred: predicted coordinates (I, B, L, n_atom, 3)
        - true: true coordinates (B, L, n_atom, 3)
    Output: RMSD after superposition
    '''
    def rmsd(V, W, eps=1e-6):
        L = V.shape[1]
        return torch.sqrt(torch.sum((V-W)*(V-W), dim=(1,2)) / L + eps)
    def centroid(X):
        return X.mean(dim=-2, keepdim=True)

    I, B, L, n_atom = pred.shape[:4]
    
    # center to centroid
    pred = pred - centroid(pred.view(I,B,n_atom*L,3)).view(I,B,1,1,3)
    true = true - centroid(true.view(B,n_atom*L,3)).view(B,1,1,3)

    # reshape true crds to match the shape to pred crds
    true = true.unsqueeze(0).expand(I,-1,-1,-1,-1)
    pred = pred.view(I*B, L*n_atom, 3)
    true = true.view(I*B, L*n_atom, 3)

    # Computation of the covariance matrix
    C = torch.matmul(pred.permute(0,2,1), true)

    # Compute optimal rotation matrix using SVD
    V, S, W = torch.svd(C)

    # get sign to ensure right-handedness
    d = torch.ones([I*B,3,3], device=pred.device)
    d[:,:,-1] = torch.sign(torch.det(V)*torch.det(W)).unsqueeze(1)

    # Rotation matrix U
    U = torch.matmul(d*V, W.permute(0,2,1)) # (IB, 3, 3)

    # Rotate pred
    rP = torch.matmul(pred, U) # (IB, L*3, 3)

    # get RMS
    rms_raw = rmsd(rP, true).reshape(I,B)
    if log:
        rms = torch.log(rms_raw+1.0) # (I, B)
    else:
        rms = rms_raw
    rms = rms.mean(dim=-1) # (I)

    # weighting loss
    w_loss = torch.pow(torch.full((I,), gamma, device=pred.device), torch.arange(I, device=pred.device))
    w_loss = torch.flip(w_loss, (0,))
    w_loss = w_loss / w_loss.sum()

    tot_rms = (w_loss * rms).sum()
    return tot_rms, rms_raw.mean(dim=-1).detach()
//  [python]
def calc_crd_rmsd(pred, true, log=False, gamma=0.95):
    '''
    Calculate coordinate RMSD
    Input:
        - pred: predicted coordinates (I, B, L, n_atom, 3)
        - true: true coordinates (B, L, n_atom, 3)
    Output: RMSD after superposition
    '''
    def rmsd(V, W, eps=1e-6):
        L = V.shape[1]
        return torch.sqrt(torch.sum((V-W)*(V-W), dim=(1,2)) / L + eps)
    def centroid(X):
        return X.mean(dim=-2, keepdim=True)

    I, B, L, n_atom = pred.shape[:4]
    
    # center to centroid
    pred = pred - centroid(pred.view(I,B,n_atom*L,3)).view(I,B,1,1,3)
    true = true - centroid(true.view(B,n_atom*L,3)).view(B,1,1,3)

    # reshape true crds to match the shape to pred crds
    true = true.unsqueeze(0).expand(I,-1,-1,-1,-1)
    pred = pred.view(I*B, L*n_atom, 3)
    true = true.view(I*B, L*n_atom, 3)

    # Computation of the covariance matrix
    C = torch.matmul(pred.permute(0,2,1), true)

    # Compute optimal rotation matrix using SVD
    U, S, V = torch.linalg.svd(C)

    # get sign to ensure right-handedness
    d = torch.ones([I*B,3,3], device=pred.device)
    d[:,:,-1] = torch.sign(torch.det(U)*torch.det(V)).unsqueeze(1)

    # Rotation matrix U
    U = torch.matmul(d*U, V.permute(0,2,1)) # (IB, 3, 3)

    # Rotate pred
    rP = torch.matmul(pred, U) # (IB, L*3, 3)

    # get RMS
    rms_raw = rmsd(rP, true).reshape(I,B)
    if log:
        rms = torch.log(rms_raw+1.0) # (I, B)
    else:
        rms = rms_raw
    rms = rms.mean(dim=-1) # (I)

    # weighting loss
    w_loss = torch.pow(torch.full((I,), gamma, device=pred.device), torch.arange(I, device=pred.device))
    w_loss = torch.flip(w_loss, (0,))
    w_loss = w_loss / w_loss.sum()

    tot_rms = (w_loss * rms).sum()
    return tot_rms, rms_raw.mean(dim=-1).detach()
//  [python]
def robust_svd(mat: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
    """Try to perform SVD and handle errors.
    """

    assert is_matrix(mat), f"shape {mat.shape}"
    try:
        U, S, V = torch.svd(mat)
    except Exception as e:  # this can fail, see https://github.com/pytorch/pytorch/issues/25978
        if is_square_matrix(mat):
            s = torch.symeig(mat).eigenvalues
            eps = get_condition(mat.dtype) * torch.max(abs(s))
        else:
            eps = get_condition(mat.dtype) * u.frobenius_norm(mat) / mat.shape[0]
        print(f"Warning, SVD diverged with {e}, regularizing with {eps}")
        mat = regularize_mat2(mat, eps * 2)
        U, S, V = torch.svd(mat)
    return U, S, V
//  [python]
def robust_svd(mat: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
    """Try to perform SVD and handle errors.
    """

    assert is_matrix(mat), f"shape {mat.shape}"
    try:
        U, S, V = torch.linalg.svd(mat)
    except Exception as e:  # this can fail, see https://github.com/pytorch/pytorch/issues/25978
        if is_square_matrix(mat):
            s = torch.symeig(mat).eigenvalues
            eps = get_condition(mat.dtype) * torch.max(abs(s))
        else:
            eps = get_condition(mat.dtype) * u.frobenius_norm(mat) / mat.shape[0]
        print(f"Warning, SVD diverged with {e}, regularizing with {eps}")
        mat = regularize_mat2(mat, eps * 2)
        U, S, V = torch.linalg.svd(mat)
    return U, S, V
//  [python]
def lyapunov_truncated(A, C, use_svd=False, top_k=None, check_error=False):
    """Truncated solution to AX+XA=C. top_k specified how many dimensions of A to use. If None, use threshold for
    acceptable condition."""

    rankA = u.rank(A)
    rankC = u.rank(C)
    if rankA < rankC:
        print("Warning, losing precision, rank A: {rankA}, rank C: {rankC}")

    assert A.shape[0] == A.shape[1]
    assert len(A.shape) == 2
    cond = get_condition(A.dtype)

    if use_svd:
        U, S, V = torch.svd(A)
    else:
        # flip to be in decreasing order like for SVD
        # TODO: can optimize this case by skipping flip
        S, U = torch.symeig(A, eigenvectors=True)
        S = torch.flip(S, [0])
        U = torch.flip(U, [1])
    cutoff = torch.max(S) * cond
    rank = torch.sum(S > cutoff)
    if top_k is not None:
        top_k = rank

    S = torch.where(S > cutoff, S, torch.zeros_like(S))
    S = S.diag() @ torch.ones_like(A)
    U = U[:, :top_k]
    projected = U.t() @ C @ U
    divider = S + S.t()
    divider = divider[:top_k, :top_k]

    divided = torch.where(S > 0, projected / divider, torch.zeros_like(projected))
    X = U @ divided @ U.t()
    if check_error:
        error = A @ X + X @ A - C
        relative_error = torch.max(torch.abs(error)) / torch.max(torch.abs(A))
        if relative_error > 1e-3:
            print('rel error', relative_error)
    return X
//  [python]
def lyapunov_truncated(A, C, use_svd=False, top_k=None, check_error=False):
    """Truncated solution to AX+XA=C. top_k specified how many dimensions of A to use. If None, use threshold for
    acceptable condition."""

    rankA = u.rank(A)
    rankC = u.rank(C)
    if rankA < rankC:
        print("Warning, losing precision, rank A: {rankA}, rank C: {rankC}")

    assert A.shape[0] == A.shape[1]
    assert len(A.shape) == 2
    cond = get_condition(A.dtype)

    if use_svd:
        U, S, V = torch.linalg.svd(A)
    else:
        # flip to be in decreasing order like for SVD
        # TODO: can optimize this case by skipping flip
        S, U = torch.symeig(A, eigenvectors=True)
        S = torch.flip(S, [0])
        U = torch.flip(U, [1])
    cutoff = torch.max(S) * cond
    rank = torch.sum(S > cutoff)
    if top_k is not None:
        top_k = rank

    S = torch.where(S > cutoff, S, torch.zeros_like(S))
    S = S.diag() @ torch.ones_like(A)
    U = U[:, :top_k]
    projected = U.t() @ C @ U
    divider = S + S.t()
    divider = divider[:top_k, :top_k]

    divided = torch.where(S > 0, projected / divider, torch.zeros_like(projected))
    X = U @ divided @ U.t()
    if check_error:
        error = A @ X + X @ A - C
        relative_error = torch.max(torch.abs(error)) / torch.max(torch.abs(A))
        if relative_error > 1e-3:
            print('rel error', relative_error)
    return X
//  [python]
def _F_to_E(F, K):
    E = torch.matmul(torch.matmul(K.t(), F), K)
    U, S, V = torch.svd(E, some=False) # https://github.com/marktao99/python/blob/da2682f8832483650b85b0be295ae7eaf179fcc5/CVP/samples/sfm.py#L139
    # print(S.numpy())
    S_110 = torch.diag(torch.tensor([1., 1., 0.], dtype=F.dtype))
    E_110 = torch.mm(U, torch.mm(S_110, V.t()))
    # print(E_110.numpy())
    return E_110
//  [python]
def _F_to_E(F, K):
    E = torch.matmul(torch.matmul(K.t(), F), K)
    U, S, V = torch.linalg.svd(E, full_matrices=False)
    S_110 = torch.diag(torch.tensor([1., 1., 0.], dtype=F.dtype))
    E_110 = torch.mm(U, torch.mm(S_110, V.t()))
    return E_110
//  [python]
def forward(self,batch_x):
        batch_x = torch.mean(batch_x, dim=(3, 4))
        # batch_x = torch.permute(batch_x, (0, 2, 1))  # (B, N, C)
        batch_size, num_features, N  = batch_x.shape
        batch_x = batch_x.view(batch_size,-1,num_features,N)


        bvp = []
        for b in range(batch_size):
            X = batch_x[b]  # 변환된 부분: 입력 신호를 PyTorch 텐서로 변환
            U, _, _ = torch.svd(X)  # 변환된 부분: numpy.linalg.svd 대신 torch.svd 사용
            S = U[:, :, 0]
            S = torch.unsqueeze(S, 2)  # 변환된 부분: np.expand_dims 대신 torch.unsqueeze 사용
            sst = torch.matmul(S, torch.transpose(S, 1, 2))  # 변환된 부분: np.swapaxes 대신 torch.transpose 사용
            p = torch.tile(torch.eye(3), (S.shape[0], 1, 1)).to("cuda") # 변환된 부분: np.tile 대신 torch.tile 사용
            P = p - sst
            Y = torch.matmul(P, X)
            bvp.append(Y[:, 1, :])
        bvp = torch.cat(bvp)
        bvp = bvp.view(batch_size,-1)

        return bvp
//  [python]
def forward(self,batch_x):
        batch_x = torch.mean(batch_x, dim=(3, 4))
        # batch_x = torch.permute(batch_x, (0, 2, 1))  # (B, N, C)
        batch_size, num_features, N  = batch_x.shape
        batch_x = batch_x.view(batch_size,-1,num_features,N)


        bvp = []
        for b in range(batch_size):
            X = batch_x[b]  # 변환된 부분: 입력 신호를 PyTorch 텐서로 변환
            U, S, V = torch.linalg.svd(X)  # 변환된 부분: numpy.linalg.svd 대신 torch.linalg.svd 사용
            S = U[:, :, 0]
            S = torch.unsqueeze(S, 2)  # 변환된 부분: np.expand_dims 대신 torch.unsqueeze 사용
            sst = torch.matmul(S, torch.transpose(S, 1, 2))  # 변환된 부분: np.swapaxes 대신 torch.transpose 사용
            p = torch.tile(torch.eye(3), (S.shape[0], 1, 1)).to("cuda") # 변환된 부분: np.tile 대신 torch.tile 사용
            P = p - sst
            Y = torch.matmul(P, X)
            bvp.append(Y[:, 1, :])
        bvp = torch.cat(bvp)
        bvp = bvp.view(batch_size,-1)

        return bvp
//  [python]
def backward(ctx, grad_output):
        jitter = 1.0e-8  # do i really need this?
        z, epsilon, L = ctx.saved_tensors

        dim = L.shape[0]
        g = grad_output
        loc_grad = sum_leftmost(grad_output, -1)

        identity = eye_like(g, dim)
        R_inv = torch.triangular_solve(identity, L.t(), transpose=False, upper=True)[0]

        z_ja = z.unsqueeze(-1)
        g_R_inv = torch.matmul(g, R_inv).unsqueeze(-2)
        epsilon_jb = epsilon.unsqueeze(-2)
        g_ja = g.unsqueeze(-1)
        diff_L_ab = 0.5 * sum_leftmost(g_ja * epsilon_jb + g_R_inv * z_ja, -2)

        Sigma_inv = torch.mm(R_inv, R_inv.t())
        V, D, _ = torch.svd(Sigma_inv + jitter)
        D_outer = D.unsqueeze(-1) + D.unsqueeze(0)

        expand_tuple = tuple([-1] * (z.dim() - 1) + [dim, dim])
        z_tilde = identity * torch.matmul(z, V).unsqueeze(-1).expand(*expand_tuple)
        g_tilde = identity * torch.matmul(g, V).unsqueeze(-1).expand(*expand_tuple)

        Y = sum_leftmost(torch.matmul(z_tilde, torch.matmul(1.0 / D_outer, g_tilde)), -2)
        Y = torch.mm(V, torch.mm(Y, V.t()))
        Y = Y + Y.t()

        Tr_xi_Y = torch.mm(torch.mm(Sigma_inv, Y), R_inv) - torch.mm(Y, torch.mm(Sigma_inv, R_inv))
        diff_L_ab += 0.5 * Tr_xi_Y
        L_grad = torch.tril(diff_L_ab)

        return loc_grad, L_grad, None
//  [python]
def backward(ctx, grad_output):
        jitter = 1.0e-8  # do i really need this?
        z, epsilon, L = ctx.saved_tensors

        dim = L.shape[0]
        g = grad_output
        loc_grad = sum_leftmost(grad_output, -1)

        identity = eye_like(g, dim)
        R_inv = torch.triangular_solve(identity, L.t(), transpose=False, upper=True)[0]

        z_ja = z.unsqueeze(-1)
        g_R_inv = torch.matmul(g, R_inv).unsqueeze(-2)
        epsilon_jb = epsilon.unsqueeze(-2)
        g_ja = g.unsqueeze(-1)
        diff_L_ab = 0.5 * sum_leftmost(g_ja * epsilon_jb + g_R_inv * z_ja, -2)

        Sigma_inv = torch.mm(R_inv, R_inv.t())
        U, S, V = torch.linalg.svd(Sigma_inv + jitter)
        D_outer = S.unsqueeze(-1) + S.unsqueeze(0)

        expand_tuple = tuple([-1] * (z.dim() - 1) + [dim, dim])
        z_tilde = identity * torch.matmul(z, V).unsqueeze(-1).expand(*expand_tuple)
        g_tilde = identity * torch.matmul(g, V).unsqueeze(-1).expand(*expand_tuple)

        Y = sum_leftmost(torch.matmul(z_tilde, torch.matmul(1.0 / D_outer, g_tilde)), -2)
        Y = torch.mm(V, torch.mm(Y, V.t()))
        Y = Y + Y.t()

        Tr_xi_Y = torch.mm(torch.mm(Sigma_inv, Y), R_inv) - torch.mm(Y, torch.mm(Sigma_inv, R_inv))
        diff_L_ab += 0.5 * Tr_xi_Y
        L_grad = torch.tril(diff_L_ab)

        return loc_grad, L_grad, None
//  [python]
def _H_from_XY(X, Y):
    N = list(X.size())[0]
    A = torch.zeros(2*N, 9, dtype=torch.float32)
    A[0::2, 0:2] = X
    A[0::2, 2:3] = torch.ones(N, 1)
    A[1::2, 3:5] = X
    A[1::2, 5:6] = torch.ones(N, 1)
    A[0::2, 6:8] = X
    A[1::2, 6:8] = X
    A[:, 8:9] = torch.ones(2*N, 1)
    Y_vec = torch.reshape(Y, (2*N, 1))
    A[:, 6:7] = -A[:, 6:7] * Y_vec
    A[:, 7:8] = -A[:, 7:8] * Y_vec
    A[:, 8:9] = -A[:, 8:9] * Y_vec
    U, S, V = torch.svd(A)
    H = torch.reshape(V[:, -1], (3, 3))
    H = H / H[2, 2]
    return H
//  [python]
def _H_from_XY(X, Y):
    N = list(X.size())[0]
    A = torch.zeros(2*N, 9, dtype=torch.float32)
    A[0::2, 0:2] = X
    A[0::2, 2:3] = torch.ones(N, 1)
    A[1::2, 3:5] = X
    A[1::2, 5:6] = torch.ones(N, 1)
    A[0::2, 6:8] = X
    A[1::2, 6:8] = X
    A[:, 8:9] = torch.ones(2*N, 1)
    Y_vec = torch.reshape(Y, (2*N, 1))
    A[:, 6:7] = -A[:, 6:7] * Y_vec
    A[:, 7:8] = -A[:, 7:8] * Y_vec
    A[:, 8:9] = -A[:, 8:9] * Y_vec
    U, S, V = torch.linalg.svd(A)
    H = torch.reshape(V[:, -1], (3, 3))
    H = H / H[2, 2]
    return H
//  [python]
def _get_M2s(E):
    # Getting 4 possible poses from E
    U, S, V = torch.svd(E)
    W = torch.tensor([[0,-1,0], [1,0,0], [0,0,1]], dtype=E.dtype, device=E.device)
    if torch.det(torch.mm(U, torch.mm(W, V.t())))<0:
        W = -W
    # print('-- delta_t_gt', delta_t_gt)

    t_recover = U[:, 2:3]/torch.norm(U[:, 2:3])
    # print('---', E.numpy())
    # t_recover_rescale = U[:, 2]/torch.norm(U[:, 2])*np.linalg.norm(t_gt) # -t_recover_rescale is also an option
    R_recover_1 = torch.mm(U, torch.mm(W, V.t()))
    R_recover_2 = torch.mm(U, torch.mm(W.t(), V.t())) # also an option
    # print('-- t_recover', t_recover.numpy())
    # print('-- R_recover_1', R_recover_1.numpy(), torch.det(R_recover_1).numpy())
    # print('-- R_recover_2', R_recover_2.numpy(), torch.det(R_recover_2).numpy())

    R2s = [R_recover_1, R_recover_2]
    t2s = [t_recover, -t_recover]
    M2s = [torch.cat((x, y), 1) for x, y in [(x,y) for x in R2s for y in t2s]]
    return R2s, t2s, M2s
//  [python]
def _get_M2s(E):
    # Getting 4 possible poses from E
    U, S, V = torch.linalg.svd(E)
    W = torch.tensor([[0,-1,0], [1,0,0], [0,0,1]], dtype=E.dtype, device=E.device)
    if torch.det(torch.mm(U, torch.mm(W, V.t())))<0:
        W = -W
    # print('-- delta_t_gt', delta_t_gt)

    t_recover = U[:, 2:3]/torch.norm(U[:, 2:3])
    # print('---', E.numpy())
    # t_recover_rescale = U[:, 2]/torch.norm(U[:, 2])*np.linalg.norm(t_gt) # -t_recover_rescale is also an option
    R_recover_1 = torch.mm(U, torch.mm(W, V.t()))
    R_recover_2 = torch.mm(U, torch.mm(W.t(), V.t())) # also an option
    # print('-- t_recover', t_recover.numpy())
    # print('-- R_recover_1', R_recover_1.numpy(), torch.det(R_recover_1).numpy())
    # print('-- R_recover_2', R_recover_2.numpy(), torch.det(R_recover_2).numpy())

    R2s = [R_recover_1, R_recover_2]
    t2s = [t_recover, -t_recover]
    M2s = [torch.cat((x, y), 1) for x, y in [(x,y) for x in R2s for y in t2s]]
    return R2s, t2s, M2s
//  [python]
def compute_R_similarity_matrix(self, point_name, states, idxs, sim_func=generalized_squared_cosine):
        """
        Compute R-based similarity matrix
        :param point_name: Observation point
        :param states: List of states (or list of list) used to compute similarities
        :param idxs: Sample indices in state collection
        :param sim_func: Similarity function (default: generalized_squared_cosine)
        :return: Similarity matrix as torch Tensor
        """
        # Cell states
        cell_states = list()

        # For each states
        for state in states:
            # Get observed data
            cell_states.append(self._get_observer_data(point_name, state, idxs, concat=True, concat_dim=2))
        # end for

        # Similarity matrix
        sim_matrix = torch.zeros(len(cell_states), len(cell_states))

        # For each pair
        for i in range(len(cell_states)):
            for j in range(len(cell_states)):
                # R matrices
                R_i = torch.mm(cell_states[i].t(), cell_states[i])
                R_j = torch.mm(cell_states[j].t(), cell_states[j])

                # SVDs
                Ui, Si, _ = torch.svd(R_i)
                Uj, Sj, _ = torch.svd(R_j)

                # Similarity matrix
                sim_matrix[i, j] = sim_func(Si, Ui, Sj, Uj)
            # end for
        # end for

        return sim_matrix