Ejemplo n.º 1
0
    def exp(self, tangent_vec, base_point, n_steps=N_STEPS, step='euler'):
        """Exponential map associated to the affine connection.

        Exponential map at base_point of tangent_vec computed by integration
        of the geodesic equation (initial value problem), using the
        christoffel symbols

        Parameters
        ----------
        tangent_vec : array-like, shape=[n_samples, dimension]
            Tangent vector at the base point.
        base_point : array-like, shape=[n_samples, dimension]
            Point on the manifold.
        n_steps : int
            The number of discrete time steps to take in the integration.
        step : str, {'euler', 'rk4'}
            The numerical scheme to use for integration.

        Returns
        -------
        exp : array-like, shape=[n_samples, dimension]
            Point on the manifold.
        """
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)
        initial_state = (base_point, tangent_vec)
        flow, _ = integrate(self.geodesic_equation,
                            initial_state,
                            n_steps=n_steps,
                            step=step)
        return flow[-1]
Ejemplo n.º 2
0
    def exp(self, tangent_vec, base_point, n_steps=N_STEPS, step='euler',
            point_type=None):
        """Exponential map associated to the affine connection.

        Exponential map at base_point of tangent_vec computed by integration
        of the geodesic equation (initial value problem), using the
        christoffel symbols

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim]
            Tangent vector at the base point.
        base_point : array-like, shape=[..., dim]
            Point on the manifold.
        n_steps : int
            Number of discrete time steps to take in the integration.
            Optional, default: N_STEPS.
        step : str, {'euler', 'rk4'}
            The numerical scheme to use for integration.
            Optional, default: 'euler'.
        point_type : str, {'vector', 'matrix'}
            Type of representation used for points.
            Optional, default: None.

        Returns
        -------
        exp : array-like, shape=[..., dim]
            Point on the manifold.
        """
        initial_state = (base_point, tangent_vec)
        flow, _ = integrate(
            self.geodesic_equation, initial_state, n_steps=n_steps, step=step)

        exp = flow[-1]
        return exp
Ejemplo n.º 3
0
    def test_integrator(self):
        initial_state = self.euclidean.random_point(2)

        def function(_, velocity):
            return velocity, gs.zeros_like(velocity)

        for step in ['euler', 'rk4']:
            flow, _ = integrator.integrate(function, initial_state, step=step)
            result = flow[-1]
            expected = initial_state[0] + initial_state[1]

            self.assertAllClose(result, expected)
Ejemplo n.º 4
0
    def test_integrator(self):
        initial_state = self.euclidean.random_point(2)

        def function(state, _time):
            _, velocity = state
            return gs.stack([velocity, gs.zeros_like(velocity)])

        for step in ["euler", "rk2", "rk4"]:
            flow = integrator.integrate(function, initial_state, step=step)
            result = flow[-1][0]
            expected = initial_state[0] + initial_state[1]

            self.assertAllClose(result, expected)
Ejemplo n.º 5
0
    def parallel_transport(
        self,
        tangent_vec_a,
        base_point,
        tangent_vec_b=None,
        end_point=None,
        n_steps=10,
        step="rk4",
    ):
        r"""Compute the parallel transport of a tangent vec along a geodesic.

        Approximation of the solution of the parallel transport of a tangent
        vector a along the geodesic defined by :math:`t \mapsto exp_{(
        base\_point)}(t* tangent\_vec\_b)`. The parallel transport equation is
        formulated in this case in [TP2021]_.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., n, n]
            Tangent vector at `base_point` to transport.
        tangent_vec_b : array-like, shape=[..., n, n]
            Tangent vector ar `base_point`, initial velocity of the geodesic to
            transport along.
        base_point : array-like, shape=[..., n, n]
            Initial point of the geodesic.
        end_point : array-like, shape=[..., n, n]
            Point to transport to.
            Optional, default: None.
        n_steps : int
            Number of steps to use to approximate the solution of the
            ordinary differential equation.
            Optional, default: 100
        step : str, {'euler', 'rk2', 'rk4'}
            Scheme to use in the integration scheme.
            Optional, default: 'rk4'.

        Returns
        -------
        transported :  array-like, shape=[..., n, n]
            Transported tangent vector at `exp_(base_point)(tangent_vec_b)`.

        References
        ----------
        .. [TP2021] Yann Thanwerdas, Xavier Pennec. O(n)-invariant Riemannian
            metrics on SPD matrices. 2021. ⟨hal-03338601v2⟩

        See Also
        --------
        Integration module: geomstats.integrator
        """
        if end_point is None:
            end_point = self.exp(tangent_vec_b, base_point)

        horizontal_lift_a = gs.linalg.solve_sylvester(base_point, base_point,
                                                      tangent_vec_a)

        square_root_bp, inverse_square_root_bp = SymmetricMatrices.powerm(
            base_point, [0.5, -0.5])
        end_point_lift = Matrices.mul(square_root_bp, end_point,
                                      square_root_bp)
        square_root_lift = SymmetricMatrices.powerm(end_point_lift, 0.5)

        horizontal_velocity = gs.matmul(inverse_square_root_bp,
                                        square_root_lift)
        partial_horizontal_velocity = Matrices.mul(horizontal_velocity,
                                                   square_root_bp)
        partial_horizontal_velocity += Matrices.transpose(
            partial_horizontal_velocity)

        def force(state, time):
            horizontal_geodesic_t = (
                1 - time) * square_root_bp + time * horizontal_velocity
            geodesic_t = ((1 - time)**2 * base_point + time *
                          (1 - time) * partial_horizontal_velocity +
                          time**2 * end_point)

            align = Matrices.mul(
                horizontal_geodesic_t,
                Matrices.transpose(horizontal_velocity - square_root_bp),
                state,
            )
            right = align + Matrices.transpose(align)
            return gs.linalg.solve_sylvester(geodesic_t, geodesic_t, -right)

        flow = integrate(force, horizontal_lift_a, n_steps=n_steps, step=step)
        final_align = Matrices.mul(end_point, flow[-1])
        return final_align + Matrices.transpose(final_align)
