Source code for geomstats.geometry.lie_group

"""Lie groups.

Lead author: Nina Miolane.
"""

import abc

import geomstats.backend as gs
from geomstats.geometry.invariant_metric import InvariantMetric
from geomstats.geometry.manifold import Manifold
from geomstats.geometry.matrices import Matrices

ATOL = 1e-6


[docs] class MatrixLieGroup(Manifold, abc.ABC): """Class for matrix Lie groups.""" def __init__(self, representation_dim, lie_algebra=None, **kwargs): super().__init__(shape=(representation_dim, representation_dim), **kwargs) self.lie_algebra = lie_algebra self.representation_dim = representation_dim @property def identity(self): """Matrix identity.""" return gs.eye(self.representation_dim)
[docs] @staticmethod def compose(point_a, point_b): """Perform function composition corresponding to the Lie group. Multiply the elements `point_a` and `point_b`. Parameters ---------- point_a : array-like, shape=[..., {dim, [n, n]}] Left factor in the product. point_b : array-like, shape=[..., {dim, [n, n]}] Right factor in the product. Returns ------- composed : array-like, shape=[..., {dim, [n, n]}] Product of point_a and point_b along the first dimension. """ return Matrices.mul(point_a, point_b)
[docs] @classmethod def inverse(cls, point): """Compute the inverse law of the Lie group. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]}] Point to be inverted. Returns ------- inverse : array-like, shape=[..., {dim, [n, n]}] Inverted point. """ return gs.linalg.inv(point)
[docs] def tangent_translation_map(self, point, left=True, inverse=False): r"""Compute the push-forward map by the left/right translation. Compute the push-forward map, of the left/right translation by the point. It corresponds to the tangent map, or differential of the group multiplication by the point or its inverse. For groups with a vector representation, it is only implemented at identity, but it can be used at other points by passing `inverse=True`. This method wraps the jacobian translation which actually computes the matrix representation of the map. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]] Point. left : bool Whether to calculate the differential of left or right translations. Optional, default: True inverse : bool, Whether to inverse the jacobian matrix. If True, the push forward by the translation by the inverse of point is returned. Optional, default: False. Returns ------- tangent_map : callable Tangent map of the left/right translation by point. It can be applied to tangent vectors. """ if inverse: point = self.inverse(point) if left: return lambda tangent_vec: self.compose(point, tangent_vec) return lambda tangent_vec: self.compose(tangent_vec, point)
[docs] def lie_bracket(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the lie bracket of two tangent vectors. For matrix Lie groups with tangent vectors A,B at the same base point P this is given by (translate to identity, compute commutator, go back) :math:`[A,B] = A_P^{-1}B - B_P^{-1}A` Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at base point. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at base point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- bracket : array-like, shape=[..., n, n] Lie bracket. """ if base_point is None: base_point = self.identity inverse_base_point = self.inverse(base_point) first_term = Matrices.mul(inverse_base_point, tangent_vec_b) first_term = Matrices.mul(tangent_vec_a, first_term) second_term = Matrices.mul(inverse_base_point, tangent_vec_a) second_term = Matrices.mul(tangent_vec_b, second_term) return first_term - second_term
[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=[..., dim_embedding] Vector. base_point : array-like, shape=[..., dim_embedding] Point in the Lie group. Optional. default: identity. atol : float Precision at which to evaluate if the rotation part is skew-symmetric. Optional. default: 1e-6 Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ if base_point is None: tangent_vec_at_id = vector else: tangent_vec_at_id = self.compose(self.inverse(base_point), vector) return self.lie_algebra.belongs(tangent_vec_at_id, atol)
[docs] def to_tangent(self, vector, base_point=None): """Project a vector onto the tangent space at a base point. Parameters ---------- vector : array-like, shape=[..., {dim, [n, n]}] Vector to project. Its shape must match the shape of base_point. base_point : array-like, shape=[..., {dim, [n, n]}], optional Point of the group. Optional, default: identity. Returns ------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point. """ if base_point is None: return self.lie_algebra.projection(vector) tangent_vec_at_id = self.compose(self.inverse(base_point), vector) regularized = self.lie_algebra.projection(tangent_vec_at_id) return self.compose(base_point, regularized)
[docs] @classmethod def exp(cls, tangent_vec, base_point=None): r""" Exponentiate a left-invariant vector field from a base point. The vector input is not an element of the Lie algebra, but of the tangent space at base_point: if :math:`g` denotes `base_point`, :math:`v` the tangent vector, and :math:`V = g^{-1} v` the associated Lie algebra vector, then .. math:: \exp(v, g) = mul(g, \exp(V)) Therefore, the Lie exponential is obtained when base_point is None, or the identity. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point. base_point : array-like, shape=[..., n, n] Base point. Optional, defaults to identity if None. Returns ------- point : array-like, shape=[..., n, n] Left multiplication of `exp(algebra_mat)` with `base_point`. """ expm = gs.linalg.expm if base_point is None: return expm(tangent_vec) lie_algebra_vec = cls.compose(cls.inverse(base_point), tangent_vec) return cls.compose(base_point, cls.exp(lie_algebra_vec))
[docs] @classmethod def log(cls, point, base_point=None): r""" Compute a left-invariant vector field bringing base_point to point. The output is a vector of the tangent space at base_point, so not a Lie algebra element if it is not the identity. Parameters ---------- point : array-like, shape=[..., n, n] Point. base_point : array-like, shape=[..., n, n] Base point. Optional, defaults to identity if None. Returns ------- tangent_vec : array-like, shape=[..., n, n] Matrix such that `exp(tangent_vec, base_point) = point`. Notes ----- Denoting `point` by :math:`g` and `base_point` by :math:`h`, the output satisfies: .. math:: g = \exp(\log(g, h), h) """ logm = gs.linalg.logm if base_point is None: return logm(point) lie_algebra_vec = logm(cls.compose(cls.inverse(base_point), point)) return cls.compose(base_point, lie_algebra_vec)
[docs] class LieGroup(Manifold, abc.ABC): """Class for Lie groups. In this class, point_type ('vector' or 'matrix') will be used to describe the format of the points on the Lie group. If point_type is 'vector', the format of the inputs is [..., dimension], where dimension is the dimension of the Lie group. If point_type is 'matrix' the format of the inputs is [..., n, n] where n is the parameter of GL(n) e.g. the amount of rows and columns of the matrix. Parameters ---------- dim : int Dimension of the Lie group. lie_algebra : MatrixLieAlgebra Lie algebra for matrix groups. Optional, default: None. Attributes ---------- lie_algebra : MatrixLieAlgebra or None Tangent space at the identity. """ def __init__(self, lie_algebra=None, **kwargs): super().__init__(**kwargs) self.lie_algebra = lie_algebra
[docs] def default_metric(self): """Metric to equip the space with if equip is True.""" return ( InvariantMetric, {"left": True, "metric_mat_at_identity": gs.eye(self.dim)}, )
@property def identity(self): """Identity of the group. Returns ------- identity : array-like, shape={[dim], [n, n]} Identity of the Lie group. """ raise NotImplementedError("The Lie group identity is not implemented.")
[docs] def compose(self, point_a, point_b): """Perform function composition corresponding to the Lie group. Multiply the elements `point_a` and `point_b`. Parameters ---------- point_a : array-like, shape=[..., {dim, [n, n]}] Left factor in the product. point_b : array-like, shape=[..., {dim, [n, n]}] Right factor in the product. Returns ------- composed : array-like, shape=[..., {dim, [n, n]}] Product of point_a and point_b along the first dimension. """ raise NotImplementedError("The Lie group composition is not implemented.")
[docs] @classmethod def inverse(cls, point): """Compute the inverse law of the Lie group. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]}] Point to be inverted. Returns ------- inverse : array-like, shape=[..., {dim, [n, n]}] Inverted point. """ raise NotImplementedError("The Lie group inverse is not implemented.")
[docs] def jacobian_translation(self, point, left=True): """Compute the Jacobian of left/right translation by a point. Compute the Jacobian matrix of the left translation by the point. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]] Point. left : bool Indicate whether to calculate the differential of left or right translations. Optional, default: True. Returns ------- jacobian : array-like, shape=[..., dim, dim] Jacobian of the left/right translation by point. """ raise NotImplementedError( "The jacobian of the Lie group translation is not implemented." )
[docs] def tangent_translation_map(self, point, left=True, inverse=False): r"""Compute the push-forward map by the left/right translation. Compute the push-forward map, of the left/right translation by the point. It corresponds to the tangent map, or differential of the group multiplication by the point or its inverse. For groups with a vector representation, it is only implemented at identity, but it can be used at other points by passing `inverse=True`. This method wraps the jacobian translation which actually computes the matrix representation of the map. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]] Point. left: bool Whether to calculate the differential of left or right translations. Optional, default: True. inverse : bool, Whether to inverse the jacobian matrix. If True, the push forward by the translation by the inverse of point is returned. Optional, default: False. Returns ------- tangent_map : callable Tangent map of the left/right translation by point. It can be applied to tangent vectors. """ if self.point_ndim == 2: if inverse: point = self.inverse(point) if left: return lambda tangent_vec: Matrices.mul(point, tangent_vec) return lambda tangent_vec: Matrices.mul(tangent_vec, point) jacobian = self.jacobian_translation(point, left) if inverse: jacobian = gs.linalg.inv(jacobian) return lambda tangent_vec: gs.einsum("...ij,...j->...i", jacobian, tangent_vec)
[docs] def exp_from_identity(self, tangent_vec): """Compute the group exponential of tangent vector from the identity. Parameters ---------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Tangent vector at base point. Returns ------- point : array-like, shape=[..., {dim, [n, n]}] Group exponential. """ raise NotImplementedError( "The group exponential from the identity is not implemented." )
[docs] def exp_not_from_identity(self, tangent_vec, base_point): """Calculate the group exponential at base_point. Parameters ---------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Tangent vector at base point. base_point : array-like, shape=[..., {dim, [n, n]}] Base point. Returns ------- exp : array-like, shape=[..., {dim, [n, n]}] Group exponential. """ if self.point_ndim == 1: tangent_translation = self.tangent_translation_map( point=base_point, left=True, inverse=True ) tangent_vec_at_id = tangent_translation(tangent_vec) exp_from_identity = self.exp_from_identity(tangent_vec=tangent_vec_at_id) exp = self.compose(base_point, exp_from_identity) exp = self.regularize(exp) return exp lie_vec = self.compose(self.inverse(base_point), tangent_vec) return self.compose(base_point, self.exp_from_identity(lie_vec))
[docs] def exp(self, tangent_vec, base_point=None): """Compute the group exponential at `base_point` of `tangent_vec`. Parameters ---------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Tangent vector at base point. base_point : array-like, shape=[..., {dim, [n, n]}] Base point. Optional, default: self.identity Returns ------- result : array-like, shape=[..., {dim, [n, n]}] Group exponential. """ identity = self.identity if base_point is None: base_point = identity base_point = self.regularize(base_point) if gs.allclose(base_point, identity): return self.exp_from_identity(tangent_vec) return self.exp_not_from_identity(tangent_vec, base_point)
[docs] def log_from_identity(self, point): """Compute the group logarithm of `point` from the identity. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]}] Point. Returns ------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Group logarithm. """ raise NotImplementedError( "The group logarithm from the identity is not implemented." )
[docs] def log_not_from_identity(self, point, base_point): """Compute the group logarithm of `point` from `base_point`. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]}] Point. base_point : array-like, shape=[..., {dim, [n, n]}] Base point. Returns ------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Group logarithm. """ if self.point_ndim == 1: tangent_translation = self.tangent_translation_map( point=base_point, left=True, ) point_near_id = self.compose(self.inverse(base_point), point) log_from_id = self.log_from_identity(point=point_near_id) log = tangent_translation(log_from_id) return log lie_point = self.compose(self.inverse(base_point), point) return self.compose(base_point, self.log_from_identity(lie_point))
[docs] def log(self, point, base_point=None): """Compute the group logarithm of `point` relative to `base_point`. Parameters ---------- point : array-like, shape=[..., {dim, [n, n]}] Point. base_point : array-like, shape=[..., {dim, [n, n]}] Base point. Optional, defaults to identity if None. Returns ------- tangent_vec : array-like, shape=[..., {dim, [n, n]}] Group logarithm. """ # TODO (ninamiolane): Build a standalone decorator that *only* # deals with point_type None and base_point None identity = self.identity if base_point is None: base_point = identity point = self.regularize(point) base_point = self.regularize(base_point) if gs.allclose(base_point, identity): return self.log_from_identity(point) return self.log_not_from_identity(point, base_point)
[docs] def lie_bracket(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the lie bracket of two tangent vectors. For matrix Lie groups with tangent vectors A,B at the same base point P this is given by (translate to identity, compute commutator, go back) :math:`[A,B] = A_P^{-1}B - B_P^{-1}A` Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at base point. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at base point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- bracket : array-like, shape=[..., n, n] Lie bracket. """ if base_point is None: base_point = self.identity inverse_base_point = self.inverse(base_point) first_term = Matrices.mul(inverse_base_point, tangent_vec_b) first_term = Matrices.mul(tangent_vec_a, first_term) second_term = Matrices.mul(inverse_base_point, tangent_vec_a) second_term = Matrices.mul(tangent_vec_b, second_term) return first_term - second_term
[docs] def is_tangent(self, vector, base_point=None, atol=ATOL): """Check whether the vector is tangent at base_point. Parameters ---------- vector : array-like, shape=[..., dim_embedding] Vector. base_point : array-like, shape=[..., dim_embedding] Point in the Lie group. Optional. default: identity. atol : float Precision at which to evaluate if the rotation part is skew-symmetric. Optional. default: 1e-6 Returns ------- is_tangent : bool Boolean denoting if vector is a tangent vector at the base point. """ if base_point is None: tangent_vec_at_id = vector else: tangent_vec_at_id = self.compose(self.inverse(base_point), vector) return self.lie_algebra.belongs(tangent_vec_at_id, atol)
[docs] def to_tangent(self, vector, base_point=None): """Project a vector onto the tangent space at a base point. Parameters ---------- vector : array-like, shape=[..., {dim, [n, n]}] Vector to project. Its shape must match the shape of base_point. base_point : array-like, shape=[..., {dim, [n, n]}], optional Point of the group. Optional, default: identity. Returns ------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point. """ if base_point is None: return self.lie_algebra.projection(vector) tangent_vec_at_id = self.compose(self.inverse(base_point), vector) regularized = self.lie_algebra.projection(tangent_vec_at_id) return self.compose(base_point, regularized)