Source code for geomstats.geometry.complex_poincare_disk

"""The complex Poincaré disk manifold.

The Poincaré complex disk is a representation of the Hyperbolic space of dimension 2.

Lead author: Yann Cabanes.

References
----------
.. [Cabanes2022] Yann Cabanes. Multidimensional complex stationary
    centered Gaussian autoregressive time series machine learning
    in Poincaré and Siegel disks: application for audio and radar
    clutter classification, PhD thesis, 2022
.. [JV2016] B. Jeuris and R. Vandebril. The Kahler mean of Block-Toeplitz
    matrices with Toeplitz structured blocks, 2016.
    https://epubs.siam.org/doi/pdf/10.1137/15M102112X
.. [Yang2013] Marc Arnaudon, Frédéric Barbaresco and Le Yang.
    Riemannian medians and means with applications to radar signal processing,
    IEEE Journal of Selected Topics in Signal Processing, vol. 7, no. 4, pp. 595-604,
    Aug. 2013, doi: 10.1109/JSTSP.2013.2261798.
    https://ieeexplore.ieee.org/document/6514112
"""

import geomstats.backend as gs
from geomstats.geometry.base import ComplexVectorSpaceOpenSet
from geomstats.geometry.complex_riemannian_metric import ComplexRiemannianMetric
from geomstats.geometry.hermitian import Hermitian


[docs] class ComplexPoincareDisk(ComplexVectorSpaceOpenSet): """Class for the complex Poincaré disk. The Poincaré disk is a representation of the Hyperbolic space of dimension 2. Its complex dimension is 1. """ def __init__(self, equip=True): super().__init__( dim=1, embedding_space=Hermitian(dim=1), equip=equip, )
[docs] @staticmethod def default_metric(): """Metric to equip the space with if equip is True.""" return ComplexPoincareDiskMetric
[docs] @staticmethod def belongs(point, atol=gs.atol): """Check if a point belongs to the complex unit disk. Check if a point belongs to the Poincaré complex disk, i.e. Check if its norm is lower than one. Parameters ---------- point : array-like, shape=[..., 1] Point to be checked. atol : float Tolerance. Optional, default: backend atol. Returns ------- belongs : array-like, shape=[...] Boolean denoting if point belongs to the complex Poincaré disk. """ if point.shape[-1] != 1: return gs.zeros(point.shape[:-1], dtype=bool) return gs.all(gs.abs(point) < 1, axis=-1)
[docs] @staticmethod def projection(point): """Project a point on the complex unit disk. Parameters ---------- point : array-like, shape=[..., 1] Point to project. atol : float Tolerance. Optional, default: backend atol. Returns ------- projected : array-like, shape=[..., 1] Projected point. """ scalars = gs.where( gs.abs(point) >= 1 - gs.atol, (1 - gs.atol) / gs.abs(point), 1.0, ) return scalars * point
[docs] @staticmethod def random_point(n_samples=1, bound=0.8): """Generate random points in the complex unit disk. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. bound : float Bound of the interval in which to sample in the tangent space. Optional, default: 0.8. Returns ------- samples : array-like, shape=[..., 1] Points sampled in the unit disk. """ size = (n_samples, 1) if n_samples != 1 else (1,) modulus = gs.random.rand(*size, dtype=gs.get_default_cdtype()) angle = 2 * gs.pi * gs.random.rand(*size, dtype=gs.get_default_cdtype()) return modulus * gs.exp(1j * angle)
[docs] class ComplexPoincareDiskMetric(ComplexRiemannianMetric): """Class for the complex Poincaré metric."""
[docs] def metric_matrix(self, base_point): """Compute the metric matrix at base point. Parameters ---------- base_point : array-like, shape=[..., 1] Base point. Returns ------- inner_prod_mat : array-like, shape=[...] Inner product matrix. """ inner_prod_mat = 1 / (1 - gs.abs(base_point) ** 2) ** 2 return gs.expand_dims(inner_prod_mat, axis=-1)
[docs] @staticmethod def exp(tangent_vec, base_point): """Compute the complex Poincaré disk exponential map. Parameters ---------- tangent_vec : array-like, shape=[..., 1] Tangent vector at base point. base_point : array-like, shape=[..., 1] Base point. Returns ------- exp : array-like, shape=[..., 1] Riemannian exponential. """ base_point, tangent_vec = gs.broadcast_arrays(base_point, tangent_vec) theta = gs.angle(tangent_vec) s = 2 * gs.abs(tangent_vec) / (1 - gs.abs(base_point) ** 2) exp_i_theta = gs.exp(1j * theta) exp_minus_s = gs.exp(-s) num = base_point + exp_i_theta + (base_point - exp_i_theta) * exp_minus_s den = ( 1 + gs.conj(base_point) * exp_i_theta + (1 - gs.conj(base_point) * exp_i_theta) * exp_minus_s ) return num / den
@staticmethod def _tau(point_a, point_b, atol=gs.atol): """Compute the coefficient tau. The coefficient tau is used to compute the distance between point_a and point_b; it is also used to compute the logarithm map between point_a and point_b. Parameters ---------- point_a : array-like, shape=[..., 1] Point. point_b : array-like, shape=[..., 1] Point. atol : float Tolerance. Optional, default: backend atol. Returns ------- tau : array-like, shape=[...] Coefficient tau. """ num = gs.abs(point_a - point_b) den = gs.maximum(gs.abs(1 - point_a * gs.conj(point_b)), atol) delta = gs.squeeze(gs.minimum(num / den, 1 - atol), axis=-1) return (1 / 2) * gs.log((1 + delta) / (1 - delta))
[docs] def log(self, point, base_point): """Compute the complex Poincaré disk logarithm map. Compute the Riemannian logarithm at point base_point, of point wrt the positive reals metric. This gives a tangent vector at point base_point. Parameters ---------- point : array-like, shape=[..., 1] Point. base_point : array-like, shape=[..., 1] Base point. Returns ------- log : array-like, shape=[..., 1] Riemannian logarithm. """ angle = gs.angle(point - base_point) - gs.angle(1 - gs.conj(base_point) * point) return gs.exp(1j * angle) * gs.einsum( "...,...i->...i", self._tau(base_point, point), (1 - gs.abs(base_point) ** 2), )
[docs] def squared_dist(self, point_a, point_b, atol=gs.atol): """Compute the complex Poincaré disk squared distance. Compute the Riemannian squared distance between point_a and point_b. Parameters ---------- point_a : array-like, shape=[..., 1] Point. point_b : array-like, shape=[..., 1] Point. Returns ------- squared_dist : array-like, shape=[...] Riemannian squared distance. """ dist = self._tau(point_a, point_b, atol=atol) return gs.power(dist, 2)
[docs] def dist(self, point_a, point_b, atol=gs.atol): """Compute the complex Poincaré disk distance. Compute the Riemannian distance between point_a and point_b. Parameters ---------- point_a : array-like, shape=[..., 1] Point. point_b : array-like, shape=[..., 1] Point. atol : float Tolerance. Optional, default: backend atol. Returns ------- dist : array-like, shape=[...] Riemannian distance. """ return self._tau(point_a, point_b, atol=atol)