Esempio n. 1
0
    def log(self, point, base_point):
        r"""Compute the Riemannian logarithm of point w.r.t. base_point.

        Given :math:`P, P'` in Gr(n, k) the logarithm from :math:`P`
        to :math:`P` is given by the infinitesimal rotation [Batzies2015]_:

        .. math::

            \omega = \frac 1 2 \log \big((2 P' - 1)(2 P - 1)\big)

        Parameters
        ----------
        point : array-like, shape=[n_samples, n, n]
            Point in the Grassmannian.
        base_point : array-like, shape=[n_samples, n, n]
            Point in the Grassmannian.

        Returns
        -------
        tangent_vec : array-like, shape=[n_samples, n, n]
            Tangent vector at `base_point`.

        References
        ----------
        .. [Batzies2015] Batzies, Hüper, Machado, Leite.
            "Geometric Mean and Geodesic Regression on Grassmannians"
            Linear Algebra and its Applications, 466, 83-101, 2015.
        """
        GLn = GeneralLinear(self.n)
        id_n = GLn.identity()
        sym2 = 2 * point - id_n
        sym1 = 2 * base_point - id_n
        rot = GLn.mul(sym2, sym1)
        return GLn.log(rot) / 2
Esempio n. 2
0
    def log(self, point, base_point=None, **kwargs):
        """Compute logarithm map associated to the canonical metric.

        Log map at `base_point` of `point`. The geodesics of this
        metric correspond to a direct product metric between rotation and
        translation: the translation part is a straight line, while the
        rotation part has constant angular velocity (which corresponds to one-
        parameter subgroups of the rotation group).

        Parameters
        ----------
        point : array-like, shape=[..., n + 1, n + 1]
            Point on the manifold.
        base_point : array-like, shape=[..., n + 1, n + 1]
            Point on the manifold.

        Returns
        -------
        tangent_vec : array-like, shape=[..., n + 1, n + 1]
            Tangent vector at the base point.

        References
        ----------
        [Zefran98]  Zefran, M., V. Kumar, and C.B. Croke.
                    “On the Generation of Smooth Three-Dimensional Rigid Body
                    Motions.” IEEE Transactions on Robotics and Automation 14,
                    no. 4 (August 1998): 576–89.
                    https://doi.org/10.1109/70.704225.
        """
        max_shape = point.shape if point.ndim == 3 else base_point.shape
        rotation_bp = base_point[..., :self.n, :self.n]
        rotation_p = point[..., :self.n, :self.n]
        rotation_log = GeneralLinear.log(rotation_p, rotation_bp)
        translation_log = (point[..., :self.n, self.n] -
                           base_point[..., :self.n, self.n])

        log = homogeneous_representation(rotation_log, translation_log,
                                         max_shape, 0.0)
        return log
Esempio n. 3
0
    def log(self, point, base_point, **kwargs):
        r"""Compute the Riemannian logarithm of point w.r.t. base_point.

        Given :math:`P, P'` in Gr(n, k) the logarithm from :math:`P`
        to :math:`P` is induced by the infinitesimal rotation [Batzies2015]_:

        .. math::

            Y = \frac 1 2 \log \big((2 P' - 1)(2 P - 1)\big)

        The tangent vector :math:`X` at :math:`P`
        is then recovered by :math:`X = [Y, P]`.

        Parameters
        ----------
        point : array-like, shape=[..., n, n]
            Point.
        base_point : array-like, shape=[..., n, n]
            Base point.

        Returns
        -------
        tangent_vec : array-like, shape=[..., n, n]
            Riemannian logarithm, a tangent vector at `base_point`.

        References
        ----------
        .. [Batzies2015] Batzies, Hüper, Machado, Leite.
            "Geometric Mean and Geodesic Regression on Grassmannians"
            Linear Algebra and its Applications, 466, 83-101, 2015.
        """
        GLn = GeneralLinear(self.n)
        id_n = GLn.identity
        id_n, point, base_point = gs.convert_to_wider_dtype(
            [id_n, point, base_point])
        sym2 = 2 * point - id_n
        sym1 = 2 * base_point - id_n
        rot = GLn.compose(sym2, sym1)
        return Matrices.bracket(GLn.log(rot) / 2, base_point)
