def test_belongs(self): theta = gs.pi / 3 point_1 = gs.array( [ [gs.cos(theta), -gs.sin(theta), 2.0], [gs.sin(theta), gs.cos(theta), 3.0], [0.0, 0.0, 1.0], ] ) result = self.group.belongs(point_1) self.assertTrue(result) point_2 = gs.array( [ [gs.cos(theta), -gs.sin(theta), 2.0], [gs.sin(theta), gs.cos(theta), 3.0], [0.0, 0.0, 0.0], ] ) result = self.group.belongs(point_2) self.assertFalse(result) point = gs.array([point_1, point_2]) expected = gs.array([True, False]) result = self.group.belongs(point) self.assertAllClose(result, expected) point = point_1[0] result = self.group.belongs(point) self.assertFalse(result) point = gs.zeros((2, 3)) result = self.group.belongs(point) self.assertFalse(result) point = gs.zeros((2, 2, 3)) result = self.group.belongs(point) self.assertFalse(gs.all(result)) self.assertAllClose(result.shape, (2,))
def extrinsic_to_intrinsic_coords(self, point_extrinsic): """Convert from extrinsic to intrinsic coordinates. Parameters ---------- point_extrinsic : array-like, shape=[..., dim + 1] Point in the embedded manifold in extrinsic coordinates, i. e. in the coordinates of the embedding manifold. Returns ------- point_intrinsic : array-like, shape=[..., dim] Point in intrinsic coordinates. """ belong_point = self.belongs(point_extrinsic) if not gs.all(belong_point): raise NameError("Point that does not belong to the hyperboloid " "found") return\ Hyperbolic.change_coordinates_system(point_extrinsic, 'extrinsic', 'intrinsic')
def test_log_is_tangent(self, connection_args, space, point, base_point, is_tangent_atol): """Check that the connection logarithm gives a tangent vector. Parameters ---------- connection_args : tuple Arguments to pass to constructor of the connection. space : Manifold Manifold where connection is defined. point : array-like Point on the manifold. base_point : array-like Point on the manifold. is_tangent_atol : float Absolute tolerance for the is_tangent function. """ connection = self.connection(*connection_args) log = connection.log(gs.array(point), gs.array(base_point)) result = gs.all( space.is_tangent(log, gs.array(base_point), is_tangent_atol)) self.assertAllClose(result, gs.array(True))
def belongs(self, point): """Evaluate if a point belongs to the manifold of Dirichlet distributions. Check that point defines parameters for a Dirichlet distributions, i.e. belongs to the positive quadrant of the Euclidean space. Parameters ---------- point : array-like, shape=[..., dim] Point to be checked. Returns ------- belongs : array-like, shape=[...,] Boolean indicating whether point represents a Dirichlet distribution. """ point_dim = point.shape[-1] belongs = point_dim == self.dim belongs = gs.logical_and( belongs, gs.all(gs.greater(point, 0.), axis=-1)) return belongs
def permute(self, graph_to_permute, permutation): r"""Permutation action applied to graph observation. Parameters ---------- graph_to_permute : array-like, shape=[..., n, n] Input graphs to be permuted. permutation: array-like, shape=[..., n] Node permutations where in position i we have the value j meaning the node i should be permuted with node j. Returns ------- graphs_permuted : array-like, shape=[..., n, n] Graphs permuted. """ nodes = self.nodes single_graph = len(graph_to_permute.shape) < 3 if single_graph: graph_to_permute = [graph_to_permute] permutation = [permutation] result = [] for i, p in enumerate(permutation): if gs.all(gs.array(nodes) == gs.array(p)): result.append(graph_to_permute[i]) else: gtype = graph_to_permute[i].dtype permutation_matrix = gs.array_from_sparse( data=gs.ones(nodes, dtype=gtype), indices=list(zip(list(range(nodes)), p)), target_shape=(nodes, nodes), ) result.append( self.adjmat.mul( permutation_matrix, graph_to_permute[i], gs.transpose(permutation_matrix), )) return result[0] if single_graph else gs.array(result)
def test_is_diagonal(self): base_point = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.is_diagonal(base_point) expected = False self.assertAllClose(result, expected) diagonal = gs.eye(3) result = self.space.is_diagonal(diagonal) self.assertTrue(result) base_point = gs.stack([base_point, diagonal]) result = self.space.is_diagonal(base_point) expected = gs.array([False, True]) self.assertAllClose(result, expected) base_point = gs.stack([diagonal] * 2) result = self.space.is_diagonal(base_point) self.assertTrue(gs.all(result)) base_point = gs.reshape(gs.arange(6), (2, 3)) result = self.space.is_diagonal(base_point) self.assertTrue(~result)
def equal(mat_a, mat_b, atol=TOLERANCE): """Test if matrices a and b are close. Parameters ---------- mat_a : array-like, shape=[..., dim1, dim2] Matrix. mat_b : array-like, shape=[..., dim2, dim3] Matrix. atol : float Tolerance. Optional, default: 1e-5. Returns ------- eq : array-like, shape=[...,] Boolean evaluating if the matrices are close. """ is_vectorized = \ (gs.ndim(gs.array(mat_a)) == 3) or (gs.ndim(gs.array(mat_b)) == 3) axes = (1, 2) if is_vectorized else (0, 1) return gs.all(gs.isclose(mat_a, mat_b, atol=atol), axes)
def spherical_to_extrinsic(self, point_spherical): """Convert point from spherical to extrinsic coordinates. Convert from the spherical coordinates in the hypersphere to the extrinsic coordinates in Euclidean space. Spherical coordinates are defined from the north pole, i.e. that angles [0., 0.] correspond to point [0., 0., 1.]. Only implemented in dimension 2. Parameters ---------- point_spherical : array-like, shape=[..., dim] Point on the sphere, in spherical coordinates. Returns ------- point_extrinsic : array_like, shape=[..., dim + 1] Point on the sphere, 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." ) theta = point_spherical[..., 0] phi = point_spherical[..., 1] point_extrinsic = gs.stack( [gs.sin(theta) * gs.cos(phi), gs.sin(theta) * gs.sin(phi), gs.cos(theta)], axis=-1, ) if not gs.all(self.belongs(point_extrinsic)): raise ValueError("Points do not belong to the manifold.") return point_extrinsic
def vector_from_symmetric_matrix(self, mat): """ Convert the symmetric part of a symmetric matrix into a vector. """ mat = gs.to_ndarray(mat, to_ndim=3) assert gs.all(self.embedding_manifold.is_symmetric(mat)) mat = self.embedding_manifold.make_symmetric(mat) _, mat_dim, _ = mat.shape vec_dim = int(mat_dim * (mat_dim + 1) / 2) vec = gs.zeros(vec_dim) idx = 0 for i in range(mat_dim): for j in range(i + 1): if i == j: vec[idx] = mat[j, j] else: vec[idx] = mat[j, i] idx += 1 return vec
def test_geodesic_and_belongs(self): n_geodesic_points = 10 initial_point = self.space.random_uniform(2) vector = gs.array([[2.0, 0.0, -1.0, -2.0, 1.0]] * 2) initial_tangent_vec = self.space.to_tangent(vector=vector, base_point=initial_point) geodesic = self.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) t = gs.linspace(start=0.0, stop=1.0, num=n_geodesic_points) points = geodesic(t) result = gs.stack([self.space.belongs(pt) for pt in points]) self.assertTrue(gs.all(result)) initial_point = initial_point[0] initial_tangent_vec = initial_tangent_vec[0] geodesic = self.metric.geodesic( initial_point=initial_point, initial_tangent_vec=initial_tangent_vec) points = geodesic(t) result = self.space.belongs(points) expected = gs.array(n_geodesic_points * [True]) self.assertAllClose(expected, result)
def belongs(self, point, point_type=None): """Test if a point belongs to the manifold. Parameters ---------- point : array-like, shape=[..., {dim, [dim_2, dim_2]}] Point. point_type : str, {'vector', 'matrix'} Representation of point. Optional, default: None. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if the point belongs to the manifold. """ if point_type is None: point_type = self.default_point_type geomstats.errors.check_parameter_accepted_values( point_type, 'point_type', ['vector', 'matrix']) if point_type == 'vector': intrinsic = self.metric.is_intrinsic(point) belongs = self._iterate_over_manifolds('belongs', {'point': point}, intrinsic) belongs = gs.stack(belongs, axis=1) else: belongs = gs.stack([ space.belongs(point[:, i]) for i, space in enumerate(self.manifolds) ], axis=1) belongs = gs.all(belongs, axis=1) belongs = gs.to_ndarray(belongs, to_ndim=2, axis=1) return belongs
def belongs(self, point, point_type=None): """Test if a point belongs to the manifold. Parameters ---------- point : array-like, shape=[n_samples, dim] or shape=[n_samples, dim_2, dim_2] Point. point_type : str, {'vector', 'matrix'} Representation of point. Returns ------- belongs : array-like, shape=[n_samples, 1] Array of booleans evaluating if the corresponding points belong to the manifold. """ if point_type is None: point_type = self.default_point_type if point_type == 'vector': point = gs.to_ndarray(point, to_ndim=2) intrinsic = self.metric._is_intrinsic(point) belongs = self._iterate_over_manifolds('belongs', {'point': point}, intrinsic) belongs = gs.hstack(belongs) elif point_type == 'matrix': point = gs.to_ndarray(point, to_ndim=3) belongs = gs.stack([ space.belongs(point[:, i]) for i, space in enumerate(self.manifolds) ], axis=1) belongs = gs.all(belongs, axis=1) belongs = gs.to_ndarray(belongs, to_ndim=2, axis=1) return belongs
def spherical_to_extrinsic(self, point_spherical): """Convert point from spherical to extrinsic coordinates. Convert from the spherical coordinates in the hypersphere to the extrinsic coordinates in Euclidean space. Only implemented in dimension 2. Parameters ---------- point_spherical : array-like, shape=[..., dim] Point on the sphere, in spherical coordinates. Returns ------- point_extrinsic : array_like, shape=[..., dim + 1] Point on the sphere, 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.') theta = point_spherical[..., 0] phi = point_spherical[..., 1] point_extrinsic = gs.stack([ gs.sin(theta) * gs.cos(phi), gs.sin(theta) * gs.sin(phi), gs.cos(theta) ], axis=-1) if not gs.all(self.belongs(point_extrinsic)): raise ValueError('Points do not belong to the manifold.') return point_extrinsic
def test_kendall_sectional_curvature(self): """Sectional curvature of Kendall shape space is larger than 1. The sectional curvature always increase by taking the quotient in a Riemannian submersion. Thus, it should larger in kendall shape space thane the sectional curvature of the pre-shape space which is 1 as it a hypersphere. The sectional curvature is computed here with the generic directional_curvature and sectional curvature methods. """ space = self.space metric = self.shape_metric n_samples = 4 * self.k_landmarks * self.m_ambient base_point = self.space.random_point(1) vec_a = gs.random.rand(n_samples, self.k_landmarks, self.m_ambient) tg_vec_a = space.to_tangent(space.center(vec_a), base_point) hor_a = space.horizontal_projection(tg_vec_a, base_point) vec_b = gs.random.rand(n_samples, self.k_landmarks, self.m_ambient) tg_vec_b = space.to_tangent(space.center(vec_b), base_point) hor_b = space.horizontal_projection(tg_vec_b, base_point) tidal_force = metric.directional_curvature(hor_a, hor_b, base_point) numerator = metric.inner_product(tidal_force, hor_a, base_point) denominator = ( metric.inner_product(hor_a, hor_a, base_point) * metric.inner_product(hor_b, hor_b, base_point) - metric.inner_product(hor_a, hor_b, base_point) ** 2 ) condition = ~gs.isclose(denominator, 0.0) kappa = numerator[condition] / denominator[condition] kappa_direct = metric.sectional_curvature(hor_a, hor_b, base_point)[condition] self.assertAllClose(kappa, kappa_direct) result = kappa > 1.0 - 1e-12 self.assertTrue(gs.all(result))
def test_parallel_transport(self): metric = self.group.left_canonical_metric tan_a = self.tangent_vec tan_b = self.group.to_tangent( gs.random.rand(self.n_samples, self.group.n + 1, self.group.n + 1), self.point) end_point = metric.exp(tan_b, self.point) def is_isometry(tan_a, trans_a, basepoint, endpoint): is_tangent = self.group.is_tangent(trans_a, endpoint, atol=1e-6) is_equinormal = gs.isclose(metric.norm(trans_a, endpoint), metric.norm(tan_a, basepoint)) return gs.logical_and(is_tangent, is_equinormal) transported = metric.parallel_transport(tan_a, tan_b, self.point) result = is_isometry(tan_a, transported, self.point, end_point) expected_end_point = metric.exp(tan_b, self.point) self.assertTrue(gs.all(result)) self.assertAllClose(end_point, expected_end_point) new_tan_b = metric.log(self.point, end_point) result_vec = metric.parallel_transport(transported, new_tan_b, end_point) self.assertAllClose(result_vec, tan_a)
def belongs(self, point, atol=gs.atol): """Test if a point belongs to the manifold. Parameters ---------- point : array-like, shape=[..., {dim, [n_manifolds, dim_each]}] Point. atol : float, Tolerance. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if the point belongs to the manifold. """ point_type = self.default_point_type if point_type == "vector": intrinsic = self.metric.is_intrinsic(point) belongs = self._iterate_over_manifolds("belongs", { "point": point, "atol": atol }, intrinsic) belongs = gs.stack(belongs, axis=-1) else: belongs = gs.stack( [ space.belongs(point[..., i, :], atol) for i, space in enumerate(self.manifolds) ], axis=-1, ) belongs = gs.all(belongs, axis=-1) return belongs
def fit(self, X, y): """Fit Wrapped Gaussian process regression model. The Wrapped Gaussian process is fit through the following steps: - Compute the tangent dataset using the prior - Fit a Gaussian process regression on the tangent dataset - Store the resulting euclidean Gaussian process Parameters ---------- X : array-like of shape (n_samples, n_features) or list of object Feature vectors or other representations of training data. y : array-like of shape (n_samples,) or (n_samples, n_targets) Target values. The target must belongs to the manifold space Returns ------- self : object WrappedGaussianProcessRegressor class instance. """ if not gs.all(self.space.belongs(y)): raise AttributeError( "The target values must belongs to the given space") # compute the tangent dataset using the prior tangent_y = self._get_tangent_targets(X, y) # fit a gpr on the tangent dataset self._euclidean_gpr.fit(X, tangent_y) # update the attributes of the wgpr using the new attributes of the gpr self.__dict__.update(self._euclidean_gpr.__dict__) self.y_train_ = y self.tangent_y_train_ = tangent_y # = self._euclidean_gpr.y_train_ return self
def is_vertical(self, tangent_vec, base_point, atol=gs.atol): """Evaluate if the tangent vector is vertical at base_point. Parameters ---------- tangent_vec : array-like, shape=[..., {ambient_dim, [n, n]}] Tangent vector. base_point : array-like, shape=[..., {ambient_dim, [n, n]}] Point on the manifold. Optional, default: None. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_vertical : bool Boolean denoting if tangent vector is vertical. """ return gs.all(gs.isclose(tangent_vec, self.vertical_projection( tangent_vec, base_point), atol=atol), axis=(-2, -1))
def belongs(self, point, point_type=None): """Check if the point belongs to the manifold. Parameters ---------- point point_type : str, {'vector', 'matrix'} Returns ------- belongs: array-like, shape=[n_samples, 1] """ if point_type is None: point_type = self.default_point_type assert point_type in ['vector', 'matrix'] if point_type == 'vector': point = gs.to_ndarray(point, to_ndim=2) else: point = gs.to_ndarray(point, to_ndim=3) n_manifolds = self.n_manifolds belongs = gs.empty((point.shape[0], n_manifolds)) cum_dim = 0 # FIXME: this only works if the points are in intrinsic representation for i in range(n_manifolds): manifold_i = self.manifolds[i] cum_dim_next = cum_dim + manifold_i.dimension point_i = point[:, cum_dim:cum_dim_next] belongs_i = manifold_i.belongs(point_i) belongs[:, i] = belongs_i cum_dim = cum_dim_next belongs = gs.all(belongs, axis=1) belongs = gs.to_ndarray(belongs, to_ndim=2) return belongs
def belongs(self, mat, atol=gs.atol): r"""Check if the matrix belongs to the space. Parameters ---------- mat : array-like, shape=[..., n, n] Matrix to be checked. atol : float Tolerance. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Boolean denoting if mat is an SPD matrix. """ is_symmetric = self.sym.belongs(mat, atol) eigvalues = gs.linalg.eigvalsh(mat) is_semipositive = gs.all(eigvalues > -atol, axis=-1) is_rankk = gs.sum(gs.where(eigvalues < atol, 0, 1), axis=-1) == self.rank belongs = gs.logical_and(gs.logical_and(is_symmetric, is_semipositive), is_rankk) return belongs
def test_to_tangent_is_tangent_in_ambient_space(self, space_args, vector, base_point, is_tangent_atol): """Check that tangent vectors are in ambient space's tangent space. This projects a vector to the tangent space of the manifold, and then checks that tangent vector belongs to ambient space's tangent space. Parameters ---------- space_args : tuple Arguments to pass to constructor of the manifold. vector : array-like Vector to be projected on the tangent space at base_point. base_point : array-like Point on the manifold. is_tangent_atol : float Absolute tolerance for the is_tangent function. """ space = self.space(*space_args) tangent_vec = space.to_tangent(gs.array(vector), gs.array(base_point)) result = gs.all( space.ambient_space.is_tangent(tangent_vec, is_tangent_atol)) self.assertAllClose(result, gs.array(True))
def to_vector(mat): """Convert a symmetric matrix into a vector. Parameters ---------- mat : array-like, shape=[..., n, n] Matrix. Returns ------- vec : array-like, shape=[..., n(n+1)/2] Vector. """ if not gs.all(Matrices.is_symmetric(mat)): logging.warning('non-symmetric matrix encountered.') mat = Matrices.to_symmetric(mat) _, dim, _ = mat.shape indices_i, indices_j = gs.triu_indices(dim) vec = [] for i, j in zip(indices_i, indices_j): vec.append(mat[:, i, j]) vec = gs.stack(vec, axis=1) return vec
def add_points(self, points): assert gs.all(S1.belongs(points)) if not isinstance(points, list): points = points.tolist() self.points.extend(points)
def test_random_and_belongs(self): """Test of random point sampling method.""" mat = self.space.random_point(5) result = self.space.belongs(mat) self.assertTrue(gs.all(result))
def add_points(self, points): assert gs.all(H2.belongs(points)) points = self.convert_to_klein_coordinates(points) if not isinstance(points, list): points = points.tolist() self.points.extend(points)
def test_random_von_mises_fisher_belongs(self, dim, n_samples): space = self.space(dim) result = space.belongs(space.random_von_mises_fisher(n_samples=n_samples)) self.assertAllClose(gs.all(result), gs.array(True))
def test_riemannian_submersion(self, n, mat): bundle = self.bundle(n) point = bundle.riemannian_submersion(mat) result = gs.all(bundle.belongs(point)) self.assertTrue(result)
def test_cholesky_factor_belongs(self, n, mat): result = SPDMatrices(n).cholesky_factor(gs.array(mat)) self.assertAllClose( gs.all(PositiveLowerTriangularMatrices(n).belongs(result)), True )
def test_to_center_is_centered_vectorization(self): point = gs.ones((self.n_samples, self.k_landmarks, self.m_ambient)) point = self.space.center(point) result = gs.all(self.space.is_centered(point)) self.assertTrue(result)
def add_points(self, points): if not gs.all(S2.belongs(points)): raise ValueError('Points do not belong to the sphere.') if not isinstance(points, list): points = list(points) self.points.extend(points)