Exemplo n.º 1
0
    def setUp(self):
        gs.random.seed(1234)
        self.n = 3
        self.n_samples = 2
        self.group = GeneralLinear(n=self.n)
        self.group_pos = GeneralLinear(self.n, positive_det=True)

        warnings.simplefilter('ignore', category=ImportWarning)
Exemplo n.º 2
0
    def setUp(self):
        gs.random.seed(1234)
        self.n = 3
        self.n_samples = 2
        self.group = GeneralLinear(n=self.n)
        # We generate invertible matrices using so3_group
        self.so3_group = SpecialOrthogonal(n=self.n)

        warnings.simplefilter('ignore', category=ImportWarning)
Exemplo n.º 3
0
    def test_orbit(self):
        point = gs.array([[gs.exp(4.), 0.], [0., gs.exp(2.)]])
        sqrt = gs.array([[gs.exp(2.), 0.], [0., gs.exp(1.)]])
        idty = GeneralLinear(2).identity

        path = GeneralLinear(2).orbit(point)
        time = gs.linspace(0., 1., 3)

        result = path(time)
        expected = gs.array([idty, sqrt, point])
        self.assertAllClose(result, expected)
Exemplo n.º 4
0
    def test_orbit_vectorization(self):
        point = gs.array([[gs.exp(4.), 0.], [0., gs.exp(2.)]])
        sqrt = gs.array([[gs.exp(2.), 0.], [0., gs.exp(1.)]])
        identity = GeneralLinear(2).identity

        path = GeneralLinear(2).orbit(gs.stack([point] * 2), identity)
        time = gs.linspace(0., 1., 3)

        result = path(time)
        expected = gs.array([identity, sqrt, point])
        expected = gs.stack([expected] * 2)
        self.assertAllClose(result, expected)
Exemplo n.º 5
0
    def exp(self, tangent_vec, base_point):
        """Compute the affine-invariant exponential map.

        Compute the Riemannian exponential at point base_point
        of tangent vector tangent_vec wrt the metric defined in inner_product.
        This gives a symmetric positive definite matrix.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., n, n]
        base_point : array-like, shape=[..., n, n]

        Returns
        -------
        exp : array-like, shape=[..., n, n]
        """
        power_affine = self.power_affine

        if power_affine == 1:
            sqrt_base_point = SymmetricMatrices.powerm(base_point, 1. / 2)
            inv_sqrt_base_point = SymmetricMatrices.powerm(sqrt_base_point, -1)
            exp = self._aux_exp(tangent_vec, sqrt_base_point,
                                inv_sqrt_base_point)
        else:
            modified_tangent_vec = self.space.differential_power(
                power_affine, tangent_vec, base_point)
            power_sqrt_base_point = SymmetricMatrices.powerm(
                base_point, power_affine / 2)
            power_inv_sqrt_base_point = GeneralLinear.inverse(
                power_sqrt_base_point)
            exp = self._aux_exp(modified_tangent_vec, power_sqrt_base_point,
                                power_inv_sqrt_base_point)
            exp = SymmetricMatrices.powerm(exp, 1 / power_affine)

        return exp
Exemplo n.º 6
0
        def inner_prod(tangent_vec_a, tangent_vec_b, base_point):
            affine_part = self.bundle.ambient_metric.inner_product(
                tangent_vec_a, tangent_vec_b, base_point)
            n = tangent_vec_b.shape[-1]

            inverse_base_point = GeneralLinear.inverse(base_point)
            operator = gs.eye(n) + base_point * inverse_base_point
            inverse_operator = GeneralLinear.inverse(operator)

            diagonal_a = gs.einsum("...ij,...ji->...i", inverse_base_point,
                                   tangent_vec_a)
            diagonal_b = gs.einsum("...ij,...ji->...i", inverse_base_point,
                                   tangent_vec_b)
            aux = gs.einsum("...i,...j->...ij", diagonal_a, diagonal_b)
            other_part = 2 * Matrices.frobenius_product(aux, inverse_operator)
            return affine_part - other_part
