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_at_identity(self, tangent_vec_a, tangent_vec_b): """ Inner product matrix at the tangent space at the identity. """ assert self.group.point_representation in ('vector', 'matrix') if self.group.point_representation == 'vector': tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2) tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2) inner_prod = gs.einsum('ij,ijk,ik->i', tangent_vec_a, self.inner_product_mat_at_identity, tangent_vec_b) inner_prod = gs.to_ndarray(inner_prod, to_ndim=2, axis=1) elif self.group.point_representation == 'matrix': logging.warning( 'Only the canonical inner product -Frobenius inner product-' ' is implemented for Lie groups whose elements are represented' ' by matrices.') tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=3) tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=3) aux_prod = gs.matmul(gs.transpose(tangent_vec_a, axes=(0, 2, 1)), tangent_vec_b) inner_prod = gs.trace(aux_prod) return inner_prod
def transform(self, X, y=None): """Project X on the principal components. Parameters ---------- X : array-like, shape=[..., n_features] Data, where n_samples is the number of samples and n_features is the number of features. y : Ignored (Compliance with scikit-learn interface) Returns ------- X_new : array-like, shape=[..., n_components] Projected data. """ tangent_vecs = self.metric.log(X, base_point=self.base_point_fit) if self.point_type == "matrix": if Matrices.is_symmetric(tangent_vecs).all(): X = SymmetricMatrices.to_vector(tangent_vecs) else: X = gs.reshape(tangent_vecs, (len(X), -1)) else: X = tangent_vecs X = X - self.mean_ X_transformed = gs.matmul(X, gs.transpose(self.components_)) return X_transformed
def convert_to_planar_coordinates(self, points): """Convert polar coordinates to spherical one.""" coords_r, coords_theta = self.convert_to_polar_coordinates(points) coords_x = coords_r * gs.cos(coords_theta) coords_y = coords_r * gs.sin(coords_theta) planar_coords = gs.transpose(gs.stack((coords_x, coords_y))) return planar_coords
def exp(self, tangent_vec, base_point=None): """ Riemannian exponential of a tangent vector wrt to a 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) n_tangent_vecs, _ = tangent_vec.shape n_base_points, _ = base_point.shape jacobian = self.group.jacobian_translation( point=base_point, left_or_right=self.left_or_right) assert jacobian.ndim == 3 inv_jacobian = gs.linalg.inv(jacobian) inv_jacobian_transposed = gs.transpose(inv_jacobian, axes=(0, 2, 1)) tangent_vec_at_id = gs.einsum('ij,ijk->ik', 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 process_function(return_dict, ip=ip, ep=ep): solution = solve_bvp( bvp, bc, t_int, initialize(ip, ep), fun_jac=fun_jac) solution_at_t = solution.sol(t) geodesic = solution_at_t[:self.dim, :] geod.append(gs.squeeze(gs.transpose(geodesic))) return_dict[0] = geod
def metric_matrix(self, base_point=None): """Compute the inner-product matrix. Compute the inner-product matrix of the Fisher information metric at the tangent space at base point. Parameters ---------- base_point : array-like, shape=[..., 2] Base point. Returns ------- mat : array-like, shape=[..., 2, 2] Inner-product matrix. """ if base_point is None: raise ValueError("A base point must be given to compute the " "metric matrix") base_point = gs.to_ndarray(base_point, to_ndim=2) kappa, gamma = base_point[:, 0], base_point[:, 1] mat_diag = gs.transpose( gs.array([gs.polygamma(1, kappa) - 1 / kappa, kappa / gamma**2])) mat = from_vector_to_diagonal_matrix(mat_diag) return gs.squeeze(mat)
def test_squared_dist_vectorization(self): n_samples = self.n_samples one_point_a = gs.array([0., 1.]) one_point_b = gs.array([2., 10.]) n_points_a = gs.array([ [2., 1.], [-2., -4.], [-5., 1.]]) n_points_b = gs.array([ [2., 10.], [8., -1.], [-3., 6.]]) result = self.metric.squared_dist(one_point_a, one_point_b) vec = one_point_a - one_point_b expected = gs.dot(vec, gs.transpose(vec)) self.assertAllClose(result, expected) result = self.metric.squared_dist(n_points_a, one_point_b) self.assertAllClose(gs.shape(result), (n_samples,)) result = self.metric.squared_dist(one_point_a, n_points_b) self.assertAllClose(gs.shape(result), (n_samples,)) result = self.metric.squared_dist(n_points_a, n_points_b) expected = gs.array([81., 109., 29.]) self.assertAllClose(gs.shape(result), (n_samples,)) self.assertAllClose(result, expected)
def belongs(self, point, tolerance=TOLERANCE): """ Check if an (n,n)-matrix is an orthogonal projector onto a subspace of rank k. """ point = gs.to_ndarray(point, to_ndim=3) n_points, n, k = point.shape if (n, k) != (self.n, self.k): return gs.array([[False]] * n_points) point_transpose = gs.transpose(point, axes=(0, 2, 1)) identity = gs.to_ndarray(gs.eye(k), to_ndim=3) identity = gs.tile(identity, (n_points, 1, 1)) diff = gs.einsum('nij,njk->nik', point_transpose, point) - identity diff_norm = gs.linalg.norm(diff, axis=(1, 2)) belongs = gs.less_equal(diff_norm, tolerance) belongs = gs.to_ndarray(belongs, to_ndim=1) belongs = gs.to_ndarray(belongs, to_ndim=2, axis=1) return belongs raise NotImplementedError( 'The Grassmann `belongs` is not implemented.' 'It shall test whether p*=p, p^2 = p and rank(p) = k.')
def inverse(self, point): """ Compute the group inverse in SE(3). Formula: (R, t)^{-1} = (R^{-1}, R^{-1}.(-t)) :param point: 6d vector element in SE(3) :returns inverse_point: 6d vector inverse of point """ rotations = self.rotations dim_rotations = rotations.dimension point = self.regularize(point) n_points, _ = point.shape rot_vec = point[:, :dim_rotations] translation = point[:, dim_rotations:] inverse_point = gs.zeros_like(point) inverse_rotation = -rot_vec inv_rot_mat = rotations.matrix_from_rotation_vector(inverse_rotation) inverse_translation = gs.zeros((n_points, self.n)) for i in range(n_points): inverse_translation[i] = gs.dot(-translation[i], gs.transpose(inv_rot_mat[i])) inverse_point[:, :dim_rotations] = inverse_rotation inverse_point[:, dim_rotations:] = inverse_translation inverse_point = self.regularize(inverse_point) return inverse_point
def test_inner_product(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector_1 = gs.array([ [1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) tangent_vector_2 = gs.array([ [1., 4., 3.], [5., 0., 0.], [3., 1., 1.]]) result = self.metric.inner_product( tangent_vector_1, tangent_vector_2, base_point=base_point) expected = gs.trace( gs.matmul( gs.transpose(tangent_vector_1), tangent_vector_2)) self.assertAllClose(result, expected)
def left_exp_from_identity(self, tangent_vec): """ Compute the *left* Riemannian exponential from the identity of the Lie group of tangent vector tangent_vec. The left Riemannian exponential has a special role since the left Riemannian exponential of the canonical metric parameterizes the points. Note: In the case where the method is called by a right-invariant metric, it used the left-invariant metric associated to the same inner-product at the identity. """ import geomstats.spd_matrices_space as spd_matrices_space 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 = spd_matrices_space.sqrtm( self.inner_product_mat_at_identity) mat = gs.transpose(sqrt_inner_product_mat, axes=(0, 2, 1)) exp = gs.matmul(tangent_vec, mat) exp = gs.squeeze(exp, axis=0) exp = self.group.regularize(exp) return exp
def random_tangent_vec_uniform(self, n_samples=1, base_point=None): """Define a uniform random sample of tangent vectors.""" 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 random_uniform(self, n_samples=1): """Define a log-uniform random sample of SPD matrices.""" mat = 2 * gs.random.rand(n_samples, self.n, self.n) - 1 spd_mat = self.embedding_manifold.exp( mat + gs.transpose(mat, axes=(0, 2, 1))) return spd_mat
def test_aux_differential_square_root_velocity(self): """Test differential of square root velocity transform. Check that its value at (curve, tangent_vec) coincides with the derivative at zero of the square root velocity transform of a path of curves starting at curve with initial derivative tangent_vec. """ dim = 3 n_sampling_points = 2000 sampling_times = gs.linspace(0.0, 1.0, n_sampling_points) curve_a = self.curve_fun_a(sampling_times) tangent_vec = gs.transpose( gs.tile(gs.linspace(1.0, 2.0, n_sampling_points), (dim, 1)) ) result = self.srv_metric_r3.aux_differential_square_root_velocity( tangent_vec, curve_a ) n_curves = 2000 times = gs.linspace(0.0, 1.0, n_curves) path_of_curves = curve_a + gs.einsum("i,jk->ijk", times, tangent_vec) srv_path = self.srv_metric_r3.square_root_velocity(path_of_curves) expected = n_curves * (srv_path[1] - srv_path[0]) self.assertAllClose(result, expected, atol=1e-3, rtol=1e-3)
def make_symmetric(matrix): """Make a matrix fully symmetric to avoid numerical issues.""" matrix = gs.to_ndarray(matrix, to_ndim=3) n_mats, m, n = matrix.shape assert m == n matrix = gs.to_ndarray(matrix, to_ndim=3) return (matrix + gs.transpose(matrix, axes=(0, 2, 1))) / 2
def test_horizontal_geodesic(self): """Test horizontal geodesic. Check that the time derivative of the geodesic is horizontal at all time. """ curve_b = gs.transpose( gs.stack( ( gs.zeros(self.n_sampling_points), gs.zeros(self.n_sampling_points), gs.linspace(1.0, 0.5, self.n_sampling_points), ) ) ) horizontal_geod_fun = self.quotient_srv_metric_r3.horizontal_geodesic( self.curve_a, curve_b ) n_times = 20 times = gs.linspace(0.0, 1.0, n_times) horizontal_geod = horizontal_geod_fun(times) velocity_vec = n_times * (horizontal_geod[1:] - horizontal_geod[:-1]) _, _, vertical_norms = self.quotient_srv_metric_r3.split_horizontal_vertical( velocity_vec, horizontal_geod[:-1] ) result = gs.sum(vertical_norms ** 2, axis=1) ** (1 / 2) expected = gs.zeros(n_times - 1) self.assertAllClose(result, expected, atol=1e-3)
def test_dist_vectorization(self): n_samples = self.n_samples one_point_a = gs.array([0., 1.]) one_point_b = gs.array([2., 10.]) n_points_a = gs.array([[2., 1.], [-2., -4.], [-5., 1.]]) n_points_b = gs.array([[2., 10.], [8., -1.], [-3., 6.]]) result = self.metric.dist(one_point_a, one_point_b) vec = one_point_a - one_point_b expected = gs.sqrt(gs.dot(vec, gs.transpose(vec))) expected = helper.to_scalar(expected) self.assertAllClose(result, expected) result = self.metric.dist(n_points_a, one_point_b) self.assertAllClose(gs.shape(result), (n_samples, 1)) result = self.metric.dist(one_point_a, n_points_b) self.assertAllClose(gs.shape(result), (n_samples, 1)) result = self.metric.dist(n_points_a, n_points_b) expected = gs.array([[9.], [gs.sqrt(109.)], [gs.sqrt(29.)]]) self.assertAllClose(gs.shape(result), (n_samples, 1)) self.assertAllClose(result, expected)
def tangent_natural_to_standard(self, vec, base_point): """Convert tangent vector from natural coordinates to standard coordinates. The change of variable is symmetric. Parameters ---------- base_point : array-like, shape=[..., 2] Point of the Gamma manifold, given in natural coordinates. vec : array-like, shape=[..., 2] Tangent vector at base_point, given in natural coordinates. Returns ------- vec : array-like, shape=[..., 2] Tangent vector at base_point, given in standard coordinates. """ vec = gs.to_ndarray(vec, to_ndim=2) base_point = gs.broadcast_to(base_point, vec.shape) n_points = base_point.shape[0] kappa, scale = ( base_point[..., 0], base_point[..., 1], ) jac = gs.array([[gs.ones(n_points), gs.zeros(n_points)], [1 / scale, -kappa / scale**2]]) jac = gs.transpose(jac, [2, 0, 1]) vec = gs.einsum("...jk,...k->...j", jac, vec) return gs.squeeze(vec)
def random_uniform(self, n_samples=1, tol=1e-6): """Sample in SE(n) from the uniform distribution. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. tol : unused Returns ------- samples : array-like, shape=[..., n + 1, n + 1] Sample in SE(n). """ random_translation = self.translations.random_uniform(n_samples) random_rotation = self.rotations.random_uniform(n_samples) random_rotation = gs.to_ndarray(random_rotation, to_ndim=3) random_translation = gs.to_ndarray(random_translation, to_ndim=2) random_translation = gs.transpose( gs.to_ndarray(random_translation, to_ndim=3, axis=1), (0, 2, 1)) random_point = gs.concatenate((random_rotation, random_translation), axis=2) last_line = gs.zeros((n_samples, 1, self.n + 1)) random_point = gs.concatenate((random_point, last_line), axis=1) random_point = gs.assignment(random_point, 1, (-1, -1), axis=0) if gs.shape(random_point)[0] == 1: random_point = gs.squeeze(random_point, axis=0) return random_point
def basis_representation(self, matrix_representation): """Calculate the coefficients of given matrix in the basis. Compute a 1d-array that corresponds to the input matrix in the basis representation. Parameters ---------- matrix_representation : array-like, shape=[..., n, n] Matrix. Returns ------- basis_representation : array-like, shape=[..., dim] Representation in the basis. """ if self.n == 2: return matrix_representation[..., 1, 0][..., None] if self.n == 3: vec = gs.stack([ matrix_representation[..., 2, 1], matrix_representation[..., 0, 2], matrix_representation[..., 1, 0]]) return gs.transpose(vec) return gs.triu_to_vec(matrix_representation, k=1)
def square_root_velocity_inverse(self, srv, starting_point): """ Retreive a curve from its square root velocity representation and starting point. """ if not isinstance(self.embedding_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(np.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.embedding_metric.norm(srv) delta_points = 1 / n_sampling_points_minus_one * srv_norm * srv delta_points = gs.reshape(delta_points, srv_shape) curve = np.concatenate((starting_point, delta_points), -2) curve = np.cumsum(curve, -2) return curve
def inner_product_matrix(self, base_point=None): """ Inner product matrix at the tangent space at a base point. """ if self.group.point_representation == '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 base_point = self.group.regularize(base_point) jacobian = self.group.jacobian_translation( point=base_point, left_or_right=self.left_or_right) assert jacobian.ndim == 3 inv_jacobian = gs.linalg.inv(jacobian) inv_jacobian_transposed = gs.transpose(inv_jacobian, axes=(0, 2, 1)) inner_product_mat_at_id = self.inner_product_mat_at_identity inner_product_mat_at_id = gs.to_ndarray(inner_product_mat_at_id, to_ndim=3) metric_mat = gs.matmul(inv_jacobian_transposed, inner_product_mat_at_id) metric_mat = gs.matmul(metric_mat, inv_jacobian) return metric_mat
def projection(self, mat): """ Project a matrix on SO(n), using the Frobenius norm. """ # TODO(nina): projection when the point_type is not 'matrix'? 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 == self.n if self.n == 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.nonzero(gs.linalg.det(rot_mat) < 0) diag = gs.array([1, 1, -1]) new_mat_diag_s = gs.tile(gs.diag(diag), 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 gs.ndim(rot_mat) == 3 return rot_mat
def log(self, point, base_point=None): """ Riemannian logarithm of a point wrt a 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) log = gs.einsum('ij,ijk->ik', log_from_id, gs.transpose(jacobian, axes=(0, 2, 1))) assert log.ndim == 2 return log
def closest_neighbor_index(self, point, neighbors): """Closest neighbor of point among neighbors. Parameters ---------- point : array-like, shape=[..., dim] Point. neighbors : array-like, shape=[n_neighbors, dim] Neighbors. Returns ------- closest_neighbor_index : int Index of closest neighbor. """ n_points = point.shape[0] if gs.ndim(point) == gs.ndim( neighbors) else 1 n_neighbors = neighbors.shape[0] if n_points > 1 and n_neighbors > 1: neighbors = gs.repeat(neighbors, n_points, axis=0) point = gs.concatenate([point for _ in range(n_neighbors)]) closest_neighbor_index = gs.argmin( gs.transpose( gs.reshape(self.dist(point, neighbors), (n_neighbors, n_points)), ), axis=1, ) if n_points == 1: return closest_neighbor_index[0] return closest_neighbor_index
def grad(y_pred, y_true, metric): """Closed-form for the gradient of the loss function. Parameters ---------- y_pred : array-like, shape=[..., dim] Prediction. y_true : array-like, shape=[..., dim] Ground-truth. metric : RiemannianMetric Metric. Returns ------- loss_grad : array-like, shape=[...,] Gradient of the loss. """ tangent_vec = metric.log(base_point=y_pred, point=y_true) grad_vec = - 2. * tangent_vec inner_prod_mat = metric.metric_matrix(base_point=y_pred) is_vectorized = inner_prod_mat.ndim == 3 axes = (0, 2, 1) if is_vectorized else (1, 0) loss_grad = gs.einsum( '...i,...ij->...i', grad_vec, gs.transpose(inner_prod_mat, axes=axes)) return loss_grad
def test_inner_product_vectorization(self): n_samples = 3 one_point_a = gs.array([[-1., 0.]]) one_point_b = gs.array([[1.0, 0.]]) n_points_a = gs.array([[-1., 0.], [1., 0.], [2., math.sqrt(3)]]) n_points_b = gs.array([[2., -math.sqrt(3)], [4.0, math.sqrt(15)], [-4.0, math.sqrt(15)]]) result = self.metric.inner_product(one_point_a, one_point_b) expected = gs.dot(one_point_a, gs.transpose(one_point_b)) expected -= (2 * one_point_a[:, self.time_like_dim] * one_point_b[:, self.time_like_dim]) expected = helper.to_scalar(expected) result_no = self.metric.inner_product(n_points_a, one_point_b) result_on = self.metric.inner_product(one_point_a, n_points_b) result_nn = self.metric.inner_product(n_points_a, n_points_b) self.assertAllClose(result, expected) self.assertAllClose(gs.shape(result_no), (n_samples, 1)) self.assertAllClose(gs.shape(result_on), (n_samples, 1)) self.assertAllClose(gs.shape(result_nn), (n_samples, 1)) with self.session(): expected = np.zeros(n_samples) for i in range(n_samples): expected[i] = gs.eval(gs.dot(n_points_a[i], n_points_b[i])) expected[i] -= (2 * gs.eval(n_points_a[i, self.time_like_dim]) * gs.eval(n_points_b[i, self.time_like_dim])) expected = helper.to_scalar(gs.array(expected)) self.assertAllClose(result_nn, expected)
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 = gs.einsum( '...,...i->...i', 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 group_exp(self, tangent_vec, base_point=None): """ Compute the group exponential at point base_point of tangent vector tangent_vec. """ if base_point is None: base_point = self.identity base_point = self.regularize(base_point) if base_point is self.identity: return self.group_exp_from_identity(tangent_vec) tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2) jacobian = self.jacobian_translation(point=base_point, left_or_right='left') inv_jacobian = gs.linalg.inv(jacobian) tangent_vec_at_id = gs.einsum( 'ij,ijk->ik', tangent_vec, gs.transpose(inv_jacobian, axes=(0, 2, 1))) group_exp_from_identity = self.group_exp_from_identity( tangent_vec=tangent_vec_at_id) group_exp = self.compose(base_point, group_exp_from_identity) group_exp = self.regularize(group_exp) return group_exp