def test_exp_and_log_from_identity_left_metrics(self): """ Test that the riemannian exponential and the riemannian logarithm are inverse. Expect their composition to give the identity function. """ # - exp then log # For left metric, point and point_small result = helper.exp_then_log_from_identity(metric=self.left_metric, tangent_vec=self.point_1) expected = self.point_1 self.assertTrue(gs.allclose(result, expected)) result = helper.exp_then_log_from_identity( metric=self.left_metric, tangent_vec=self.point_small) expected = self.point_small self.assertTrue(gs.allclose(result, expected)) # - log then exp # For left metric, point and point_small result = helper.log_then_exp_from_identity(metric=self.left_metric, point=self.point_1) expected = self.point_1 self.assertTrue(gs.allclose(result, expected)) result = helper.log_then_exp_from_identity(metric=self.left_metric, point=self.point_small) expected = self.point_small self.assertTrue(gs.allclose(result, expected))
def test_compose_and_inverse(self): # 1. Compose transformation by its inverse on the right # Expect the group identity rot_vec_1 = self.so3_group.random_uniform() mat_1 = self.so3_group.matrix_from_rotation_vector(rot_vec_1) inv_mat_1 = self.group.inverse(mat_1) result_1 = self.group.compose(mat_1, inv_mat_1) expected_1 = self.group.identity norm = gs.linalg.norm(expected_1) atol = RTOL if norm != 0: atol = RTOL * norm self.assertTrue( gs.allclose(result_1, expected_1, atol=atol), '\nresult:\n{}' '\nexpected:\n{}'.format(result_1, expected_1)) # 2. Compose transformation by its inverse on the left # Expect the group identity rot_vec_2 = self.so3_group.random_uniform() mat_2 = self.so3_group.matrix_from_rotation_vector(rot_vec_2) inv_mat_2 = self.group.inverse(mat_2) result_2 = self.group.compose(inv_mat_2, mat_2) expected_2 = self.group.identity norm = gs.linalg.norm(expected_2) atol = RTOL if norm != 0: atol = RTOL * norm self.assertTrue(gs.allclose(result_2, expected_2, atol=atol))
def test_log_after_exp_with_angles_close_to_pi( self, metric, tangent_vec, base_point ): """ Test that the Riemannian left exponential and the Riemannian left logarithm are inverse. Expect their composition to give the identity function. """ group = SpecialEuclidean(3, "vector") result = metric.log(metric.exp(tangent_vec, base_point), base_point) expected = group.regularize_tangent_vec( tangent_vec=tangent_vec, base_point=base_point, metric=metric ) inv_expected = gs.concatenate([-expected[:3], expected[3:6]]) norm = gs.linalg.norm(expected) atol = ATOL if norm != 0: atol = ATOL * norm self.assertTrue( gs.allclose(result, expected, atol=atol) or gs.allclose(result, inv_expected, atol=atol) )
def test_compose(self): # 1. Composition by identity, on the right # Expect the original transformation rot_vec_1 = self.so3_group.random_uniform() mat_1 = self.so3_group.matrix_from_rotation_vector(rot_vec_1) result_1 = self.group.compose(mat_1, self.group.identity) expected_1 = mat_1 self.assertTrue(gs.allclose(result_1, expected_1)) # 2. Composition by identity, on the left # Expect the original transformation rot_vec_2 = self.so3_group.random_uniform() mat_2 = self.so3_group.matrix_from_rotation_vector(rot_vec_2) result_2 = self.group.compose(self.group.identity, mat_2) expected_2 = mat_2 norm = gs.linalg.norm(expected_2) atol = RTOL if norm != 0: atol = RTOL * norm self.assertTrue( gs.allclose(result_2, expected_2, atol=atol), '\nresult:\n{}' '\nexpected:\n{}'.format(result_2, expected_2))
def test_squared_dist_is_symmetric(self): n_samples = self.n_samples point_1 = self.space.random_uniform(n_samples=1) point_2 = self.space.random_uniform(n_samples=1) sq_dist_1_2 = self.metric.squared_dist(point_1, point_2) sq_dist_2_1 = self.metric.squared_dist(point_2, point_1) self.assertTrue(gs.allclose(sq_dist_1_2, sq_dist_2_1)) point_1 = self.space.random_uniform(n_samples=1) point_2 = self.space.random_uniform(n_samples=n_samples) sq_dist_1_2 = self.metric.squared_dist(point_1, point_2) sq_dist_2_1 = self.metric.squared_dist(point_2, point_1) self.assertTrue(gs.allclose(sq_dist_1_2, sq_dist_2_1)) point_1 = self.space.random_uniform(n_samples=n_samples) point_2 = self.space.random_uniform(n_samples=1) sq_dist_1_2 = self.metric.squared_dist(point_1, point_2) sq_dist_2_1 = self.metric.squared_dist(point_2, point_1) self.assertTrue(gs.allclose(sq_dist_1_2, sq_dist_2_1)) point_1 = self.space.random_uniform(n_samples=n_samples) point_2 = self.space.random_uniform(n_samples=n_samples) sq_dist_1_2 = self.metric.squared_dist(point_1, point_2) sq_dist_2_1 = self.metric.squared_dist(point_2, point_1) self.assertTrue(gs.allclose(sq_dist_1_2, sq_dist_2_1))
def test_log_vectorization(self): n_samples = self.n_samples one_base_point = self.space.random_uniform(n_samples=1) n_base_point = self.space.random_uniform(n_samples=n_samples) one_point = self.space.random_uniform(n_samples=1) n_point = self.space.random_uniform(n_samples=n_samples) # Test with different points, one base point results = self.metric.log(n_point, one_base_point) self.assertTrue( gs.allclose(results.shape, (n_samples, self.space.n, self.space.n))) # Test with the same number of points and base points results = self.metric.log(n_point, n_base_point) self.assertTrue( gs.allclose(results.shape, (n_samples, self.space.n, self.space.n))) # Test with the one point and n base points results = self.metric.log(one_point, n_base_point) self.assertTrue( gs.allclose(results.shape, (n_samples, self.space.n, self.space.n)))
def test_rotation_vector_and_rotation_matrix_with_angles_close_to_pi(self, point): group = self.space(3, point_type="vector") mat = group.matrix_from_rotation_vector(point) result = group.rotation_vector_from_matrix(mat) expected1 = group.regularize(point) expected2 = -1 * expected1 expected = gs.allclose(result, expected1) or gs.allclose(result, expected2) self.assertTrue(expected)
def test_quaternion_and_matrix_with_angles_close_to_pi(self, point): group = self.space(3, point_type="vector") mat = group.matrix_from_rotation_vector(point) quat = group.quaternion_from_matrix(mat) result = group.matrix_from_quaternion(quat) expected1 = mat expected2 = gs.linalg.inv(mat) expected = gs.allclose(result, expected1) or gs.allclose(result, expected2) self.assertTrue(expected)
def test_quaternion_and_rotation_vector_with_angles_close_to_pi(self, point): group = self.space(3, point_type="vector") quaternion = group.quaternion_from_rotation_vector(point) result = group.rotation_vector_from_quaternion(quaternion) expected1 = group.regularize(point) expected2 = -1 * expected1 expected = gs.allclose(result, expected1) or gs.allclose(result, expected2) self.assertTrue(expected)
def test_make_symmetric(self): sym_mat = gs.array([[1, 2], [2, 1]]) result = spd_matrices_space.make_symmetric(sym_mat) expected = sym_mat self.assertTrue(gs.allclose(result, expected)) mat = gs.array([[1, 2, 3], [0, 0, 0], [3, 1, 1]]) result = spd_matrices_space.make_symmetric(mat) expected = gs.array([[1, 1, 3], [1, 0, 0.5], [3, 0.5, 1]]) self.assertTrue(gs.allclose(result, expected))
def test_inner_product_matrix(self): base_point = self.group.identity result = self.left_metric.inner_product_matrix(base_point=base_point) expected = self.left_metric.inner_product_mat_at_identity self.assertTrue(gs.allclose(result, expected)) result = self.right_metric.inner_product_matrix(base_point=base_point) expected = self.right_metric.inner_product_mat_at_identity self.assertTrue(gs.allclose(result, expected))
def test_compose_regularize_angles_close_to_pi(self, point): group = self.space(3, point_type="vector") result = group.compose(point, group.identity) expected = group.regularize(point) inv_expected = -expected self.assertTrue( gs.allclose(result, expected) or gs.allclose(result, inv_expected)) result = group.compose(group.identity, point) expected = group.regularize(point) inv_expected = -expected self.assertTrue( gs.allclose(result, expected) or gs.allclose(result, inv_expected))
def vector_from_symmetric_matrix_and_symmetric_matrix_from_vector(self): sym_mat_1 = gs.array([[1., 0.6, -3.], [0.6, 7., 0.], [-3., 0., 8.]]) vector_1 = self.space.vector_from_symmetric_matrix(sym_mat_1) result_1 = self.space.symmetric_matrix_from_vector(vector_1) expected_1 = sym_mat_1 self.assertTrue(gs.allclose(result_1, expected_1)) vector_2 = gs.array([1, 2, 3, 4, 5, 6]) sym_mat_2 = self.space.symmetric_matrix_from_vector(vector_2) result_2 = self.space.vector_from_symmetric_matrix(sym_mat_2) expected_2 = vector_2 self.assertTrue(gs.allclose(result_2, expected_2))
def test_group_exp_after_log_with_angles_close_to_pi( self, point, base_point): """ This tests that the composition of log and exp gives identity. """ # TODO(nguigs): fix this test for tf group = self.space(3, point_type="vector") result = group.exp(group.log(point, base_point), base_point) expected = group.regularize(point) inv_expected = -expected self.assertTrue( gs.allclose(result, expected, atol=5e-3) or gs.allclose(result, inv_expected, atol=5e-3))
def test_vector_and_symmetric_matrix_vectorization(self): """Test of vectorization.""" n_samples = 5 vector = gs.random.rand(n_samples, 6) sym_mat = self.space.symmetric_matrix_from_vector(vector) result = self.space.vector_from_symmetric_matrix(sym_mat) expected = vector self.assertTrue(gs.allclose(result, expected)) vector = self.space.vector_from_symmetric_matrix(sym_mat) result = self.space.symmetric_matrix_from_vector(vector) expected = sym_mat self.assertTrue(gs.allclose(result, expected))
def test_vector_from_symmetric_matrix_and_symmetric_matrix_from_vector(self): """Test for matrix to vector and vector to matrix conversions.""" sym_mat_1 = gs.array([[1.0, 0.6, -3.0], [0.6, 7.0, 0.0], [-3.0, 0.0, 8.0]]) vector_1 = self.space.to_vector(sym_mat_1) result_1 = self.space.from_vector(vector_1) expected_1 = sym_mat_1 self.assertTrue(gs.allclose(result_1, expected_1)) vector_2 = gs.array([1, 2, 3, 4, 5, 6]) sym_mat_2 = self.space.from_vector(vector_2) result_2 = self.space.to_vector(sym_mat_2) expected_2 = vector_2 self.assertTrue(gs.allclose(result_2, expected_2))
def vector_and_symmetric_matrix_vectorization(self): n_samples = self.n_samples vector = gs.random.rand(n_samples, 6) sym_mat = self.space.symmetric_matrix_from_vector(vector) result = self.space.vector_from_symmetric_matrix(sym_mat) expected = vector self.assertTrue(gs.allclose(result, expected)) sym_mat = self.space.random_uniform(n_samples) vector = self.space.vector_from_symmetric_matrix(sym_mat) result = self.space.symmetric_matrix_from_vector(vector) expected = sym_mat self.assertTrue(gs.allclose(result, expected))
def log(self, point, base_point=None): """Compute the group logarithm of `point` relative to `base_point`. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]}] Point. base_point : array-like, shape=[..., {dim, [n, n]}] Base point. Optional, defaults to identity if None. Returns ------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Group logarithm. """ # TODO (ninamiolane): Build a standalone decorator that *only* # deals with point_type None and base_point None identity = self.get_identity(point_type=self.default_point_type) if base_point is None: base_point = identity point = self.regularize(point) base_point = self.regularize(base_point) if gs.allclose(base_point, identity): result = self.log_from_identity(point) else: result = self.log_not_from_identity(point, base_point) return result
def exp(self, tangent_vec, base_point=None): """Compute the group exponential at `base_point` of `tangent_vec`. Parameters ---------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Tangent vector at base point. base_point : array-like, shape=[..., {dim, [n, n]}] Base point. Optional, default: self.identity Returns ------- result : array-like, shape=[..., {dim, [n, n]}] Group exponential. """ identity = self.get_identity() if base_point is None: base_point = identity base_point = self.regularize(base_point) if gs.allclose(base_point, identity): result = self.exp_from_identity(tangent_vec) else: result = self.exp_not_from_identity(tangent_vec, base_point) return result
def test_expm_and_logm_vectorization(self): point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]], [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]]) result = gs.linalg.expm(gs.linalg.logm(point)) expected = point self.assertTrue(gs.allclose(result, expected))
def test_logm(self): point = gs.array([[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]]) result = gs.linalg.logm(point) expected = gs.array([[0.693147180, 0., 0.], [0., 1.098612288, 0.], [0., 0., 1.38629436]]) self.assertTrue(gs.allclose(result, expected))
def log(self, point, base_point=None, point_type=None): """Compute the group logarithm of `point` relative to `base_point`. Parameters ---------- point : array-like, shape=[n_samples, {dim,[n,n]}] base_point : array-like, shape=[n_samples, {dim,[n,n]}] point_type : str, {'vector', 'matrix'} Returns ------- tangent_vec : array-like, shape=[n_samples, {dim,[n,n]}] """ # TODO(ninamiolane): Build a standalone decorator that *only* # deals with point_type None and base_point None if point_type is None: point_type = self.default_point_type identity = self.get_identity(point_type=point_type) if base_point is None: base_point = identity point = self.regularize(point, point_type=point_type) base_point = self.regularize(base_point, point_type=point_type) if gs.allclose(base_point, identity): result = self.log_from_identity(point, point_type=point_type) else: result = self.log_not_from_identity(point, base_point, point_type) return result
def group_log(self, point, base_point=None, point_type=None): """ Compute the group logarithm at point base_point of the point point. """ if point_type is None: point_type = self.default_point_type identity = self.get_identity(point_type=point_type) identity = self.regularize(identity, point_type=point_type) if base_point is None: base_point = identity base_point = self.regularize(base_point, point_type=point_type) if gs.allclose(base_point, identity): return self.group_log_from_identity(point, point_type=point_type) point = self.regularize(point, point_type=point_type) jacobian = self.jacobian_translation(point=base_point, left_or_right='left', point_type=point_type) point_near_id = self.compose(self.inverse(base_point), point, point_type=point_type) group_log_from_id = self.group_log_from_identity(point=point_near_id, point_type=point_type) group_log = gs.einsum('ij,ijk->ik', group_log_from_id, gs.transpose(jacobian, axes=(0, 2, 1))) assert group_log.ndim == 2 return group_log
def test_group_log_and_exp(self): point_1 = 5 * gs.eye(4) group_log_1 = spd_matrices_space.group_log(point_1) result_1 = spd_matrices_space.group_exp(group_log_1) expected_1 = point_1 self.assertTrue(gs.allclose(result_1, expected_1))
def test_inner_product_matrix_and_its_inverse(self): inner_prod_mat = self.left_diag_metric.inner_product_mat_at_identity inv_inner_prod_mat = gs.linalg.inv(inner_prod_mat) result = gs.matmul(inv_inner_prod_mat, inner_prod_mat) expected = gs.eye(self.group.dimension) expected = gs.to_ndarray(expected, to_ndim=3, axis=0) self.assertTrue(gs.allclose(result, expected))
def _iterate_once(self, current_median, X, weights, lr): """Compute a single iteration of Weiszfeld Algorithm. Parameters ---------- current_median : array-like, shape={representation shape} current median. X : array-like, shape=[..., {representation shape}] data for which geometric has to be found. weights : array-like, shape=[N] weights for weighted sum. lr : float learning rate for the current iteration. Returns ------- updated_median: array-like, shape={representation shape} updated median after single iteration. """ def _scalarmul(scalar, array): if gs.ndim(array) == 2: return scalar[:, None] * array return scalar[:, None, None] * array dists = self.metric.dist(current_median, X) if gs.allclose(dists, 0.0): return current_median logs = self.metric.log(X, current_median) mul = gs.divide(weights, dists, ignore_div_zero=True) v_k = gs.sum(_scalarmul(mul, logs), axis=0) / gs.sum(mul) updated_median = self.metric.exp(lr * v_k, current_median) return updated_median
def is_tangent(self, vector, base_point=None, atol=ATOL): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., dim_embedding] Vector. base_point : array-like, shape=[..., dim_embedding] Point in the Lie group. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ if base_point is None: base_point = self.identity if gs.allclose(base_point, self.identity): tangent_vec_at_id = vector else: tangent_vec_at_id = self.compose( self.inverse(base_point), vector) is_tangent = self._is_in_lie_algebra(tangent_vec_at_id, atol) return is_tangent
def test_exp_and_log_and_projection_to_tangent_space_general_case(self): """Test Log and Exp. Test that the Riemannian exponential and the Riemannian logarithm are inverse. Expect their composition to give the identity function. NB: points on the n-dimensional sphere are (n+1)-D vectors of norm 1. """ # Riemannian Exp then Riemannian Log # General case # NB: Riemannian log gives a regularized tangent vector, # so we take the norm modulo 2 * pi. base_point = gs.array([0.0, -3.0, 0.0, 3.0, 4.0]) base_point = base_point / gs.linalg.norm(base_point) vector = gs.array([3.0, 2.0, 0.0, 0.0, -1.0]) vector = self.space.to_tangent(vector=vector, base_point=base_point) exp = self.metric.exp(tangent_vec=vector, base_point=base_point) result = self.metric.log(point=exp, base_point=base_point) expected = vector norm_expected = gs.linalg.norm(expected) regularized_norm_expected = gs.mod(norm_expected, 2 * gs.pi) expected = expected / norm_expected * regularized_norm_expected # The Log can be the opposite vector on the tangent space, # whose Exp gives the base_point are_close = gs.allclose(result, expected) norm_2pi = gs.isclose(gs.linalg.norm(result - expected), 2 * gs.pi) self.assertTrue(are_close or norm_2pi)
def rotate_points(points, end_point): """Apply to points the rotation from north_pole to end_point. A QR decomposition is used to find the rotation that maps the north pole (1, 0,...,0) to the end_point, then this rotation is applied to the input points. Parameters ---------- points : array-like, shape=[..., n] Points to rotate. end_point : array-like, shape=[n, ] Point to parametrise the rotation. Returns ------- rotated_points : array-like, shape=[..., n] Points after the rotation. """ n = end_point.shape[0] base_point = gs.array([1.0] + [0] * (n - 1)) embedded = gs.concatenate([end_point[None, :], gs.zeros((n - 1, n))]) norm = gs.linalg.norm(end_point) q, _ = gs.linalg.qr(gs.transpose(embedded) / norm) new_points = gs.matmul(points[None, :], gs.transpose(q)) * norm if not gs.allclose(gs.matmul(q, base_point[:, None])[:, 0], end_point): new_points = -new_points return new_points[0]
def exp(self, tangent_vec, base_point=None, point_type=None): """Compute the group exponential at `base_point` of `tangent_vec`. Parameters ---------- tangent_vec : array-like, shape=[n_samples, {dim,[n,n]}] base_point : array-like, shape=[n_samples, {dim,[n,n]}] default: self.identity point_type : str, {'vector', 'matrix'} default: the default point type the type of the point Returns ------- result : array-like, shape=[n_samples, {dim,[n,n]}] The exponentiated tangent vector """ if point_type is None: point_type = self.default_point_type identity = self.get_identity(point_type=point_type) if base_point is None: base_point = identity base_point = self.regularize(base_point, point_type=point_type) if gs.allclose(base_point, identity): result = self.exp_from_identity(tangent_vec, point_type=point_type) else: result = self.exp_not_from_identity( tangent_vec, base_point, point_type) return result