def compose(self, point_a, point_b, point_type=None): r"""Compose two elements of SE(n). Parameters ---------- point_1 : array-like, shape=[n_samples, {dim, [n + 1, n + 1]}] point_2 : array-like, shape=[n_samples, {dim, [n + 1, n + 1]}] point_type: str, {'vector', 'matrix'}, optional default: self.default_point_type Equation --------- (:math: `(R_1, t_1) \\cdot (R_2, t_2) = (R_1 R_2, R_1 t_2 + t_1)`) Returns ------- composition : the composition of point_1 and point_2 """ rotations = self.rotations dim_rotations = rotations.dim point_a = self.regularize(point_a, point_type=point_type) point_b = self.regularize(point_b, point_type=point_type) if point_type == 'vector': n_points_a, _ = point_a.shape n_points_b, _ = point_b.shape if not (point_a.shape == point_b.shape or n_points_a == 1 or n_points_b == 1): raise ValueError() rot_vec_a = point_a[:, :dim_rotations] rot_mat_a = rotations.matrix_from_rotation_vector(rot_vec_a) rot_vec_b = point_b[:, :dim_rotations] rot_mat_b = rotations.matrix_from_rotation_vector(rot_vec_b) translation_a = point_a[:, dim_rotations:] translation_b = point_b[:, dim_rotations:] composition_rot_mat = gs.matmul(rot_mat_a, rot_mat_b) composition_rot_vec = rotations.rotation_vector_from_matrix( composition_rot_mat) composition_translation = gs.einsum( '...j,...kj->...k', translation_b, rot_mat_a) + translation_a composition = gs.concatenate( (composition_rot_vec, composition_translation), axis=-1) return self.regularize(composition, point_type=point_type) if point_type == 'matrix': return GeneralLinear.compose(point_a, point_b) raise ValueError('Invalid point_type, expected \'vector\' or ' '\'matrix\'.')
def log(self, point, base_point, **kwargs): r"""Compute the Riemannian logarithm of point w.r.t. base_point. Given :math:`P, P'` in Gr(n, k) the logarithm from :math:`P` to :math:`P` is induced by the infinitesimal rotation [Batzies2015]_: .. math:: Y = \frac 1 2 \log \big((2 P' - 1)(2 P - 1)\big) The tangent vector :math:`X` at :math:`P` is then recovered by :math:`X = [Y, P]`. Parameters ---------- point : array-like, shape=[..., n, n] Point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- tangent_vec : array-like, shape=[..., n, n] Riemannian logarithm, a tangent vector at `base_point`. References ---------- .. [Batzies2015] Batzies, Hüper, Machado, Leite. "Geometric Mean and Geodesic Regression on Grassmannians" Linear Algebra and its Applications, 466, 83-101, 2015. """ GLn = GeneralLinear(self.n) id_n = GLn.identity id_n, point, base_point = gs.convert_to_wider_dtype( [id_n, point, base_point]) sym2 = 2 * point - id_n sym1 = 2 * base_point - id_n rot = GLn.compose(sym2, sym1) return Matrices.bracket(GLn.log(rot) / 2, base_point)
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)
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 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)