class TestGeneralLinear(geomstats.tests.TestCase):
    def setUp(self):
        gs.random.seed(1234)
        self.n = 3
        self.n_samples = 2
        self.group = GeneralLinear(n=self.n)

        warnings.simplefilter('ignore', category=ImportWarning)

    def test_belongs_shape(self):
        mat = gs.eye(3)
        result = self.group.belongs(mat)
        self.assertAllClose(gs.shape(result), ())

        mat = gs.ones((3, 3))
        result = self.group.belongs(mat)
        self.assertAllClose(gs.shape(result), ())

    def test_belongs(self):
        mat = gs.eye(3)
        result = self.group.belongs(mat)
        expected = True
        self.assertAllClose(result, expected)

        mat = gs.ones((3, 3))
        result = self.group.belongs(mat)
        expected = False
        self.assertAllClose(result, expected)

    def test_belongs_vectorization_shape(self):
        mats = gs.array([gs.eye(3), gs.ones((3, 3))])
        result = self.group.belongs(mats)
        self.assertAllClose(gs.shape(result), (2, ))

    def test_belongs_vectorization(self):
        mats = gs.array([gs.eye(3), gs.ones((3, 3))])
        result = self.group.belongs(mats)
        expected = gs.array([True, False])
        self.assertAllClose(result, expected)

    def test_random_and_belongs(self):
        point = self.group.random_uniform()
        result = self.group.belongs(point)
        expected = True
        self.assertAllClose(result, expected)

    def test_random_and_belongs_vectorization(self):
        n_samples = 4
        point = self.group.random_uniform(n_samples)
        result = self.group.belongs(point)
        expected = gs.array([True] * n_samples)
        self.assertAllClose(result, expected)

    def test_replace_values(self):
        points = gs.ones((3, 3, 3))
        new_points = gs.zeros((2, 3, 3))
        indcs = [True, False, True]
        update = self.group._replace_values(points, new_points, indcs)
        self.assertAllClose(
            update,
            gs.stack([gs.zeros((3, 3)),
                      gs.ones((3, 3)),
                      gs.zeros((3, 3))]))

    def test_compose(self):
        mat1 = gs.array([[1., 0.], [0., 2.]])
        mat2 = gs.array([[2., 0.], [0., 1.]])
        result = self.group.compose(mat1, mat2)
        expected = 2. * GeneralLinear(2).identity
        self.assertAllClose(result, expected)

    def test_inv(self):
        mat_a = gs.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 10.]])
        imat_a = 1. / 3. * gs.array([[-2., -4., 3.], [-2., 11., -6.],
                                     [3., -6., 3.]])
        expected = imat_a
        result = self.group.inverse(mat_a)
        self.assertAllClose(result, expected)

    def test_inv_vectorized(self):
        mat_a = gs.array([[0., 1., 0.], [1., 0., 0.], [0., 0., 1.]])
        mat_b = -gs.eye(3, 3)
        result = self.group.inverse(gs.array([mat_a, mat_b]))
        expected = gs.array([mat_a, mat_b])
        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    def test_log_and_exp(self):
        point = 5 * gs.eye(self.n)
        group_log = self.group.log(point)

        result = self.group.exp(group_log)
        expected = point
        self.assertAllClose(result, expected)

    def test_exp_vectorization(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])

        expected = gs.array([[[7.38905609, 0., 0.], [0., 20.0855369, 0.],
                              [0., 0., 54.5981500]],
                             [[2.718281828, 0., 0.], [0., 148.413159, 0.],
                              [0., 0., 403.42879349]]])
        result = self.group.exp(point)
        self.assertAllClose(result, expected, rtol=1e-3)

    @geomstats.tests.np_and_tf_only
    def test_log_vectorization(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])
        expected = gs.array([[[0.693147180, 0., 0.], [0., 1.09861228866, 0.],
                              [0., 0., 1.38629436]],
                             [[0., 0., 0.], [0., 1.609437912, 0.],
                              [0., 0., 1.79175946]]])
        result = self.group.log(point)
        self.assertAllClose(result, expected, atol=1e-4)

    @geomstats.tests.np_and_tf_only
    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)

    @geomstats.tests.np_and_tf_only
    def test_expm_and_logm_vectorization_symmetric(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])
        result = self.group.exp(self.group.log(point))
        expected = point
        self.assertAllClose(result, expected)
