Source code for geomstats.geometry.functions

"""Module for function spaces as geometric objects."""

import math

import geomstats.algebra_utils as utils
import geomstats.backend as gs
from geomstats.geometry.manifold import Manifold
from geomstats.geometry.riemannian_metric import RiemannianMetric


[docs] class HilbertSphere(Manifold): """Class for space of one dimensional functions with norm 1. The tangent space is given by functions that have zero inner-product with the base point Parameters ---------- domain : array-like, shape=[n_samples,] Grid points on the domain. References ---------- .. [Sea2016] Srivastava, Anuj, and Eric P. Klassen. Functional and shape data analysis. Vol. 1. New York: Springer, 2016. """ def __init__(self, domain, equip=True): self.domain = domain super().__init__( dim=math.inf, shape=(len(domain),), equip=equip, ) sinf_domain = (domain - min(domain)) / (max(domain) - min(domain)) if gs.allclose(domain, sinf_domain): self._sinf = self else: self._sinf = HilbertSphere(sinf_domain)
[docs] @staticmethod def default_metric(): """Metric to equip the space with if equip is True.""" return HilbertSphereMetric
[docs] def projection(self, point): """Project a point to the infinite dimensional hypersphere. Parameters ---------- point : array-like, shape=[..., n_samples] Discrete evaluation of a function. Returns ------- projected_point : array-like, shape=[..., n_samples] Point projected to the hypersphere. """ norm = self.metric.norm(point) return gs.einsum("...,...i->...i", 1.0 / norm, point)
[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=[..., n_samples] Point to test. atol : float Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the space. """ norms = self.metric.norm(point) return gs.isclose(norms, 1.0, atol)
[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=[..., n_samples] Vector. base_point : array-like, shape=[..., n_samples] 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. """ inner_product = self.metric.inner_product(vector, base_point) return gs.isclose(inner_product, gs.zeros_like(inner_product), atol)
[docs] def to_tangent(self, vector, base_point): """Project a vector to a tangent space of the manifold. Parameters ---------- vector : array-like, shape=[..., n_samples] Vector. base_point : array-like, shape=[..., n_samples] Point on the manifold. Returns ------- tangent_vec : array-like, shape=[..., n_samples] Tangent vector at base point. """ sq_norm = self.metric.squared_norm(base_point) inner_product = self.metric.inner_product(vector, base_point) coef = inner_product / sq_norm return vector - gs.einsum("...,...j->...j", coef, base_point)
[docs] def random_point(self, n_samples=1, bound=1.0): """Sample random points on the manifold. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float This parameter is ignored Returns ------- samples : array-like, shape=[..., dim] """ n_domain = len(self.domain) shape = ( (n_domain,) if n_samples == 1 else ( n_samples, n_domain, ) ) point = gs.random.rand(*shape) return self.projection(point)
[docs] class HilbertSphereMetric(RiemannianMetric): """A Riemannian metric on the Hilbert sphere (functions of norm 1)."""
[docs] def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the inner-product of two tangent vectors at a base point. Parameters ---------- tangent_vec_a : array-like, shape=[..., n_samples] First tangent vector at base point. tangent_vec_b : array-like, shape=[..., n_samples] Second tangent vector at base point. base_point : array-like, shape=[..., n_samples], optional Point on the hypersphere. Returns ------- inner_prod : array-like, shape=[...,] Inner-product of the two tangent vectors. """ tangent_vec_a, tangent_vec_b = gs.broadcast_arrays(tangent_vec_a, tangent_vec_b) x = gs.broadcast_to(self._space._sinf.domain, tangent_vec_a.shape) return gs.trapz(tangent_vec_a * tangent_vec_b, x=x, axis=-1)
[docs] def exp(self, tangent_vec, base_point): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[..., n_samples] Tangent vector at a base point. base_point : array-like, shape=[..., n_samples] Point on the hypersphere. Returns ------- exp : array-like, shape=[..., n_samples] Point on the hypersphere equal to the Riemannian exponential of tangent_vec at the base point. """ proj_tangent_vec = self._space._sinf.to_tangent(tangent_vec, base_point) norm = self._space._sinf.metric.norm(proj_tangent_vec) norm = gs.clip(norm, -gs.pi, gs.pi) coef_1 = utils.taylor_exp_even_func(norm, utils.cos_close_0, order=4) coef_2 = utils.taylor_exp_even_func(norm, utils.sinc_close_0, order=4) exp = gs.einsum("...,...j->...j", coef_1, base_point) + gs.einsum( "...,...j->...j", coef_2, proj_tangent_vec ) return exp
[docs] def log(self, point, base_point): """Compute the Riemannian logarithm of a point. Parameters ---------- point : array-like, shape=[..., n_samples] Point on the hypersphere. base_point : array-like, shape=[..., n_samples] Point on the hypersphere. Returns ------- log : array-like, shape=[..., n_samples] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ inner_prod = self.inner_product(base_point, point) cos_angle = gs.clip(inner_prod, -1.0, 1.0) theta = gs.arccos(cos_angle) coef_1_ = utils.taylor_exp_even_func(theta, utils.inv_sinc_close_0, order=5) coef_2_ = utils.taylor_exp_even_func(theta, utils.inv_tanc_close_0, order=5) log = gs.einsum("...,...j->...j", theta * coef_1_, point) - gs.einsum( "...,...j->...j", theta * coef_2_, base_point ) return log