Ejemplo n.º 6
0
    def exp(self, tangent_vec, base_point=None, n_steps=10, step='rk4',
            **kwargs):
        r"""Compute Riemannian exponential of tan. vector wrt to base point.

        If :math: `\gamma` is a geodesic, then it satisfies the
        Euler-Poincare equation [Kolev]_:
        .. math:

                        \dot{\gamma}(t) = (dL_{\gamma(t)}) X(t)
                        \dot{X}(t) = ad^*_{X(t)}X(t)

        where :math: `ad^*` is the dual adjoint map with respect to the
        metric. For a right-invariant metric, :math: `dR` is used instead of
        :math: `dL` and :math: `ad^*` is replaced by :math: `-ad^*`. The
        exponential map is approximated by numerical integration
        of this equation, with initial conditions :math: `\dot{\gamma}(0)`
        given by the argument `tangent_vec` and :math: `\gamma(0)` by
        `base_point`. A Runge-Kutta scheme of order 2 or 4 is used for
        integration.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., n, n]
            Tangent vector at a base point.
        base_point : array-like, shape=[..., n, n]
            Point in the group.
            Optional, defaults to identity if None.
        n_steps : int,
            Number of integration steps.
            Optional, default : 15.
        step : str, {'euler', 'rk2', 'rk4'}
            Scheme to use in the integration.
            Optional, default : 'rk4'.

        Returns
        -------
        exp : array-like, shape=[..., n, n]
            Point in the group equal to the Riemannian exponential
            of tangent_vec at the base point.

        References
        ----------
        https://en.wikipedia.org/wiki/Runge–Kutta_methods

        .. [Kolev]   Kolev, Boris. “Lie Groups and Mechanics: An Introduction.”
                     Journal of Nonlinear Mathematical Physics 11, no. 4, 2004:
                     480–98. https://doi.org/10.2991/jnmp.2004.11.4.5.
        """
        group = self.group
        basis = self.orthonormal_basis(self.lie_algebra.basis)
        sign = 1. if self.left_or_right == 'left' else -1.

        def lie_acceleration(point, vector):
            """Compute the right-hand side of the geodesic equation."""
            velocity = self.group.tangent_translation_map(
                point, left_or_right=self.left_or_right)(vector)
            coefficients = gs.array([self.structure_constant(
                vector, basis_vector, vector) for basis_vector in basis])
            acceleration = gs.einsum('i...,ijk->...jk', coefficients, basis)
            return velocity, sign * acceleration

        if base_point is None:
            base_point = group.identity
            left_angular_vel = tangent_vec
        else:
            left_angular_vel = self.group.tangent_translation_map(
                base_point,
                left_or_right=self.left_or_right, inverse=True)(tangent_vec)
        initial_state = (base_point, group.regularize(left_angular_vel))
        flow, _ = integrate(lie_acceleration, initial_state, n_steps=n_steps,
                            step=step, **kwargs)
        return flow[-1]
