"""Manifold module.
In other words, a topological space that locally resembles
Euclidean space near each point.
Lead author: Nina Miolane.
"""
import abc
import geomstats.backend as gs
import geomstats.errors
from geomstats.geometry.riemannian_metric import RiemannianMetric
POINT_TYPES = {1: "vector", 2: "matrix"}
[docs]class Manifold(abc.ABC):
r"""Class for manifolds.
Parameters
----------
dim : int
Dimension of the manifold.
shape : tuple of int
Shape of one element of the manifold.
Optional, default : None.
metric : RiemannianMetric
Metric object to use on the manifold.
default_point_type : str, {\'vector\', \'matrix\'}
Point type.
Optional, default: 'vector'.
default_coords_type : str, {\'intrinsic\', \'extrinsic\', etc}
Coordinate type.
Optional, default: 'intrinsic'.
"""
def __init__(
self,
dim,
shape,
metric=None,
default_point_type=None,
default_coords_type="intrinsic",
**kwargs
):
super(Manifold, self).__init__(**kwargs)
geomstats.errors.check_integer(dim, "dim")
if not isinstance(shape, tuple):
raise ValueError("Expected a tuple for the shape argument.")
if default_point_type is None:
default_point_type = POINT_TYPES[len(shape)]
geomstats.errors.check_parameter_accepted_values(
default_point_type, "default_point_type", ["vector", "matrix"]
)
self.dim = dim
self.shape = shape
self.default_point_type = default_point_type
self.default_coords_type = default_coords_type
self._metric = metric
[docs] @abc.abstractmethod
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.
"""
[docs] @abc.abstractmethod
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.
"""
[docs] @abc.abstractmethod
def to_tangent(self, vector, base_point):
"""Project a vector to a tangent space of the manifold.
Parameters
----------
vector : array-like, shape=[..., dim]
Vector.
base_point : array-like, shape=[..., dim]
Point on the manifold.
Returns
-------
tangent_vec : array-like, shape=[..., dim]
Tangent vector at base point.
"""
[docs] @abc.abstractmethod
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=[..., {dim, [n, n]}]
Points sampled on the manifold.
"""
[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=[..., dim]
Regularized point.
"""
regularized_point = point
return regularized_point
@property
def metric(self):
"""Riemannian Metric associated to the Manifold."""
return self._metric
@metric.setter
def metric(self, metric):
if metric is not None:
if not isinstance(metric, RiemannianMetric):
raise ValueError("The argument must be a RiemannianMetric object")
if metric.dim != self.dim:
metric.dim = self.dim
self._metric = metric
[docs] def random_tangent_vec(self, base_point, n_samples=1):
"""Generate random tangent vec.
Parameters
----------
n_samples : int
Number of samples.
Optional, default: 1.
base_point : array-like, shape=[..., dim]
Point.
Returns
-------
tangent_vec : array-like, shape=[..., dim]
Tangent vec at base point.
"""
if (
n_samples > 1
and base_point.ndim > len(self.shape)
and n_samples != len(base_point)
):
raise ValueError(
"The number of base points must be the same as the "
"number of samples, when different from 1."
)
return gs.squeeze(
self.to_tangent(
gs.random.normal(size=(n_samples,) + self.shape), base_point
)
)