Exemplo n.º 7
0
    def left_log_from_identity(self, point):
        """Compute Riemannian log of a point wrt. id of left-invar. metric.

        Compute Riemannian logarithm of a point wrt the identity associated
        to the left-invariant metric.

        If the method is called by a right-invariant metric, it uses the
        left-invariant metric associated to the same inner-product matrix
        at the identity.

        Parameters
        ----------
        point : array-like, shape=[..., dim]
            Point in the group.

        Returns
        -------
        log : array-like, shape=[..., dim]
            Tangent vector at the identity equal to the Riemannian logarithm
            of point at the identity.
        """
        point = self.group.regularize(point)
        inner_prod_mat = self.metric_mat_at_identity
        inv_inner_prod_mat = GeneralLinear.inverse(inner_prod_mat)
        sqrt_inv_inner_prod_mat = gs.linalg.sqrtm(inv_inner_prod_mat)
        log = gs.einsum('...i,...ij->...j', point, sqrt_inv_inner_prod_mat)
        log = self.group.regularize_tangent_vec_at_identity(tangent_vec=log,
                                                            metric=self)
        return log
Exemplo n.º 8
0
 def test_horizontal_projection(self):
     mat = self.bundle.random_point()
     vec = self.bundle.random_point()
     horizontal_vec = self.bundle.horizontal_projection(vec, mat)
     product = Matrices.mul(horizontal_vec, GeneralLinear.inverse(mat))
     is_horizontal = Matrices.is_symmetric(product)
     self.assertTrue(is_horizontal)
Exemplo n.º 9
0
 def orbit_data(self):
     point = gs.array([[gs.exp(4.0), 0.0], [0.0, gs.exp(2.0)]])
     sqrt = gs.array([[gs.exp(2.0), 0.0], [0.0, gs.exp(1.0)]])
     identity = GeneralLinear(2).identity
     time = gs.linspace(0.0, 1.0, 3)
     smoke_data = [
         dict(
             n=2,
             point=point,
             base_point=identity,
             time=time,
             expected=gs.array([identity, sqrt, point]),
         ),
         dict(
             n=2,
             point=[point, point],
             base_point=identity,
             time=time,
             expected=[
                 gs.array([identity, sqrt, point]),
                 gs.array([identity, sqrt, point]),
             ],
         ),
     ]
     return self.generate_tests(smoke_data)
Exemplo n.º 10
0
def submersion(point, k):
    r"""Submersion that defines the Grassmann manifold.

    The Grassmann manifold is defined here as embedded in the set of
    symmetric matrices, as the pre-image of the function defined around the
    projector on the space spanned by the first k columns of the identity
    matrix by (see Exercise E.25 in [Pau07]_).
    .. math:

            \begin{pmatrix} I_k + A & B^T \\ B & D \end{pmatrix} \mapsto
                (D - B(I_k + A)^{-1}B^T, A + A^2 + B^TB

    This map is a submersion and its zero space is the set of orthogonal
    rank-k projectors.

    References
    ----------
    .. [Pau07]   Paulin, Frédéric. “Géométrie différentielle élémentaire,” 2007.
                 https://www.imo.universite-paris-saclay.fr/~paulin
                 /notescours/cours_geodiff.pdf.
    """
    _, eigvecs = gs.linalg.eigh(point)
    eigvecs = gs.flip(eigvecs, -1)
    flipped_point = Matrices.mul(Matrices.transpose(eigvecs), point, eigvecs)
    b = flipped_point[..., k:, :k]
    d = flipped_point[..., k:, k:]
    a = flipped_point[..., :k, :k] - gs.eye(k)
    first = d - Matrices.mul(b, GeneralLinear.inverse(a + gs.eye(k)),
                             Matrices.transpose(b))
    second = a + Matrices.mul(a, a) + Matrices.mul(Matrices.transpose(b), b)
    row_1 = gs.concatenate([first, gs.zeros_like(b)], axis=-1)
    row_2 = gs.concatenate([Matrices.transpose(gs.zeros_like(b)), second],
                           axis=-1)
    return gs.concatenate([row_1, row_2], axis=-2)