Ejemplo n.º 7
0
    def parallel_transport(
        self,
        tangent_vec,
        base_point,
        direction=None,
        end_point=None,
        n_steps=100,
        step="rk4",
    ):
        r"""Compute the parallel transport of a tangent vec along a geodesic.

        Approximation of the solution of the parallel transport of a tangent
        vector a along the geodesic between two points `base_point` and `end_point`
        or alternatively defined by :math:`t\mapsto exp_{(base\_point)}(
        t*direction)`

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., k_landmarks, m_ambient]
            Tangent vector at `base_point` to transport.
        base_point : array-like, shape=[..., k_landmarks, m_ambient]
            Initial point of the geodesic to transport along.
        direction : array-like, shape=[..., k_landmarks, m_ambient]
            Tangent vector ar `base_point`, initial velocity of the geodesic to
            transport  along.
            Optional, default: None.
        end_point : array-like, shape=[..., k_landmarks, m_ambient]
            Point to transport to. Unused if `tangent_vec_b` is given.
            Optional, default: None.
        n_steps : int
            Number of steps to use to approximate the solution of the
            ordinary differential equation.
            Optional, default: 100.
        step : str, {'euler', 'rk2', 'rk4'}
            Scheme to use in the integration scheme.
            Optional, default: 'rk4'.

        Returns
        -------
        transported :  array-like, shape=[..., k_landmarks, m_ambient]
            Transported tangent vector at `exp_(base_point)(tangent_vec_b)`.

        References
        ----------
        .. [GMTP21]   Guigui, Nicolas, Elodie Maignant, Alain Trouvé, and Xavier
                      Pennec. “Parallel Transport on Kendall Shape Spaces.”
                      5th conference on Geometric Science of Information,
                      Paris 2021. Lecture Notes in Computer Science.
                      Springer, 2021. https://hal.inria.fr/hal-03160677.

        See Also
        --------
        Integration module: geomstats.integrator
        """
        if direction is None:
            if end_point is not None:
                direction = self.log(end_point, base_point)
            else:
                raise ValueError(
                    "Either an end_point or a tangent_vec_b must be given to define the"
                    " geodesic along which to transport.")
        horizontal_a = self.fiber_bundle.horizontal_projection(
            tangent_vec, base_point)
        horizontal_b = self.fiber_bundle.horizontal_projection(
            direction, base_point)

        def force(state, time):
            gamma_t = self.ambient_metric.exp(time * horizontal_b, base_point)
            speed = self.ambient_metric.parallel_transport(
                horizontal_b, base_point, time * horizontal_b)
            coef = self.inner_product(speed, state, gamma_t)
            normal = gs.einsum("...,...ij->...ij", coef, gamma_t)

            align = gs.matmul(Matrices.transpose(speed), state)
            right = align - Matrices.transpose(align)
            left = gs.matmul(Matrices.transpose(gamma_t), gamma_t)
            skew_ = gs.linalg.solve_sylvester(left, left, right)
            vertical_ = -gs.matmul(gamma_t, skew_)
            return vertical_ - normal

        flow = integrate(force, horizontal_a, n_steps=n_steps, step=step)
        return flow[-1]
Ejemplo n.º 8
0
    def parallel_transport(self,
                           tangent_vec_a,
                           tangent_vec_b,
                           base_point,
                           n_steps=100,
                           step="rk4"):
        r"""Compute the parallel transport of a tangent vec along a geodesic.

        Approximation of the solution of the parallel transport of a tangent
        vector a along the geodesic defined by :math:`t \mapsto exp_(
        base_point)(t* tangent_vec_b)`.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., k, m]
            Tangent vector at `base_point` to transport.
        tangent_vec_b : array-like, shape=[..., k, m]
            Tangent vector ar `base_point`, initial velocity of the geodesic to
            transport  along.
        base_point : array-like, shape=[..., k, m]
            Initial point of the geodesic.
        n_steps : int
            Number of steps to use to approximate the solution of the
            ordinary differential equation.
            Optional, default: 100
        step : str, {'euler', 'rk2', 'rk4'}
            Scheme to use in the integration scheme.
            Optional, default: 'rk4'.

        Returns
        -------
        transported :  array-like, shape=[..., k, m]
            Transported tangent vector at `exp_(base_point)(tangent_vec_b)`.

        References
        ----------
        [GMTP21]_   Guigui, Nicolas, Elodie Maignant, Alain Trouvé, and Xavier
                    Pennec. “Parallel Transport on Kendall Shape Spaces.”
                    5th conference on Geometric Science of Information,
                    Paris 2021. Lecture Notes in Computer Science.
                    Springer, 2021. https://hal.inria.fr/hal-03160677.

        See Also
        --------
        Integration module: geomstats.integrator
        """
        horizontal_a = self.fiber_bundle.horizontal_projection(
            tangent_vec_a, base_point)
        horizontal_b = self.fiber_bundle.horizontal_projection(
            tangent_vec_b, base_point)

        def force(state, time):
            gamma_t = self.ambient_metric.exp(time * horizontal_b, base_point)
            speed = self.ambient_metric.parallel_transport(
                horizontal_b, time * horizontal_b, base_point)
            coef = self.inner_product(speed, state, gamma_t)
            normal = gs.einsum("...,...ij->...ij", coef, gamma_t)

            align = gs.matmul(Matrices.transpose(speed), state)
            right = align - Matrices.transpose(align)
            left = gs.matmul(Matrices.transpose(gamma_t), gamma_t)
            skew_ = gs.linalg.solve_sylvester(left, left, right)
            vertical_ = -gs.matmul(gamma_t, skew_)
            return vertical_ - normal

        flow = integrate(force, horizontal_a, n_steps=n_steps, step=step)
        return flow[-1]
