Source code for geomstats.geometry.base

"""Abstract classes for manifolds.

Lead authors: Nicolas Guigui and Nina Miolane.
"""

import abc
import math

import geomstats.backend as gs
from geomstats.geometry.complex_manifold import ComplexManifold
from geomstats.geometry.manifold import Manifold
from geomstats.geometry.pullback_metric import PullbackMetric
from geomstats.vectorization import get_batch_shape


[docs] class VectorSpace(Manifold, abc.ABC): """Abstract class for vector spaces. Parameters ---------- shape : tuple Shape of the elements of the vector space. The dimension is the product of these values by default. """ def __init__(self, shape, dim=None, **kwargs): if dim is None: dim = math.prod(shape) super().__init__(dim=dim, shape=shape, **kwargs) self._basis = None
[docs] 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=[.., *point_shape] Point to test. atol : float Unused here. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the space. """ belongs = self.shape == point.shape[-self.point_ndim :] shape = point.shape[: -self.point_ndim] if belongs: return gs.ones(shape, dtype=bool) return gs.zeros(shape, dtype=bool)
[docs] def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether the vector is tangent at base_point. Tangent vectors are identified with points of the vector space so this checks the shape of the input vector. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point in the vector space. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : array-like, shape=[...,] Boolean denoting if vector is a tangent vector at the base point. """ belongs = self.belongs(vector, atol) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(belongs, base_point.shape[: -self.point_ndim]) return belongs
[docs] def to_tangent(self, vector, base_point=None): """Project a vector to a tangent space of the vector space. This method is for compatibility and returns vector. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point in the vector space Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vector at base point. """ tangent_vec = self.projection(vector) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(tangent_vec, base_point.shape) return tangent_vec
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample in the vector space with a uniform distribution in a box. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Side of hypercube support of the uniform distribution. Optional, default: 1.0 Returns ------- point : array-like, shape=[..., dim] Sample. """ size = (self.dim,) if n_samples != 1: size = (n_samples,) + size return bound * (gs.random.rand(*size) - 0.5) * 2
[docs] def random_tangent_vec(self, base_point=None, n_samples=1): """Generate random tangent vec. This method is not recommended for statistical purposes, as the tangent vectors generated are not drawn from a distribution related to the Riemannian metric. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. base_point : array-like, shape={[n_samples, *point_shape], [*point_shape,]} Point. Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vec at base point. """ if base_point is None: return self.random_point(n_samples) if ( n_samples > 1 and base_point.ndim > self.point_ndim and n_samples != base_point.shape[0] ): raise ValueError( "The number of base points must be the same as the " "number of samples, when the number of base points is different from 1." ) if n_samples == 1 and base_point.ndim > self.point_ndim: n_samples = base_point.shape[0] return self.random_point(n_samples)
@property def basis(self): """Basis of the vector space.""" if self._basis is None: self._basis = self._create_basis() return self._basis @abc.abstractmethod def _create_basis(self): """Create a canonical basis."""
[docs] class MatrixVectorSpace(VectorSpace, abc.ABC): """A matrix vector space."""
[docs] @abc.abstractmethod def basis_representation(self, matrix_representation): """Compute the coefficients of matrices in the given basis. This takes a matrix (the matrix representation of a point) and transforms it into its corresponding vector representation (the coefficients wrt a given basis). Previously, this method was called `to_vector`. `basis_representation` makes it more clear that the vector representation depends on the chosen basis. Parameters ---------- matrix_representation : array-like, shape=[..., *point_shape] Matrix. Returns ------- basis_representation : array-like, shape=[..., dim] Coefficients in the basis. """ raise NotImplementedError("basis_representation not implemented.")
[docs] def matrix_representation(self, basis_representation): """Compute the matrix representation for the given basis coefficients. This takes a vector representation of a point (the coefficients wrt a given basis) and creates the corresponding matrix representation. Parameters ---------- basis_representation : array-like, shape=[..., dim] Coefficients in the basis. Returns ------- matrix_representation : array-like, shape=[..., *point_shape] Matrix. """ return gs.einsum("...i,ijk ->...jk", basis_representation, self.basis)
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample in the vector space with a uniform distribution in a box. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Side of hypercube support of the uniform distribution. Optional, default: 1.0 Returns ------- point : array-like, shape=[..., *point_shape] Sample. """ return self.matrix_representation(super().random_point(n_samples, bound))
[docs] class ComplexVectorSpace(ComplexManifold, abc.ABC): """Abstract class for complex vector spaces. Parameters ---------- shape : tuple Shape of the elements of the vector space. The dimension is the product of these values by default. """ def __init__(self, shape, dim=None, **kwargs): if dim is None: dim = math.prod(shape) super().__init__(shape=shape, dim=dim, **kwargs)
[docs] 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=[.., *point_shape] Point to test. atol : float Unused here. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the space. """ belongs = self.shape == point.shape[-self.point_ndim :] shape = point.shape[: -self.point_ndim] if belongs: return gs.ones(shape, dtype=bool) return gs.zeros(shape, dtype=bool)
[docs] def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether the vector is tangent at base_point. Tangent vectors are identified with points of the vector space so this checks the shape of the input vector. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point in the vector space. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ belongs = self.belongs(vector, atol) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(belongs, base_point.shape[: -self.point_ndim]) return belongs
[docs] def to_tangent(self, vector, base_point=None): """Project a vector to a tangent space of the vector space. This method is for compatibility and returns vector. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point in the vector space Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vector at base point. """ tangent_vec = self.projection(vector) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(tangent_vec, base_point.shape) return tangent_vec
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample in the complex vector space with a uniform distribution in a box. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Side of hypercube support of the uniform distribution. Optional, default: 1.0 Returns ------- point : array-like, shape=[..., *point_shape] Sample. """ size = self.shape if n_samples != 1: size = (n_samples,) + self.shape point = bound * ( gs.random.rand(*size, dtype=gs.get_default_cdtype()) - 0.5 - 0.5j ) return point
[docs] class ComplexMatrixVectorSpace(ComplexVectorSpace): """A matrix vector space."""
[docs] class LevelSet(Manifold, abc.ABC): """Class for manifolds embedded in a vector space by a submersion. Parameters ---------- intrinsic : bool Coordinates type. """ def __init__(self, intrinsic=False, shape=None, **kwargs): self.embedding_space = self._define_embedding_space() if shape is None: shape = self.embedding_space.shape super().__init__(intrinsic=intrinsic, shape=shape, **kwargs) @abc.abstractmethod def _define_embedding_space(self): """Define embedding space of the manifold. Returns ------- embedding_space : Manifold Instance of Manifold. """
[docs] @abc.abstractmethod def submersion(self, point): r"""Submersion that defines the manifold. :math:`\mathrm{submersion}(x)=0` defines the manifold. Parameters ---------- point : array-like, shape=[..., *point_shape] Returns ------- submersed_point : array-like """
[docs] @abc.abstractmethod def tangent_submersion(self, vector, point): """Tangent submersion. Parameters ---------- vector : array-like, shape=[..., *point_shape] point : array-like, shape=[..., *point_shape] Returns ------- submersed_vector : array-like """
[docs] def belongs(self, point, atol=gs.atol): """Evaluate if a point belongs to the manifold. Parameters ---------- point : array-like, shape=[..., *point_shape] Point to evaluate. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the manifold. """ belongs = self.embedding_space.belongs(point, atol) if not gs.any(belongs): return belongs submersed_point = self.submersion(point) n_batch = gs.ndim(point) - len(self.shape) axis = tuple(range(-len(submersed_point.shape) + n_batch, 0)) if gs.is_complex(submersed_point): constraint = gs.isclose(submersed_point, 0.0 + 0.0j, atol=atol) else: constraint = gs.isclose(submersed_point, 0.0, atol=atol) if axis: constraint = gs.all(constraint, axis=axis) return gs.logical_and(belongs, constraint)
[docs] def is_tangent(self, vector, base_point, atol=gs.atol): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ belongs = self.embedding_space.is_tangent(vector, base_point, atol) if not gs.any(belongs): return belongs submersed_vector = self.tangent_submersion(vector, base_point) n_batch = len(get_batch_shape(self.point_ndim, base_point, vector)) axis = tuple(range(-len(submersed_vector.shape) + n_batch, 0)) constraint = gs.isclose(submersed_vector, 0.0, atol=atol) if axis: constraint = gs.all(constraint, axis=axis) return gs.logical_and(belongs, constraint)
[docs] def intrinsic_to_extrinsic_coords(self, point_intrinsic): """Convert from intrinsic to extrinsic coordinates. Parameters ---------- point_intrinsic : array-like, shape=[..., *point_shape] Point in the embedded manifold in intrinsic coordinates. Returns ------- point_extrinsic : array-like, shape=[..., *embedding_space.point_shape] Point in the embedded manifold in extrinsic coordinates. """ raise NotImplementedError("intrinsic_to_extrinsic_coords is not implemented.")
[docs] def extrinsic_to_intrinsic_coords(self, point_extrinsic): """Convert from extrinsic to intrinsic coordinates. Parameters ---------- point_extrinsic : array-like, shape=[..., *embedding_space.point_shape] Point in the embedded manifold in extrinsic coordinates, i. e. in the coordinates of the embedding manifold. Returns ------- point_intrinsic : array-lie, shape=[..., *point_shape] Point in the embedded manifold in intrinsic coordinates. """ raise NotImplementedError("extrinsic_to_intrinsic_coords is not implemented.")
[docs] class OpenSet(Manifold, abc.ABC): """Class for manifolds that are open sets. NB: if the embedding space is a vector space, use `VectorSpaceOpenSet`. Parameters ---------- embedding_space: Manifold Embedding space that contains the manifold. """ def __init__(self, embedding_space, shape=None, **kwargs): self.embedding_space = embedding_space if shape is None: shape = embedding_space.shape super().__init__(shape=shape, **kwargs)
[docs] def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ return self.embedding_space.is_tangent(vector, base_point, atol)
[docs] def to_tangent(self, vector, base_point=None): """Project a vector to a tangent space of the manifold. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vector at base point. """ return self.embedding_space.to_tangent(vector, base_point)
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample random points on the manifold. Points are sampled from the embedding space using the distribution set for that manifold and then projected to the manifold. As a result, this is not a uniform distribution on the manifold itself. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Bound of the interval in which to sample for the embedding space. Optional, default: 1. Returns ------- samples : array-like, shape=[..., *point_shape] Points sampled on the hypersphere. """ sample = self.embedding_space.random_point(n_samples, bound) return self.projection(sample)
[docs] class VectorSpaceOpenSet(OpenSet, abc.ABC): """Class for manifolds that are open sets of a vector space. In this case, tangent vectors are identified with vectors of the embedding space. Parameters ---------- embedding_space: VectorSpace Embedding space that contains the manifold. """
[docs] def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ is_tangent = self.embedding_space.belongs(vector, atol) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(is_tangent, base_point.shape[: -self.point_ndim]) return is_tangent
[docs] def to_tangent(self, vector, base_point=None): """Project a vector to a tangent space of the manifold. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vector at base point. """ tangent_vec = self.embedding_space.projection(vector) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(tangent_vec, base_point.shape) return tangent_vec
[docs] class ComplexVectorSpaceOpenSet(ComplexManifold, abc.ABC): """Class for manifolds that are open sets of a complex vector space. In this case, tangent vectors are identified with vectors of the embedding space. Parameters ---------- dim: int Dimension of the manifold. It is often the same as the embedding space dimension but may differ in some cases. embedding_space: VectorSpace Embedding space that contains the manifold. """ def __init__(self, embedding_space, shape=None, **kwargs): if shape is None: shape = embedding_space.shape super().__init__(shape=shape, intrinsic=False, **kwargs) self.embedding_space = embedding_space
[docs] def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ is_tangent = self.embedding_space.belongs(vector, atol) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(is_tangent, base_point.shape[: -self.point_ndim]) return is_tangent
[docs] def to_tangent(self, vector, base_point=None): """Project a vector to a tangent space of the manifold. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vector at base point. """ tangent_vec = self.embedding_space.projection(vector) if base_point is not None and base_point.ndim > vector.ndim: return gs.broadcast_to(tangent_vec, base_point.shape) return tangent_vec
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample random points on the manifold. If the manifold is compact, a uniform distribution is used. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Bound of the interval in which to sample for non compact manifolds. Optional, default: 1. Returns ------- samples : array-like, shape=[..., *point_shape] Points sampled on the hypersphere. """ sample = self.embedding_space.random_point(n_samples, bound) return self.projection(sample)
[docs] class ImmersedSet(Manifold, abc.ABC): """Class for manifolds embedded in a vector space by an immersion. The manifold is represented with intrinsic coordinates, such that the immersion gives a parameterization of the manifold in these coordinates. Parameters ---------- dim : int Dimension of the embedded manifold. """ def __init__(self, dim, equip=True): super().__init__(dim=dim, shape=(dim,), intrinsic=True, equip=equip) self.embedding_space = self._define_embedding_space()
[docs] @staticmethod def default_metric(): """Metric to equip the space with if equip is True.""" return PullbackMetric
@abc.abstractmethod def _define_embedding_space(self): """Define embedding space of the manifold. Returns ------- embedding_space : Manifold Instance of Manifold. """
[docs] @abc.abstractmethod def immersion(self, point): """Evaluate the immersion function at a point. Parameters ---------- point : array-like, shape=[..., dim] Point in the immersed manifold. Returns ------- immersion : array-like, shape=[..., dim_embedding] Immersion of the point. """
[docs] def tangent_immersion(self, tangent_vec, base_point): """Evaluate the tangent immersion at a tangent vec and point. Parameters ---------- tangent_vec : array-like, shape=[..., dim] base_point : array-like, shape=[..., dim] Point in the immersed manifold. Returns ------- tangent_vec_emb : array-like, shape=[..., dim_embedding] """ jacobian_immersion = self.jacobian_immersion(base_point) return gs.matvec(jacobian_immersion, tangent_vec)
[docs] def jacobian_immersion(self, base_point): """Evaluate the Jacobian of the immersion at a point. Parameters ---------- base_point : array-like, shape=[..., dim] Point in the immersed manifold. Returns ------- jacobian_immersion : array-like, shape=[..., dim_embedding, dim] """ return gs.autodiff.jacobian_vec(self.immersion)(base_point)
[docs] def hessian_immersion(self, base_point): """Compute the Hessian of the immersion. Parameters ---------- base_point : array-like, shape=[..., dim] Base point. Returns ------- hessian_immersion : array-like, shape=[..., embedding_dim, dim, dim] Hessian at the base point """ return gs.autodiff.hessian_vec( self.immersion, func_out_ndim=self.embedding_space.dim )(base_point)
[docs] def is_tangent(self, vector, base_point, atol=gs.atol): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., dim] Vector. base_point : array-like, shape=[..., dim] Point on the manifold. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ raise NotImplementedError("`is_tangent` is not implemented yet")
[docs] def belongs(self, point, atol=gs.atol): """Evaluate if a point belongs to the manifold. Parameters ---------- point : array-like, shape=[..., dim] Point to evaluate. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the manifold. """ raise NotImplementedError("`is_tangent` is not implemented yet")
[docs] def projection(self, point): """Project a point to the embedded manifold. This is simply point, since we are in intrinsic coordinates. Parameters ---------- point : array-like, shape=[..., dim_embedding] Point in the embedding manifold. Returns ------- projected_point : array-like, shape=[..., dim] Point in the embedded manifold. """ raise NotImplementedError("`projection` is not implemented yet")
[docs] def to_tangent(self, vector, base_point): """Project a vector to a tangent space of the manifold. This is simply the vector since we are in intrinsic coordinates. Parameters ---------- vector : array-like, shape=[..., dim_embedding] Vector. base_point : array-like, shape=[..., dim] Point in the embedded manifold. Returns ------- tangent_vec : array-like, shape=[..., dim] Tangent vector at base point. """ raise NotImplementedError("`to_tangent` is not implemented yet")
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample random points on the manifold according to some distribution. If the manifold is compact, preferably a uniform distribution will be used. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Bound of the interval in which to sample for non compact manifolds. Optional, default: 1. Returns ------- samples : array-like, shape=[..., *point_shape] Points sampled on the manifold. """ raise NotImplementedError("`random_point` is not implemented yet")
[docs] class DiffeomorphicManifold(Manifold): """A manifold defined by a diffeomorphism.""" def __init__(self, diffeo, image_space, **kwargs): self.diffeo = diffeo self.image_space = image_space super().__init__(**kwargs)
[docs] def belongs(self, point, atol=gs.atol): """Evaluate if a point belongs to the manifold. Parameters ---------- point : array-like, shape=[..., *point_shape] Point to evaluate. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the manifold. """ if not self.intrinsic: raise ValueError("`belongs` is not implemented.") return self.image_space.belongs(self.diffeo(point), atol=atol)
[docs] def is_tangent(self, vector, base_point=None, atol=gs.atol): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ if not self.intrinsic: raise ValueError("`is_tangent` is not implemented.") image_point = self.diffeo(base_point) image_vector = self.diffeo.tangent( vector, base_point=base_point, image_point=image_point ) return self.image_space.is_tangent(image_vector, image_point, atol=atol)
[docs] def to_tangent(self, vector, base_point=None): """Project a vector to a tangent space of the manifold. Parameters ---------- vector : array-like, shape=[..., *point_shape] Vector. base_point : array-like, shape=[..., *point_shape] Point on the manifold. Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vector at base point. """ image_point = self.diffeo(base_point) image_vector = self.diffeo.tangent( vector, base_point=base_point, image_point=image_point ) image_tangent_vec = self.image_space.to_tangent(image_vector, image_point) return self.diffeo.inverse_tangent( image_tangent_vec, image_point=image_point, base_point=base_point )
[docs] def random_point(self, n_samples=1, **kwargs): """Sample random points on the manifold according to some distribution. If the manifold is compact, preferably a uniform distribution will be used. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. Returns ------- samples : array-like, shape=[..., *point_shape] Points sampled on the manifold. """ image_point = self.image_space.random_point(n_samples=n_samples, **kwargs) return self.diffeo.inverse(image_point)
[docs] def regularize(self, point): """Regularize a point to the canonical representation for the manifold. Parameters ---------- point : array-like, shape=[..., dim] Point. Returns ------- regularized_point : array-like, shape=[..., *point_shape] Regularized point. """ image_point = self.diffeo(point) regularized_image_point = self.image_space.regularize(image_point) return self.diffeo.inverse(regularized_image_point)
[docs] def random_tangent_vec(self, base_point=None, n_samples=1): """Generate random tangent vec. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. base_point : array-like, shape={[n_samples, *point_shape], [*point_shape,]} Point. Returns ------- tangent_vec : array-like, shape=[..., *point_shape] Tangent vec at base point. """ image_point = self.diffeo(base_point) image_tangent_vec = self.image_space.random_tangent_vec( image_point, n_samples=n_samples ) return self.diffeo.inverse_tangent( image_tangent_vec, image_point=image_point, base_point=base_point )
[docs] class DiffeomorphicVectorSpace(VectorSpace, DiffeomorphicManifold): """A vector space defined by a diffeomorphism."""
[docs] def projection(self, point): r"""Make a matrix null-row-sum symmetric. It considers only the first :math:`n-1 \times n-1` components. Parameters ---------- point : array-like, shape=[..., n, n] Matrix. Returns ------- sym : array-like, shape=[..., n, n] Symmetric matrix. """ image_point = self.diffeo(point) proj_image_point = self.image_space.projection(image_point) return self.diffeo.inverse(proj_image_point)
[docs] class DiffeomorphicMatrixVectorSpace(MatrixVectorSpace, DiffeomorphicVectorSpace): """A matrix vector space defined by a diffeomorphism."""
[docs] def basis_representation(self, matrix_representation): """Convert a symmetric matrix into a vector. Parameters ---------- matrix_representation : array-like, shape=[..., n, n] Matrix. Returns ------- basis_representation : array-like, shape=[..., n(n+1)/2] Vector. """ image_matrix_representation = self.diffeo(matrix_representation) return self.image_space.basis_representation(image_matrix_representation)
[docs] def matrix_representation(self, basis_representation): """Convert a vector into a symmetric matrix. Parameters ---------- basis_representation : array-like, shape=[..., n(n+1)/2] Vector. Returns ------- matrix_representation : array-like, shape=[..., n, n] Symmetric matrix. """ image_point = self.image_space.matrix_representation(basis_representation) return self.diffeo.inverse(image_point)