Exemplo n.º 11
0
 def belongs(mat, atol=TOLERANCE):
     """Check if a matrix is symmetric and invertible."""
     is_symmetric = GeneralLinear.is_symmetric(mat)
     eigvalues, _ = gs.linalg.eigh(mat)
     is_positive = gs.all(eigvalues > 0, axis=-1)
     belongs = gs.logical_and(is_symmetric, is_positive)
     return belongs
Exemplo n.º 12
0
 def test_horizontal_lift_and_tangent_submersion(self):
     mat = self.bundle.total_space.random_uniform()
     tangent_vec = GeneralLinear.to_symmetric(
         self.bundle.total_space.random_uniform())
     horizontal = self.bundle.horizontal_lift(tangent_vec, mat)
     result = self.bundle.tangent_submersion(horizontal, mat)
     self.assertAllClose(result, tangent_vec)
Exemplo n.º 13
0
 def test_is_horizontal(self):
     mat = self.bundle.total_space.random_uniform()
     tangent_vec = GeneralLinear.to_symmetric(
         self.bundle.total_space.random_uniform())
     horizontal = self.bundle.horizontal_lift(tangent_vec, mat)
     result = self.bundle.is_horizontal(horizontal, mat)
     self.assertTrue(result)
Exemplo n.º 14
0
    def parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point):
        r"""Parallel transport of a tangent vector.

        Closed-form solution for the parallel transport of a tangent vector a
        along the geodesic defined by exp_(base_point)(tangent_vec_b).
        Denoting `tangent_vec_a` by `S`, `base_point` by `A`, let
        `B = Exp_A(tangent_vec_b)` and :math: `E = (BA^{- 1})^({ 1 / 2})`.
        Then the
        parallel transport to `B`is:

        ..math::
                        S' = ESE^T

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim + 1]
            Tangent vector at base point to be transported.
        tangent_vec_b : array-like, shape=[..., dim + 1]
            Tangent vector at base point, initial speed of the geodesic along
            which the parallel transport is computed.
        base_point : array-like, shape=[..., dim + 1]
            Point on the manifold of SPD matrices.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., dim + 1]
            Transported tangent vector at exp_(base_point)(tangent_vec_b).
        """
        end_point = self.exp(tangent_vec_b, base_point)
        inverse_base_point = GeneralLinear.inverse(base_point)
        congruence_mat = Matrices.mul(end_point, inverse_base_point)
        congruence_mat = gs.linalg.sqrtm(congruence_mat)
        return Matrices.congruent(tangent_vec_a, congruence_mat)
Exemplo n.º 15
0
    def setUp(self):
        gs.random.seed(0)
        n = 3
        self.base = SPDMatrices(n)
        self.base_metric = SPDMetricBuresWasserstein(n)
        self.group = SpecialOrthogonal(n)
        self.bundle = FiberBundle(GeneralLinear(n),
                                  base=self.base,
                                  group=self.group)
        self.quotient_metric = QuotientMetric(self.bundle,
                                              ambient_metric=MatricesMetric(
                                                  n, n))

        def submersion(point):
            return GeneralLinear.mul(point, GeneralLinear.transpose(point))

        def tangent_submersion(tangent_vec, base_point):
            product = GeneralLinear.mul(base_point,
                                        GeneralLinear.transpose(tangent_vec))
            return 2 * GeneralLinear.to_symmetric(product)

        def horizontal_lift(tangent_vec, point, base_point=None):
            if base_point is None:
                base_point = submersion(point)
            sylvester = gs.linalg.solve_sylvester(base_point, base_point,
                                                  tangent_vec)
            return GeneralLinear.mul(sylvester, point)

        self.bundle.submersion = submersion
        self.bundle.tangent_submersion = tangent_submersion
        self.bundle.horizontal_lift = horizontal_lift
        self.bundle.lift = gs.linalg.cholesky