class TestGeneralLinear(geomstats.tests.TestCase):
    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)

    def test_belongs_shape(self):
        mat = gs.eye(3)
        result = self.group.belongs(mat)
        self.assertAllClose(gs.shape(result), ())

        mat = gs.ones((3, 3))
        result = self.group.belongs(mat)
        self.assertAllClose(gs.shape(result), ())

    def test_belongs(self):
        mat = gs.eye(3)
        result = self.group.belongs(mat)
        expected = True
        self.assertAllClose(result, expected)

        mat = gs.ones((3, 3))
        result = self.group.belongs(mat)
        expected = False
        self.assertAllClose(result, expected)

        mat = gs.ones(3)
        result = self.group.belongs(mat)
        expected = False
        self.assertAllClose(result, expected)

    def test_belongs_vectorization_shape(self):
        mats = gs.array([gs.eye(3), gs.ones((3, 3))])
        result = self.group.belongs(mats)
        self.assertAllClose(gs.shape(result), (2, ))

    def test_belongs_vectorization(self):
        mats = gs.array([gs.eye(3), gs.ones((3, 3))])
        result = self.group.belongs(mats)
        expected = gs.array([True, False])
        self.assertAllClose(result, expected)

    def test_random_and_belongs(self):
        for group in [self.group, self.group_pos]:
            point = group.random_point()
            result = group.belongs(point)
            self.assertTrue(result)

    def test_random_and_belongs_vectorization(self):
        n_samples = 4
        expected = gs.array([True] * n_samples)
        for group in [self.group, self.group_pos]:
            point = group.random_point(n_samples)
            result = group.belongs(point)
            self.assertAllClose(result, expected)

    def test_compose(self):
        mat1 = gs.array([[1., 0.], [0., 2.]])
        mat2 = gs.array([[2., 0.], [0., 1.]])
        result = self.group.compose(mat1, mat2)
        expected = 2. * GeneralLinear(2).identity
        self.assertAllClose(result, expected)

    def test_inv(self):
        mat_a = gs.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 10.]])
        imat_a = 1. / 3. * gs.array([[-2., -4., 3.], [-2., 11., -6.],
                                     [3., -6., 3.]])
        expected = imat_a
        result = self.group.inverse(mat_a)
        self.assertAllClose(result, expected)

    def test_inv_vectorized(self):
        mat_a = gs.array([[0., 1., 0.], [1., 0., 0.], [0., 0., 1.]])
        mat_b = -gs.eye(3, 3)
        result = self.group.inverse(gs.array([mat_a, mat_b]))
        expected = gs.array([mat_a, mat_b])
        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    def test_log_and_exp(self):
        point = 5 * gs.eye(self.n)
        group_log = self.group.log(point)

        result = self.group.exp(group_log)
        expected = point
        self.assertAllClose(result, expected)

    def test_exp_vectorization(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])

        expected = gs.array([[[7.38905609, 0., 0.], [0., 20.0855369, 0.],
                              [0., 0., 54.5981500]],
                             [[2.718281828, 0., 0.], [0., 148.413159, 0.],
                              [0., 0., 403.42879349]]])

        expected = gs.cast(expected, gs.float64)
        point = gs.cast(point, gs.float64)

        result = self.group.exp(point)
        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    def test_log_vectorization(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])
        expected = gs.array([[[0.693147180, 0., 0.], [0., 1.09861228866, 0.],
                              [0., 0., 1.38629436]],
                             [[0., 0., 0.], [0., 1.609437912, 0.],
                              [0., 0., 1.79175946]]])
        result = self.group.log(point)
        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    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.)]])
        identity = GeneralLinear(2).identity

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

        result = path(time)
        expected = gs.array([identity, sqrt, point])
        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    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)

    @geomstats.tests.np_and_tf_only
    def test_expm_and_logm_vectorization_symmetric(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])
        result = self.group.exp(self.group.log(point))
        expected = point
        self.assertAllClose(result, expected)

    def test_projection_and_belongs(self):
        shape = (self.n_samples, self.n, self.n)
        result = helper.test_projection_and_belongs(self.group, shape)
        for res in result:
            self.assertTrue(res)

    def test_projection_and_belongs_pos(self):
        shape = (self.n_samples, self.n, self.n)
        result = helper.test_projection_and_belongs(self.group_pos, shape)
        for res in result:
            self.assertTrue(res)
