Example #1
0
    def log(self, point, base_point, **kwargs):
        """Compute the affine-invariant logarithm map.

        Compute the Riemannian logarithm at point base_point,
        of point wrt the metric defined in inner_product.
        This gives a tangent vector at point base_point.

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

        Returns
        -------
        log : array-like, shape=[..., n, n]
            Riemannian logarithm of point at base_point.
        """
        power_affine = self.power_affine

        if power_affine == 1:
            powers = SymmetricMatrices.powerm(base_point, [1. / 2, -1. / 2])
            log = self._aux_log(point, powers[0], powers[1])
        else:
            power_point = SymmetricMatrices.powerm(point, power_affine)
            powers = SymmetricMatrices.powerm(
                base_point, [power_affine / 2, -power_affine / 2])
            log = self._aux_log(
                power_point, powers[0], powers[1])
            log = self.space.inverse_differential_power(
                power_affine, log, base_point)
        return log
Example #2
0
    def log(self, point, base_point, **kwargs):
        """Compute the Bures-Wasserstein logarithm map.

        Compute the Riemannian logarithm at point base_point,
        of point wrt the Bures-Wasserstein metric.
        This gives a tangent vector at point base_point.

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

        Returns
        -------
        log : array-like, shape=[..., n, n]
            Riemannian logarithm.
        """
        # compute B^1/2(B^-1/2 A B^-1/2)B^-1/2 instead of sqrtm(AB^-1)
        sqrt_bp, inv_sqrt_bp = SymmetricMatrices.powerm(
            base_point, [0.5, -0.5])
        pdt = SymmetricMatrices.powerm(Matrices.mul(sqrt_bp, point, sqrt_bp),
                                       0.5)
        sqrt_product = Matrices.mul(sqrt_bp, pdt, inv_sqrt_bp)
        transp_sqrt_product = Matrices.transpose(sqrt_product)
        return sqrt_product + transp_sqrt_product - 2 * base_point
Example #3
0
    def log(self, point, base_point, **kwargs):
        """Compute the Euclidean logarithm map.

        Compute the Euclidean logarithm at point base_point, of point.
        This gives a tangent vector at point base_point.

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

        Returns
        -------
        log : array-like, shape=[..., n, n]
            Euclidean logarithm.
        """
        power_euclidean = self.power_euclidean

        if power_euclidean == 1:
            log = point - base_point
        else:
            log = SPDMatrices.inverse_differential_power(
                power_euclidean,
                SymmetricMatrices.powerm(point, power_euclidean) -
                SymmetricMatrices.powerm(base_point, power_euclidean),
                base_point,
            )

        return log