Exemplo n.º 16
0
    def random_uniform(self, n_samples=1):
        """Sample random points from a uniform distribution.

        Following [Chikuse03]_, :math: `n_samples * n * k` scalars are sampled
        from a standard normal distribution and reshaped to matrices,
        the projectors on their first k columns follow a uniform distribution.

        Parameters
        ----------
        n_samples : int
            The number of points to sample
            Optional. default: 1.

        Returns
        -------
        projectors : array-like, shape=[..., n, n]
            Points following a uniform distribution.

        References
        ----------
        .. [Chikuse03] Yasuko Chikuse, Statistics on special manifolds,
        New York: Springer-Verlag. 2003, 10.1007/978-0-387-21540-2
        """
        points = gs.random.normal(size=(n_samples, self.n, self.k))
        full_rank = Matrices.mul(Matrices.transpose(points), points)
        projector = Matrices.mul(points, GeneralLinear.inverse(full_rank),
                                 Matrices.transpose(points))
        return projector[0] if n_samples == 1 else projector
Exemplo n.º 17
0
    def metric_matrix(self, base_point=None):
        """Compute inner product matrix at the tangent space at a base point.

        Parameters
        ----------
        base_point : array-like, shape=[..., dim], optional
            Point in the group (the default is identity).

        Returns
        -------
        metric_mat : array-like, shape=[..., dim, dim]
            Metric matrix at base_point.
        """
        if base_point is None:
            return self.metric_mat_at_identity

        base_point = self.group.regularize(base_point)
        jacobian = self.group.jacobian_translation(
            point=base_point, left_or_right=self.left_or_right)

        inv_jacobian = GeneralLinear.inverse(jacobian)
        inv_jacobian_transposed = Matrices.transpose(inv_jacobian)

        metric_mat = Matrices.mul(
            inv_jacobian_transposed, self.metric_mat_at_identity, inv_jacobian)
        return metric_mat
Exemplo n.º 18
0
    def connection_at_identity(self, tangent_vec_a, tangent_vec_b):
        r"""Compute the Levi-Civita connection at identity.

        For two tangent vectors at identity :math: `x,y`, one can associate
        left (respectively right) invariant vector fields :math: `\tilde{x},
        \tilde{y}`. Then the vector :math: `(\nabla_\tilde{x}(\tilde{x}))_{
        Id}` is computed using the lie bracket and the dual adjoint map. This
        is a bilinear map that characterizes the connection [Gallier]_.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., n, n]
            Tangent vector at identity.
        tangent_vec_b : array-like, shape=[..., n, n]
            Tangent vector at identity.

        Returns
        -------
        nabla : array-like, shape=[..., n, n]
            Tangent vector at identity.

        References
        ----------
        .. [Gallier]   Gallier, Jean, and Jocelyn Quaintance. Differential
                       Geometry and Lie Groups: A Computational Perspective.
                       Geonger International Publishing, 2020.
                       https://doi.org/10.1007/978-3-030-46040-2.
        """
        sign = 1. if self.left_or_right == 'left' else -1.
        return sign / 2 * (GeneralLinear.bracket(tangent_vec_a, tangent_vec_b)
                           - self.dual_adjoint(tangent_vec_a, tangent_vec_b)
                           - self.dual_adjoint(tangent_vec_b, tangent_vec_a))
    def transp(self, base_point, end_point, tangent):
        """

        transports a tangent vector at a base_point
        to the tangent space at end_point.
        """

        if self.exact_transport:
            # https://github.com/geomstats/geomstats/blob/master/geomstats/geometry/spd_matrices.py#L613
            inverse_base_point = GeneralLinear.inverse(base_point)
            congruence_mat = GeneralLinear.mul(end_point, inverse_base_point)
            congruence_mat = gs.linalg.sqrtm(congruence_mat.cpu()).to(tangent)
            return GeneralLinear.congruent(tangent, congruence_mat)

        # https://github.com/NicolasBoumal/manopt/blob/master/manopt/manifolds/symfixedrank/sympositivedefinitefactory.m#L181
        return tangent
