def test_projection_and_belongs(self): gs.random.seed(3) group = SpecialOrthogonal(n=4) mat = gs.random.rand(4, 4) point = group.projection(mat) result = group.belongs(point) self.assertTrue(result) mat = gs.random.rand(2, 4, 4) point = group.projection(mat) result = group.belongs(point, atol=1e-4) self.assertTrue(gs.all(result))
class _SpecialEuclideanMatrices(MatrixLieGroup, LevelSet): """Class for special Euclidean group. Parameters ---------- n : int Integer dimension of the underlying Euclidean space. Matrices will be of size: (n+1) x (n+1). Attributes ---------- rotations : SpecialOrthogonal Subgroup of rotations of size n. translations : Euclidean Subgroup of translations of size n. left_canonical_metric : InvariantMetric The left invariant metric that corresponds to the Frobenius inner product at the identity. right_canonical_metric : InvariantMetric The right invariant metric that corresponds to the Frobenius inner product at the identity. metric : MatricesMetric The Euclidean (Frobenius) inner product. """ def __init__(self, n, **kwargs): super().__init__(n=n + 1, dim=int((n * (n + 1)) / 2), embedding_space=GeneralLinear(n + 1, positive_det=True), submersion=submersion, value=gs.eye(n + 1), tangent_submersion=tangent_submersion, lie_algebra=SpecialEuclideanMatrixLieAlgebra(n=n), **kwargs) self.rotations = SpecialOrthogonal(n=n) self.translations = Euclidean(dim=n) self.n = n self.left_canonical_metric = SpecialEuclideanMatrixCannonicalLeftMetric( group=self) if self._metric is None: self._metric = self.left_canonical_metric @property def identity(self): """Return the identity matrix.""" return gs.eye(self.n + 1, self.n + 1) def random_point(self, n_samples=1, bound=1.0): """Sample in SE(n) from the uniform distribution. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound: float Bound of the interval in which to sample each entry of the translation part. Optional, default: 1. Returns ------- samples : array-like, shape=[..., n + 1, n + 1] Sample in SE(n). """ random_translation = self.translations.random_point(n_samples) random_rotation = self.rotations.random_uniform(n_samples) output_shape = ((n_samples, self.n + 1, self.n + 1) if n_samples != 1 else (self.n + 1, ) * 2) random_point = homogeneous_representation(random_rotation, random_translation, output_shape) return random_point @classmethod def inverse(cls, point): """Return the inverse of a point. Parameters ---------- point : array-like, shape=[..., n + 1, n + 1] Point to be inverted. Returns ------- inverse : array-like, shape=[..., n + 1, n + 1] Inverse of point. """ n = point.shape[-1] - 1 transposed_rot = Matrices.transpose(point[..., :n, :n]) translation = point[..., :n, -1] translation = gs.einsum("...ij,...j->...i", transposed_rot, translation) return homogeneous_representation(transposed_rot, -translation, point.shape) def projection(self, mat): """Project a matrix on SE(n). The upper-left n x n block is projected to SO(n) by minimizing the Frobenius norm. The last columns is kept unchanged and used as the translation part. The last row is discarded. Parameters ---------- mat : array-like, shape=[..., n + 1, n + 1] Matrix. Returns ------- projected : array-like, shape=[..., n + 1, n + 1] Rotation-translation matrix in homogeneous representation. """ n = mat.shape[-1] - 1 projected_rot = self.rotations.projection(mat[..., :n, :n]) translation = mat[..., :n, -1] return homogeneous_representation(projected_rot, translation, mat.shape)
class TestSpecialOrthogonal2(geomstats.tests.TestCase): def setup_method(self): warnings.simplefilter("ignore", category=ImportWarning) warnings.simplefilter("ignore", category=UserWarning) gs.random.seed(1234) self.group = SpecialOrthogonal(n=2, point_type="vector") # -- Set attributes self.n_samples = 4 def test_projection(self): # Test 2D and nD cases rot_mat = gs.eye(2) delta = 1e-12 * gs.ones((2, 2)) rot_mat_plus_delta = rot_mat + delta result = self.group.projection(rot_mat_plus_delta) expected = rot_mat self.assertAllClose(result, expected) def test_projection_vectorization(self): n_samples = self.n_samples mats = gs.ones((n_samples, 2, 2)) result = self.group.projection(mats) self.assertAllClose(gs.shape(result), (n_samples, 2, 2)) def test_skew_matrix_from_vector(self): rot_vec = gs.array([0.9]) skew_matrix = self.group.skew_matrix_from_vector(rot_vec) result = gs.matmul(skew_matrix, skew_matrix) diag = gs.array([-0.81, -0.81]) expected = algebra_utils.from_vector_to_diagonal_matrix(diag) self.assertAllClose(result, expected) def test_skew_matrix_and_vector(self): rot_vec = gs.array([0.8]) skew_mat = self.group.skew_matrix_from_vector(rot_vec) result = self.group.vector_from_skew_matrix(skew_mat) expected = rot_vec self.assertAllClose(result, expected) def test_skew_matrix_from_vector_vectorization(self): n_samples = self.n_samples rot_vecs = self.group.random_uniform(n_samples=n_samples) result = self.group.skew_matrix_from_vector(rot_vecs) self.assertAllClose(gs.shape(result), (n_samples, 2, 2)) def test_random_uniform_shape(self): result = self.group.random_uniform() self.assertAllClose(gs.shape(result), (self.group.dim, )) 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 = self.n_samples points = self.group.random_uniform(n_samples=n_samples) result = self.group.belongs(points) expected = gs.array([True] * n_samples) self.assertAllClose(result, expected) def test_regularize(self): angle = 2 * gs.pi + 1 result = self.group.regularize(gs.array([angle])) expected = gs.array([1.0]) self.assertAllClose(result, expected) def test_regularize_vectorization(self): n_samples = self.n_samples rot_vecs = self.group.random_uniform(n_samples=n_samples) result = self.group.regularize(rot_vecs) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) def test_matrix_from_rotation_vector(self): angle = gs.pi / 3 expected = gs.array([[1.0 / 2, -gs.sqrt(3.0) / 2], [gs.sqrt(3.0) / 2, 1.0 / 2]]) result = self.group.matrix_from_rotation_vector(gs.array([angle])) self.assertAllClose(result, expected) def test_matrix_from_rotation_vector_vectorization(self): n_samples = self.n_samples rot_vecs = self.group.random_uniform(n_samples=n_samples) rot_mats = self.group.matrix_from_rotation_vector(rot_vecs) self.assertAllClose(gs.shape(rot_mats), (n_samples, self.group.n, self.group.n)) def test_rotation_vector_from_matrix(self): angle = 0.12 rot_mat = gs.array([[gs.cos(angle), -gs.sin(angle)], [gs.sin(angle), gs.cos(angle)]]) result = self.group.rotation_vector_from_matrix(rot_mat) expected = gs.array([0.12]) self.assertAllClose(result, expected) def test_rotation_vector_and_rotation_matrix(self): """ This tests that the composition of rotation_vector_from_matrix and matrix_from_rotation_vector is the identity. """ # TODO(nguigs): bring back a 1d representation of SO2 point = gs.array([0.78]) rot_mat = self.group.matrix_from_rotation_vector(point) result = self.group.rotation_vector_from_matrix(rot_mat) expected = point self.assertAllClose(result, expected) def test_rotation_vector_and_rotation_matrix_vectorization(self): rot_vecs = gs.array([[2.0], [1.3], [0.8], [0.03]]) rot_mats = self.group.matrix_from_rotation_vector(rot_vecs) result = self.group.rotation_vector_from_matrix(rot_mats) expected = self.group.regularize(rot_vecs) self.assertAllClose(result, expected) def test_compose(self): point_a = gs.array([0.12]) point_b = gs.array([-0.15]) result = self.group.compose(point_a, point_b) expected = self.group.regularize(gs.array([-0.03])) self.assertAllClose(result, expected) def test_compose_and_inverse(self): angle = 0.986 point = gs.array([angle]) inv_point = self.group.inverse(point) result = self.group.compose(point, inv_point) expected = self.group.identity self.assertAllClose(result, expected) result = self.group.compose(inv_point, point) expected = self.group.identity self.assertAllClose(result, expected) def test_compose_vectorization(self): point_type = "vector" self.group.default_point_type = point_type n_samples = self.n_samples n_points_a = self.group.random_uniform(n_samples=n_samples) n_points_b = self.group.random_uniform(n_samples=n_samples) one_point = self.group.random_uniform(n_samples=1) result = self.group.compose(one_point, n_points_a) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) result = self.group.compose(n_points_a, one_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) result = self.group.compose(n_points_a, n_points_b) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) def test_inverse_vectorization(self): n_samples = self.n_samples points = self.group.random_uniform(n_samples=n_samples) result = self.group.inverse(points) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) def test_group_exp(self): """ The Riemannian exp and log are inverse functions of each other. This test is the inverse of test_log's. """ rot_vec_base_point = gs.array([gs.pi / 5]) rot_vec = gs.array([2 * gs.pi / 5]) expected = gs.array([3 * gs.pi / 5]) result = self.group.exp(base_point=rot_vec_base_point, tangent_vec=rot_vec) self.assertAllClose(result, expected) def test_group_exp_vectorization(self): n_samples = self.n_samples one_tangent_vec = self.group.random_uniform(n_samples=1) one_base_point = self.group.random_uniform(n_samples=1) n_tangent_vec = self.group.random_uniform(n_samples=n_samples) n_base_point = self.group.random_uniform(n_samples=n_samples) # Test with the 1 base point, and n tangent vecs result = self.group.exp(n_tangent_vec, one_base_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) # Test with the several base point, and one tangent vec result = self.group.exp(one_tangent_vec, n_base_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) # Test with the same number n of base point and n tangent vec result = self.group.exp(n_tangent_vec, n_base_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) def test_group_log(self): """ The Riemannian exp and log are inverse functions of each other. This test is the inverse of test_exp's. """ rot_vec_base_point = gs.array([gs.pi / 5]) rot_vec = gs.array([2 * gs.pi / 5]) expected = gs.array([1 * gs.pi / 5]) result = self.group.log(point=rot_vec, base_point=rot_vec_base_point) self.assertAllClose(result, expected) def test_group_log_vectorization(self): n_samples = self.n_samples one_point = self.group.random_uniform(n_samples=1) one_base_point = self.group.random_uniform(n_samples=1) n_point = self.group.random_uniform(n_samples=n_samples) n_base_point = self.group.random_uniform(n_samples=n_samples) # Test with the 1 base point, and several different points result = self.group.log(n_point, one_base_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) # Test with the several base point, and 1 point result = self.group.log(one_point, n_base_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) # Test with the same number n of base point and point result = self.group.log(n_point, n_base_point) self.assertAllClose(gs.shape(result), (n_samples, self.group.dim)) def test_group_exp_then_log_from_identity(self): """ Test that the group exponential and the group logarithm are inverse. Expect their composition to give the identity function. """ tangent_vec = gs.array([0.12]) result = helper.group_exp_then_log_from_identity( group=self.group, tangent_vec=tangent_vec) expected = self.group.regularize(tangent_vec) self.assertAllClose(result, expected) def test_group_log_then_exp_from_identity(self): """ Test that the group exponential and the group logarithm are inverse. Expect their composition to give the identity function. """ point = gs.array([0.12]) result = helper.group_log_then_exp_from_identity(group=self.group, point=point) expected = self.group.regularize(point) self.assertAllClose(result, expected) def test_group_exp_then_log(self): """ This tests that the composition of log and exp gives identity. """ base_point = gs.array([0.12]) tangent_vec = gs.array([0.35]) result = helper.group_exp_then_log(group=self.group, tangent_vec=tangent_vec, base_point=base_point) expected = self.group.regularize_tangent_vec(tangent_vec=tangent_vec, base_point=base_point) self.assertAllClose(result, expected) def test_group_log_then_exp(self): """ This tests that the composition of log and exp gives identity. """ base_point = gs.array([0.12]) point = gs.array([0.35]) result = helper.group_log_then_exp(group=self.group, point=point, base_point=base_point) expected = self.group.regularize(point) self.assertAllClose(result, expected)