Esempio n. 6
0
class TestGeneralLinearMethods(geomstats.tests.TestCase):
    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)

    @geomstats.tests.np_only
    def test_belongs(self):
        """
        A rotation matrix belongs to the matrix Lie group
        of invertible matrices.
        """
        rot_vec = gs.array([0.2, -0.1, 0.1])
        rot_mat = self.so3_group.matrix_from_rotation_vector(rot_vec)
        result = self.group.belongs(rot_mat)
        expected = gs.array([[True]])

        self.assertAllClose(result, expected)

    def test_compose(self):
        # 1. Composition by identity, on the right
        # Expect the original transformation
        rot_vec = gs.array([0.2, -0.1, 0.1])
        mat = self.so3_group.matrix_from_rotation_vector(rot_vec)

        result = self.group.compose(mat, self.group.identity)
        expected = mat
        expected = helper.to_matrix(mat)

        self.assertAllClose(result, expected)

        # 2. Composition by identity, on the left
        # Expect the original transformation
        rot_vec = gs.array([0.2, 0.1, -0.1])
        mat = self.so3_group.matrix_from_rotation_vector(rot_vec)

        result = self.group.compose(self.group.identity, mat)
        expected = mat

        self.assertAllClose(result, expected)

    def test_inverse(self):
        mat = gs.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 10.]])
        result = self.group.inverse(mat)
        expected = 1. / 3. * gs.array([[-2., -4., 3.], [-2., 11., -6.],
                                       [3., -6., 3.]])
        expected = helper.to_matrix(expected)

        self.assertAllClose(result, expected)

    def test_compose_and_inverse(self):
        # 1. Compose transformation by its inverse on the right
        # Expect the group identity
        rot_vec = gs.array([0.2, 0.1, 0.1])
        mat = self.so3_group.matrix_from_rotation_vector(rot_vec)
        inv_mat = self.group.inverse(mat)

        result = self.group.compose(mat, inv_mat)
        expected = self.group.identity
        expected = helper.to_matrix(expected)

        self.assertAllClose(result, expected)

        # 2. Compose transformation by its inverse on the left
        # Expect the group identity
        rot_vec = gs.array([0.7, 0.1, 0.1])
        mat = self.so3_group.matrix_from_rotation_vector(rot_vec)
        inv_mat = self.group.inverse(mat)

        result = self.group.compose(inv_mat, mat)
        expected = self.group.identity
        expected = helper.to_matrix(expected)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    def test_group_log_and_exp(self):
        point = 5 * gs.eye(self.n)

        group_log = self.group.log(point)
        result = self.group.exp(group_log)
        expected = point
        expected = helper.to_matrix(expected)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_tf_only
    def test_group_exp_vectorization(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])

        expected = gs.array([[[7.38905609, 0., 0.], [0., 20.0855369, 0.],
                              [0., 0., 54.5981500]],
                             [[2.718281828, 0., 0.], [0., 148.413159, 0.],
                              [0., 0., 403.42879349]]])

        result = self.group.exp(point)

        self.assertAllClose(result, expected, rtol=1e-3)

    @geomstats.tests.np_and_tf_only
    def test_group_log_vectorization(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])

        expected = gs.array([[[0.693147180, 0., 0.], [0., 1.09861228866, 0.],
                              [0., 0., 1.38629436]],
                             [[0., 0., 0.], [0., 1.609437912, 0.],
                              [0., 0., 1.79175946]]])

        result = self.group.log(point)

        self.assertAllClose(result, expected, atol=1e-4)

    @geomstats.tests.np_and_tf_only
    def test_expm_and_logm_vectorization_symmetric(self):
        point = gs.array([[[2., 0., 0.], [0., 3., 0.], [0., 0., 4.]],
                          [[1., 0., 0.], [0., 5., 0.], [0., 0., 6.]]])
        result = self.group.exp(self.group.log(point))
        expected = point

        self.assertAllClose(result, expected)
