def log(self, point, base_point, **kwargs): """Compute the Cholesky logarithm map. Compute the Riemannian logarithm at point base_point, of point wrt the Cholesky metric. This gives a tangent vector at point base_point. Parameters ---------- point : array-like, shape=[..., n, n] Point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- log : array-like, shape=[..., n, n] Riemannian logarithm. """ sl_base_point = Matrices.to_strictly_lower_triangular(base_point) sl_point = Matrices.to_strictly_lower_triangular(point) diag_base_point = Matrices.diagonal(base_point) diag_point = Matrices.diagonal(point) diag_product_logm = gs.log(gs.divide(diag_point, diag_base_point)) sl_log = sl_point - sl_base_point diag_log = gs.vec_to_diag(diag_base_point * diag_product_logm) log = sl_log + diag_log return log
def squared_dist(self, point_a, point_b, **kwargs): """Compute the Cholesky Metric squared distance. Compute the Riemannian squared distance between point_a and point_b. Parameters ---------- point_a : array-like, shape=[..., n, n] Point. point_b : array-like, shape=[..., n, n] Point. Returns ------- _ : array-like, shape=[...] Riemannian squared distance. """ log_diag_a = gs.log(Matrices.diagonal(point_a)) log_diag_b = gs.log(Matrices.diagonal(point_b)) diag_diff = log_diag_a - log_diag_b squared_dist_diag = gs.sum((diag_diff) ** 2, axis=-1) sl_a = Matrices.to_strictly_lower_triangular(point_a) sl_b = Matrices.to_strictly_lower_triangular(point_b) sl_diff = sl_a - sl_b squared_dist_sl = Matrices.frobenius_product(sl_diff, sl_diff) return squared_dist_sl + squared_dist_diag
def exp(self, tangent_vec, base_point, **kwargs): """Compute the Cholesky exponential map. Compute the Riemannian exponential at point base_point of tangent vector tangent_vec wrt the Cholesky metric. This gives a lower triangular matrix with positive elements. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- exp : array-like, shape=[..., n, n] Riemannian exponential. """ sl_base_point = Matrices.to_strictly_lower_triangular(base_point) sl_tangent_vec = Matrices.to_strictly_lower_triangular(tangent_vec) diag_base_point = Matrices.diagonal(base_point) diag_tangent_vec = Matrices.diagonal(tangent_vec) diag_product_expm = gs.exp(gs.divide(diag_tangent_vec, diag_base_point)) sl_exp = sl_base_point + sl_tangent_vec diag_exp = gs.vec_to_diag(diag_base_point * diag_product_expm) exp = sl_exp + diag_exp return exp
def test_diagonal(self): mat = gs.eye(3) result = Matrices.diagonal(mat) expected = gs.ones(3) self.assertAllClose(result, expected) mat = gs.stack([mat] * 2) result = Matrices.diagonal(mat) expected = gs.ones((2, 3)) self.assertAllClose(result, expected)
def __init__(self, n): super(FullRankCorrelationMatrices, self).__init__( dim=int(n * (n - 1) / 2), embedding_space=SPDMatrices(n=n), submersion=Matrices.diagonal, value=gs.ones(n), tangent_submersion=lambda v, x: Matrices.diagonal(v), ) self.n = n
def __init__(self, n, **kwargs): kwargs.setdefault("metric", FullRankCorrelationAffineQuotientMetric(n)) super(FullRankCorrelationMatrices, self).__init__( dim=int(n * (n - 1) / 2), embedding_space=SPDMatrices(n=n), submersion=Matrices.diagonal, value=gs.ones(n), tangent_submersion=lambda v, x: Matrices.diagonal(v), **kwargs) self.n = n
def tangent_riemannian_submersion(self, tangent_vec, base_point): """Compute the differential of the submersion. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector. base_point : array-like, shape=[..., n, n] Base point. Returns ------- result : array-like, shape=[..., n, n] """ diagonal_bp = Matrices.diagonal(base_point) diagonal_tv = Matrices.diagonal(tangent_vec) diagonal = diagonal_tv / diagonal_bp aux = base_point * (diagonal[..., None, :] + diagonal[..., :, None]) mat = tangent_vec - 0.5 * aux return FullRankCorrelationMatrices.diag_action(diagonal_bp ** (-0.5), mat)
def diag_inner_product(tangent_vec_a, tangent_vec_b, base_point): """Compute the inner product using only diagonal elements. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at base point. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at base point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- ip_diagonal : array-like, shape=[...] Inner-product. """ inv_sqrt_diagonal = gs.power(Matrices.diagonal(base_point), -2) tangent_vec_a_diagonal = Matrices.diagonal(tangent_vec_a) tangent_vec_b_diagonal = Matrices.diagonal(tangent_vec_b) prod = tangent_vec_a_diagonal * tangent_vec_b_diagonal * inv_sqrt_diagonal ip_diagonal = gs.sum(prod, axis=-1) return ip_diagonal
def riemannian_submersion(point): """Compute the correlation matrix associated to an SPD matrix. Parameters ---------- point : array-like, shape=[..., n, n] SPD matrix. Returns ------- cor : array_like, shape=[..., n, n] Full rank correlation matrix. """ diagonal = Matrices.diagonal(point)**(-0.5) return point * gs.outer(diagonal, diagonal)
def from_covariance(cls, point): r"""Compute the correlation matrix associated to an SPD matrix. The correlation matrix associated to an SPD matrix (the covariance) :math:`\Sigma` is given by :math:`D \Sigma D` where :math:`D` is the inverse square-root of the diagonal of :math:`\Sigma`. Parameters ---------- point : array-like, shape=[..., n, n] Symmetric Positive definite matrix. Returns ------- corr : array-like, shape=[..., n, n] Correlation matrix obtained by dividing all elements by the diagonal entries. """ diag_vec = Matrices.diagonal(point)**(-0.5) return cls.diag_action(diag_vec, point)
def projection(self, point): """Project a matrix to the Cholesksy space. First it is projected to space lower triangular matrices and then diagonal elements are exponentiated to make it positive. Parameters ---------- point : array-like, shape=[..., n, n] Matrix to project. Returns ------- projected: array-like, shape=[..., n, n] SPD matrix. """ vec_diag = gs.abs(Matrices.diagonal(point) - 0.1) + 0.1 diag = gs.vec_to_diag(vec_diag) strictly_lower_triangular = Matrices.to_lower_triangular(point) projection = diag + strictly_lower_triangular return projection
def belongs(self, mat, atol=gs.atol): """Check if mat is lower triangular with >0 diagonal. Parameters ---------- mat : array-like, shape=[..., n, n] Matrix to be checked. atol : float Tolerance. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Boolean denoting if mat belongs to cholesky space. """ is_lower_triangular = self.ambient_space.belongs(mat, atol) diagonal = Matrices.diagonal(mat) is_positive = gs.all(diagonal > 0, axis=-1) belongs = gs.logical_and(is_lower_triangular, is_positive) return belongs
def horizontal_lift(self, tangent_vec, base_point=None, fiber_point=None): """Compute the horizontal lift wrt the affine-invariant metric. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector of the manifold of full-rank correlation matrices. fiber_point : array-like, shape=[..., n, n] SPD matrix in the fiber above point. base_point : array-like, shape=[..., n, n] Full-rank correlation matrix. Returns ------- hor_lift : array-like, shape=[..., n, n] Horizontal lift of tangent_vec from point to base_point. """ if fiber_point is None and base_point is not None: return self.horizontal_projection(tangent_vec, base_point) diagonal_point = Matrices.diagonal(fiber_point) ** 0.5 lift = FullRankCorrelationMatrices.diag_action(diagonal_point, tangent_vec) hor_lift = self.horizontal_projection(lift, base_point=fiber_point) return hor_lift