def test_belongs(self): base_point_square = gs.zeros((self.n, self.n)) base_point_nonsquare = gs.zeros((self.m, self.n)) result = self.space.belongs(base_point_square) expected = True self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_square) expected = False self.assertAllClose(result, expected) result = self.space.belongs(base_point_nonsquare) expected = False self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_nonsquare) expected = True self.assertAllClose(result, expected) result = self.space.belongs(gs.zeros((2, 2, 3))) self.assertFalse(gs.all(result)) result = self.space.belongs(gs.zeros((2, 3, 3))) self.assertTrue(gs.all(result))
def test_product_distance_extrinsic_representation(self): point_type = 'extrinsic' point_a_intrinsic = gs.array([0.01, 0.0]) point_b_intrinsic = gs.array([0.0, 0.0]) hyperbolic_space = Hyperbolic(dimension=2, point_type=point_type) point_a = hyperbolic_space.intrinsic_to_extrinsic_coords( point_a_intrinsic) point_b = hyperbolic_space.intrinsic_to_extrinsic_coords( point_b_intrinsic) duplicate_point_a = gs.zeros((2, ) + point_a.shape) duplicate_point_a[0] = point_a duplicate_point_a[1] = point_a duplicate_point_b = gs.zeros((2, ) + point_b.shape) duplicate_point_b[0] = point_b duplicate_point_b[1] = point_b single_disk = PoincarePolydisk(n_disks=1, point_type=point_type) two_disks = PoincarePolydisk(n_disks=2, point_type=point_type) distance_single_disk = single_disk.metric.dist(point_a, point_b) distance_two_disks = two_disks.metric.dist(duplicate_point_a, duplicate_point_b) result = distance_two_disks expected = 3**0.5 * distance_single_disk self.assertAllClose(result, expected)
def initialize(point_0, point_1, init="polynomial"): """Initialize the solution of the boundary value problem.""" if init == "polynomial": _, curve, velocity = self._approx_geodesic_bvp( point_0, point_1, n_times=n_steps) return gs.vstack((curve.T, velocity.T)) lin_init = gs.zeros([2 * self.dim, n_steps]) lin_init[:self.dim, :] = gs.transpose( gs.linspace(point_0, point_1, n_steps)) lin_init[self.dim:, :-1] = n_steps * (lin_init[:self.dim, 1:] - lin_init[:self.dim, :-1]) lin_init[self.dim:, -1] = lin_init[self.dim:, -2] return lin_init
def test_variance(self): points = gs.array([ [1., 2.], [2., 3.], [3., 4.], [4., 5.]]) weights = gs.array([1., 2., 1., 2.]) base_point = gs.zeros(2) result = self.metric.variance(points, weights, base_point) # we expect the average of the points' sq norms. expected = (1 * 5. + 2 * 13. + 1 * 25. + 2 * 41.) / 6. expected = helper.to_scalar(expected) self.assertAllClose(result, expected)
def exp(self, tangent_vec, base_point): """Compute Riemannian exponential of tan. vector wrt to 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 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 tangent_spherical_to_extrinsic(self, tangent_vec_spherical, base_point_spherical): """Convert tangent vector from spherical to extrinsic coordinates. Convert from the spherical coordinates in the hypersphere to the extrinsic coordinates in Euclidean space for a tangent vector. Only implemented in dimension 2. Parameters ---------- tangent_vec_spherical : array-like, shape=[..., dim] Tangent vector to the sphere, in spherical coordinates. base_point_spherical : array-like, shape=[..., dim] Point on the sphere, in spherical coordinates. Returns ------- tangent_vec_extrinsic : array-like, shape=[..., dim + 1] Tangent vector to the sphere, at base point, in extrinsic coordinates in Euclidean space. """ if self.dim != 2: raise NotImplementedError( 'The conversion from spherical coordinates' ' to extrinsic coordinates is implemented' ' only in dimension 2.') n_samples = base_point_spherical.shape[0] theta = base_point_spherical[:, 0] phi = base_point_spherical[:, 1] zeros = gs.zeros(n_samples) jac = gs.concatenate([ gs.array([[[ gs.cos(theta[i]) * gs.cos(phi[i]), -gs.sin(theta[i]) * gs.sin(phi[i]) ], [ gs.cos(theta[i]) * gs.sin(phi[i]), gs.sin(theta[i]) * gs.cos(phi[i]) ], [-gs.sin(theta[i]), zeros[i]]]]) for i in range(n_samples) ], axis=0) tangent_vec_extrinsic = gs.einsum('...ij,...j->...i', jac, tangent_vec_spherical) return tangent_vec_extrinsic
def inner_product_shape_test_data(self): space = NFoldManifold(SpecialOrthogonal(3), 2) n_samples = 4 point = gs.stack([gs.eye(3)] * space.n_copies * n_samples) point = gs.reshape(point, (n_samples, *space.shape)) tangent_vec = space.to_tangent(gs.zeros((n_samples, *space.shape)), point) smoke_data = [ dict(space=space, n_samples=4, point=point, tangent_vec=tangent_vec) ] return self.generate_tests(smoke_data)
def test_horizontal_geodesic(self, n_sampling_points, curve_a, n_times): """Test horizontal geodesic. Check that the time derivative of the geodesic is horizontal at all time. """ curve_b = gs.transpose( gs.stack(( gs.zeros(n_sampling_points), gs.zeros(n_sampling_points), gs.linspace(1.0, 0.5, n_sampling_points), ))) quotient_srv_metric_r3 = DiscreteCurves( ambient_manifold=r3).quotient_square_root_velocity_metric horizontal_geod_fun = quotient_srv_metric_r3.horizontal_geodesic( curve_a, curve_b) 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 = 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 __init__(self, n): dim = int(n * (n + 1) / 2) super(SpecialEuclideanMatrixLieAlgebra, self).__init__(dim, n) self.skew = SkewSymmetricMatrices(n) basis = homogeneous_representation(self.skew.basis, gs.zeros((self.skew.dim, n)), (self.skew.dim, n + 1, n + 1), 0.) basis = list(basis) for row in gs.arange(n): basis.append(gs.array_from_sparse([(row, n)], [1.], (n + 1, n + 1))) self.basis = gs.stack(basis)
def from_vector_to_diagonal_matrix(vector, num_diag=0): """Create diagonal matrices from rows of a matrix. Parameters ---------- vector : array-like, shape=[m, n] num_diag : int number of diagonal in result matrix. If 0, the result matrix is a diagonal matrix; if positive, the result matrix has an upper-right non-zero diagonal; if negative, the result matrix has a lower-left non-zero diagonal. Optional, Default: 0. Returns ------- diagonals : array-like, shape=[m, n, n] 3-dimensional array where the `i`-th n-by-n array `diagonals[i, :, :]` is a diagonal matrix containing the `i`-th row of `vector`. """ num_columns = gs.shape(vector)[-1] identity = gs.eye(num_columns) identity = gs.cast(identity, vector.dtype) diagonals = gs.einsum("...i,ij->...ij", vector, identity) diagonals = gs.to_ndarray(diagonals, to_ndim=3) num_lines = diagonals.shape[0] if num_diag > 0: left_zeros = gs.zeros((num_lines, num_columns, num_diag)) lower_zeros = gs.zeros((num_lines, num_diag, num_columns + num_diag)) diagonals = gs.concatenate((left_zeros, diagonals), axis=2) diagonals = gs.concatenate((diagonals, lower_zeros), axis=1) elif num_diag < 0: num_diag = gs.abs(num_diag) right_zeros = gs.zeros((num_lines, num_columns, num_diag)) upper_zeros = gs.zeros((num_lines, num_diag, num_columns + num_diag)) diagonals = gs.concatenate((diagonals, right_zeros), axis=2) diagonals = gs.concatenate((upper_zeros, diagonals), axis=1) return gs.squeeze(diagonals) if gs.ndim(vector) == 1 else diagonals
def log(self, curve, base_curve): """Compute Riemannian logarithm of a curve wrt a base curve. Parameters ---------- curve : base_curve : Returns ------- log : """ if not isinstance(self.ambient_metric, EuclideanMetric): raise AssertionError('The logarithm map is only implemented ' 'for dicretized curves embedded in a ' 'Euclidean space.') curve = gs.to_ndarray(curve, to_ndim=3) base_curve = gs.to_ndarray(base_curve, to_ndim=3) n_curves, n_sampling_points, n_coords = curve.shape curve_srv = self.square_root_velocity(curve) base_curve_srv = self.square_root_velocity(base_curve) base_curve_velocity = (n_sampling_points - 1) * (base_curve[:, 1:, :] - base_curve[:, :-1, :]) base_curve_velocity_norm = self.pointwise_norm(base_curve_velocity, base_curve[:, :-1, :]) inner_prod = self.pointwise_inner_product(curve_srv - base_curve_srv, base_curve_velocity, base_curve[:, :-1, :]) coef_1 = gs.sqrt(base_curve_velocity_norm) coef_2 = 1 / base_curve_velocity_norm**(3 / 2) * inner_prod term_1 = gs.einsum('ij,ijk->ijk', coef_1, curve_srv - base_curve_srv) term_2 = gs.einsum('ij,ijk->ijk', coef_2, base_curve_velocity) log_derivative = term_1 + term_2 log_starting_points = self.ambient_metric.log( point=curve[:, 0, :], base_point=base_curve[:, 0, :]) log_starting_points = gs.transpose( gs.tile(log_starting_points, (1, 1, 1)), (1, 0, 2)) log_cumsum = gs.hstack( [gs.zeros((n_curves, 1, n_coords)), gs.cumsum(log_derivative, -2)]) log = log_starting_points + 1 / (n_sampling_points - 1) * log_cumsum return log
def test_variance_euclidean(self): points = gs.array([ [1., 2.], [2., 3.], [3., 4.], [4., 5.]]) weights = gs.array([1., 2., 1., 2.]) base_point = gs.zeros(2) result = variance( points, weights=weights, base_point=base_point, metric=self.euclidean.metric) # we expect the average of the points' sq norms. expected = gs.array((1 * 5. + 2 * 13. + 1 * 25. + 2 * 41.) / 6.) self.assertAllClose(result, expected)
def belongs(self, mat, tolerance=TOLERANCE): """ Check if a matrix belongs to the manifold of symmetric positive definite matrices. """ mat = gs.to_ndarray(mat, to_ndim=3) n_mats, mat_dim, _ = mat.shape mask_is_symmetric = is_symmetric(mat, tolerance=tolerance) eigenvalues = gs.zeros((n_mats, mat_dim)) eigenvalues[mask_is_symmetric] = gs.linalg.eigvalsh( mat[mask_is_symmetric]) mask_pos_eigenvalues = gs.all(eigenvalues > 0) return mask_is_symmetric & mask_pos_eigenvalues
def get_identity(self, point_type=None): """Get the identity of the group. Parameters ---------- point_type : str, {'vector', 'matrix'} The point_type of the returned value. Optional, default: self.default_point_type Returns ------- identity : array-like, shape=[n] """ identity = gs.zeros(self.dim) return identity
def symmetric_matrix_from_vector(self, vec): """Convert a vector into a symmetric matrix.""" vec = gs.to_ndarray(vec, to_ndim=2) _, vec_dim = vec.shape mat_dim = int((gs.sqrt(8 * vec_dim + 1) - 1) / 2) mat = gs.zeros((mat_dim, ) * 2) lower_triangle_indices = gs.tril_indices(mat_dim) diag_indices = gs.diag_indices(mat_dim) mat[lower_triangle_indices] = 2 * vec mat[diag_indices] = vec mat = self.embedding_manifold.make_symmetric(mat) return mat
def __init__(self, n): assert isinstance(n, int) and n > 1 if n is not 3: raise NotImplementedError('Only SE(3) is implemented.') self.n = n self.dimension = int((n * (n - 1)) / 2 + n) super(SpecialEuclideanGroup, self).__init__(dimension=self.dimension, identity=gs.zeros(self.dimension)) # TODO(xxx): keep the names rotations and translations here? self.rotations = SpecialOrthogonalGroup(n=n) self.translations = EuclideanSpace(dimension=n) self.point_representation = 'vector' if n == 3 else 'matrix'
def _create_basis(self): """Create the canonical basis.""" n = self.n basis = homogeneous_representation( self.skew.basis, gs.zeros((self.skew.dim, n)), (self.skew.dim, n + 1, n + 1), 0.0, ) basis = list(basis) for row in gs.arange(n): basis.append( gs.array_from_sparse([(row, n)], [1.0], (n + 1, n + 1))) return gs.stack(basis)
def predict(self, data): """Predict the closest cluster for each sample in X. Parameters ---------- data : array-like, shape=[n_samples, dim,] Training data, where n_samples is the number of samples and dim is the number of dimensions. Returns ------- labels : array-like, shape=[n_samples,] Index of the cluster each sample belongs to. """ labels = gs.zeros(len(data)) for point_index, point_value in enumerate(data): distances = gs.zeros(len(self.cluster_centers_)) for cluster_index, cluster_value in enumerate(self.cluster_centers_): distances[cluster_index] = self.metric.dist(point_value, cluster_value) labels[point_index] = gs.argmin(distances) return labels
def lie_bracket_test_data(self): group = SpecialOrthogonal(3, point_type="vector") smoke_data = [ dict( tangent_vec_a=gs.array([0.0, 0.0, -1.0]), tangent_vec_b=gs.array([0.0, 0.0, -1.0]), base_point=group.identity, expected=gs.zeros(3), ), dict( tangent_vec_a=gs.array([0.0, 0.0, 1.0]), tangent_vec_b=gs.array([0.0, 1.0, 0.0]), base_point=group.identity, expected=gs.array([-1.0, 0.0, 0.0]), ), dict( tangent_vec_a=gs.array([[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]), tangent_vec_b=gs.array([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0]]), base_point=gs.array([group.identity, group.identity]), expected=gs.array([gs.zeros(3), gs.array([-1.0, 0.0, 0.0])]), ), ] return self.generate_tests(smoke_data)
def exponential_matrix(self, rot_vec): """ Compute the exponential of the rotation matrix represented by rot_vec. """ rot_vec = self.rotations.regularize(rot_vec) n_rot_vecs, _ = rot_vec.shape angle = gs.linalg.norm(rot_vec, axis=1) angle = gs.to_ndarray(angle, to_ndim=2, axis=1) skew_rot_vec = so_group.skew_matrix_from_vector(rot_vec) coef_1 = gs.empty_like(angle) coef_2 = gs.empty_like(coef_1) mask_0 = gs.equal(angle, 0) mask_0 = gs.squeeze(mask_0, axis=1) mask_close_to_0 = gs.isclose(angle, 0) mask_close_to_0 = gs.squeeze(mask_close_to_0, axis=1) mask_else = ~mask_0 & ~mask_close_to_0 coef_1[mask_close_to_0] = (1. / 2. - angle[mask_close_to_0] ** 2 / 24.) coef_2[mask_close_to_0] = (1. / 6. - angle[mask_close_to_0] ** 3 / 120.) # TODO(nina): check if the discountinuity as 0 is expected. coef_1[mask_0] = 0 coef_2[mask_0] = 0 coef_1[mask_else] = (angle[mask_else] ** (-2) * (1. - gs.cos(angle[mask_else]))) coef_2[mask_else] = (angle[mask_else] ** (-2) * (1. - (gs.sin(angle[mask_else]) / angle[mask_else]))) term_1 = gs.zeros((n_rot_vecs, self.n, self.n)) term_2 = gs.zeros_like(term_1) for i in range(n_rot_vecs): term_1[i] = gs.eye(self.n) + skew_rot_vec[i] * coef_1[i] term_2[i] = gs.matmul(skew_rot_vec[i], skew_rot_vec[i]) * coef_2[i] exponential_mat = term_1 + term_2 assert exponential_mat.ndim == 3 return exponential_mat
def random_uniform(self, n_samples=1, point_type=None): """Sample in SE(n) with the uniform distribution. Parameters ---------- n_samples: int, optional default: 1 point_type: str, {'vector', 'matrix'}, optional default: self.default_point_type Returns ------- random_point: array-like, shape=[n_samples, {dim, [n + 1, n + 1]}] An array of random elements in SE(n) having the given point_type. """ if point_type is None: point_type = self.default_point_type random_translation = self.translations.random_uniform(n_samples) if point_type == 'vector': random_rot_vec = self.rotations.random_uniform( n_samples, point_type=point_type) return gs.concatenate([random_rot_vec, random_translation], axis=-1) if point_type == 'matrix': random_rotation = self.rotations.random_uniform( n_samples, point_type=point_type) 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 raise ValueError('Invalid point_type, expected \'vector\' or ' '\'matrix\'.')
def is_centered_test_data(self): random_data = [ dict( k_landmarks=4, m_ambient=3, point=gs.ones((4, 3)), expected=gs.array(False), ), dict( k_landmarks=4, m_ambient=3, point=gs.zeros((4, 3)), expected=gs.array(True), ), ] return self.generate_tests([], random_data)
def __init__(self, n_meridians=40, n_circles_latitude=None, points=None): if n_circles_latitude is None: n_circles_latitude = max(n_meridians / 2, 4) u, v = np.mgrid[0:2 * gs.pi:n_meridians * 1j, 0:gs.pi:n_circles_latitude * 1j] self.center = gs.zeros(3) self.radius = 1 self.sphere_x = self.center[0] + self.radius * gs.cos(u) * gs.sin(v) self.sphere_y = self.center[1] + self.radius * gs.sin(u) * gs.sin(v) self.sphere_z = self.center[2] + self.radius * gs.cos(v) self.points = [] if points is not None: self.add_points(points)
def torsion(self, base_point): """Compute torsion tensor associated with the Levi-Civita connection. The torsion tensor associated with the Levi-Civita connection is zero. Parameters ---------- base_point: array-like, shape=[n_samples, dimension] or shape=[1, dimension] Returns ------- torsion: array-like, shape=[dimension, dimension, dimension] """ torsion = gs.zeros((self.dimension, ) * 3) return torsion
def __init__(self, n_meridians=40, n_circles_latitude=None, points=None): if n_circles_latitude is None: n_circles_latitude = max(n_meridians / 2, 4) u, v = gs.meshgrid(gs.arange(0, 2 * gs.pi, 2 * gs.pi / n_meridians), gs.arange(0, gs.pi, gs.pi / n_circles_latitude)) self.center = gs.zeros(3) self.radius = 1 self.sphere_x = self.center[0] + self.radius * gs.cos(u) * gs.sin(v) self.sphere_y = self.center[1] + self.radius * gs.sin(u) * gs.sin(v) self.sphere_z = self.center[2] + self.radius * gs.cos(v) self.points = [] if points is not None: self.add_points(points)
def get_identity(self, point_type='vector'): """Get the identity of the group. Parameters ---------- point_type : str, Point_type of the returned value. Unused here. Returns ------- identity : array-like, shape=[3,] """ identity = gs.zeros(self.dim) if point_type == 'matrix': identity = gs.eye(self.n) return identity
def baker_campbell_hausdorff(self, matrix_a, matrix_b, order=2): """Calculate the Baker-Campbell-Hausdorff approximation of given order. The implementation is based on [CM2009a]_ with the pre-computed constants taken from [CM2009b]_. Our coefficients are truncated to enable us to calculate BCH up to order 15. This represents Z = log(exp(X)exp(Y)) as an infinite linear combination of the form Z = sum z_i e_i where z_i are rational numbers and e_i are iterated Lie brackets starting with e_1 = X, e_2 = Y, each e_i is given by some i',i'': e_i = [e_i', e_i'']. Parameters ---------- matrix_a, matrix_b : array-like, shape=[n_sample, n, n] order : int The order to which the approximation is calculated. Note that this is NOT the same as using only e_i with i < order. References ---------- .. [CM2009a] F. Casas and A. Murua. An efficient algorithm for computing the Baker–Campbell–Hausdorff series and some of its applications. Journal of Mathematical Physics 50, 2009 .. [CM2009b] http://www.ehu.eus/ccwmuura/research/bchHall20.dat """ if order > 15: raise NotImplementedError("BCH is not implemented for order > 15.") number_of_hom_degree = gs.array( [2, 1, 2, 3, 6, 9, 18, 30, 56, 99, 186, 335, 630, 1161, 2182]) n_terms = gs.sum(number_of_hom_degree[:order]) ei = gs.zeros((n_terms, self.n, self.n)) ei[0] = matrix_a ei[1] = matrix_b result = matrix_a + matrix_b for i in gs.arange(2, n_terms): i_p = BCH_COEFFICIENTS[i, 1] - 1 i_pp = BCH_COEFFICIENTS[i, 2] - 1 ei[i] = self.lie_bracket(ei[i_p], ei[i_pp]) result += (BCH_COEFFICIENTS[i, 3] / float(BCH_COEFFICIENTS[i, 4]) * ei[i]) return result
def matrix_from_tait_bryan_angles_extrinsic_zyx(self, tait_bryan_angles): """ Convert a rotation given in terms of the tait bryan angles, [angle_1, angle_2, angle_3] in extrinsic (fixed) coordinate system in order zyx, into a rotation matrix. rot_mat = X(angle_1).Y(angle_2).Z(angle_3) where: - X(angle_1) is a rotation of angle angle_1 around axis x. - Y(angle_2) is a rotation of angle angle_2 around axis y. - Z(angle_3) is a rotation of angle angle_3 around axis z. """ assert self.n == 3, ('The Tait-Bryan angles representation' ' does not exist' ' for rotations in %d dimensions.' % self.n) tait_bryan_angles = gs.to_ndarray(tait_bryan_angles, to_ndim=2) n_tait_bryan_angles, _ = tait_bryan_angles.shape rot_mat = gs.zeros((n_tait_bryan_angles, ) + (self.n, ) * 2) angle_1 = tait_bryan_angles[:, 0] angle_2 = tait_bryan_angles[:, 1] angle_3 = tait_bryan_angles[:, 2] for i in range(n_tait_bryan_angles): cos_angle_1 = gs.cos(angle_1[i]) sin_angle_1 = gs.sin(angle_1[i]) cos_angle_2 = gs.cos(angle_2[i]) sin_angle_2 = gs.sin(angle_2[i]) cos_angle_3 = gs.cos(angle_3[i]) sin_angle_3 = gs.sin(angle_3[i]) column_1 = [[cos_angle_2 * cos_angle_3], [(cos_angle_1 * sin_angle_3 + cos_angle_3 * sin_angle_1 * sin_angle_2)], [(sin_angle_1 * sin_angle_3 - cos_angle_1 * cos_angle_3 * sin_angle_2)]] column_2 = [[-cos_angle_2 * sin_angle_3], [(cos_angle_1 * cos_angle_3 - sin_angle_1 * sin_angle_2 * sin_angle_3)], [(cos_angle_3 * sin_angle_1 + cos_angle_1 * sin_angle_2 * sin_angle_3)]] column_3 = [[sin_angle_2], [-cos_angle_2 * sin_angle_1], [cos_angle_1 * cos_angle_2]] rot_mat[i] = gs.hstack((column_1, column_2, column_3)) return rot_mat
def sample(self, n_samples): """Generate samples for SPD manifold""" if isinstance(self.manifold.metric, SPDMetricLogEuclidean): sym_matrix = self.manifold.logm(self.mean) mean_euclidean = gs.hstack(( gs.diagonal(sym_matrix)[None, :], gs.sqrt(2.0) * gs.triu_to_vec(sym_matrix, k=1)[None, :], ))[0] _samples = self.samples_sym(mean_euclidean, self.cov, n_samples) else: samples_sym = self.samples_sym(gs.zeros(self.manifold.dim), self.cov, n_samples) mean_half = self.manifold.powerm(self.mean, 0.5) _samples = Matrices.mul(mean_half, samples_sym, mean_half) return self.manifold.expm(_samples)