def retraction(self, tangent_vec, base_point): """ Retraction map, based on QR-decomposion: P_x(V) = qf(X + V) """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, n, p = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) matrix_q, matrix_r = gs.linalg.qr(base_point+tangent_vec) diagonal = gs.diagonal(matrix_r, axis1=1, axis2=2) sign = gs.sign(gs.sign(diagonal) + 0.5) diag = gs.diag(sign) result = gs.einsum('nij,njk->nik', matrix_q, diag) return result
def exp(self, tangent_vec, base_point): """ 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. """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, mat_dim, _ = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) sqrt_base_point = gs.linalg.sqrtm(base_point) inv_sqrt_base_point = gs.linalg.inv(sqrt_base_point) tangent_vec_at_id = gs.matmul(inv_sqrt_base_point, tangent_vec) tangent_vec_at_id = gs.matmul(tangent_vec_at_id, inv_sqrt_base_point) exp_from_id = gs.linalg.expm(tangent_vec_at_id) exp = gs.matmul(exp_from_id, sqrt_base_point) exp = gs.matmul(sqrt_base_point, exp) return exp
def test_lifting_vectorization_shape(self): n_samples = self.n_samples n = self.n p = self.p one_point = self.point_a one_base_point = self.point_b n_points = gs.tile( gs.to_ndarray(self.point_a, to_ndim=3), (n_samples, 1, 1)) n_base_points = gs.tile( gs.to_ndarray(self.point_b, to_ndim=3), (n_samples, 1, 1)) result = self.metric.lifting(one_point, one_base_point) self.assertAllClose(gs.shape(result), (n, p)) result = self.metric.lifting(n_points, one_base_point) self.assertAllClose(gs.shape(result), (n_samples, n, p)) result = self.metric.lifting(one_point, n_base_points) self.assertAllClose(gs.shape(result), (n_samples, n, p)) result = self.metric.lifting(n_points, n_base_points) self.assertAllClose(gs.shape(result), (n_samples, n, p))
def log(self, point, base_point): """ Compute the Riemannian logarithm at point base_point, of point wrt the metric defined in inner_product. This gives a tangent vector at point base_point. """ point = gs.to_ndarray(point, to_ndim=3) n_points, _, _ = point.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, mat_dim, _ = base_point.shape assert (n_points == n_base_points or n_points == 1 or n_base_points == 1) if n_points == 1: point = gs.tile(point, (n_base_points, 1, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_points, 1, 1)) sqrt_base_point = gs.zeros((n_base_points, ) + (mat_dim, ) * 2) sqrt_base_point = gs.linalg.sqrtm(base_point) inv_sqrt_base_point = gs.linalg.inv(sqrt_base_point) point_near_id = gs.matmul(inv_sqrt_base_point, point) point_near_id = gs.matmul(point_near_id, inv_sqrt_base_point) log_at_id = gs.linalg.logm(point_near_id) log = gs.matmul(sqrt_base_point, log_at_id) log = gs.matmul(log, sqrt_base_point) return log
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point): """ Compute the inner product of tangent_vec_a and tangent_vec_b at point base_point using the affine invariant Riemannian metric. """ power_affine = self.power_affine tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=3) n_tangent_vecs_a, _, _ = tangent_vec_a.shape tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=3) n_tangent_vecs_b, _, _ = tangent_vec_b.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, _, _ = base_point.shape spd_space = self.space assert (n_tangent_vecs_a == n_tangent_vecs_b == n_base_points or n_tangent_vecs_a == n_tangent_vecs_b and n_base_points == 1 or n_base_points == n_tangent_vecs_a and n_tangent_vecs_b == 1 or n_base_points == n_tangent_vecs_b and n_tangent_vecs_a == 1 or n_tangent_vecs_a == 1 and n_tangent_vecs_b == 1 or n_base_points == 1 and n_tangent_vecs_a == 1 or n_base_points == 1 and n_tangent_vecs_b == 1) if n_tangent_vecs_a == 1: tangent_vec_a = gs.tile( tangent_vec_a, (gs.maximum(n_base_points, n_tangent_vecs_b), 1, 1)) if n_tangent_vecs_b == 1: tangent_vec_b = gs.tile( tangent_vec_b, (gs.maximum(n_base_points, n_tangent_vecs_a), 1, 1)) if n_base_points == 1: base_point = gs.tile( base_point, (gs.maximum(n_tangent_vecs_a, n_tangent_vecs_b), 1, 1)) if power_affine == 1: inv_base_point = gs.linalg.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 = gs.linalg.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) inner_product = gs.to_ndarray(inner_product, to_ndim=2, axis=1) return inner_product
def test_exp_diagonal(self, dim, param, param_list): """Check that the diagonal x1 = ... = xn is totally geodesic.""" base_point = param * gs.ones(dim) initial_vectors = gs.transpose(gs.tile(param_list, (dim, 1))) result = self.metric(dim).exp(initial_vectors, base_point) expected = gs.squeeze(gs.transpose(gs.tile(result[..., 0], (dim, 1)))) return self.assertAllClose(expected, result)
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """ Inner product between two tangent vectors at a base point. """ tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2) tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2) n_tangent_vec_a = gs.shape(tangent_vec_a)[0] n_tangent_vec_b = gs.shape(tangent_vec_b)[0] inner_prod_mat = self.inner_product_matrix(base_point) inner_prod_mat = gs.to_ndarray(inner_prod_mat, to_ndim=3) n_mats = gs.shape(inner_prod_mat)[0] n_inner_prod = gs.maximum(n_tangent_vec_a, n_tangent_vec_b) n_inner_prod = gs.maximum(n_inner_prod, n_mats) n_tiles_a = gs.divide(n_inner_prod, n_tangent_vec_a) n_tiles_a = gs.cast(n_tiles_a, gs.int32) tangent_vec_a = gs.tile(tangent_vec_a, [n_tiles_a, 1]) n_tiles_b = gs.divide(n_inner_prod, n_tangent_vec_b) n_tiles_b = gs.cast(n_tiles_b, gs.int32) tangent_vec_b = gs.tile(tangent_vec_b, [n_tiles_b, 1]) n_tiles_mat = gs.divide(n_inner_prod, n_mats) n_tiles_mat = gs.cast(n_tiles_mat, gs.int32) inner_prod_mat = gs.tile(inner_prod_mat, [n_tiles_mat, 1, 1]) aux = gs.einsum('nj,njk->nk', tangent_vec_a, inner_prod_mat) inner_prod = gs.einsum('nk,nk->n', aux, tangent_vec_b) inner_prod = gs.to_ndarray(inner_prod, to_ndim=2, axis=1) assert gs.ndim(inner_prod) == 2, inner_prod.shape return inner_prod
def test_exp_vectorization_shape(self): n_samples = self.n_samples n = self.n p = self.p one_base_point = self.point_a one_tangent_vec = self.tangent_vector_1 n_base_points = gs.tile( gs.to_ndarray(self.point_a, to_ndim=3), (n_samples, 1, 1)) n_tangent_vecs = gs.tile( gs.to_ndarray(self.tangent_vector_2, to_ndim=3), (n_samples, 1, 1)) # With single tangent vec and base point result = self.metric.exp(one_tangent_vec, one_base_point) self.assertAllClose(gs.shape(result), (n, p)) # With n_samples tangent vecs and base points result = self.metric.exp(n_tangent_vecs, one_base_point) self.assertAllClose(gs.shape(result), (n_samples, n, p)) result = self.metric.exp(one_tangent_vec, n_base_points) self.assertAllClose(gs.shape(result), (n_samples, n, p))
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """ Inner product defined by the Riemannian metric at point base_point between tangent vectors tangent_vec_a and tangent_vec_b. """ tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2) tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2) n_tangent_vec_a, _ = tangent_vec_a.shape n_tangent_vec_b, _ = tangent_vec_b.shape inner_prod_mat = self.inner_product_matrix(base_point) inner_prod_mat = gs.to_ndarray(inner_prod_mat, to_ndim=3) n_mats, _, _ = inner_prod_mat.shape n_inner_prod = gs.maximum(n_tangent_vec_a, n_tangent_vec_b) n_inner_prod = gs.maximum(n_inner_prod, n_mats) n_tiles_a = gs.divide(n_inner_prod, n_tangent_vec_a) n_tiles_a = gs.cast(n_tiles_a, gs.int32) tangent_vec_a = gs.tile(tangent_vec_a, [n_tiles_a, 1]) n_tiles_b = gs.divide(n_inner_prod, n_tangent_vec_b) n_tiles_b = gs.cast(n_tiles_b, gs.int32) tangent_vec_b = gs.tile(tangent_vec_b, [n_tiles_b, 1]) n_tiles_mat = gs.divide(n_inner_prod, n_mats) n_tiles_mat = gs.cast(n_tiles_mat, gs.int32) inner_prod_mat = gs.tile(inner_prod_mat, [n_tiles_mat, 1, 1]) aux = gs.einsum('nj,njk->nk', tangent_vec_a, inner_prod_mat) inner_prod = gs.einsum('nk,nk->n', aux, tangent_vec_b) inner_prod = gs.to_ndarray(inner_prod, to_ndim=2, axis=1) assert gs.ndim(inner_prod) == 2, inner_prod.shape return inner_prod
def test_retraction_vectorization_shape(self): n_samples = self.n_samples n = self.n p = self.p one_point = self.point_a n_points = gs.tile( gs.to_ndarray(one_point, to_ndim=3), (n_samples, 1, 1)) one_tangent_vec = self.tangent_vector_1 n_tangent_vecs = gs.tile( gs.to_ndarray(self.tangent_vector_2, to_ndim=3), (n_samples, 1, 1)) result = self.metric.retraction(one_tangent_vec, one_point) self.assertAllClose(gs.shape(result), (n, p)) result = self.metric.retraction(n_tangent_vecs, one_point) self.assertAllClose(gs.shape(result), (n_samples, n, p)) result = self.metric.retraction(one_tangent_vec, n_points) self.assertAllClose(gs.shape(result), (n_samples, n, p)) result = self.metric.retraction(n_tangent_vecs, n_points) self.assertAllClose(gs.shape(result), (n_samples, n, p))
def left_exp_from_identity(self, tangent_vec): """ Riemannian exponential of a tangent vector 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. """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2) tangent_vec = self.group.regularize_tangent_vec_at_identity( tangent_vec=tangent_vec, metric=self) sqrt_inner_product_mat = gs.linalg.sqrtm( self.inner_product_mat_at_identity) mat = gs.transpose(sqrt_inner_product_mat, axes=(0, 2, 1)) n_tangent_vecs, _ = tangent_vec.shape n_mats, _, _ = mat.shape if n_mats == 1: mat = gs.tile(mat, (n_tangent_vecs, 1, 1)) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_mats, 1)) exp = gs.einsum('ni,nij->nj', tangent_vec, mat) exp = self.group.regularize(exp) return exp
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point): """Compute the Log-Euclidean inner product. Compute the inner product of tangent_vec_a and tangent_vec_b at point base_point using the log-Euclidean 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 : float """ tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=3) n_tangent_vecs_a, _, _ = tangent_vec_a.shape tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=3) n_tangent_vecs_b, _, _ = tangent_vec_b.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, _, _ = base_point.shape spd_space = self.space assert (n_tangent_vecs_a == n_tangent_vecs_b == n_base_points or n_tangent_vecs_a == n_tangent_vecs_b and n_base_points == 1 or n_base_points == n_tangent_vecs_a and n_tangent_vecs_b == 1 or n_base_points == n_tangent_vecs_b and n_tangent_vecs_a == 1 or n_tangent_vecs_a == 1 and n_tangent_vecs_b == 1 or n_base_points == 1 and n_tangent_vecs_a == 1 or n_base_points == 1 and n_tangent_vecs_b == 1) if n_tangent_vecs_a == 1: tangent_vec_a = gs.tile( tangent_vec_a, (gs.maximum(n_base_points, n_tangent_vecs_b), 1, 1)) if n_tangent_vecs_b == 1: tangent_vec_b = gs.tile( tangent_vec_b, (gs.maximum(n_base_points, n_tangent_vecs_a), 1, 1)) if n_base_points == 1: base_point = gs.tile( base_point, (gs.maximum(n_tangent_vecs_a, n_tangent_vecs_b), 1, 1)) modified_tangent_vec_a = spd_space.differential_log( tangent_vec_a, base_point) modified_tangent_vec_b = spd_space.differential_log( tangent_vec_b, base_point) product = gs.matmul(modified_tangent_vec_a, modified_tangent_vec_b) inner_product = gs.trace(product, axis1=1, axis2=2) inner_product = gs.to_ndarray(inner_product, to_ndim=2, axis=1) return inner_product
def log(self, point, base_point=None): """Compute Riemannian logarithm of a point from a base point. Parameters ---------- point : array-like, shape=[n_samples, dimension] Point in the group. base_point : array-like, shape=[n_samples, dimension], optional Point in the group, from which to compute the log, (the default is identity). Returns ------- log : array-like, shape=[n_samples, dimension] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ if base_point is None: base_point = self.group.identity base_point = self.group.regularize(base_point) if base_point is self.group.identity: return self.log_from_identity(point) point = self.group.regularize(point) n_points, _ = point.shape n_base_points, _ = base_point.shape if self.left_or_right == 'left': point_near_id = self.group.compose( self.group.inverse(base_point), point) else: point_near_id = self.group.compose( point, self.group.inverse(base_point)) log_from_id = self.log_from_identity(point_near_id) jacobian = self.group.jacobian_translation( base_point, left_or_right=self.left_or_right) n_logs, _ = log_from_id.shape n_jacobians, _, _ = jacobian.shape if n_logs == 1: log_from_id = gs.tile(log_from_id, (n_jacobians, 1)) if n_jacobians == 1: jacobian = gs.tile(jacobian, (n_logs, 1, 1)) log = gs.einsum( 'ij,ijk->ik', log_from_id, gs.transpose(jacobian, axes=(0, 2, 1))) assert gs.ndim(log) == 2 return log
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point): """Compute the Procrustes inner product. Compute the inner product of tangent_vec_a and tangent_vec_b at point base_point using the Procrustes 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 : float """ tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=3) n_tangent_vecs_a, _, _ = tangent_vec_a.shape tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=3) n_tangent_vecs_b, _, _ = tangent_vec_b.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, _, _ = base_point.shape assert (n_tangent_vecs_a == n_tangent_vecs_b == n_base_points or n_tangent_vecs_a == n_tangent_vecs_b and n_base_points == 1 or n_base_points == n_tangent_vecs_a and n_tangent_vecs_b == 1 or n_base_points == n_tangent_vecs_b and n_tangent_vecs_a == 1 or n_tangent_vecs_a == 1 and n_tangent_vecs_b == 1 or n_base_points == 1 and n_tangent_vecs_a == 1 or n_base_points == 1 and n_tangent_vecs_b == 1) if n_tangent_vecs_a == 1: tangent_vec_a = gs.tile( tangent_vec_a, (gs.maximum(n_base_points, n_tangent_vecs_b), 1, 1)) if n_tangent_vecs_b == 1: tangent_vec_b = gs.tile( tangent_vec_b, (gs.maximum(n_base_points, n_tangent_vecs_a), 1, 1)) if n_base_points == 1: base_point = gs.tile( base_point, (gs.maximum(n_tangent_vecs_a, n_tangent_vecs_b), 1, 1)) spd_space = self.space modified_tangent_vec_a =\ spd_space.inverse_differential_power(2, tangent_vec_a, base_point) product = gs.matmul(modified_tangent_vec_a, tangent_vec_b) result = gs.trace(product, axis1=1, axis2=2) / 2 return result
def exp(self, tangent_vec, base_point): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[n_samples, n, p] Tangent vector at a base point. base_point : array-like, shape=[n_samples, n, p] Point in the Stiefel manifold. Returns ------- exp : array-like, shape=[n_samples, n, p] Point in the Stiefel manifold equal to the Riemannian exponential of tangent_vec at the base point. """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, n, p = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) matrix_a = gs.einsum('nij, njk->nik', gs.transpose(base_point, axes=(0, 2, 1)), tangent_vec) matrix_k = (tangent_vec - gs.einsum('nij,njk->nik', base_point, matrix_a)) matrix_q, matrix_r = gs.linalg.qr(matrix_k) matrix_ar = gs.concatenate( [matrix_a, -gs.transpose(matrix_r, axes=(0, 2, 1))], axis=2) zeros = gs.zeros((gs.maximum(n_base_points, n_tangent_vecs), p, p)) matrix_rz = gs.concatenate([matrix_r, zeros], axis=2) block = gs.concatenate([matrix_ar, matrix_rz], axis=1) matrix_mn_e = gs.linalg.expm(block) exp = gs.einsum('nij,njk->nik', gs.concatenate([base_point, matrix_q], axis=2), matrix_mn_e[:, :, 0:p]) return exp
def exp(self, tangent_vec, base_point): """ Riemannian exponential of a tangent vector wrt to a base point. """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, n, p = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) matrix_a = gs.einsum( 'nij, njk->nik', gs.transpose(base_point, axes=(0, 2, 1)), tangent_vec) matrix_k = (tangent_vec - gs.einsum('nij,njk->nik', base_point, matrix_a)) matrix_q, matrix_r = gs.linalg.qr(matrix_k) matrix_ar = gs.concatenate( [matrix_a, -gs.transpose(matrix_r, axes=(0, 2, 1))], axis=2) zeros = gs.zeros( (gs.maximum(n_base_points, n_tangent_vecs), p, p)) matrix_rz = gs.concatenate( [matrix_r, zeros], axis=2) block = gs.concatenate([matrix_ar, matrix_rz], axis=1) matrix_mn_e = gs.linalg.expm(block) exp = gs.einsum( 'nij,njk->nik', gs.concatenate( [base_point, matrix_q], axis=2), matrix_mn_e[:, :, 0:p]) return exp
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 ndim = gs.maximum(gs.ndim(tangent_vec), gs.ndim(base_point)) tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, mat_dim, _ = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) if power_affine == 1: sqrt_base_point = gs.linalg.sqrtm(base_point) inv_sqrt_base_point = gs.linalg.inv(sqrt_base_point) 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 = gs.linalg.powerm(base_point, power_affine / 2) power_inv_sqrt_base_point = gs.linalg.inv(power_sqrt_base_point) exp = self._aux_exp(modified_tangent_vec, power_sqrt_base_point, power_inv_sqrt_base_point) exp = gs.linalg.powerm(exp, 1 / power_affine) if ndim == 2: return exp[0] return exp
def log(self, point, base_point): """Compute the affine-invariant logarithm map. Compute the Riemannian logarithm at point base_point, of point wrt the metric defined in inner_product. This gives a tangent vector at point base_point. Parameters ---------- point : array-like, shape=[n_samples, n, n] base_point : array-like, shape=[n_samples, n, n] Returns ------- log : array-like, shape=[n_samples, n, n] """ power_affine = self.power_affine ndim = gs.maximum(gs.ndim(point), gs.ndim(base_point)) point = gs.to_ndarray(point, to_ndim=3) n_points, _, _ = point.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, mat_dim, _ = base_point.shape assert (n_points == n_base_points or n_points == 1 or n_base_points == 1) if n_points == 1: point = gs.tile(point, (n_base_points, 1, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_points, 1, 1)) if power_affine == 1: sqrt_base_point = gs.linalg.sqrtm(base_point) inv_sqrt_base_point = gs.linalg.inv(sqrt_base_point) log = self._aux_log(point, sqrt_base_point, inv_sqrt_base_point) else: power_point = gs.linalg.powerm(point, power_affine) power_sqrt_base_point = gs.linalg.powerm( base_point, power_affine / 2) power_inv_sqrt_base_point = gs.linalg.inv(power_sqrt_base_point) log = self._aux_log( power_point, power_sqrt_base_point, power_inv_sqrt_base_point) log = self.space.inverse_differential_power(power_affine, log, base_point) if ndim == 2: return log[0] return log
def exp(self, tangent_vec, base_point=None): """Compute Riemannian exponential of tan. vector wrt to base point. Parameters ---------- tangent_vec : array-like, shape=[n_samples, dimension] Tangent vector at a base point. base_point : array-like, shape=[n_samples, dimension] Point in the group. Returns ------- exp : array-like, shape=[n_samples, dimension] Point in the group equal to the Riemannian exponential of tangent_vec at the base point. """ if base_point is None: base_point = self.group.identity base_point = self.group.regularize(base_point) if base_point is self.group.identity: return self.exp_from_identity(tangent_vec) tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2) assert gs.ndim(tangent_vec) == 2 n_tangent_vecs, _ = tangent_vec.shape n_base_points, _ = base_point.shape if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1)) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1)) jacobian = self.group.jacobian_translation( point=base_point, left_or_right=self.left_or_right) assert gs.ndim(jacobian) == 3 inv_jacobian = gs.linalg.inv(jacobian) inv_jacobian_transposed = gs.transpose(inv_jacobian, axes=(0, 2, 1)) tangent_vec_at_id = gs.einsum( 'ni,nij->nj', tangent_vec, inv_jacobian_transposed) exp_from_id = self.exp_from_identity(tangent_vec_at_id) if self.left_or_right == 'left': exp = self.group.compose(base_point, exp_from_id) else: exp = self.group.compose(exp_from_id, base_point) exp = self.group.regularize(exp) return exp
def jacobian_translation(self, point, left_or_right='left', point_type=None): """ Compute the jacobian matrix of the differential of the left/right translations from the identity to point in SE(n). """ if point_type is None: point_type = self.default_point_type assert left_or_right in ('left', 'right') dim = self.dimension rotations = self.rotations translations = self.translations dim_rotations = rotations.dimension dim_translations = translations.dimension point = self.regularize(point, point_type=point_type) if point_type == 'vector': n_points, _ = point.shape rot_vec = point[:, :dim_rotations] jacobian = gs.zeros((n_points, ) + (dim, ) * 2) jacobian_rot = self.rotations.jacobian_translation( point=rot_vec, left_or_right=left_or_right, point_type=point_type) block_zeros_1 = gs.zeros( (n_points, dim_rotations, dim_translations)) jacobian_block_line_1 = gs.concatenate( [jacobian_rot, block_zeros_1], axis=2) if left_or_right == 'left': rot_mat = self.rotations.matrix_from_rotation_vector(rot_vec) jacobian_trans = rot_mat block_zeros_2 = gs.zeros( (n_points, dim_translations, dim_rotations)) jacobian_block_line_2 = gs.concatenate( [block_zeros_2, jacobian_trans], axis=2) else: inv_skew_mat = -self.rotations.skew_matrix_from_vector(rot_vec) eye = gs.to_ndarray(gs.eye(self.n), to_ndim=3) eye = gs.tile(eye, [n_points, 1, 1]) jacobian_block_line_2 = gs.concatenate([inv_skew_mat, eye], axis=2) jacobian = gs.concatenate( [jacobian_block_line_1, jacobian_block_line_2], axis=1) assert gs.ndim(jacobian) == 3 elif point_type == 'matrix': raise NotImplementedError() return jacobian
def coefficients(ind_k): """Christoffel symbols for contravariant index ind_k.""" param_k = base_point[..., ind_k] param_sum = gs.sum(base_point, -1) c1 = ( 1 / gs.polygamma(1, param_k) / ( 1 / gs.polygamma(1, param_sum) - gs.sum(1 / gs.polygamma(1, base_point), -1) ) ) c2 = -c1 * gs.polygamma(2, param_sum) / gs.polygamma(1, param_sum) mat_ones = gs.ones((n_points, self.dim, self.dim)) mat_diag = from_vector_to_diagonal_matrix( -gs.polygamma(2, base_point) / gs.polygamma(1, base_point) ) arrays = [ gs.zeros((1, ind_k)), gs.ones((1, 1)), gs.zeros((1, self.dim - ind_k - 1)), ] vec_k = gs.tile(gs.hstack(arrays), (n_points, 1)) val_k = gs.polygamma(2, param_k) / gs.polygamma(1, param_k) vec_k = gs.einsum("i,ij->ij", val_k, vec_k) mat_k = from_vector_to_diagonal_matrix(vec_k) mat = ( gs.einsum("i,ijk->ijk", c2, mat_ones) - gs.einsum("i,ijk->ijk", c1, mat_diag) + mat_k ) return 1 / 2 * mat
def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether vector is tangent to the manifold at base_point. In what follows, the ellipsis ... indicates either nothing or any number n of elements, i.e. shape=[..., dim] means shape=[dim] or shape=[n, dim] for any n. All functions/methods of geomstats should work for any number of inputs. In the case of the function `is_tangent`, it means: for any number of input vectors. Parameters ---------- vector : array-like, shape=[..., dim] Vector. base_point : array-like, shape=[..., dim] Point on the manifold. Optional, default: None. atol : float Absolute tolerance threshold Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ # Perform operations to determine if vector is a tangent vector, # for example: is_tangent = gs.shape(vector)[-1] == self.dim if gs.ndim(vector) == 2: is_tangent = gs.tile([is_tangent], (vector.shape[0], )) return is_tangent
def belongs(self, point, atol=gs.atol): """Evaluate if the point belongs to the vector space. This method checks the shape of the input point. Parameters ---------- point : array-like, shape=[.., {dim, [n, n]}] Point to test. atol : float Unused here. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the space. """ if self.default_point_type == 'vector': point_shape = point.shape[-1:] minimal_ndim = 1 else: point_shape = point.shape[-2:] minimal_ndim = 2 belongs = point_shape == self.shape if point.ndim == minimal_ndim: return belongs return gs.tile(gs.array([belongs]), [point.shape[0]])
def random_tangent_vec_uniform(self, n_samples=1, base_point=None): if base_point is None: base_point = gs.eye(self.n) base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, _, _ = base_point.shape assert n_base_points == n_samples or n_base_points == 1 if n_base_points == 1: base_point = gs.tile(base_point, (n_samples, 1, 1)) sqrt_base_point = gs.linalg.sqrtm(base_point) tangent_vec_at_id = (2 * gs.random.rand(n_samples, self.n, self.n) - 1) tangent_vec_at_id = (tangent_vec_at_id + gs.transpose(tangent_vec_at_id, axes=(0, 2, 1))) tangent_vec = gs.matmul(sqrt_base_point, tangent_vec_at_id) tangent_vec = gs.matmul(tangent_vec, sqrt_base_point) return tangent_vec
def square_root_velocity_inverse(self, srv, starting_point): """Retrieve a curve from sqrt velocity rep and starting point. Parameters ---------- srv : starting_point : Returns ------- curve : """ if not isinstance(self.ambient_metric, EuclideanMetric): raise AssertionError('The square root velocity inverse is only ' 'implemented for dicretized curves embedded ' 'in a Euclidean space.') if gs.ndim(srv) != gs.ndim(starting_point): starting_point = gs.transpose(gs.tile(starting_point, (1, 1, 1)), axes=(1, 0, 2)) srv_shape = srv.shape srv = gs.to_ndarray(srv, to_ndim=3) n_curves, n_sampling_points_minus_one, n_coords = srv.shape srv = gs.reshape(srv, (n_curves * n_sampling_points_minus_one, n_coords)) srv_norm = self.ambient_metric.norm(srv) delta_points = 1 / n_sampling_points_minus_one * srv_norm * srv delta_points = gs.reshape(delta_points, srv_shape) curve = gs.concatenate((starting_point, delta_points), -2) curve = gs.cumsum(curve, -2) return curve
def belongs(self, point, atol=gs.atol): """Test if a point belongs to the hyperbolic space. Test if a point belongs to the hyperbolic space in its hyperboloid representation. Parameters ---------- point : array-like, shape=[..., dim] Point to be tested. atol : float, optional Tolerance at which to evaluate how close the squared norm is to the reference value. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Array of booleans indicating whether the corresponding points belong to the hyperbolic space. """ point_dim = point.shape[-1] if point_dim is not self.dim + 1: belongs = False if point_dim is self.dim and self.coords_type == "intrinsic": belongs = True if gs.ndim(point) == 2: belongs = gs.tile([belongs], (point.shape[0], )) return belongs return super(Hyperboloid, self).belongs(point, atol)
def closest_rotation_matrix(mat): """ Compute the closest rotation matrix of a given matrix mat, in terms of the Frobenius norm. """ mat = gs.to_ndarray(mat, to_ndim=3) n_mats, mat_dim_1, mat_dim_2 = mat.shape assert mat_dim_1 == mat_dim_2 if mat_dim_1 == 3: mat_unitary_u, diag_s, mat_unitary_v = gs.linalg.svd(mat) rot_mat = gs.matmul(mat_unitary_u, mat_unitary_v) mask = gs.where(gs.linalg.det(rot_mat) < 0) new_mat_diag_s = gs.tile(gs.diag([1, 1, -1]), len(mask)) rot_mat[mask] = gs.matmul( gs.matmul(mat_unitary_u[mask], new_mat_diag_s), mat_unitary_v[mask]) else: aux_mat = gs.matmul(gs.transpose(mat, axes=(0, 2, 1)), mat) inv_sqrt_mat = gs.zeros_like(mat) for i in range(n_mats): sym_mat = aux_mat[i] assert spd_matrices_space.is_symmetric(sym_mat) inv_sqrt_mat[i] = gs.linalg.inv(spd_matrices_space.sqrtm(sym_mat)) rot_mat = gs.matmul(mat, inv_sqrt_mat) assert rot_mat.ndim == 3 return rot_mat
def coefficients(ind_k): param_k = base_point[..., ind_k] param_sum = gs.sum(base_point, -1) c1 = 1 / gs.polygamma( 1, param_k) / (1 / gs.polygamma(1, param_sum) - gs.sum(1 / gs.polygamma(1, base_point), -1)) c2 = -c1 * gs.polygamma(2, param_sum) / gs.polygamma(1, param_sum) mat_ones = gs.ones((n_points, self.dim, self.dim)) mat_diag = from_vector_to_diagonal_matrix( -gs.polygamma(2, base_point) / gs.polygamma(1, base_point)) arrays = [ gs.zeros((1, ind_k)), gs.ones((1, 1)), gs.zeros((1, self.dim - ind_k - 1)) ] vec_k = gs.tile(gs.hstack(arrays), (n_points, 1)) val_k = gs.polygamma(2, param_k) / gs.polygamma(1, param_k) vec_k = gs.einsum('i,ij->ij', val_k, vec_k) mat_k = from_vector_to_diagonal_matrix(vec_k) mat = gs.einsum('i,ijk->ijk', c2, mat_ones)\ - gs.einsum('i,ijk->ijk', c1, mat_diag) + mat_k return 1 / 2 * mat
def belongs(self, point, atol=gs.atol): """Test if a point belongs to the hypersphere. This tests whether the point's squared norm in Euclidean space is 1. Parameters ---------- point : array-like, shape=[..., dim + 1] Point in Euclidean space. atol : float Tolerance at which to evaluate norm == 1. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the hypersphere. """ point_dim = gs.shape(point)[-1] if point_dim != self.dim + 1: if point_dim is self.dim: logging.warning( 'Use the extrinsic coordinates to ' 'represent points on the hypersphere.') belongs = False if gs.ndim(point) == 2: belongs = gs.tile([belongs], (point.shape[0],)) return belongs sq_norm = gs.sum(point ** 2, axis=-1) diff = gs.abs(sq_norm - 1) return gs.less_equal(diff, atol)
def belongs(self, point, tolerance=TOLERANCE): """Test if a point belongs to the hyperbolic space. Test if a point belongs to the hyperbolic space in its hyperboloid representation. Parameters ---------- point : array-like, shape=[..., dim] Point to be tested. tolerance : float, optional Tolerance at which to evaluate how close the squared norm is to the reference value. Optional, default: 1e-6. Returns ------- belongs : array-like, shape=[...,] Array of booleans indicating whether the corresponding points belong to the hyperbolic space. """ point_dim = point.shape[-1] if point_dim is not self.dim + 1: belongs = False if point_dim is self.dim and self.coords_type == 'intrinsic': belongs = True if gs.ndim(point) == 2: belongs = gs.tile([belongs], (point.shape[0], )) return belongs sq_norm = self.embedding_metric.squared_norm(point) euclidean_sq_norm = gs.sum(point**2, axis=-1) diff = gs.abs(sq_norm + 1) belongs = diff < tolerance * euclidean_sq_norm return belongs