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=[..., dim] Point in the group. Returns ------- log : array-like, shape=[..., 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.metric_mat_at_identity inv_inner_prod_mat = GeneralLinear.inverse(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 submersion(point, k): r"""Submersion that defines the Grassmann manifold. The Grassmann manifold is defined here as embedded in the set of symmetric matrices, as the pre-image of the function defined around the projector on the space spanned by the first k columns of the identity matrix by (see Exercise E.25 in [Pau07]_). .. math: \begin{pmatrix} I_k + A & B^T \\ B & D \end{pmatrix} \mapsto (D - B(I_k + A)^{-1}B^T, A + A^2 + B^TB This map is a submersion and its zero space is the set of orthogonal rank-k projectors. References ---------- .. [Pau07] Paulin, Frédéric. “Géométrie différentielle élémentaire,” 2007. https://www.imo.universite-paris-saclay.fr/~paulin /notescours/cours_geodiff.pdf. """ _, eigvecs = gs.linalg.eigh(point) eigvecs = gs.flip(eigvecs, -1) flipped_point = Matrices.mul(Matrices.transpose(eigvecs), point, eigvecs) b = flipped_point[..., k:, :k] d = flipped_point[..., k:, k:] a = flipped_point[..., :k, :k] - gs.eye(k) first = d - Matrices.mul(b, GeneralLinear.inverse(a + gs.eye(k)), Matrices.transpose(b)) second = a + Matrices.mul(a, a) + Matrices.mul(Matrices.transpose(b), b) row_1 = gs.concatenate([first, gs.zeros_like(b)], axis=-1) row_2 = gs.concatenate([Matrices.transpose(gs.zeros_like(b)), second], axis=-1) return gs.concatenate([row_1, row_2], axis=-2)
def metric_matrix(self, base_point=None): """Compute inner product matrix at the tangent space at a base point. Parameters ---------- base_point : array-like, shape=[..., dim], optional Point in the group (the default is identity). Returns ------- metric_mat : array-like, shape=[..., dim, dim] 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 else: 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.inverse(jacobian) inv_jacobian_transposed = Matrices.transpose(inv_jacobian) metric_mat = gs.einsum('...ij,...jk->...ik', inv_jacobian_transposed, self.metric_mat_at_identity) metric_mat = gs.einsum('...ij,...jk->...ik', metric_mat, inv_jacobian) return metric_mat
def metric_matrix(self, base_point=None): """Compute inner product matrix at the tangent space at a base point. Parameters ---------- base_point : array-like, shape=[..., dim], optional Point in the group (the default is identity). Returns ------- metric_mat : array-like, shape=[..., dim, dim] Metric matrix at base_point. """ if base_point is None: return self.metric_mat_at_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.inverse(jacobian) inv_jacobian_transposed = Matrices.transpose(inv_jacobian) metric_mat = Matrices.mul( inv_jacobian_transposed, self.metric_mat_at_identity, inv_jacobian) return metric_mat
def test_horizontal_projection(self): mat = self.bundle.total_space.random_uniform() vec = self.bundle.total_space.random_uniform() horizontal_vec = self.bundle.horizontal_projection(vec, mat) product = GeneralLinear.mul(horizontal_vec, GeneralLinear.inverse(mat)) is_horizontal = GeneralLinear.is_symmetric(product) self.assertTrue(is_horizontal)
def test_horizontal_projection(self): mat = self.bundle.random_point() vec = self.bundle.random_point() horizontal_vec = self.bundle.horizontal_projection(vec, mat) product = Matrices.mul(horizontal_vec, GeneralLinear.inverse(mat)) is_horizontal = Matrices.is_symmetric(product) self.assertTrue(is_horizontal)
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 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, n] base_point : array-like, shape=[..., n, n] Returns ------- exp : array-like, shape=[..., 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.inverse( 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 random_uniform(self, n_samples=1): """Sample random points from a uniform distribution. Following [Chikuse03]_, :math: `n_samples * n * k` scalars are sampled from a standard normal distribution and reshaped to matrices, the projectors on their first k columns follow a uniform distribution. Parameters ---------- n_samples : int The number of points to sample Optional. default: 1. Returns ------- projectors : array-like, shape=[..., n, n] Points following a uniform distribution. References ---------- .. [Chikuse03] Yasuko Chikuse, Statistics on special manifolds, New York: Springer-Verlag. 2003, 10.1007/978-0-387-21540-2 """ points = gs.random.normal(size=(n_samples, self.n, self.k)) full_rank = Matrices.mul(Matrices.transpose(points), points) projector = Matrices.mul(points, GeneralLinear.inverse(full_rank), Matrices.transpose(points)) return projector[0] if n_samples == 1 else projector
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=[..., dim + 1] Tangent vector at base point to be transported. tangent_vec_b : array-like, shape=[..., dim + 1] Tangent vector at base point, initial speed of the geodesic along which the parallel transport is computed. base_point : array-like, shape=[..., dim + 1] Point on the manifold of SPD matrices. Returns ------- transported_tangent_vec: array-like, shape=[..., dim + 1] Transported tangent vector at exp_(base_point)(tangent_vec_b). """ end_point = self.exp(tangent_vec_b, base_point) inverse_base_point = GeneralLinear.inverse(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 test_horizontal_projection(self, n, vec, mat): bundle = self.space(n) base = self.base(n) horizontal_vec = bundle.horizontal_projection(vec, mat) inverse = GeneralLinear.inverse(mat) product_1 = Matrices.mul(horizontal_vec, inverse) product_2 = Matrices.mul(inverse, horizontal_vec) is_horizontal = gs.all( base.is_tangent(product_1 + product_2, mat, atol=gs.atol * 10)) self.assertTrue(is_horizontal)
def test_horizontal_projection(self): mat = self.bundle.random_point() vec = self.bundle.random_point() horizontal_vec = self.bundle.horizontal_projection(vec, mat) inverse = GeneralLinear.inverse(mat) product_1 = Matrices.mul(horizontal_vec, inverse) product_2 = Matrices.mul(inverse, horizontal_vec) is_horizontal = self.base.is_tangent( product_1 + product_2, mat, atol=gs.atol * 10) self.assertTrue(is_horizontal)
def horizontal_lift(self, tangent_vec, base_point=None, fiber_point=None): """Horizontal lift of a tangent vector.""" if fiber_point is None: fiber_point = self.lift(base_point) transposed_point = Matrices.transpose(fiber_point) alignment = Matrices.mul(transposed_point, fiber_point) projector = Matrices.mul(fiber_point, GeneralLinear.inverse(alignment)) right_term = Matrices.mul(transposed_point, tangent_vec, fiber_point) sylvester = gs.linalg.solve_sylvester(alignment, alignment, right_term) skew_term = Matrices.mul(projector, sylvester) orth_proj = gs.eye(self.n) - Matrices.mul(projector, transposed_point) orth_part = Matrices.mul(orth_proj, tangent_vec, projector) return skew_term + orth_part
def vertical_projection(self, tangent_vec, base_point, **kwargs): """Compute the vertical projection wrt the affine-invariant metric. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector. base_point : array-like, shape=[..., n, n] Base point. Returns ------- ver : array-like, shape=[..., n, n] Vertical projection. """ n = self.n inverse_base_point = GeneralLinear.inverse(base_point) operator = gs.eye(n) + base_point * inverse_base_point inverse_operator = GeneralLinear.inverse(operator) vector = gs.einsum("...ij,...ji->...i", inverse_base_point, tangent_vec) diagonal = gs.einsum("...ij,...j->...i", inverse_operator, vector) return base_point * (diagonal[..., None, :] + diagonal[..., :, None])
def transp(self, base_point, end_point, tangent): """ transports a tangent vector at a base_point to the tangent space at end_point. """ if self.exact_transport: # https://github.com/geomstats/geomstats/blob/master/geomstats/geometry/spd_matrices.py#L613 inverse_base_point = GeneralLinear.inverse(base_point) congruence_mat = GeneralLinear.mul(end_point, inverse_base_point) congruence_mat = gs.linalg.sqrtm(congruence_mat.cpu()).to(tangent) return GeneralLinear.congruent(tangent, congruence_mat) # https://github.com/NicolasBoumal/manopt/blob/master/manopt/manifolds/symfixedrank/sympositivedefinitefactory.m#L181 return tangent
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, 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=[..., n, n] Inner-product. """ power_affine = self.power_affine spd_space = SPDMatrices if power_affine == 1: inv_base_point = GeneralLinear.inverse(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
def parallel_transport(self, tangent_vec, base_point, direction=None, end_point=None): r"""Parallel transport of a tangent vector. Closed-form solution for the parallel transport of a tangent vector along the geodesic between two points `base_point` and `end_point` or alternatively defined by :math:`t\mapsto exp_(base_point)( t*direction)`. Denoting `tangent_vec_a` by `S`, `base_point` by `A`, and `end_point` by `B` or `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 : array-like, shape=[..., n, n] Tangent vector at base point to be transported. base_point : array-like, shape=[..., n, n] Point on the manifold of SPD matrices. Point to transport from direction : array-like, shape=[..., n, n] Tangent vector at base point, initial speed of the geodesic along which the parallel transport is computed. Unused if `end_point` is given. Optional, default: None. end_point : array-like, shape=[..., n, n] Point on the manifold of SPD matrices. Point to transport to. Optional, default: None. Returns ------- transported_tangent_vec: array-like, shape=[..., n, n] Transported tangent vector at exp_(base_point)(tangent_vec_b). """ if end_point is None: end_point = self.exp(direction, base_point) inverse_base_point = GeneralLinear.inverse(base_point) congruence_mat = Matrices.mul(end_point, inverse_base_point) congruence_mat = gs.linalg.sqrtm(congruence_mat) return Matrices.congruent(tangent_vec, congruence_mat)
def exp(self, tangent_vec, base_point, **kwargs): """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, n] Tangent vector at base point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- exp : array-like, shape=[..., n, n] Riemannian exponential. """ power_affine = self.power_affine if power_affine == 1: powers = SymmetricMatrices.powerm(base_point, [1.0 / 2, -1.0 / 2]) exp = self._aux_exp(tangent_vec, powers[0], powers[1]) else: modified_tangent_vec = SPDMatrices.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.inverse(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
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) # We generate invertible matrices using so3_group self.so3_group = SpecialOrthogonal(n=self.n) warnings.simplefilter('ignore', category=ImportWarning) @geomstats.tests.np_only def test_belongs(self): """ A rotation matrix belongs to the matrix Lie group of invertible matrices. """ rot_vec = gs.array([0.2, -0.1, 0.1]) rot_mat = self.so3_group.matrix_from_rotation_vector(rot_vec) result = self.group.belongs(rot_mat) expected = gs.array([[True]]) self.assertAllClose(result, expected) def test_compose(self): # 1. Composition by identity, on the right # Expect the original transformation rot_vec = gs.array([0.2, -0.1, 0.1]) mat = self.so3_group.matrix_from_rotation_vector(rot_vec) result = self.group.compose(mat, self.group.identity) expected = mat expected = helper.to_matrix(mat) self.assertAllClose(result, expected) # 2. Composition by identity, on the left # Expect the original transformation rot_vec = gs.array([0.2, 0.1, -0.1]) mat = self.so3_group.matrix_from_rotation_vector(rot_vec) result = self.group.compose(self.group.identity, mat) expected = mat self.assertAllClose(result, expected) def test_inverse(self): mat = gs.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 10.]]) result = self.group.inverse(mat) expected = 1. / 3. * gs.array([[-2., -4., 3.], [-2., 11., -6.], [3., -6., 3.]]) expected = helper.to_matrix(expected) self.assertAllClose(result, expected) def test_compose_and_inverse(self): # 1. Compose transformation by its inverse on the right # Expect the group identity rot_vec = gs.array([0.2, 0.1, 0.1]) mat = self.so3_group.matrix_from_rotation_vector(rot_vec) inv_mat = self.group.inverse(mat) result = self.group.compose(mat, inv_mat) expected = self.group.identity expected = helper.to_matrix(expected) self.assertAllClose(result, expected) # 2. Compose transformation by its inverse on the left # Expect the group identity rot_vec = gs.array([0.7, 0.1, 0.1]) mat = self.so3_group.matrix_from_rotation_vector(rot_vec) inv_mat = self.group.inverse(mat) result = self.group.compose(inv_mat, mat) expected = self.group.identity expected = helper.to_matrix(expected) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_group_log_and_exp(self): point = 5 * gs.eye(self.n) group_log = self.group.log(point) result = self.group.exp(group_log) expected = point expected = helper.to_matrix(expected) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_group_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_group_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_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)
def test_horizontal_projection(self, n, mat, vec): bundle = self.bundle(n) horizontal_vec = bundle.horizontal_projection(vec, mat) product = Matrices.mul(horizontal_vec, GeneralLinear.inverse(mat)) is_horizontal = Matrices.is_symmetric(product) self.assertTrue(is_horizontal)
class TestGeneralLinear(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.n = 3 self.n_samples = 2 self.group = GeneralLinear(n=self.n) self.group_pos = GeneralLinear(self.n, positive_det=True) warnings.simplefilter('ignore', category=ImportWarning) def test_belongs_shape(self): mat = gs.eye(3) result = self.group.belongs(mat) self.assertAllClose(gs.shape(result), ()) mat = gs.ones((3, 3)) result = self.group.belongs(mat) self.assertAllClose(gs.shape(result), ()) def test_belongs(self): mat = gs.eye(3) result = self.group.belongs(mat) expected = True self.assertAllClose(result, expected) mat = gs.ones((3, 3)) result = self.group.belongs(mat) expected = False self.assertAllClose(result, expected) mat = gs.ones(3) result = self.group.belongs(mat) expected = False self.assertAllClose(result, expected) def test_belongs_vectorization_shape(self): mats = gs.array([gs.eye(3), gs.ones((3, 3))]) result = self.group.belongs(mats) self.assertAllClose(gs.shape(result), (2, )) def test_belongs_vectorization(self): mats = gs.array([gs.eye(3), gs.ones((3, 3))]) result = self.group.belongs(mats) expected = gs.array([True, False]) self.assertAllClose(result, expected) def test_random_and_belongs(self): for group in [self.group, self.group_pos]: point = group.random_point() result = group.belongs(point) self.assertTrue(result) def test_random_and_belongs_vectorization(self): n_samples = 4 expected = gs.array([True] * n_samples) for group in [self.group, self.group_pos]: point = group.random_point(n_samples) result = group.belongs(point) 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.inverse(mat_a) self.assertAllClose(result, expected) 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.inverse(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) 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]]]) expected = gs.cast(expected, gs.float64) point = gs.cast(point, gs.float64) result = self.group.exp(point) self.assertAllClose(result, expected) @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) @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.)]]) identity = GeneralLinear(2).identity path = GeneralLinear(2).orbit(point) time = gs.linspace(0., 1., 3) result = path(time) expected = gs.array([identity, sqrt, point]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_orbit_vectorization(self): point = gs.array([[gs.exp(4.), 0.], [0., gs.exp(2.)]]) sqrt = gs.array([[gs.exp(2.), 0.], [0., gs.exp(1.)]]) identity = GeneralLinear(2).identity path = GeneralLinear(2).orbit(gs.stack([point] * 2), identity) time = gs.linspace(0., 1., 3) result = path(time) expected = gs.array([identity, sqrt, point]) expected = gs.stack([expected] * 2) 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) def test_projection_and_belongs(self): shape = (self.n_samples, self.n, self.n) result = helper.test_projection_and_belongs(self.group, shape) for res in result: self.assertTrue(res) def test_projection_and_belongs_pos(self): shape = (self.n_samples, self.n, self.n) result = helper.test_projection_and_belongs(self.group_pos, shape) for res in result: self.assertTrue(res)
class TestGeneralLinear(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) def test_belongs_shape(self): mat = gs.eye(3) result = self.group.belongs(mat) self.assertAllClose(gs.shape(result), ()) mat = gs.ones((3, 3)) result = self.group.belongs(mat) self.assertAllClose(gs.shape(result), ()) def test_belongs(self): mat = gs.eye(3) result = self.group.belongs(mat) expected = True self.assertAllClose(result, expected) mat = gs.ones((3, 3)) result = self.group.belongs(mat) expected = False self.assertAllClose(result, expected) def test_belongs_vectorization_shape(self): mats = gs.array([gs.eye(3), gs.ones((3, 3))]) result = self.group.belongs(mats) self.assertAllClose(gs.shape(result), (2, )) def test_belongs_vectorization(self): mats = gs.array([gs.eye(3), gs.ones((3, 3))]) result = self.group.belongs(mats) expected = gs.array([True, False]) self.assertAllClose(result, expected) def test_random_and_belongs(self): point = self.group.random_uniform() result = self.group.belongs(point) expected = True self.assertAllClose(result, expected) def test_random_and_belongs_vectorization(self): n_samples = 4 point = self.group.random_uniform(n_samples) result = self.group.belongs(point) expected = gs.array([True] * n_samples) self.assertAllClose(result, expected) def test_replace_values(self): points = gs.ones((3, 3, 3)) new_points = gs.zeros((2, 3, 3)) indcs = [True, False, True] update = self.group._replace_values(points, new_points, indcs) self.assertAllClose( update, gs.stack([gs.zeros((3, 3)), gs.ones((3, 3)), gs.zeros((3, 3))])) 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.inverse(mat_a) self.assertAllClose(result, expected) 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.inverse(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) 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)