Exemplo n.º 20
0
    def metric_matrix(self, base_point=None):
        """Compute inner product matrix at the tangent space at a base point.

        Parameters
        ----------
        base_point : array-like, shape=[..., dim], optional
            Point in the group (the default is identity).

        Returns
        -------
        metric_mat : array-like, shape=[..., dim, dim]
            Metric matrix at base_point.
        """
        if self.group.default_point_type == 'matrix':
            raise NotImplementedError(
                'inner_product_matrix not implemented for Lie groups'
                ' whose elements are represented as matrices.')

        if base_point is None:
            base_point = self.group.identity
        else:
            base_point = self.group.regularize(base_point)

        jacobian = self.group.jacobian_translation(
            point=base_point, left_or_right=self.left_or_right)

        inv_jacobian = GeneralLinear.inverse(jacobian)
        inv_jacobian_transposed = Matrices.transpose(inv_jacobian)

        metric_mat = gs.einsum('...ij,...jk->...ik', inv_jacobian_transposed,
                               self.metric_mat_at_identity)
        metric_mat = gs.einsum('...ij,...jk->...ik', metric_mat, inv_jacobian)
        return metric_mat
Exemplo n.º 21
0
    def curvature_at_identity(
            self, tangent_vec_a, tangent_vec_b, tangent_vec_c):
        r"""Compute the curvature at identity.

        For three tangent vectors at identity :math: `x,y,z`,
        the curvature is defined by
        :math: `R(x, y)z = \nabla_{[x,y]}z
        - \nabla_x\nabla_y z + - \nabla_y\nabla_x z`.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., n, n]
            Tangent vector at identity.
        tangent_vec_b : array-like, shape=[..., n, n]
            Tangent vector at identity.
        tangent_vec_c : array-like, shape=[..., n, n]
            Tangent vector at identity.

        Returns
        -------
        curvature : array-like, shape=[..., n, n]
            Tangent vector at identity.
        """
        bracket = GeneralLinear.bracket(tangent_vec_a, tangent_vec_b)
        bracket_term = self.connection_at_identity(bracket, tangent_vec_c)

        left_term = self.connection_at_identity(
            tangent_vec_a, self.connection_at_identity(
                tangent_vec_b, tangent_vec_c))

        right_term = self.connection_at_identity(
            tangent_vec_b, self.connection_at_identity(
                tangent_vec_a, tangent_vec_c))

        return bracket_term - left_term + right_term
Exemplo n.º 22
0
    def test_to_grassmanniann_vectorized(self):
        inf_rots = gs.array([gs.pi * r_z / n for n in [2, 3, 4]])
        rots = GeneralLinear.exp(inf_rots)
        points = Matrices.mul(rots, point1)

        result = Stiefel.to_grassmannian(points)
        expected = gs.array([p_xy, p_xy, p_xy])
        self.assertAllClose(result, expected)
Exemplo n.º 23
0
    def compose(self, point_a, point_b, point_type=None):
        r"""Compose two elements of SE(n).

        Parameters
        ----------
        point_1 : array-like, shape=[n_samples, {dim, [n + 1, n + 1]}]
        point_2 : array-like, shape=[n_samples, {dim, [n + 1, n + 1]}]
        point_type: str, {'vector', 'matrix'}, optional
            default: self.default_point_type

        Equation
        ---------
        (:math: `(R_1, t_1) \\cdot (R_2, t_2) = (R_1 R_2, R_1 t_2 + t_1)`)

        Returns
        -------
        composition : the composition of point_1 and point_2

        """
        rotations = self.rotations
        dim_rotations = rotations.dim

        point_a = self.regularize(point_a, point_type=point_type)
        point_b = self.regularize(point_b, point_type=point_type)

        if point_type == 'vector':
            n_points_a, _ = point_a.shape
            n_points_b, _ = point_b.shape

            if not (point_a.shape == point_b.shape or n_points_a == 1
                    or n_points_b == 1):
                raise ValueError()

            rot_vec_a = point_a[:, :dim_rotations]
            rot_mat_a = rotations.matrix_from_rotation_vector(rot_vec_a)

            rot_vec_b = point_b[:, :dim_rotations]
            rot_mat_b = rotations.matrix_from_rotation_vector(rot_vec_b)

            translation_a = point_a[:, dim_rotations:]
            translation_b = point_b[:, dim_rotations:]

            composition_rot_mat = gs.matmul(rot_mat_a, rot_mat_b)
            composition_rot_vec = rotations.rotation_vector_from_matrix(
                composition_rot_mat)

            composition_translation = gs.einsum(
                '...j,...kj->...k', translation_b, rot_mat_a) + translation_a

            composition = gs.concatenate(
                (composition_rot_vec, composition_translation), axis=-1)
            return self.regularize(composition, point_type=point_type)

        if point_type == 'matrix':
            return GeneralLinear.compose(point_a, point_b)

        raise ValueError('Invalid point_type, expected \'vector\' or '
                         '\'matrix\'.')