Ejemplo n.º 9
0
    def parallel_transport(
        self,
        tangent_vec,
        base_point,
        direction=None,
        end_point=None,
        n_steps=10,
        step="rk4",
        return_endpoint=False,
    ):
        r"""Compute the parallel transport of a tangent vec along a geodesic.

        Approximate solution for the parallel transport of a tangent vector a
        along the geodesic between two points `base_point` and `end_point`
        or alternatively defined by :math:`t\mapsto exp_(base_point)(
        t*direction)`. The parallel transport equation is written entirely
        in the Lie algebra and solved with an integration scheme.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., n, n]
            Tangent vector at base point to be transported.
        base_point : array-like, shape=[..., n, n]
            Point on the manifold.
        direction : array-like, shape=[..., n, n]
            Tangent vector at base point, along which the parallel transport
            is computed.
            Optional, default: None
        end_point : array-like, shape=[..., n, n]
            Point on the manifold. Point to transport to.
            Unused if `tangent_vec_b` is given
            Optional, default: None
        n_steps : int
            Number of integration steps to take.
            Optional, default : 10.
        step : str, {'euler', 'rk2', 'rk4'}
            Scheme to use for the approximation of the solution of the ODE
            Optional, default : rk4
        return_endpoint : bool
            Whether the end-point of the geodesic should be returned.
            Optional, default : False.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., n, n]
            Transported tangent vector at `exp_(base_point)(tangent_vec_b)`.
        end_point : array-like, shape=[..., n, n]
            `exp_(base_point)(tangent_vec_b)`, only returned if
            `return_endpoint` is set to `True`.

        See Also
        --------
        geomstats.integrator

        References
        ----------
        [GP21]_    Guigui, Nicolas, and Xavier Pennec. “A Reduced Parallel
                   Transport Equation on Lie Groups with a Left-Invariant
                   Metric.” 5th conference on Geometric Science of Information,
                   Paris 2021. Springer. Lecture Notes in Computer Science.
                   https://hal.inria.fr/hal-03154318.
        """
        if direction is None:
            if end_point is not None:
                tangent_vec_b_ = self.log(end_point, base_point)
            else:
                raise ValueError(
                    "Either an end_point or a tangent_vec_b must be given to define the"
                    " geodesic along which to transport."
                )
        else:
            tangent_vec_b_ = direction

        group = self.group
        translation_map = group.tangent_translation_map(
            base_point, left_or_right=self.left_or_right, inverse=True
        )
        left_angular_vel_a = group.to_tangent(translation_map(tangent_vec))
        left_angular_vel_b = group.to_tangent(translation_map(tangent_vec_b_))

        def acceleration(state, time):
            """Compute the right-hand-side of the parallel transport eq."""
            omega, zeta = state[1:]
            gam_dot, omega_dot = self.geodesic_equation(state[:2], time)
            zeta_dot = -self.connection_at_identity(omega, zeta)
            return gs.stack([gam_dot, omega_dot, zeta_dot])

        if (base_point.ndim == 2 or base_point.shape[0] == 1) and (
            3 in (tangent_vec.ndim, tangent_vec_b_.ndim)
        ):
            n_sample = (
                tangent_vec.shape[0]
                if tangent_vec.ndim == 3
                else tangent_vec_b_.shape[0]
            )
            base_point = gs.stack([base_point] * n_sample)

        initial_state = gs.stack([base_point, left_angular_vel_b, left_angular_vel_a])
        flow = integrate(acceleration, initial_state, n_steps=n_steps, step=step)
        gamma, _, zeta_t = flow[-1]
        transported = group.tangent_translation_map(
            gamma, left_or_right=self.left_or_right, inverse=False
        )(zeta_t)
        return (transported, gamma) if return_endpoint else transported