# 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.

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.
"""

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)

```