Exemplo n.º 24
0
    def _log_translation_transform(self, rot_vec):
        exp_transform = self._exp_translation_transform(rot_vec)

        inv_determinant = .5 / utils.taylor_exp_even_func(
            rot_vec**2, utils.cosc_close_0, order=4)
        transform = gs.einsum('...l, ...jk -> ...jk', inv_determinant,
                              GeneralLinear.transpose(exp_transform))

        return transform
Exemplo n.º 25
0
    def test_integrability_tensor(self):
        mat = self.bundle.total_space.random_point()
        point = self.bundle.submersion(mat)
        tangent_vec = GeneralLinear.to_symmetric(
            self.bundle.total_space.random_point()) / 5

        self.assertRaises(
            NotImplementedError, lambda: self.bundle.integrability_tensor(
                tangent_vec, tangent_vec, point))
Exemplo n.º 26
0
    def random_uniform(self, n_samples=1):
        """Define a log-uniform random sample of SPD matrices."""
        n = self.n
        size = (n_samples, n, n) if n_samples != 1 else (n, n)

        mat = 2 * gs.random.rand(*size) - 1
        spd_mat = GeneralLinear.exp(mat + Matrices.transpose(mat))

        return spd_mat
Exemplo n.º 27
0
    def test_exp(self):
        mat = self.bundle.total_space.random_uniform()
        point = self.bundle.submersion(mat)
        tangent_vec = GeneralLinear.to_symmetric(
            self.bundle.total_space.random_uniform()) / 5

        result = self.quotient_metric.exp(tangent_vec, point)
        expected = self.base_metric.exp(tangent_vec, point)
        self.assertAllClose(result, expected)
Exemplo n.º 28
0
 def _to_lie_algebra(self, tangent_vec):
     """Project vector rotation part onto skew-symmetric matrices."""
     translation_mask = gs.hstack(
         [gs.ones((self.n, ) * 2), 2 * gs.ones((self.n, 1))])
     translation_mask = gs.concatenate(
         [translation_mask, gs.zeros((1, self.n + 1))], axis=0)
     tangent_vec = tangent_vec * gs.where(translation_mask != 0.,
                                          gs.array(1.), gs.array(0.))
     tangent_vec = (tangent_vec - GeneralLinear.transpose(tangent_vec)) / 2.
     return tangent_vec * translation_mask
 def test_horizontal_projection(self, n, vec, mat):
     bundle = self.space(n)
     base = self.base(n)
     horizontal_vec = bundle.horizontal_projection(vec, mat)
     inverse = GeneralLinear.inverse(mat)
     product_1 = Matrices.mul(horizontal_vec, inverse)
     product_2 = Matrices.mul(inverse, horizontal_vec)
     is_horizontal = gs.all(
         base.is_tangent(product_1 + product_2, mat, atol=gs.atol * 10))
     self.assertTrue(is_horizontal)
Exemplo n.º 30
0
 def compose_data(self):
     smoke_data = [
         dict(
             n=2,
             mat1=[[1.0, 0.0], [0.0, 2.0]],
             mat2=[[2.0, 0.0], [0.0, 1.0]],
             expected=2.0 * GeneralLinear(2).identity,
         )
     ]
     return self.generate_tests(smoke_data)