Esempio n. 7
0
    def log_from_identity(self, point, point_type=None):
        """Compute the group logarithm of the point at the identity.

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

        Returns
        -------
        group_log: array-like, shape=[n_samples, {dim, [n + 1, n + 1]}]
            the group logarithm in the Lie algbra
        """
        point = self.regularize(point, point_type=point_type)

        rotations = self.rotations
        dim_rotations = rotations.dim

        if point_type == 'vector':
            rot_vec = point[:, :dim_rotations]
            angle = gs.linalg.norm(rot_vec, axis=1)
            angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

            translation = point[:, dim_rotations:]

            skew_rot_vec = rotations.skew_matrix_from_vector(rot_vec)
            sq_skew_rot_vec = gs.matmul(skew_rot_vec, skew_rot_vec)

            mask_close_0 = gs.isclose(angle, 0.)
            mask_close_pi = gs.isclose(angle, gs.pi)
            mask_else = ~mask_close_0 & ~mask_close_pi

            mask_close_0_float = gs.cast(mask_close_0, gs.float32)
            mask_close_pi_float = gs.cast(mask_close_pi, gs.float32)
            mask_else_float = gs.cast(mask_else, gs.float32)

            mask_0 = gs.isclose(angle, 0., atol=1e-6)
            mask_0_float = gs.cast(mask_0, gs.float32)
            angle += mask_0_float * gs.ones_like(angle)

            coef_1 = -0.5 * gs.ones_like(angle)
            coef_2 = gs.zeros_like(angle)

            coef_2 += mask_close_0_float * (1. / 12. + angle**2 / 720. +
                                            angle**4 / 30240. +
                                            angle**6 / 1209600.)

            delta_angle = angle - gs.pi
            coef_2 += mask_close_pi_float * (
                1. / PI2 + (PI2 - 8.) * delta_angle / (4. * PI3) -
                ((PI2 - 12.) * delta_angle**2 / (4. * PI4)) +
                ((-192. + 12. * PI2 + PI4) * delta_angle**3 / (48. * PI5)) -
                ((-240. + 12. * PI2 + PI4) * delta_angle**4 / (48. * PI6)) +
                ((-2880. + 120. * PI2 + 10. * PI4 + PI6) * delta_angle**5 /
                 (480. * PI7)) -
                ((-3360 + 120. * PI2 + 10. * PI4 + PI6) * delta_angle**6 /
                 (480. * PI8)))

            psi = 0.5 * angle * gs.sin(angle) / (1 - gs.cos(angle))
            coef_2 += mask_else_float * (1 - psi) / (angle**2)

            n_points, _ = point.shape
            log_translation = gs.zeros((n_points, self.n))
            for i in range(n_points):
                translation_i = translation[i]
                term_1_i = coef_1[i] * gs.dot(translation_i,
                                              gs.transpose(skew_rot_vec[i]))
                term_2_i = coef_2[i] * gs.dot(translation_i,
                                              gs.transpose(sq_skew_rot_vec[i]))
                mask_i_float = gs.get_mask_i_float(i, n_points)
                log_translation += gs.outer(
                    mask_i_float, translation_i + term_1_i + term_2_i)

            return gs.concatenate([rot_vec, log_translation], axis=1)

        if point_type == 'matrix':
            return GeneralLinear.log(point)

        raise ValueError('Invalid point_type, expected \'vector\' or '
                         '\'matrix\'.')