Example #4
0
    def log(self, point, base_point):
        """Compute the affine-invariant logarithm map.

        Compute the Riemannian logarithm at point base_point,
        of point wrt the metric defined in inner_product.
        This gives a tangent vector at point base_point.

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

        Returns
        -------
        log : 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)
            log = self._aux_log(point, sqrt_base_point, inv_sqrt_base_point)
        else:
            power_point = SymmetricMatrices.powerm(point, power_affine)
            power_sqrt_base_point = SymmetricMatrices.powerm(
                base_point, power_affine / 2)
            power_inv_sqrt_base_point = gs.linalg.inv(power_sqrt_base_point)
            log = self._aux_log(power_point, power_sqrt_base_point,
                                power_inv_sqrt_base_point)
            log = self.space.inverse_differential_power(
                power_affine, log, base_point)
        return log
Example #5
0
    def exp(self, tangent_vec, base_point, **kwargs):
        """Compute the Euclidean exponential map.

        Compute the Euclidean exponential at point base_point
        of tangent vector tangent_vec.
        This gives a symmetric positive definite matrix.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., n, n]
            Tangent vector at base point.
        base_point : array-like, shape=[..., n, n]
            Base point.

        Returns
        -------
        exp : array-like, shape=[..., n, n]
            Euclidean exponential.
        """
        power_euclidean = self.power_euclidean

        if power_euclidean == 1:
            exp = tangent_vec + base_point
        else:
            exp = SymmetricMatrices.powerm(
                SymmetricMatrices.powerm(base_point, power_euclidean) +
                SPDMatrices.differential_power(power_euclidean, tangent_vec,
                                               base_point),
                1 / power_euclidean,
            )
        return exp
Example #6
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
Example #7
0
    def exp_domain(tangent_vec, base_point):
        """Compute the domain of the Euclidean exponential map.

        Compute the real interval of time where the Euclidean geodesic starting
        at point `base_point` in direction `tangent_vec` is defined.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., n, n]
            Tangent vector at base point.
        base_point : array-like, shape=[..., n, n]
            Base point.

        Returns
        -------
        exp_domain : array-like, shape=[..., 2]
            Interval of time where the geodesic is defined.
        """
        invsqrt_base_point = SymmetricMatrices.powerm(base_point, -.5)

        reduced_vec = gs.matmul(invsqrt_base_point, tangent_vec)
        reduced_vec = gs.matmul(reduced_vec, invsqrt_base_point)
        eigvals = gs.linalg.eigvalsh(reduced_vec)
        min_eig = gs.amin(eigvals, axis=1)
        max_eig = gs.amax(eigvals, axis=1)
        inf_value = gs.where(
            max_eig <= 0., gs.array(-math.inf), - 1. / max_eig)
        inf_value = gs.to_ndarray(inf_value, to_ndim=2)
        sup_value = gs.where(
            min_eig >= 0., gs.array(-math.inf), - 1. / min_eig)
        sup_value = gs.to_ndarray(sup_value, to_ndim=2)
        domain = gs.concatenate((inf_value, sup_value), axis=1)

        return domain
Example #8
0
    def random_uniform(self, n_samples=1):
        r"""Sample on St(n,p) from the uniform distribution.

        If :math:`Z(p,n) \sim N(0,1)`, then :math:`St(n,p) \sim U`,
        according to Haar measure:
        :math:`St(n,p) := Z(Z^TZ)^{-1/2}`.

        Parameters
        ----------
        n_samples : int
            Number of samples.
            Optional, default: 1.

        Returns
        -------
        samples : array-like, shape=[..., n, p]
            Samples on the Stiefel manifold.
        """
        n, p = self.n, self.p
        size = (n_samples, n, p) if n_samples != 1 else (n, p)

        std_normal = gs.random.normal(size=size)
        std_normal_transpose = Matrices.transpose(std_normal)
        aux = Matrices.mul(std_normal_transpose, std_normal)
        inv_sqrt_aux = SymmetricMatrices.powerm(aux, -1.0 / 2)
        samples = Matrices.mul(std_normal, inv_sqrt_aux)

        return samples
Example #9
0
    def parallel_transport(self,
                           tangent_vec,
                           base_point,
                           direction=None,
                           end_point=None):
        r"""Parallel transport of a tangent vector.

        Closed-form solution for the parallel transport of a tangent vector
        along the geodesic between two points `base_point` and `end_point`
        or alternatively defined by :math:`t \mapsto exp_{(base\_point)}(
        t*direction)`.
        Denoting `tangent_vec_a` by `S`, `base_point` by `A`, and `end_point`
        by `B` or `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 : array-like, shape=[..., n, n]
            Tangent vector at base point to be transported.
        base_point : array-like, shape=[..., n, n]
            Point on the manifold of SPD matrices. Point to transport from
        direction : array-like, shape=[..., n, n]
            Tangent vector at base point, initial speed of the geodesic along
            which the parallel transport is computed. Unused if `end_point` is given.
            Optional, default: None.
        end_point : array-like, shape=[..., n, n]
            Point on the manifold of SPD matrices. Point to transport to.
            Optional, default: None.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., n, n]
            Transported tangent vector at exp_(base_point)(tangent_vec_b).
        """
        if end_point is None:
            end_point = self.exp(direction, base_point)
        # compute B^1/2(B^-1/2 A B^-1/2)B^-1/2 instead of sqrtm(AB^-1)
        sqrt_bp, inv_sqrt_bp = SymmetricMatrices.powerm(
            base_point, [1.0 / 2, -1.0 / 2])
        pdt = SymmetricMatrices.powerm(
            Matrices.mul(inv_sqrt_bp, end_point, inv_sqrt_bp), 1.0 / 2)
        congruence_mat = Matrices.mul(sqrt_bp, pdt, inv_sqrt_bp)
        return Matrices.congruent(tangent_vec, congruence_mat)
Example #10
0
 def test_powerm(self):
     """Test of powerm method."""
     sym_n = SymmetricMatrices(self.n)
     expected = gs.array(
         [[[1, 1. / 4., 0.], [1. / 4, 2., 0.], [0., 0., 1.]]])
     expected = gs.cast(expected, gs.float64)
     power = gs.array(1. / 2)
     power = gs.cast(power, gs.float64)
     result = sym_n.powerm(expected, power)
     result = gs.matmul(result, gs.transpose(result, (0, 2, 1)))
     self.assertAllClose(result, expected)
    def test_powerm(self):
        """Test of powerm method."""
        sym_n = SymmetricMatrices(self.n)
        expected = gs.array(
            [[[1, 1.0 / 4.0, 0.0], [1.0 / 4, 2.0, 0.0], [0.0, 0.0, 1.0]]]
        )

        power = gs.array(1.0 / 2.0)

        result = sym_n.powerm(expected, power)
        result = gs.matmul(result, gs.transpose(result, (0, 2, 1)))
        self.assertAllClose(result, expected)
Example #12
0
    def exp(self, tangent_vec, base_point, **kwargs):
        """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]
            Tangent vector at base point.
        base_point : array-like, shape=[..., n, n]
            Base point.

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

        if power_affine == 1:
            powers = SymmetricMatrices.powerm(base_point, [1.0 / 2, -1.0 / 2])
            exp = self._aux_exp(tangent_vec, powers[0], powers[1])
        else:
            modified_tangent_vec = SPDMatrices.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
Example #13
0
    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point):
        """Compute the affine-invariant inner-product.

        Compute the inner-product of tangent_vec_a and tangent_vec_b
        at point base_point using the affine invariant Riemannian metric.

        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
        -------
        inner_product : array-like, shape=[..., n, n]
            Inner-product.
        """
        power_affine = self.power_affine
        spd_space = SPDMatrices

        if power_affine == 1:
            inv_base_point = GeneralLinear.inverse(base_point)
            inner_product = self._aux_inner_product(
                tangent_vec_a, tangent_vec_b, inv_base_point
            )
        else:
            modified_tangent_vec_a = spd_space.differential_power(
                power_affine, tangent_vec_a, base_point
            )
            modified_tangent_vec_b = spd_space.differential_power(
                power_affine, tangent_vec_b, base_point
            )
            power_inv_base_point = SymmetricMatrices.powerm(base_point, -power_affine)
            inner_product = self._aux_inner_product(
                modified_tangent_vec_a, modified_tangent_vec_b, power_inv_base_point
            )

            inner_product = inner_product / (power_affine ** 2)

        return inner_product
Example #14
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)
 def test_powerm(self, mat, power, expected):
     result = SymmetricMatrices.powerm(gs.array(mat), power)
     self.assertAllClose(result, gs.array(expected))