def to_tangent(self, vector, base_point): """Project a vector to the tangent space. Project a vector in the embedding matrix space to the tangent space of the pre-shape space at a base point. Parameters ---------- vector : array-like, shape=[..., k_landmarks, m_ambient] Vector in Matrix space. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the pre-shape space defining the tangent space, where the vector will be projected. Returns ------- tangent_vec : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector in the tangent space of the pre-shape space at the base point. """ if not gs.all(self.is_centered(base_point)): raise ValueError("The base_point does not belong to the pre-shape" " space") vector = self.center(vector) sq_norm = Matrices.frobenius_product(base_point, base_point) inner_prod = self.ambient_metric.inner_product(base_point, vector) coef = inner_prod / sq_norm tangent_vec = vector - gs.einsum("...,...ij->...ij", coef, base_point) return tangent_vec
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 inner_product(self, tangent_vec_a, tangent_vec_b, base_point): """Compute the Euclidean inner-product. Compute the inner-product of tangent_vec_a and tangent_vec_b at point base_point using the power-Euclidean metric. 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 ------- inner_product : array-like, shape=[...,] Inner-product. """ power_euclidean = self.power_euclidean spd_space = SPDMatrices if power_euclidean == 1: inner_product = Matrices.frobenius_product(tangent_vec_a, tangent_vec_b) else: modified_tangent_vec_a = spd_space.differential_power( power_euclidean, tangent_vec_a, base_point ) modified_tangent_vec_b = spd_space.differential_power( power_euclidean, tangent_vec_b, base_point ) inner_product = Matrices.frobenius_product( modified_tangent_vec_a, modified_tangent_vec_b ) / (power_euclidean ** 2) return inner_product
def inner_prod(tangent_vec_a, tangent_vec_b, base_point): affine_part = self.bundle.ambient_metric.inner_product( tangent_vec_a, tangent_vec_b, base_point) n = tangent_vec_b.shape[-1] inverse_base_point = GeneralLinear.inverse(base_point) operator = gs.eye(n) + base_point * inverse_base_point inverse_operator = GeneralLinear.inverse(operator) diagonal_a = gs.einsum("...ij,...ji->...i", inverse_base_point, tangent_vec_a) diagonal_b = gs.einsum("...ij,...ji->...i", inverse_base_point, tangent_vec_b) aux = gs.einsum("...i,...j->...ij", diagonal_a, diagonal_b) other_part = 2 * Matrices.frobenius_product(aux, inverse_operator) return affine_part - other_part
def inner_product_at_identity(self, tangent_vec_a, tangent_vec_b): """Compute inner product at tangent space at identity. Parameters ---------- tangent_vec_a : array-like, shape=[..., {dim, [n, n]}] First tangent vector at identity. tangent_vec_b : array-like, shape=[..., {dim, [n, n]}] Second tangent vector at identity. Returns ------- inner_prod : array-like, shape=[...] Inner-product of the two tangent vectors. """ if self.default_point_type == 'vector': return super(BiInvariantMetric, self).inner_product_at_identity( tangent_vec_a, tangent_vec_b) return Matrices.frobenius_product(tangent_vec_a, tangent_vec_b)
def inner_product_at_identity(self, tangent_vec_a, tangent_vec_b): """Compute inner product at tangent space at identity. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] First tangent vector at identity. tangent_vec_b : array-like, shape=[..., n, n] Second tangent vector at identity. Returns ------- inner_prod : array-like, shape=[...] Inner-product of the two tangent vectors. """ tan_b = tangent_vec_b metric_mat = self.metric_mat_at_identity if (Matrices.is_diagonal(metric_mat) and self.lie_algebra is not None): tan_b = tangent_vec_b * self.reshaped_metric_matrix inner_prod = Matrices.frobenius_product(tangent_vec_a, tan_b) return inner_prod
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point): r"""Compute the Bures-Wasserstein inner-product. Compute the inner-product of tangent_vec_a :math: `A` and tangent_vec_b :math: `B` at point base_point :math: `S=PDP^\top` using the Bures-Wasserstein Riemannian metric: ..math:: `\frac{1}{2}\sum_{i,j}\frac{[P^\top AP]_{ij}[P^\top BP]_{ij}}{d_i+d_j}` . 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 ------- inner_product : array-like, shape=[...,] Inner-product. """ eigvals, eigvecs = gs.linalg.eigh(base_point) transp_eigvecs = Matrices.transpose(eigvecs) rotated_tangent_vec_a = Matrices.mul(transp_eigvecs, tangent_vec_a, eigvecs) rotated_tangent_vec_b = Matrices.mul(transp_eigvecs, tangent_vec_b, eigvecs) coefficients = 1 / (eigvals[..., :, None] + eigvals[..., None, :]) result = ( Matrices.frobenius_product( coefficients * rotated_tangent_vec_a, rotated_tangent_vec_b ) / 2 ) return result
class TestMatrices(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.m = 2 self.n = 3 self.space = Matrices(m=self.n, n=self.n) self.space_nonsquare = Matrices(m=self.m, n=self.n) self.metric = self.space.metric self.n_samples = 2 @geomstats.tests.np_only def test_mul(self): a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) c = gs.array([ [1., 0., 0.], [0., 1., 0.], [0., 0., 0.]]) d = gs.array([ [0., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) result = self.space.mul([a, b], [b, a]) expected = gs.array([c, d]) self.assertAllClose(result, expected) result = self.space.mul(a, [a, b]) expected = gs.array([gs.eye(3, 3, 2), c]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_bracket(self): x = gs.array([ [0., 0., 0.], [0., 0., -1.], [0., 1., 0.]]) y = gs.array([ [0., 0., 1.], [0., 0., 0.], [-1., 0., 0.]]) z = gs.array([ [0., -1., 0.], [1., 0., 0.], [0., 0., 0.]]) result = self.space.bracket([x, y], [y, z]) expected = gs.array([z, x]) self.assertAllClose(result, expected) result = self.space.bracket(x, [x, y, z]) expected = gs.array([gs.zeros((3, 3)), z, -y]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_transpose(self): tr = self.space.transpose ar = gs.array a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) self.assertAllClose(tr(a), b) self.assertAllClose(tr(ar([a, b])), ar([b, a])) def test_is_symmetric(self): not_squared = gs.array([[1., 2.], [2., 1.], [3., 1.]]) result = self.space.is_symmetric(not_squared) expected = False self.assertAllClose(result, expected) sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.is_symmetric(sym_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_is_skew_symmetric(self): skew_mat = gs.array([[0, - 2.], [2., 0]]) result = self.space.is_skew_symmetric(skew_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_skew_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_is_symmetric_vectorization(self): points = gs.array([ [[1., 2.], [2., 1.]], [[3., 4.], [4., 5.]], [[1., 2.], [3., 4.]]]) result = self.space.is_symmetric(points) expected = [True, True, False] self.assertAllClose(result, expected) @geomstats.tests.np_and_pytorch_only def test_make_symmetric(self): sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.to_symmetric(sym_mat) expected = sym_mat self.assertAllClose(result, expected) mat = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.to_symmetric(mat) expected = gs.array([[1., 1., 3.], [1., 0., 0.5], [3., 0.5, 1.]]) self.assertAllClose(result, expected) mat = gs.array([[1e100, 1e-100, 1e100], [1e100, 1e-100, 1e100], [1e-100, 1e-100, 1e100]]) result = self.space.to_symmetric(mat) res = 0.5 * (1e100 + 1e-100) expected = gs.array([[1e100, res, res], [res, 1e-100, res], [res, res, 1e100]]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_make_symmetric_and_is_symmetric_vectorization(self): points = gs.array([ [[1., 2.], [3., 4.]], [[5., 6.], [4., 9.]]]) sym_points = self.space.to_symmetric(points) result = gs.all(self.space.is_symmetric(sym_points)) expected = True self.assertAllClose(result, expected) def test_inner_product(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector_1 = gs.array([ [1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) tangent_vector_2 = gs.array([ [1., 4., 3.], [5., 0., 0.], [3., 1., 1.]]) result = self.metric.inner_product( tangent_vector_1, tangent_vector_2, base_point=base_point) expected = gs.trace( gs.matmul( gs.transpose(tangent_vector_1), tangent_vector_2)) self.assertAllClose(result, expected) def test_cong(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector = gs.array([ [1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) result = self.space.congruent(tangent_vector, base_point) expected = gs.matmul( tangent_vector, gs.transpose(base_point)) expected = gs.matmul(base_point, expected) self.assertAllClose(result, expected) def test_belongs(self): base_point_square = gs.zeros((self.n, self.n)) base_point_nonsquare = gs.zeros((self.m, self.n)) result = self.space.belongs(base_point_square) expected = True self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_square) expected = False self.assertAllClose(result, expected) result = self.space.belongs(base_point_nonsquare) expected = False self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_nonsquare) expected = True self.assertAllClose(result, expected) result = self.space.belongs(gs.zeros((2, 2, 3))) self.assertFalse(gs.all(result)) result = self.space.belongs(gs.zeros((2, 3, 3))) self.assertTrue(gs.all(result)) def test_is_diagonal(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.is_diagonal(base_point) expected = False self.assertAllClose(result, expected) diagonal = gs.eye(3) result = self.space.is_diagonal(diagonal) self.assertTrue(result) base_point = gs.stack([base_point, diagonal]) result = self.space.is_diagonal(base_point) expected = gs.array([False, True]) self.assertAllClose(result, expected) base_point = gs.stack([diagonal] * 2) result = self.space.is_diagonal(base_point) self.assertTrue(gs.all(result)) base_point = gs.reshape(gs.arange(6), (2, 3)) result = self.space.is_diagonal(base_point) self.assertTrue(~result) def test_norm(self): for n_samples in [1, 2]: mat = self.space.random_point(n_samples) result = self.metric.norm(mat) expected = self.space.frobenius_product(mat, mat) ** .5 self.assertAllClose(result, expected)
def test_frobenius_product(self, mat_a, mat_b, expected): self.assertAllClose( Matrices.frobenius_product(gs.array(mat_a), gs.array(mat_b)), gs.array(expected), )
def iterated_integrability_tensor_derivative_parallel( self, horizontal_vec_x, horizontal_vec_y, base_point): r"""Compute iterated derivatives of the integrability tensor A. The iterated horizontal covariant derivative :math:`\nabla_X (A_Y A_X Y)` (where :math:`X` and :math:`Y` are horizontal vector fields) is a key ingredient in the computation of the covariant derivative of the directional curvature in a submersion. The components :math:`\nabla_X (A_Y A_X Y)`, :math:`A_X A_Y A_X Y`, :math:`\nabla_X (A_X Y)`, and intermediate computations :math:`A_Y A_X Y` and :math:`A_X Y` are computed here for the Kendall shape space in the special case of quotient-parallel vector fields :math:`X, Y` extending the values horizontal_vec_x and horizontal_vec_y by parallel transport in a neighborhood. Such vector fields verify :math:`\nabla_X^X = A_X X` and :math: `\nabla_X^Y = A_X Y`. Parameters ---------- horizontal_vec_x : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. horizontal_vec_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point of the total space. Returns ------- nabla_x_a_y_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`\nabla_X^S (A_Y A_X Y)` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. a_x_a_y_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_X A_Y A_X Y` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. nabla_x_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`\nabla_X^S (A_X Y)` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. a_y_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_Y A_X Y` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_X Y` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. References ---------- .. [Pennec] Pennec, Xavier. Computing the curvature and its gradient in Kendall shape spaces. Unpublished. """ if not gs.all(self.is_centered(base_point)): raise ValueError("The base_point does not belong to the pre-shape" " space") if not gs.all(self.is_horizontal(horizontal_vec_x, base_point)): raise ValueError("Tangent vector x is not horizontal") if not gs.all(self.is_horizontal(horizontal_vec_y, base_point)): raise ValueError("Tangent vector y is not horizontal") p_top = Matrices.transpose(base_point) p_top_p = gs.matmul(p_top, base_point) def sylv_p(mat_b): """Solves Sylvester equation for vertical component.""" return gs.linalg.solve_sylvester(p_top_p, p_top_p, mat_b - Matrices.transpose(mat_b)) y_top = Matrices.transpose(horizontal_vec_y) x_top = Matrices.transpose(horizontal_vec_x) x_y_top = gs.matmul(y_top, horizontal_vec_x) omega_xy = sylv_p(x_y_top) vertical_vec_v = gs.matmul(base_point, omega_xy) omega_xy_x = gs.matmul(horizontal_vec_x, omega_xy) omega_xy_y = gs.matmul(horizontal_vec_y, omega_xy) v_top = Matrices.transpose(vertical_vec_v) x_v_top = gs.matmul(v_top, horizontal_vec_x) omega_xv = sylv_p(x_v_top) omega_xv_p = gs.matmul(base_point, omega_xv) y_v_top = gs.matmul(v_top, horizontal_vec_y) omega_yv = sylv_p(y_v_top) omega_yv_p = gs.matmul(base_point, omega_yv) nabla_x_v = 3.0 * omega_xv_p + omega_xy_x a_y_a_x_y = omega_yv_p + omega_xy_y tmp_mat = gs.matmul(x_top, a_y_a_x_y) a_x_a_y_a_x_y = -gs.matmul(base_point, sylv_p(tmp_mat)) omega_xv_y = gs.matmul(horizontal_vec_y, omega_xv) omega_yv_x = gs.matmul(horizontal_vec_x, omega_yv) omega_xy_v = gs.matmul(vertical_vec_v, omega_xy) norms = Matrices.frobenius_product(vertical_vec_v, vertical_vec_v) sq_norm_v_p = gs.einsum("...,...ij->...ij", norms, base_point) tmp_mat = gs.matmul(p_top, 3.0 * omega_xv_y + 2.0 * omega_yv_x) + gs.matmul(y_top, omega_xy_x) nabla_x_a_y_v = (3.0 * omega_xv_y + omega_yv_x + omega_xy_v - gs.matmul(base_point, sylv_p(tmp_mat)) + sq_norm_v_p) return nabla_x_a_y_v, a_x_a_y_a_x_y, nabla_x_v, a_y_a_x_y, vertical_vec_v