def parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point): r"""Parallel transport of a tangent vector. Closed-form solution for the parallel transport of a tangent vector a along the geodesic defined by exp_(base_point)(tangent_vec_b). Denoting `tangent_vec_a` by `S`, `base_point` by `A`, let `B = Exp_A(tangent_vec_b)` and :math: `E = (BA^{- 1})^({ 1 / 2})`. Then the parallel transport to `B`is: ..math:: S' = ESE^T Parameters ---------- tangent_vec_a : array-like, shape=[n_samples, dimension + 1] Tangent vector at base point to be transported. tangent_vec_b : array-like, shape=[n_samples, dimension + 1] Tangent vector at base point, initial speed of the geodesic along which the parallel transport is computed. base_point : array-like, shape=[n_samples, dimension + 1] point on the manifold of SPD matrices Returns ------- transported_tangent_vec: array-like, shape=[n_samples, dimension + 1] Transported tangent vector at exp_(base_point)(tangent_vec_b). """ end_point = self.exp(tangent_vec_b, base_point) inverse_base_point = GeneralLinear.inv(base_point) congruence_mat = GeneralLinear.mul(end_point, inverse_base_point) congruence_mat = gs.linalg.sqrtm(congruence_mat) return GeneralLinear.congruent(tangent_vec_a, congruence_mat)
def exp(self, tangent_vec, base_point): """Compute the affine-invariant exponential map. Compute the Riemannian exponential at point base_point of tangent vector tangent_vec wrt the metric defined in inner_product. This gives a symmetric positive definite matrix. Parameters ---------- tangent_vec : array-like, shape=[n_samples, n, n] base_point : array-like, shape=[n_samples, n, n] Returns ------- exp : array-like, shape=[n_samples, n, n] """ power_affine = self.power_affine if power_affine == 1: sqrt_base_point = SymmetricMatrices.powerm(base_point, 1. / 2) inv_sqrt_base_point = SymmetricMatrices.powerm(sqrt_base_point, -1) exp = self._aux_exp(tangent_vec, sqrt_base_point, inv_sqrt_base_point) else: modified_tangent_vec = self.space.differential_power( power_affine, tangent_vec, base_point) power_sqrt_base_point = SymmetricMatrices.powerm( base_point, power_affine / 2) power_inv_sqrt_base_point = GeneralLinear.inv( power_sqrt_base_point) exp = self._aux_exp(modified_tangent_vec, power_sqrt_base_point, power_inv_sqrt_base_point) exp = SymmetricMatrices.powerm(exp, 1 / power_affine) return exp
def left_log_from_identity(self, point): """Compute Riemannian log of a point wrt. id of left-invar. metric. Compute Riemannian logarithm of a point wrt the identity associated to the left-invariant metric. If the method is called by a right-invariant metric, it uses the left-invariant metric associated to the same inner-product matrix at the identity. Parameters ---------- point : array-like, shape=[n_samples, dim] Point in the group. Returns ------- log : array-like, shape=[n_samples, dim] Tangent vector at the identity equal to the Riemannian logarithm of point at the identity. """ point = self.group.regularize(point) inner_prod_mat = self.inner_product_mat_at_identity inv_inner_prod_mat = GeneralLinear.inv(inner_prod_mat) sqrt_inv_inner_prod_mat = gs.linalg.sqrtm(inv_inner_prod_mat) log = gs.einsum('...i,...ij->...j', point, sqrt_inv_inner_prod_mat) log = self.group.regularize_tangent_vec_at_identity(tangent_vec=log, metric=self) return log
def inner_product_matrix(self, base_point=None): """Compute inner product matrix at the tangent space at a base point. Parameters ---------- base_point : array-like, shape=[n_samples, dim], optional Point in the group (the default is identity). Returns ------- metric_mat : array-like, shape=[n_samples, dim, dim] The metric matrix at base_point. """ if self.group.default_point_type == 'matrix': raise NotImplementedError( 'inner_product_matrix not implemented for Lie groups' ' whose elements are represented as matrices.') if base_point is None: base_point = self.group.identity base_point = self.group.regularize(base_point) jacobian = self.group.jacobian_translation( point=base_point, left_or_right=self.left_or_right) inv_jacobian = GeneralLinear.inv(jacobian) inv_jacobian_transposed = Matrices.transpose(inv_jacobian) metric_mat = gs.einsum('...ij,...jk->...ik', inv_jacobian_transposed, self.inner_product_mat_at_identity) metric_mat = gs.einsum('...ij,...jk->...ik', metric_mat, inv_jacobian) return metric_mat
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point): """Compute the affine-invariant inner product. Compute the inner product of tangent_vec_a and tangent_vec_b at point base_point using the affine invariant Riemannian metric. Parameters ---------- tangent_vec_a : array-like, shape=[n_samples, n, n] tangent_vec_b : array-like, shape=[n_samples, n, n] base_point : array-like, shape=[n_samples, n, n] Returns ------- inner_product : array-like, shape=[n_samples, n, n] """ power_affine = self.power_affine spd_space = self.space if power_affine == 1: inv_base_point = GeneralLinear.inv(base_point) inner_product = self._aux_inner_product(tangent_vec_a, tangent_vec_b, inv_base_point) else: modified_tangent_vec_a = spd_space.differential_power( power_affine, tangent_vec_a, base_point) modified_tangent_vec_b = spd_space.differential_power( power_affine, tangent_vec_b, base_point) power_inv_base_point = SymmetricMatrices.powerm( base_point, -power_affine) inner_product = self._aux_inner_product(modified_tangent_vec_a, modified_tangent_vec_b, power_inv_base_point) inner_product = inner_product / (power_affine**2) return inner_product
class TestGeneralLinearMethods(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.n = 3 self.n_samples = 2 self.group = GeneralLinear(n=self.n) warnings.simplefilter('ignore', category=ImportWarning) @geomstats.tests.np_only def test_belongs(self): mats = gs.array([[[1., 0.], [0., 1]], [[0., 1.], [0., 1.]]]) result = self.group.belongs(mats) expected = gs.array([True, False]) self.assertAllClose(result, expected) def test_compose(self): mat1 = gs.array([[1., 0.], [0., 2.]]) mat2 = gs.array([[2., 0.], [0., 1.]]) result = self.group.compose(mat1, mat2) expected = 2. * GeneralLinear(2).identity() self.assertAllClose(result, expected) def test_inv(self): mat_a = gs.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 10.]]) imat_a = 1. / 3. * gs.array([[-2., -4., 3.], [-2., 11., -6.], [3., -6., 3.]]) expected = imat_a result = self.group.inv(mat_a) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_inv_vectorized(self): mat_a = gs.array([[0., 1., 0.], [1., 0., 0.], [0., 0., 1.]]) mat_b = -gs.eye(3, 3) result = self.group.inv(gs.array([mat_a, mat_b])) expected = gs.array([mat_a, mat_b]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_log_and_exp(self): point = 5 * gs.eye(self.n) group_log = self.group.log(point) result = self.group.exp(group_log) expected = point self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_exp_vectorization(self): point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]], [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]]) expected = gs.array([[[7.38905609, 0., 0.], [0., 20.0855369, 0.], [0., 0., 54.5981500]], [[2.718281828, 0., 0.], [0., 148.413159, 0.], [0., 0., 403.42879349]]]) result = self.group.exp(point) self.assertAllClose(result, expected, rtol=1e-3) @geomstats.tests.np_and_tf_only def test_log_vectorization(self): point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]], [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]]) expected = gs.array([[[0.693147180, 0., 0.], [0., 1.09861228866, 0.], [0., 0., 1.38629436]], [[0., 0., 0.], [0., 1.609437912, 0.], [0., 0., 1.79175946]]]) result = self.group.log(point) self.assertAllClose(result, expected, atol=1e-4) @geomstats.tests.np_and_tf_only def test_orbit(self): point = gs.array([[gs.exp(4.), 0.], [0., gs.exp(2.)]]) sqrt = gs.array([[gs.exp(2.), 0.], [0., gs.exp(1.)]]) idty = GeneralLinear(2).identity() path = GeneralLinear(2).orbit(point) time = gs.linspace(0., 1., 3) result = path(time) expected = gs.array([idty, sqrt, point]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_expm_and_logm_vectorization_symmetric(self): point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]], [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]]) result = self.group.exp(self.group.log(point)) expected = point self.assertAllClose(result, expected)