示例#1
0
 def test_inner_product(self, dim, tangent_vec_a, tangent_vec_b, expected):
     metric = EuclideanMetric(dim)
     self.assertAllClose(
         metric.inner_product(gs.array(tangent_vec_a),
                              gs.array(tangent_vec_b)),
         gs.array(expected),
     )
    def setUp(self):
        warnings.simplefilter("ignore", category=UserWarning)
        gs.random.seed(0)
        self.dim = 2
        self.euc = Euclidean(dim=self.dim)
        self.sphere = Hypersphere(dim=self.dim)
        self.euc_metric = EuclideanMetric(dim=self.dim)
        self.sphere_metric = HypersphereMetric(dim=self.dim)

        def _euc_metric_matrix(base_point):
            """Return matrix of Euclidean inner-product."""
            dim = base_point.shape[-1]
            return gs.eye(dim)

        def _sphere_metric_matrix(base_point):
            """Return sphere's metric in spherical coordinates."""
            theta = base_point[..., 0]
            mat = gs.array([[1.0, 0.0], [0.0, gs.sin(theta) ** 2]])
            return mat

        new_euc_metric = RiemannianMetric(dim=self.dim)
        new_euc_metric.metric_matrix = _euc_metric_matrix

        new_sphere_metric = RiemannianMetric(dim=self.dim)
        new_sphere_metric.metric_matrix = _sphere_metric_matrix

        self.new_euc_metric = new_euc_metric
        self.new_sphere_metric = new_sphere_metric
示例#3
0
    def setUp(self):
        warnings.simplefilter('ignore', category=UserWarning)

        self.dim = 4
        self.euc_metric = EuclideanMetric(dim=self.dim)

        self.connection = Connection(dim=2)
        self.hypersphere = Hypersphere(dim=2)
示例#4
0
    def setup_method(self):
        warnings.simplefilter("ignore", category=UserWarning)
        gs.random.seed(0)
        self.dim = 4
        self.euc_metric = EuclideanMetric(dim=self.dim)

        self.connection = Connection(dim=2)
        self.hypersphere = Hypersphere(dim=2)
示例#5
0
 def __init__(self, n, p):
     dim = int(p * n - (p * (p + 1) / 2))
     super(StiefelCanonicalMetric, self).__init__(dim=dim,
                                                  signature=(dim, 0, 0))
     self.embedding_metric = EuclideanMetric(n * p)
     self.n = n
     self.p = p
示例#6
0
 def metric_matrix_test_data(self):
     smoke_data = [
         dict(
             metric=EuclideanMetric(dim=4),
             point=gs.array([0.0, 1.0, 0.0, 0.0]),
             expected=gs.eye(4),
         )
     ]
     return self.generate_tests(smoke_data)
示例#7
0
    def __init__(self, n, p):
        assert isinstance(n, int) and isinstance(p, int)
        assert p <= n
        self.n = n
        self.p = p

        dimension = int(p * (n - p))
        super(GrassmannianCanonicalMetric,
              self).__init__(dimension=dimension, signature=(dimension, 0, 0))
        self.embedding_metric = EuclideanMetric(n * p)
示例#8
0
    def __init__(
        self,
        dim,
        embedding_dim,
        immersion,
        jacobian_immersion=None,
        tangent_immersion=None,
    ):
        super(PullbackMetric, self).__init__(dim=dim)
        self.embedding_metric = EuclideanMetric(embedding_dim)
        self.immersion = immersion
        if jacobian_immersion is None:
            jacobian_immersion = gs.autodiff.jacobian(immersion)
        self.jacobian_immersion = jacobian_immersion
        if tangent_immersion is None:

            def _tangent_immersion(v, x):
                return gs.matmul(jacobian_immersion(x), v)

        self.tangent_immersion = _tangent_immersion
示例#9
0
    def __init__(self, n, p):
        geomstats.errors.check_integer(p, "p")
        geomstats.errors.check_integer(n, "n")
        if p > n:
            raise ValueError("p <= n is required.")

        dim = int(p * (n - p))
        super(GrassmannianCanonicalMetric,
              self).__init__(m=n, n=n, dim=dim, signature=(dim, 0, 0))

        self.n = n
        self.p = p
        self.embedding_metric = EuclideanMetric(n * p)
示例#10
0
    def __init__(self, manifold, mean, cov):
        n = mean.shape[-1]
        metric = manifold.metric
        if metric is None:
            manifold.metric = EuclideanMetric(n)
        else:
            if type(metric) not in (EuclideanMetric, MatricesMetric):
                raise ValueError(
                    "Invalid Metric, "
                    "Should be of type EuclideanMetric or MatricesMetric")

        self.manifold = manifold
        self.mean = mean
        self.cov = cov
示例#11
0
 def test_exp(self, dim, tangent_vec, base_point, expected):
     metric = EuclideanMetric(dim)
     self.assertAllClose(
         metric.exp(gs.array(tangent_vec), gs.array(base_point)),
         gs.array(expected))
示例#12
0
class HypersphereMetric(RiemannianMetric):
    """Class for the Hypersphere Metric.

    Parameters
    ----------
    dim : int
        Dimension of the hypersphere.
    """
    def __init__(self, dim):
        super(HypersphereMetric, self).__init__(dim=dim, signature=(dim, 0, 0))
        self.embedding_metric = EuclideanMetric(dim + 1)
        self._space = _Hypersphere(dim=dim)

    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """Compute the inner-product of two tangent vectors at a base point.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim + 1]
            First tangent vector at base point.
        tangent_vec_b : array-like, shape=[..., dim + 1]
            Second tangent vector at base point.
        base_point : array-like, shape=[..., dim + 1], optional
            Point on the hypersphere.

        Returns
        -------
        inner_prod : array-like, shape=[..., 1]
            Inner-product of the two tangent vectors.
        """
        inner_prod = self.embedding_metric.inner_product(
            tangent_vec_a, tangent_vec_b, base_point)

        return inner_prod

    def squared_norm(self, vector, base_point=None):
        """Compute the squared norm of a vector.

        Squared norm of a vector associated with the inner-product
        at the tangent space at a base point.

        Parameters
        ----------
        vector : array-like, shape=[..., dim + 1]
            Vector on the tangent space of the hypersphere at base point.
        base_point : array-like, shape=[..., dim + 1], optional
            Point on the hypersphere.

        Returns
        -------
        sq_norm : array-like, shape=[..., 1]
            Squared norm of the vector.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    @geomstats.vectorization.decorator(['else', 'vector', 'vector'])
    def exp(self, tangent_vec, base_point):
        """Compute the Riemannian exponential of a tangent vector.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim + 1]
            Tangent vector at a base point.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hypersphere.

        Returns
        -------
        exp : array-like, shape=[..., dim + 1]
            Point on the hypersphere equal to the Riemannian exponential
            of tangent_vec at the base point.
        """
        # TODO (ninamiolane): Raise error when vector is not tangent
        _, extrinsic_dim = base_point.shape
        n_tangent_vecs, _ = tangent_vec.shape

        hypersphere = Hypersphere(dim=extrinsic_dim - 1)
        proj_tangent_vec = hypersphere.to_tangent(tangent_vec, base_point)
        norm_tangent_vec = self.embedding_metric.norm(proj_tangent_vec)
        norm_tangent_vec = gs.to_ndarray(norm_tangent_vec, to_ndim=1)

        mask_0 = gs.isclose(norm_tangent_vec, 0.)
        mask_non0 = ~mask_0

        coef_1 = gs.zeros((n_tangent_vecs, ))
        coef_2 = gs.zeros((n_tangent_vecs, ))
        norm2 = norm_tangent_vec[mask_0]**2
        norm4 = norm2**2
        norm6 = norm2**3

        coef_1 = gs.assignment(coef_1,
                               1. - norm2 / 2. + norm4 / 24. - norm6 / 720.,
                               mask_0)
        coef_2 = gs.assignment(coef_2,
                               1. - norm2 / 6. + norm4 / 120. - norm6 / 5040.,
                               mask_0)

        coef_1 = gs.assignment(coef_1, gs.cos(norm_tangent_vec[mask_non0]),
                               mask_non0)
        coef_2 = gs.assignment(
            coef_2,
            gs.sin(norm_tangent_vec[mask_non0]) / norm_tangent_vec[mask_non0],
            mask_non0)

        exp = (gs.einsum('...,...j->...j', coef_1, base_point) +
               gs.einsum('...,...j->...j', coef_2, proj_tangent_vec))

        return exp

    @geomstats.vectorization.decorator(['else', 'vector', 'vector'])
    def log(self, point, base_point):
        """Compute the Riemannian logarithm of a point.

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

        Returns
        -------
        log : array-like, shape=[..., dim + 1]
            Tangent vector at the base point equal to the Riemannian logarithm
            of point at the base point.
        """
        norm_base_point = self.embedding_metric.norm(base_point)
        norm_point = self.embedding_metric.norm(point)
        inner_prod = self.embedding_metric.inner_product(base_point, point)
        cos_angle = inner_prod / (norm_base_point * norm_point)
        cos_angle = gs.clip(cos_angle, -1., 1.)

        angle = gs.arccos(cos_angle)
        angle = gs.to_ndarray(angle, to_ndim=1)
        angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

        mask_0 = gs.isclose(angle, 0.)
        mask_else = gs.equal(mask_0, gs.array(False))

        mask_0_float = gs.cast(mask_0, gs.float32)
        mask_else_float = gs.cast(mask_else, gs.float32)

        coef_1 = gs.zeros_like(angle)
        coef_2 = gs.zeros_like(angle)

        coef_1 += mask_0_float * (1. + INV_SIN_TAYLOR_COEFFS[1] * angle**2 +
                                  INV_SIN_TAYLOR_COEFFS[3] * angle**4 +
                                  INV_SIN_TAYLOR_COEFFS[5] * angle**6 +
                                  INV_SIN_TAYLOR_COEFFS[7] * angle**8)
        coef_2 += mask_0_float * (1. + INV_TAN_TAYLOR_COEFFS[1] * angle**2 +
                                  INV_TAN_TAYLOR_COEFFS[3] * angle**4 +
                                  INV_TAN_TAYLOR_COEFFS[5] * angle**6 +
                                  INV_TAN_TAYLOR_COEFFS[7] * angle**8)

        # This avoids division by 0.
        angle += mask_0_float * 1.

        coef_1 += mask_else_float * angle / gs.sin(angle)
        coef_2 += mask_else_float * angle / gs.tan(angle)

        log = (gs.einsum('...i,...j->...j', coef_1, point) -
               gs.einsum('...i,...j->...j', coef_2, base_point))

        mask_same_values = gs.isclose(point, base_point)

        mask_else = gs.equal(mask_same_values, gs.array(False))
        mask_else_float = gs.cast(mask_else, gs.float32)
        mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=1)
        mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=2)
        mask_not_same_points = gs.sum(mask_else_float, axis=1)
        mask_same_points = gs.isclose(mask_not_same_points, 0.)
        mask_same_points = gs.cast(mask_same_points, gs.float32)
        mask_same_points = gs.to_ndarray(mask_same_points, to_ndim=2, axis=1)

        mask_same_points_float = gs.cast(mask_same_points, gs.float32)

        log -= mask_same_points_float * log

        return log

    def dist(self, point_a, point_b):
        """Compute the geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim + 1]
            First point on the hypersphere.
        point_b : array-like, shape=[..., dim + 1]
            Second point on the hypersphere.

        Returns
        -------
        dist : array-like, shape=[..., 1]
            Geodesic distance between the two points.
        """
        norm_a = self.embedding_metric.norm(point_a)
        norm_b = self.embedding_metric.norm(point_b)
        inner_prod = self.embedding_metric.inner_product(point_a, point_b)

        cos_angle = gs.einsum('...,...->...', inner_prod,
                              1. / (norm_a * norm_b))
        cos_angle = gs.clip(cos_angle, -1, 1)

        dist = gs.arccos(cos_angle)

        return dist

    def squared_dist(self, point_a, point_b):
        """Squared geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim]
            Point on the hypersphere.
        point_b : array-like, shape=[..., dim]
            Point on the hypersphere.

        Returns
        -------
        sq_dist : array-like, shape=[...,]
        """
        return self.dist(point_a, point_b)**2

    @staticmethod
    def parallel_transport(tangent_vec_a, tangent_vec_b, base_point):
        """Compute the 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).

        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, along which the parallel transport
            is computed.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hypersphere.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., dim + 1]
            Transported tangent vector at exp_(base_point)(tangent_vec_b).
        """
        tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2)
        tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)
        # TODO (nguigs): work around this condition:
        # assert len(base_point) == len(tangent_vec_a) == len(tangent_vec_b)
        theta = gs.linalg.norm(tangent_vec_b, axis=1)
        normalized_b = gs.einsum('n, ni->ni', 1 / theta, tangent_vec_b)
        pb = gs.einsum('ni,ni->n', tangent_vec_a, normalized_b)
        p_orth = tangent_vec_a - gs.einsum('n,ni->ni', pb, normalized_b)
        transported = - gs.einsum('n,ni->ni', gs.sin(theta) * pb, base_point)\
            + gs.einsum('n,ni->ni', gs.cos(theta) * pb, normalized_b)\
            + p_orth
        return transported

    def christoffels(self, point, point_type='spherical'):
        """Compute the Christoffel symbols at a point.

        Only implemented in dimension 2 and for spherical coordinates.

        Parameters
        ----------
        point : array-like, shape=[..., dim]
            Point on hypersphere where the Christoffel symbols are computed.

        point_type: str, {'spherical', 'intrinsic', 'extrinsic'}
            Coordinates in which to express the Christoffel symbols.

        Returns
        -------
        christoffel : array-like, shape=[..., contravariant index, 1st
                                         covariant index, 2nd covariant index]
            Christoffel symbols at point.
        """
        if self.dim != 2 or point_type != 'spherical':
            raise NotImplementedError(
                'The Christoffel symbols are only implemented'
                ' for spherical coordinates in the 2-sphere')

        point = gs.to_ndarray(point, to_ndim=2)
        christoffel = []
        for sample in point:
            gamma_0 = gs.array([[0, 0],
                                [0, -gs.sin(sample[0]) * gs.cos(sample[0])]])
            gamma_1 = gs.array([[0, gs.cos(sample[0]) / gs.sin(sample[0])],
                                [gs.cos(sample[0]) / gs.sin(sample[0]), 0]])
            christoffel.append(gs.stack([gamma_0, gamma_1]))

        christoffel = gs.stack(christoffel)
        if gs.ndim(christoffel) == 4 and gs.shape(christoffel)[0] == 1:
            christoffel = gs.squeeze(christoffel, axis=0)
        return christoffel
示例#13
0
 def test_log(self, dim, point, base_point, expected):
     metric = EuclideanMetric(dim)
     self.assertAllClose(metric.log(gs.array(point), gs.array(base_point)),
                         gs.array(expected))
示例#14
0
class HypersphereMetric(RiemannianMetric):
    """Class for the Hypersphere Metric.

    Parameters
    ----------
    dim : int
        Dimension of the hypersphere.
    """
    def __init__(self, dim):
        super(HypersphereMetric, self).__init__(dim=dim, signature=(dim, 0))
        self.embedding_metric = EuclideanMetric(dim + 1)
        self._space = _Hypersphere(dim=dim)

    def metric_matrix(self, base_point=None):
        """Metric matrix at the tangent space at a base point.

        Parameters
        ----------
        base_point : array-like, shape=[..., dim + 1]
            Base point.
            Optional, default: None.

        Returns
        -------
        mat : array-like, shape=[..., dim + 1, dim + 1]
            Inner-product matrix.
        """
        return gs.eye(self.dim + 1)

    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """Compute the inner-product of two tangent vectors at a base point.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim + 1]
            First tangent vector at base point.
        tangent_vec_b : array-like, shape=[..., dim + 1]
            Second tangent vector at base point.
        base_point : array-like, shape=[..., dim + 1], optional
            Point on the hypersphere.

        Returns
        -------
        inner_prod : array-like, shape=[...,]
            Inner-product of the two tangent vectors.
        """
        inner_prod = self.embedding_metric.inner_product(
            tangent_vec_a, tangent_vec_b, base_point)

        return inner_prod

    def squared_norm(self, vector, base_point=None):
        """Compute the squared norm of a vector.

        Squared norm of a vector associated with the inner-product
        at the tangent space at a base point.

        Parameters
        ----------
        vector : array-like, shape=[..., dim + 1]
            Vector on the tangent space of the hypersphere at base point.
        base_point : array-like, shape=[..., dim + 1], optional
            Point on the hypersphere.

        Returns
        -------
        sq_norm : array-like, shape=[..., 1]
            Squared norm of the vector.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp(self, tangent_vec, base_point, **kwargs):
        """Compute the Riemannian exponential of a tangent vector.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim + 1]
            Tangent vector at a base point.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hypersphere.

        Returns
        -------
        exp : array-like, shape=[..., dim + 1]
            Point on the hypersphere equal to the Riemannian exponential
            of tangent_vec at the base point.
        """
        hypersphere = Hypersphere(dim=self.dim)
        proj_tangent_vec = hypersphere.to_tangent(tangent_vec, base_point)
        norm2 = self.embedding_metric.squared_norm(proj_tangent_vec)

        coef_1 = utils.taylor_exp_even_func(norm2, utils.cos_close_0, order=4)
        coef_2 = utils.taylor_exp_even_func(norm2, utils.sinc_close_0, order=4)
        exp = gs.einsum("...,...j->...j", coef_1, base_point) + gs.einsum(
            "...,...j->...j", coef_2, proj_tangent_vec)

        return exp

    def log(self, point, base_point, **kwargs):
        """Compute the Riemannian logarithm of a point.

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

        Returns
        -------
        log : array-like, shape=[..., dim + 1]
            Tangent vector at the base point equal to the Riemannian logarithm
            of point at the base point.
        """
        inner_prod = self.embedding_metric.inner_product(base_point, point)
        cos_angle = gs.clip(inner_prod, -1.0, 1.0)
        squared_angle = gs.arccos(cos_angle)**2
        coef_1_ = utils.taylor_exp_even_func(squared_angle,
                                             utils.inv_sinc_close_0,
                                             order=5)
        coef_2_ = utils.taylor_exp_even_func(squared_angle,
                                             utils.inv_tanc_close_0,
                                             order=5)
        log = gs.einsum("...,...j->...j", coef_1_, point) - gs.einsum(
            "...,...j->...j", coef_2_, base_point)

        return log

    def dist(self, point_a, point_b):
        """Compute the geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim + 1]
            First point on the hypersphere.
        point_b : array-like, shape=[..., dim + 1]
            Second point on the hypersphere.

        Returns
        -------
        dist : array-like, shape=[..., 1]
            Geodesic distance between the two points.
        """
        norm_a = self.embedding_metric.norm(point_a)
        norm_b = self.embedding_metric.norm(point_b)
        inner_prod = self.embedding_metric.inner_product(point_a, point_b)

        cos_angle = inner_prod / (norm_a * norm_b)
        cos_angle = gs.clip(cos_angle, -1, 1)

        dist = gs.arccos(cos_angle)

        return dist

    def squared_dist(self, point_a, point_b, **kwargs):
        """Squared geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim]
            Point on the hypersphere.
        point_b : array-like, shape=[..., dim]
            Point on the hypersphere.

        Returns
        -------
        sq_dist : array-like, shape=[...,]
        """
        return self.dist(point_a, point_b)**2

    @staticmethod
    def parallel_transport(tangent_vec_a, tangent_vec_b, base_point, **kwargs):
        r"""Compute the parallel transport of a tangent vector.

        Closed-form solution for 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=[..., dim + 1]
            Tangent vector at base point to be transported.
        tangent_vec_b : array-like, shape=[..., dim + 1]
            Tangent vector at base point, along which the parallel transport
            is computed.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hypersphere.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., dim + 1]
            Transported tangent vector at `exp_(base_point)(tangent_vec_b)`.
        """
        theta = gs.linalg.norm(tangent_vec_b, axis=-1)
        eps = gs.where(theta == 0.0, 1.0, theta)
        normalized_b = gs.einsum("...,...i->...i", 1 / eps, tangent_vec_b)
        pb = gs.einsum("...i,...i->...", tangent_vec_a, normalized_b)
        p_orth = tangent_vec_a - gs.einsum("...,...i->...i", pb, normalized_b)
        transported = (-gs.einsum("...,...i->...i",
                                  gs.sin(theta) * pb, base_point) +
                       gs.einsum("...,...i->...i",
                                 gs.cos(theta) * pb, normalized_b) + p_orth)
        return transported

    def christoffels(self, point, point_type="spherical"):
        """Compute the Christoffel symbols at a point.

        Only implemented in dimension 2 and for spherical coordinates.

        Parameters
        ----------
        point : array-like, shape=[..., dim]
            Point on hypersphere where the Christoffel symbols are computed.

        point_type: str, {'spherical', 'intrinsic', 'extrinsic'}
            Coordinates in which to express the Christoffel symbols.
            Optional, default: 'spherical'.

        Returns
        -------
        christoffel : array-like, shape=[..., contravariant index, 1st
                                         covariant index, 2nd covariant index]
            Christoffel symbols at point.
        """
        if self.dim != 2 or point_type != "spherical":
            raise NotImplementedError(
                "The Christoffel symbols are only implemented"
                " for spherical coordinates in the 2-sphere")

        point = gs.to_ndarray(point, to_ndim=2)
        christoffel = []
        for sample in point:
            gamma_0 = gs.array([[0, 0],
                                [0, -gs.sin(sample[0]) * gs.cos(sample[0])]])
            gamma_1 = gs.array([
                [0, gs.cos(sample[0]) / gs.sin(sample[0])],
                [gs.cos(sample[0]) / gs.sin(sample[0]), 0],
            ])
            christoffel.append(gs.stack([gamma_0, gamma_1]))

        christoffel = gs.stack(christoffel)
        if gs.ndim(christoffel) == 4 and gs.shape(christoffel)[0] == 1:
            christoffel = gs.squeeze(christoffel, axis=0)
        return christoffel

    def curvature(self, tangent_vec_a, tangent_vec_b, tangent_vec_c,
                  base_point):
        r"""Compute the curvature.

        For three tangent vectors at a base point :math:`x,y,z`,
        the curvature is defined by
        :math:`R(x, y)z = \nabla_{[x,y]}z
        - \nabla_z\nabla_y z + \nabla_y\nabla_x z`, where :math:`\nabla`
        is the Levi-Civita connection. In the case of the hypersphere,
        we have the closed formula
        :math:`R(x,y)z = \langle x, z \rangle y - \langle y,z \rangle x`.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        tangent_vec_b : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        tangent_vec_c : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        base_point :  array-like, shape=[..., dim]
            Point on the hypersphere.

        Returns
        -------
        curvature : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        """
        inner_ac = self.inner_product(tangent_vec_a, tangent_vec_c)
        inner_bc = self.inner_product(tangent_vec_b, tangent_vec_c)
        first_term = gs.einsum("...,...i->...i", inner_bc, tangent_vec_a)
        second_term = gs.einsum("...,...i->...i", inner_ac, tangent_vec_b)
        return -first_term + second_term

    def _normalization_factor_odd_dim(self, variances):
        """Compute the normalization factor - odd dimension."""
        dim = self.dim
        half_dim = int((dim + 1) / 2)
        area = 2 * gs.pi**half_dim / math.factorial(half_dim - 1)
        comb = gs.comb(dim - 1, half_dim - 1)

        erf_arg = gs.sqrt(variances / 2) * gs.pi
        first_term = (area / (2**dim - 1) * comb *
                      gs.sqrt(gs.pi / (2 * variances)) * gs.erf(erf_arg))

        def summand(k):
            exp_arg = -((dim - 1 - 2 * k)**2) / 2 / variances
            erf_arg_2 = (gs.pi * variances -
                         (dim - 1 - 2 * k) * 1j) / gs.sqrt(2 * variances)
            sign = (-1.0)**k
            comb_2 = gs.comb(k, dim - 1)
            return sign * comb_2 * gs.exp(exp_arg) * gs.real(gs.erf(erf_arg_2))

        if half_dim > 2:
            sum_term = gs.sum(
                gs.stack([summand(k)] for k in range(half_dim - 2)))
        else:
            sum_term = summand(0)
        coef = area / 2 / erf_arg * gs.pi**0.5 * (-1.0)**(half_dim - 1)

        return first_term + coef / 2**(dim - 2) * sum_term

    def _normalization_factor_even_dim(self, variances):
        """Compute the normalization factor - even dimension."""
        dim = self.dim
        half_dim = (dim + 1) / 2
        area = 2 * gs.pi**half_dim / math.gamma(half_dim)

        def summand(k):
            exp_arg = -((dim - 1 - 2 * k)**2) / 2 / variances
            erf_arg_1 = (dim - 1 - 2 * k) * 1j / gs.sqrt(2 * variances)
            erf_arg_2 = (gs.pi * variances -
                         (dim - 1 - 2 * k) * 1j) / gs.sqrt(2 * variances)
            sign = (-1.0)**k
            comb = gs.comb(dim - 1, k)
            erf_terms = gs.imag(gs.erf(erf_arg_2) + gs.erf(erf_arg_1))
            return sign * comb * gs.exp(exp_arg) * erf_terms

        half_dim_2 = int((dim - 2) / 2)
        if half_dim_2 > 0:
            sum_term = gs.sum(gs.stack([summand(k)]
                                       for k in range(half_dim_2)))
        else:
            sum_term = summand(0)
        coef = (area * (-1.0)**half_dim_2 / 2**(dim - 2) *
                gs.sqrt(gs.pi / 2 / variances))

        return coef * sum_term

    def normalization_factor(self, variances):
        """Return normalization factor of the Gaussian distribution.

        Parameters
        ----------
        variances : array-like, shape=[n,]
            Variance of the distribution.

        Returns
        -------
        norm_func : array-like, shape=[n,]
            Normalisation factor for all given variances.
        """
        if self.dim % 2 == 0:
            return self._normalization_factor_even_dim(variances)
        return self._normalization_factor_odd_dim(variances)

    def norm_factor_gradient(self, variances):
        """Compute the gradient of the normalization factor.

        Parameters
        ----------
        variances : array-like, shape=[n,]
            Variance of the distribution.

        Returns
        -------
        norm_func : array-like, shape=[n,]
            Normalisation factor for all given variances.
        """
        def func(var):
            return gs.sum(self.normalization_factor(var))

        _, grad = gs.autodiff.value_and_grad(func)(variances)
        return _, grad

    def curvature_derivative(
        self,
        tangent_vec_a,
        tangent_vec_b=None,
        tangent_vec_c=None,
        tangent_vec_d=None,
        base_point=None,
    ):
        r"""Compute the covariant derivative of the curvature.

        The derivative of the curvature vanishes since the hypersphere is a
        constant curvature space.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim]
            Tangent vector at `base_point` along which the curvature is
            derived.
        tangent_vec_b : array-like, shape=[..., dim]
            Unused tangent vector at `base_point` (since curvature derivative
            vanishes).
        tangent_vec_c : array-like, shape=[..., dim]
            Unused tangent vector at `base_point` (since curvature derivative
            vanishes).
        tangent_vec_d : array-like, shape=[..., dim]
            Unused tangent vector at `base_point` (since curvature derivative
            vanishes).
        base_point : array-like, shape=[..., dim]
            Unused point on the hypersphere.

        Returns
        -------
        curvature_derivative : array-like, shape=[..., dim]
            Tangent vector at base point.
        """
        return gs.zeros_like(tangent_vec_a)
示例#15
0
 def test_norm(self, dim, vec, expected):
     metric = EuclideanMetric(dim)
     self.assertAllClose(metric.norm(gs.array(vec)), gs.array(expected))
示例#16
0
class TestConnection(geomstats.tests.TestCase):
    def setup_method(self):
        warnings.simplefilter("ignore", category=UserWarning)
        gs.random.seed(0)
        self.dim = 4
        self.euc_metric = EuclideanMetric(dim=self.dim)

        self.connection = Connection(dim=2)
        self.hypersphere = Hypersphere(dim=2)

    def test_metric_matrix(self):
        base_point = gs.array([0.0, 1.0, 0.0, 0.0])

        result = self.euc_metric.metric_matrix(base_point)
        expected = gs.eye(self.dim)

        self.assertAllClose(result, expected)

    def test_parallel_transport(self):
        n_samples = 2
        base_point = self.hypersphere.random_uniform(n_samples)
        tan_vec_a = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        tan_vec_b = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        expected = self.hypersphere.metric.parallel_transport(
            tan_vec_a, base_point, tan_vec_b)
        expected_point = self.hypersphere.metric.exp(tan_vec_b, base_point)
        base_point = gs.cast(base_point, gs.float64)
        base_point, tan_vec_a, tan_vec_b = gs.convert_to_wider_dtype(
            [base_point, tan_vec_a, tan_vec_b])
        for step, alpha in zip(["pole", "schild"], [1, 2]):
            min_n = 1 if step == "pole" else 50
            tol = 1e-5 if step == "pole" else 1e-2
            for n_rungs in [min_n, 11]:
                ladder = self.hypersphere.metric.ladder_parallel_transport(
                    tan_vec_a,
                    base_point,
                    tan_vec_b,
                    n_rungs=n_rungs,
                    scheme=step,
                    alpha=alpha,
                )
                result = ladder["transported_tangent_vec"]
                result_point = ladder["end_point"]
                self.assertAllClose(result, expected, rtol=tol, atol=tol)
                self.assertAllClose(result_point, expected_point)

    def test_parallel_transport_trajectory(self):
        n_samples = 2
        for step in ["pole", "schild"]:
            n_steps = 1 if step == "pole" else 50
            tol = 1e-6 if step == "pole" else 1e-2
            base_point = self.hypersphere.random_uniform(n_samples)
            tan_vec_a = self.hypersphere.to_tangent(
                gs.random.rand(n_samples, 3), base_point)
            tan_vec_b = self.hypersphere.to_tangent(
                gs.random.rand(n_samples, 3), base_point)
            expected = self.hypersphere.metric.parallel_transport(
                tan_vec_a, base_point, tan_vec_b)
            expected_point = self.hypersphere.metric.exp(tan_vec_b, base_point)
            ladder = self.hypersphere.metric.ladder_parallel_transport(
                tan_vec_a,
                base_point,
                tan_vec_b,
                n_rungs=n_steps,
                scheme=step,
                return_geodesics=True,
            )
            result = ladder["transported_tangent_vec"]
            result_point = ladder["end_point"]

            self.assertAllClose(result, expected, rtol=tol, atol=tol)
            self.assertAllClose(result_point, expected_point)

    def test_ladder_alpha(self):
        n_samples = 2
        base_point = self.hypersphere.random_uniform(n_samples)
        tan_vec_a = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        tan_vec_b = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)

        with pytest.raises(ValueError):
            self.hypersphere.metric.ladder_parallel_transport(
                tan_vec_a,
                base_point,
                tan_vec_b,
                n_rungs=1,
                scheme="pole",
                alpha=0.5,
                return_geodesics=False,
            )

    def test_exp_connection_metric(self):
        point = gs.array([gs.pi / 2, 0])
        vector = gs.array([0.25, 0.5])
        point_ext = self.hypersphere.spherical_to_extrinsic(point)
        vector_ext = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, point)
        self.connection.christoffels = self.hypersphere.metric.christoffels
        expected = self.hypersphere.metric.exp(vector_ext, point_ext)
        result_spherical = self.connection.exp(vector,
                                               point,
                                               n_steps=50,
                                               step="rk4")
        result = self.hypersphere.spherical_to_extrinsic(result_spherical)

        self.assertAllClose(result, expected)

    def test_exp_connection_metric_vectorization(self):
        point = gs.array([[gs.pi / 2, 0], [gs.pi / 6, gs.pi / 4]])
        vector = gs.array([[0.25, 0.5], [0.30, 0.2]])
        point_ext = self.hypersphere.spherical_to_extrinsic(point)
        vector_ext = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, point)
        self.connection.christoffels = self.hypersphere.metric.christoffels
        expected = self.hypersphere.metric.exp(vector_ext, point_ext)
        result_spherical = self.connection.exp(vector,
                                               point,
                                               n_steps=50,
                                               step="rk4")
        result = self.hypersphere.spherical_to_extrinsic(result_spherical)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_tf_and_torch_only
    def test_log_connection_metric(self):
        base_point = gs.array([gs.pi / 3, gs.pi / 4])
        point = gs.array([1.0, gs.pi / 2])
        self.connection.christoffels = self.hypersphere.metric.christoffels
        vector = self.connection.log(point=point,
                                     base_point=base_point,
                                     n_steps=75,
                                     step="rk4",
                                     tol=1e-10)
        result = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, base_point)
        p_ext = self.hypersphere.spherical_to_extrinsic(base_point)
        q_ext = self.hypersphere.spherical_to_extrinsic(point)
        expected = self.hypersphere.metric.log(base_point=p_ext, point=q_ext)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_tf_and_torch_only
    def test_log_connection_metric_vectorization(self):
        base_point = gs.array([[gs.pi / 3, gs.pi / 4], [gs.pi / 2, gs.pi / 4]])
        point = gs.array([[1.0, gs.pi / 2], [gs.pi / 6, gs.pi / 3]])
        self.connection.christoffels = self.hypersphere.metric.christoffels
        vector = self.connection.log(point=point,
                                     base_point=base_point,
                                     n_steps=75,
                                     step="rk4",
                                     tol=1e-10)
        result = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, base_point)
        p_ext = self.hypersphere.spherical_to_extrinsic(base_point)
        q_ext = self.hypersphere.spherical_to_extrinsic(point)
        expected = self.hypersphere.metric.log(base_point=p_ext, point=q_ext)

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

    def test_geodesic_and_coincides_exp_hypersphere(self):
        n_geodesic_points = 10
        initial_point = self.hypersphere.random_uniform(2)
        vector = gs.array([[2.0, 0.0, -1.0]] * 2)
        initial_tangent_vec = self.hypersphere.to_tangent(
            vector=vector, base_point=initial_point)
        geodesic = self.hypersphere.metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        t = gs.linspace(start=0.0, stop=1.0, num=n_geodesic_points)
        points = geodesic(t)
        result = points[:, -1]
        expected = self.hypersphere.metric.exp(vector, initial_point)
        self.assertAllClose(expected, result)

        initial_point = initial_point[0]
        initial_tangent_vec = initial_tangent_vec[0]
        geodesic = self.hypersphere.metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        points = geodesic(t)
        result = points[-1]
        expected = self.hypersphere.metric.exp(initial_tangent_vec,
                                               initial_point)
        self.assertAllClose(expected, result)

    def test_geodesic_and_coincides_exp_son(self):
        n_geodesic_points = 10
        space = SpecialOrthogonal(n=4)
        initial_point = space.random_uniform(2)
        vector = gs.random.rand(2, 4, 4)
        initial_tangent_vec = space.to_tangent(vector=vector,
                                               base_point=initial_point)
        geodesic = space.bi_invariant_metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        t = gs.linspace(start=0.0, stop=1.0, num=n_geodesic_points)
        points = geodesic(t)
        result = points[:, -1]
        expected = space.bi_invariant_metric.exp(initial_tangent_vec,
                                                 initial_point)
        self.assertAllClose(result, expected)

        initial_point = initial_point[0]
        initial_tangent_vec = initial_tangent_vec[0]
        geodesic = space.bi_invariant_metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        points = geodesic(t)
        result = points[-1]
        expected = space.bi_invariant_metric.exp(initial_tangent_vec,
                                                 initial_point)
        self.assertAllClose(expected, result)

    def test_geodesic_invalid_initial_conditions(self):
        space = SpecialOrthogonal(n=4)
        initial_point = space.random_uniform(2)
        vector = gs.random.rand(2, 4, 4)
        initial_tangent_vec = space.to_tangent(vector=vector,
                                               base_point=initial_point)
        end_point = space.random_uniform(2)
        with pytest.raises(RuntimeError):
            space.bi_invariant_metric.geodesic(
                initial_point=initial_point,
                initial_tangent_vec=initial_tangent_vec,
                end_point=end_point,
            )

    def test_geodesic_vectorization(self):
        space = Hypersphere(2)
        metric = space.metric
        initial_point = space.random_uniform(2)
        vector = gs.random.rand(2, 3)
        initial_tangent_vec = space.to_tangent(vector=vector,
                                               base_point=initial_point)
        end_point = space.random_uniform(2)
        time = gs.linspace(0, 1, 10)

        geo = metric.geodesic(initial_point, initial_tangent_vec)
        path = geo(time)
        result = path.shape
        expected = (2, 10, 3)
        self.assertAllClose(result, expected)

        geo = metric.geodesic(initial_point, end_point=end_point)
        path = geo(time)
        result = path.shape
        expected = (2, 10, 3)
        self.assertAllClose(result, expected)

        geo = metric.geodesic(initial_point, end_point=end_point[0])
        path = geo(time)
        result = path.shape
        expected = (2, 10, 3)
        self.assertAllClose(result, expected)

        initial_tangent_vec = space.to_tangent(vector=vector,
                                               base_point=initial_point[0])
        geo = metric.geodesic(initial_point[0], initial_tangent_vec)
        path = geo(time)
        result = path.shape
        expected = (2, 10, 3)
        self.assertAllClose(result, expected)
示例#17
0
 def test_metric_matrix(self, dim, expected):
     self.assertAllClose(
         EuclideanMetric(dim).metric_matrix(), gs.array(expected))
示例#18
0
class PullbackMetric(RiemannianMetric):
    r"""Pullback metric.

    Let :math:`f` be an immersion :math:`f: M \rightarrow N`
    of one manifold :math:`M` into the Riemannian manifold :math:`N`
    with metric :math:`g`.
    The pull-back metric :math:`f^*g` is defined on :math:`M` for a
    base point :math:`p` as:
    :math:`(f^*g)_p(u, v) = g_{f(p)}(df_p u , df_p v)
    \quad \forall u, v \in T_pM`

    Note
    ----
    The pull-back metric is currently only implemented for an
    immersion into the Euclidean space, i.e. for
    :math:`N=\mathbb{R}^n`.

    Parameters
    ----------
    dim : int
        Dimension of the underlying manifold.
    embedding_dim : int
        Dimension of the embedding Euclidean space.
    immersion : callable
        Map defining the immersion into the Euclidean space.
    """

    def __init__(
        self,
        dim,
        embedding_dim,
        immersion,
        jacobian_immersion=None,
        tangent_immersion=None,
    ):
        super(PullbackMetric, self).__init__(dim=dim)
        self.embedding_metric = EuclideanMetric(embedding_dim)
        self.immersion = immersion
        if jacobian_immersion is None:
            jacobian_immersion = gs.autodiff.jacobian(immersion)
        self.jacobian_immersion = jacobian_immersion
        if tangent_immersion is None:

            def _tangent_immersion(v, x):
                return gs.matmul(jacobian_immersion(x), v)

        self.tangent_immersion = _tangent_immersion

    def metric_matrix(self, base_point=None, n_jobs=1, **joblib_kwargs):
        r"""Metric matrix at the tangent space at a base point.

        Let :math:`f` be the immersion
        :math:`f: M \rightarrow \mathbb{R}^n` of the manifold
        :math:`M` into the Euclidean space :math:`\mathbb{R}^n`.

        The elements of the metric matrix at a base point :math:`p`
        are defined as:
        :math:`(f*g)_{ij}(p) = <df_p e_i , df_p e_j>`,
        for :math:`e_i, e_j` basis elements of :math:`M`.

        Parameters
        ----------
        base_point : array-like, shape=[..., dim]
            Base point.
            Optional, default: None.

        Returns
        -------
        mat : array-like, shape=[..., dim, dim]
            Inner-product matrix.
        """
        immersed_base_point = self.immersion(base_point)
        jacobian_immersion = self.jacobian_immersion(base_point)
        basis_elements = gs.eye(self.dim)

        @joblib.delayed
        @joblib.wrap_non_picklable_objects
        def pickable_inner_product(i, j):
            immersed_basis_element_i = gs.matmul(jacobian_immersion, basis_elements[i])
            immersed_basis_element_j = gs.matmul(jacobian_immersion, basis_elements[j])
            return self.embedding_metric.inner_product(
                immersed_basis_element_i,
                immersed_basis_element_j,
                base_point=immersed_base_point,
            )

        pool = joblib.Parallel(n_jobs=n_jobs, **joblib_kwargs)
        out = pool(
            pickable_inner_product(i, j)
            for i, j in itertools.product(range(self.dim), range(self.dim))
        )

        metric_mat = gs.reshape(gs.array(out), (-1, self.dim, self.dim))
        return metric_mat[0] if base_point.ndim == 1 else metric_mat
示例#19
0
 def __init__(self, dimension):
     super(HypersphereMetric, self).__init__(dimension=dimension,
                                             signature=(dimension, 0, 0))
     self.embedding_metric = EuclideanMetric(dimension + 1)
示例#20
0
class TestConnection(geomstats.tests.TestCase):
    def setUp(self):
        warnings.simplefilter('ignore', category=UserWarning)

        self.dim = 4
        self.euc_metric = EuclideanMetric(dim=self.dim)

        self.connection = Connection(dim=2)
        self.hypersphere = Hypersphere(dim=2)

    def test_metric_matrix(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.metric_matrix(base_point)
        expected = gs.eye(self.dim)

        self.assertAllClose(result, expected)

    def test_cometric_matrix(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_inverse_matrix(base_point)
        expected = gs.eye(self.dim)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_metric_derivative(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_derivative_matrix(base_point)
        expected = gs.zeros((self.dim, ) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_christoffels(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.christoffels(base_point)
        expected = gs.zeros((self.dim, ) * 3)

        self.assertAllClose(result, expected)

    def test_parallel_transport(self):
        n_samples = 2
        base_point = self.hypersphere.random_uniform(n_samples)
        tan_vec_a = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        tan_vec_b = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        expected = self.hypersphere.metric.parallel_transport(
            tan_vec_a, tan_vec_b, base_point)
        expected_point = self.hypersphere.metric.exp(tan_vec_b, base_point)
        base_point = gs.cast(base_point, gs.float64)
        base_point, tan_vec_a, tan_vec_b = gs.convert_to_wider_dtype(
            [base_point, tan_vec_a, tan_vec_b])
        for step, alpha in zip(['pole', 'schild'], [1, 2]):
            min_n = 1 if step == 'pole' else 50
            tol = 1e-5 if step == 'pole' else 1e-2
            for n_rungs in [min_n, 11]:
                ladder = self.hypersphere.metric.ladder_parallel_transport(
                    tan_vec_a,
                    tan_vec_b,
                    base_point,
                    scheme=step,
                    n_rungs=n_rungs,
                    alpha=alpha)
                result = ladder['transported_tangent_vec']
                result_point = ladder['end_point']
                self.assertAllClose(result, expected, rtol=tol, atol=tol)
                self.assertAllClose(result_point, expected_point)

    def test_parallel_transport_trajectory(self):
        n_samples = 2
        for step in ['pole', 'schild']:
            n_steps = 1 if step == 'pole' else 50
            tol = 1e-6 if step == 'pole' else 1e-2
            base_point = self.hypersphere.random_uniform(n_samples)
            tan_vec_a = self.hypersphere.to_tangent(
                gs.random.rand(n_samples, 3), base_point)
            tan_vec_b = self.hypersphere.to_tangent(
                gs.random.rand(n_samples, 3), base_point)
            expected = self.hypersphere.metric.parallel_transport(
                tan_vec_a, tan_vec_b, base_point)
            expected_point = self.hypersphere.metric.exp(tan_vec_b, base_point)
            ladder = self.hypersphere.metric.ladder_parallel_transport(
                tan_vec_a,
                tan_vec_b,
                base_point,
                return_geodesics=True,
                scheme=step,
                n_rungs=n_steps)
            result = ladder['transported_tangent_vec']
            result_point = ladder['end_point']

            self.assertAllClose(result, expected, rtol=tol, atol=tol)
            self.assertAllClose(result_point, expected_point)

    def test_ladder_alpha(self):
        n_samples = 2
        base_point = self.hypersphere.random_uniform(n_samples)
        tan_vec_a = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        tan_vec_b = self.hypersphere.to_tangent(gs.random.rand(n_samples, 3),
                                                base_point)
        self.assertRaises(
            ValueError, lambda: self.hypersphere.metric.
            ladder_parallel_transport(tan_vec_a,
                                      tan_vec_b,
                                      base_point,
                                      return_geodesics=False,
                                      scheme='pole',
                                      n_rungs=1,
                                      alpha=0.5))

    def test_exp_connection_metric(self):
        point = gs.array([gs.pi / 2, 0])
        vector = gs.array([0.25, 0.5])
        point_ext = self.hypersphere.spherical_to_extrinsic(point)
        vector_ext = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, point)
        self.connection.christoffels = self.hypersphere.metric.christoffels
        expected = self.hypersphere.metric.exp(vector_ext, point_ext)
        result_spherical = self.connection.exp(vector,
                                               point,
                                               n_steps=50,
                                               step='rk4')
        result = self.hypersphere.spherical_to_extrinsic(result_spherical)

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

    def test_exp_connection_metric_vectorization(self):
        point = gs.array([[gs.pi / 2, 0], [gs.pi / 6, gs.pi / 4]])
        vector = gs.array([[0.25, 0.5], [0.30, 0.2]])
        point_ext = self.hypersphere.spherical_to_extrinsic(point)
        vector_ext = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, point)
        self.connection.christoffels = self.hypersphere.metric.christoffels
        expected = self.hypersphere.metric.exp(vector_ext, point_ext)
        result_spherical = self.connection.exp(vector,
                                               point,
                                               n_steps=50,
                                               step='rk4')
        result = self.hypersphere.spherical_to_extrinsic(result_spherical)

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

    def test_log_connection_metric(self):
        base_point = gs.array([gs.pi / 3, gs.pi / 4])
        point = gs.array([1.0, gs.pi / 2])
        self.connection.christoffels = self.hypersphere.metric.christoffels
        vector = self.connection.log(point=point,
                                     base_point=base_point,
                                     n_steps=75,
                                     step='rk',
                                     tol=1e-10)
        result = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, base_point)
        p_ext = self.hypersphere.spherical_to_extrinsic(base_point)
        q_ext = self.hypersphere.spherical_to_extrinsic(point)
        expected = self.hypersphere.metric.log(base_point=p_ext, point=q_ext)

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

    def test_log_connection_metric_vectorization(self):
        base_point = gs.array([[gs.pi / 3, gs.pi / 4], [gs.pi / 2, gs.pi / 4]])
        point = gs.array([[1.0, gs.pi / 2], [gs.pi / 6, gs.pi / 3]])
        self.connection.christoffels = self.hypersphere.metric.christoffels
        vector = self.connection.log(point=point,
                                     base_point=base_point,
                                     n_steps=75,
                                     step='rk',
                                     tol=1e-10)
        result = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, base_point)
        p_ext = self.hypersphere.spherical_to_extrinsic(base_point)
        q_ext = self.hypersphere.spherical_to_extrinsic(point)
        expected = self.hypersphere.metric.log(base_point=p_ext, point=q_ext)

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

    def test_geodesic_and_coincides_exp_hypersphere(self):
        n_geodesic_points = 10
        initial_point = self.hypersphere.random_uniform(2)
        vector = gs.array([[2., 0., -1.]] * 2)
        initial_tangent_vec = self.hypersphere.to_tangent(
            vector=vector, base_point=initial_point)
        geodesic = self.hypersphere.metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        t = gs.linspace(start=0., stop=1., num=n_geodesic_points)
        points = geodesic(t)
        result = points[-1]
        expected = self.hypersphere.metric.exp(vector, initial_point)
        self.assertAllClose(expected, result)

        initial_point = initial_point[0]
        initial_tangent_vec = initial_tangent_vec[0]
        geodesic = self.hypersphere.metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        points = geodesic(t)
        result = points[-1]
        expected = self.hypersphere.metric.exp(initial_tangent_vec,
                                               initial_point)
        self.assertAllClose(expected, result)

    def test_geodesic_and_coincides_exp_son(self):
        n_geodesic_points = 10
        space = SpecialOrthogonal(n=4)
        initial_point = space.random_uniform(2)
        vector = gs.random.rand(2, 4, 4)
        initial_tangent_vec = space.to_tangent(vector=vector,
                                               base_point=initial_point)
        geodesic = space.bi_invariant_metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        t = gs.linspace(start=0., stop=1., num=n_geodesic_points)
        points = geodesic(t)
        result = points[-1]
        expected = space.bi_invariant_metric.exp(initial_tangent_vec,
                                                 initial_point)
        self.assertAllClose(result, expected)

        initial_point = initial_point[0]
        initial_tangent_vec = initial_tangent_vec[0]
        geodesic = space.bi_invariant_metric.geodesic(
            initial_point=initial_point,
            initial_tangent_vec=initial_tangent_vec)
        points = geodesic(t)
        result = points[-1]
        expected = space.bi_invariant_metric.exp(initial_tangent_vec,
                                                 initial_point)
        self.assertAllClose(expected, result)

    def test_geodesic_invalid_initial_conditions(self):
        space = SpecialOrthogonal(n=4)
        initial_point = space.random_uniform(2)
        vector = gs.random.rand(2, 4, 4)
        initial_tangent_vec = space.to_tangent(vector=vector,
                                               base_point=initial_point)
        end_point = space.random_uniform(2)
        self.assertRaises(
            RuntimeError, lambda: space.bi_invariant_metric.geodesic(
                initial_point=initial_point,
                initial_tangent_vec=initial_tangent_vec,
                end_point=end_point))
class TestRiemannianMetric(geomstats.tests.TestCase):
    def setUp(self):
        warnings.simplefilter("ignore", category=UserWarning)
        gs.random.seed(0)
        self.dim = 2
        self.euc = Euclidean(dim=self.dim)
        self.sphere = Hypersphere(dim=self.dim)
        self.euc_metric = EuclideanMetric(dim=self.dim)
        self.sphere_metric = HypersphereMetric(dim=self.dim)

        def _euc_metric_matrix(base_point):
            """Return matrix of Euclidean inner-product."""
            dim = base_point.shape[-1]
            return gs.eye(dim)

        def _sphere_metric_matrix(base_point):
            """Return sphere's metric in spherical coordinates."""
            theta = base_point[..., 0]
            mat = gs.array([[1.0, 0.0], [0.0, gs.sin(theta) ** 2]])
            return mat

        new_euc_metric = RiemannianMetric(dim=self.dim)
        new_euc_metric.metric_matrix = _euc_metric_matrix

        new_sphere_metric = RiemannianMetric(dim=self.dim)
        new_sphere_metric.metric_matrix = _sphere_metric_matrix

        self.new_euc_metric = new_euc_metric
        self.new_sphere_metric = new_sphere_metric

    def test_cometric_matrix(self):
        base_point = self.euc.random_point()

        result = self.euc_metric.metric_inverse_matrix(base_point)
        expected = gs.eye(self.dim)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_and_torch_only
    def test_metric_derivative_euc_metric(self):
        base_point = self.euc.random_point()

        result = self.euc_metric.inner_product_derivative_matrix(base_point)
        expected = gs.zeros((self.dim,) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_and_torch_only
    def test_metric_derivative_new_euc_metric(self):
        base_point = self.euc.random_point()

        result = self.new_euc_metric.inner_product_derivative_matrix(base_point)
        expected = gs.zeros((self.dim,) * 3)

        self.assertAllClose(result, expected)

    def test_inner_product_new_euc_metric(self):
        base_point = self.euc.random_point()
        tan_a = self.euc.random_point()
        tan_b = self.euc.random_point()
        expected = gs.dot(tan_a, tan_b)

        result = self.new_euc_metric.inner_product(tan_a, tan_b, base_point=base_point)

        self.assertAllClose(result, expected)

    def test_inner_product_new_sphere_metric(self):
        base_point = gs.array([gs.pi / 3.0, gs.pi / 5.0])
        tan_a = gs.array([0.3, 0.4])
        tan_b = gs.array([0.1, -0.5])
        expected = -0.12

        result = self.new_sphere_metric.inner_product(
            tan_a, tan_b, base_point=base_point
        )

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_and_torch_only
    def test_christoffels_eucl_metric(self):
        base_point = self.euc.random_point()

        result = self.euc_metric.christoffels(base_point)
        expected = gs.zeros((self.dim,) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_and_torch_only
    def test_christoffels_new_eucl_metric(self):
        base_point = self.euc.random_point()

        result = self.new_euc_metric.christoffels(base_point)
        expected = gs.zeros((self.dim,) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_tf_and_torch_only
    def test_christoffels_sphere_metrics(self):
        base_point = gs.array([gs.pi / 10.0, gs.pi / 9.0])

        expected = self.sphere_metric.christoffels(base_point)
        result = self.new_sphere_metric.christoffels(base_point)

        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_and_torch_only
    def test_exp_new_eucl_metric(self):
        base_point = self.euc.random_point()
        tan = self.euc.random_point()

        expected = base_point + tan
        result = self.new_euc_metric.exp(tan, base_point)
        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_and_torch_only
    def test_log_new_eucl_metric(self):
        base_point = self.euc.random_point()
        point = self.euc.random_point()

        expected = point - base_point
        result = self.new_euc_metric.log(point, base_point)
        self.assertAllClose(result, expected)

    @geomstats.tests.autograd_tf_and_torch_only
    def test_exp_new_sphere_metric(self):
        base_point = gs.array([gs.pi / 10.0, gs.pi / 9.0])
        tan = gs.array([gs.pi / 2.0, 0.0])

        expected = gs.array([gs.pi / 10.0 + gs.pi / 2.0, gs.pi / 9.0])
        result = self.new_sphere_metric.exp(tan, base_point)
        self.assertAllClose(result, expected)
示例#22
0
class HypersphereMetric(RiemannianMetric):
    """Class for the Hypersphere Metric.

    Parameters
    ----------
    dim : int
        Dimension of the hypersphere.
    """

    def __init__(self, dim):
        super(HypersphereMetric, self).__init__(
            dim=dim,
            signature=(dim, 0))
        self.embedding_metric = EuclideanMetric(dim + 1)
        self._space = _Hypersphere(dim=dim)

    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """Compute the inner-product of two tangent vectors at a base point.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim + 1]
            First tangent vector at base point.
        tangent_vec_b : array-like, shape=[..., dim + 1]
            Second tangent vector at base point.
        base_point : array-like, shape=[..., dim + 1], optional
            Point on the hypersphere.

        Returns
        -------
        inner_prod : array-like, shape=[...,]
            Inner-product of the two tangent vectors.
        """
        inner_prod = self.embedding_metric.inner_product(
            tangent_vec_a, tangent_vec_b, base_point)

        return inner_prod

    def squared_norm(self, vector, base_point=None):
        """Compute the squared norm of a vector.

        Squared norm of a vector associated with the inner-product
        at the tangent space at a base point.

        Parameters
        ----------
        vector : array-like, shape=[..., dim + 1]
            Vector on the tangent space of the hypersphere at base point.
        base_point : array-like, shape=[..., dim + 1], optional
            Point on the hypersphere.

        Returns
        -------
        sq_norm : array-like, shape=[..., 1]
            Squared norm of the vector.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp(self, tangent_vec, base_point):
        """Compute the Riemannian exponential of a tangent vector.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim + 1]
            Tangent vector at a base point.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hypersphere.

        Returns
        -------
        exp : array-like, shape=[..., dim + 1]
            Point on the hypersphere equal to the Riemannian exponential
            of tangent_vec at the base point.
        """
        hypersphere = Hypersphere(dim=self.dim)
        proj_tangent_vec = hypersphere.to_tangent(tangent_vec, base_point)
        norm2 = self.embedding_metric.squared_norm(proj_tangent_vec)

        coef_1 = utils.taylor_exp_even_func(
            norm2, utils.cos_close_0, order=4)
        coef_2 = utils.taylor_exp_even_func(
            norm2, utils.sinc_close_0, order=4)
        exp = (gs.einsum('...,...j->...j', coef_1, base_point)
               + gs.einsum('...,...j->...j', coef_2, proj_tangent_vec))

        return exp

    def log(self, point, base_point, **kwargs):
        """Compute the Riemannian logarithm of a point.

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

        Returns
        -------
        log : array-like, shape=[..., dim + 1]
            Tangent vector at the base point equal to the Riemannian logarithm
            of point at the base point.
        """
        inner_prod = self.embedding_metric.inner_product(base_point, point)
        cos_angle = gs.clip(inner_prod, -1., 1.)
        squared_angle = gs.arccos(cos_angle) ** 2
        coef_1_ = utils.taylor_exp_even_func(
            squared_angle, utils.inv_sinc_close_0, order=5)
        coef_2_ = utils.taylor_exp_even_func(
            squared_angle, utils.inv_tanc_close_0, order=5)
        log = (gs.einsum('...,...j->...j', coef_1_, point)
               - gs.einsum('...,...j->...j', coef_2_, base_point))

        return log

    def dist(self, point_a, point_b):
        """Compute the geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim + 1]
            First point on the hypersphere.
        point_b : array-like, shape=[..., dim + 1]
            Second point on the hypersphere.

        Returns
        -------
        dist : array-like, shape=[..., 1]
            Geodesic distance between the two points.
        """
        norm_a = self.embedding_metric.norm(point_a)
        norm_b = self.embedding_metric.norm(point_b)
        inner_prod = self.embedding_metric.inner_product(point_a, point_b)

        cos_angle = inner_prod / (norm_a * norm_b)
        cos_angle = gs.clip(cos_angle, -1, 1)

        dist = gs.arccos(cos_angle)

        return dist

    def squared_dist(self, point_a, point_b):
        """Squared geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim]
            Point on the hypersphere.
        point_b : array-like, shape=[..., dim]
            Point on the hypersphere.

        Returns
        -------
        sq_dist : array-like, shape=[...,]
        """
        return self.dist(point_a, point_b) ** 2

    @staticmethod
    def parallel_transport(tangent_vec_a, tangent_vec_b, base_point):
        r"""Compute the parallel transport of a tangent vector.

        Closed-form solution for 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=[..., dim + 1]
            Tangent vector at base point to be transported.
        tangent_vec_b : array-like, shape=[..., dim + 1]
            Tangent vector at base point, along which the parallel transport
            is computed.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hypersphere.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., dim + 1]
            Transported tangent vector at `exp_(base_point)(tangent_vec_b)`.
        """
        theta = gs.linalg.norm(tangent_vec_b, axis=-1)
        normalized_b = gs.einsum('...,...i->...i', 1 / theta, tangent_vec_b)
        pb = gs.einsum('...i,...i->...', tangent_vec_a, normalized_b)
        p_orth = tangent_vec_a - gs.einsum('...,...i->...i', pb, normalized_b)
        transported = \
            - gs.einsum('...,...i->...i', gs.sin(theta) * pb, base_point)\
            + gs.einsum('...,...i->...i', gs.cos(theta) * pb, normalized_b)\
            + p_orth
        return transported

    def christoffels(self, point, point_type='spherical'):
        """Compute the Christoffel symbols at a point.

        Only implemented in dimension 2 and for spherical coordinates.

        Parameters
        ----------
        point : array-like, shape=[..., dim]
            Point on hypersphere where the Christoffel symbols are computed.

        point_type: str, {'spherical', 'intrinsic', 'extrinsic'}
            Coordinates in which to express the Christoffel symbols.
            Optional, default: 'spherical'.

        Returns
        -------
        christoffel : array-like, shape=[..., contravariant index, 1st
                                         covariant index, 2nd covariant index]
            Christoffel symbols at point.
        """
        if self.dim != 2 or point_type != 'spherical':
            raise NotImplementedError(
                'The Christoffel symbols are only implemented'
                ' for spherical coordinates in the 2-sphere')

        point = gs.to_ndarray(point, to_ndim=2)
        christoffel = []
        for sample in point:
            gamma_0 = gs.array(
                [[0, 0], [0, - gs.sin(sample[0]) * gs.cos(sample[0])]])
            gamma_1 = gs.array([[0, gs.cos(sample[0]) / gs.sin(sample[0])],
                                [gs.cos(sample[0]) / gs.sin(sample[0]), 0]])
            christoffel.append(gs.stack([gamma_0, gamma_1]))

        christoffel = gs.stack(christoffel)
        if gs.ndim(christoffel) == 4 and gs.shape(christoffel)[0] == 1:
            christoffel = gs.squeeze(christoffel, axis=0)
        return christoffel

    def curvature(
            self, tangent_vec_a, tangent_vec_b, tangent_vec_c,
            base_point):
        r"""Compute the curvature.

        For three tangent vectors at a base point :math: `x,y,z`,
        the curvature is defined by
        :math: `R(x, y)z = \nabla_{[x,y]}z
        - \nabla_z\nabla_y z + \nabla_y\nabla_x z`, where :math: `\nabla`
        is the Levi-Civita connection. In the case of the hypersphere,
        we have the closed formula
        :math: `R(x,y)z = \langle x, z \rangle y - \langle y,z \rangle x`.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        tangent_vec_b : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        tangent_vec_c : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        base_point :  array-like, shape=[..., dim]
            Point on the group. Optional, default is the identity.

        Returns
        -------
        curvature : array-like, shape=[..., dim]
            Tangent vector at `base_point`.
        """
        inner_ac = self.inner_product(tangent_vec_a, tangent_vec_c)
        inner_bc = self.inner_product(tangent_vec_b, tangent_vec_c)
        first_term = gs.einsum('...,...i->...i', inner_bc, tangent_vec_a)
        second_term = gs.einsum('...,...i->...i', inner_ac, tangent_vec_b)
        return - first_term + second_term
示例#23
0
 def test_dist(self, dim, point_a, point_b, expected):
     metric = EuclideanMetric(dim)
     result = metric.dist(point_a, point_b)
     self.assertAllClose(result, gs.array(expected))
示例#24
0
class TestConnectionMethods(geomstats.tests.TestCase):
    def setUp(self):
        warnings.simplefilter('ignore', category=UserWarning)

        self.dimension = 4
        self.euc_metric = EuclideanMetric(dimension=self.dimension)

        self.connection = Connection(dimension=2)
        self.hypersphere = Hypersphere(dimension=2)

    def test_metric_matrix(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_matrix(base_point)
        expected = gs.array([gs.eye(self.dimension)])

        with self.session():
            self.assertAllClose(result, expected)

    def test_cometric_matrix(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_inverse_matrix(base_point)
        expected = gs.array([gs.eye(self.dimension)])

        with self.session():
            self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_metric_derivative(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_derivative_matrix(base_point)
        expected = gs.zeros((1, ) + (self.dimension, ) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_christoffels(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.christoffels(base_point)
        expected = gs.zeros((1, ) + (self.dimension, ) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_parallel_transport(self):
        n_samples = 10
        base_point = self.hypersphere.random_uniform(n_samples)
        tan_vec_a = self.hypersphere.projection_to_tangent_space(
            gs.random.rand(n_samples, 3), base_point)
        tan_vec_b = self.hypersphere.projection_to_tangent_space(
            gs.random.rand(n_samples, 3), base_point)
        expected = self.hypersphere.metric.parallel_transport(
            tan_vec_a, tan_vec_b, base_point)
        result = self.hypersphere.metric.pole_ladder_parallel_transport(
            tan_vec_a, tan_vec_b, base_point)

        self.assertAllClose(result, expected, rtol=1e-7, atol=1e-5)

    @geomstats.tests.np_only
    def test_exp(self):
        point = gs.array([[gs.pi / 2, 0], [gs.pi / 6, gs.pi / 4]])
        vector = gs.array([[0.25, 0.5], [0.30, 0.2]])
        point_ext = self.hypersphere.spherical_to_extrinsic(point)
        vector_ext = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, point)
        self.connection.christoffels = self.hypersphere.metric.christoffels
        expected = self.hypersphere.metric.exp(vector_ext, point_ext)
        result_spherical = self.connection.exp(vector,
                                               point,
                                               n_steps=50,
                                               step='rk4')
        result = self.hypersphere.spherical_to_extrinsic(result_spherical)

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

    @geomstats.tests.np_only
    def test_log(self):
        base_point = gs.array([[gs.pi / 3, gs.pi / 4], [gs.pi / 2, gs.pi / 4]])
        point = gs.array([[1.0, gs.pi / 2], [gs.pi / 6, gs.pi / 3]])
        self.connection.christoffels = self.hypersphere.metric.christoffels
        vector = self.connection.log(point=point,
                                     base_point=base_point,
                                     n_steps=75,
                                     step='rk')
        result = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, base_point)
        p_ext = self.hypersphere.spherical_to_extrinsic(base_point)
        q_ext = self.hypersphere.spherical_to_extrinsic(point)
        expected = self.hypersphere.metric.log(base_point=p_ext, point=q_ext)

        self.assertAllClose(result, expected, rtol=1e-5, atol=1e-5)
示例#25
0
 def __init__(self, space, ray_metric=EuclideanMetric(1)):
     super(SpiderMetric, self).__init__(space=space)
     self.ray_metric = ray_metric
class RiemannianMetricTestData(TestData):

    dim = 2
    euc = Euclidean(dim=dim)
    sphere = Hypersphere(dim=dim)
    euc_metric = EuclideanMetric(dim=dim)
    sphere_metric = HypersphereMetric(dim=dim)

    new_euc_metric = RiemannianMetric(dim=dim)
    new_euc_metric.metric_matrix = _euc_metric_matrix

    new_sphere_metric = RiemannianMetric(dim=dim)
    new_sphere_metric.metric_matrix = _sphere_metric_matrix

    new_euc_metric = new_euc_metric
    new_sphere_metric = new_sphere_metric

    def cometric_matrix_test_data(self):
        random_data = [
            dict(
                metric=self.euc_metric,
                base_point=self.euc.random_point(),
                expected=gs.eye(self.dim),
            )
        ]
        return self.generate_tests(random_data)

    def inner_coproduct_test_data(self):
        base_point = gs.array([0.0, 0.0, 1.0])
        cotangent_vec_a = self.sphere.to_tangent(gs.array([1.0, 2.0, 0.0]),
                                                 base_point)
        cotangent_vec_b = self.sphere.to_tangent(gs.array([1.0, 3.0, 0.0]),
                                                 base_point)

        smoke_data = [
            dict(
                metric=self.euc_metric,
                cotangent_vec_a=gs.array([1.0, 2.0]),
                cotangent_vec_b=gs.array([1.0, 2.0]),
                base_point=self.euc.random_point(),
                expected=5.0,
            ),
            dict(
                metric=self.sphere_metric,
                cotangent_vec_a=cotangent_vec_a,
                cotangent_vec_b=cotangent_vec_b,
                base_point=base_point,
                expected=7.0,
            ),
        ]
        return self.generate_tests(smoke_data)

    def hamiltonian_test_data(self):

        smoke_data = [
            dict(
                metric=self.euc_metric,
                state=(gs.array([1.0, 2.0]), gs.array([1.0, 2.0])),
                expected=2.5,
            )
        ]
        smoke_data += [
            dict(
                metric=self.sphere_metric,
                state=(gs.array([0.0, 0.0, 1.0]), gs.array([1.0, 2.0, 1.0])),
                expected=3.0,
            )
        ]
        return self.generate_tests(smoke_data)

    def inner_product_derivative_matrix_test_data(self):
        base_point = self.euc.random_point()
        random_data = [
            dict(
                metric=self.new_euc_metric,
                base_point=base_point,
                expected=gs.zeros((self.dim, ) * 3),
            )
        ]
        random_data += [
            dict(
                metric=self.euc_metric,
                base_point=base_point,
                expected=gs.zeros((self.dim, ) * 3),
            )
        ]
        return self.generate_tests([], random_data)

    def inner_product_test_data(self):
        base_point = self.euc.random_point()
        tangent_vec_a = self.euc.random_point()
        tangent_vec_b = self.euc.random_point()
        random_data = [
            dict(
                metric=self.euc_metric,
                tangent_vec_a=tangent_vec_a,
                tangent_vec_b=tangent_vec_b,
                base_point=base_point,
                expected=gs.dot(tangent_vec_a, tangent_vec_b),
            )
        ]

        smoke_data = [
            dict(
                metric=self.new_sphere_metric,
                tangent_vec_a=gs.array([0.3, 0.4]),
                tangent_vec_b=gs.array([0.1, -0.5]),
                base_point=gs.array([gs.pi / 3.0, gs.pi / 5.0]),
                expected=-0.12,
            )
        ]
        return self.generate_tests(smoke_data, random_data)

    def christoffels_test_data(self):
        base_point = gs.array([gs.pi / 10.0, gs.pi / 9.0])
        gs.array([gs.pi / 10.0, gs.pi / 9.0])
        smoke_data = []
        random_data = []
        smoke_data = [
            dict(
                metric=self.new_sphere_metric,
                base_point=gs.array([gs.pi / 10.0, gs.pi / 9.0]),
                expected=self.sphere_metric.christoffels(base_point),
            )
        ]
        random_data += [
            dict(
                metric=self.new_euc_metric,
                base_point=self.euc.random_point(),
                expected=gs.zeros((self.dim, ) * 3),
            )
        ]
        random_data += [
            dict(
                metric=self.euc_metric,
                base_point=self.euc.random_point(),
                expected=gs.zeros((self.dim, ) * 3),
            )
        ]

        return self.generate_tests(smoke_data, random_data)

    def exp_test_data(self):
        base_point = gs.array([gs.pi / 10.0, gs.pi / 9.0])
        tangent_vec = gs.array([gs.pi / 2.0, 0.0])
        expected = gs.array([gs.pi / 10.0 + gs.pi / 2.0, gs.pi / 9.0])

        euc_base_point = self.euc.random_point()
        euc_tangent_vec = self.euc.random_point()
        euc_expected = euc_base_point + euc_tangent_vec

        smoke_data = [
            dict(
                metric=self.new_sphere_metric,
                tangent_vec=tangent_vec,
                base_point=base_point,
                expected=expected,
            )
        ]
        random_data = [
            dict(
                metric=self.new_euc_metric,
                tangent_vec=euc_tangent_vec,
                base_point=euc_base_point,
                expected=euc_expected,
            )
        ]
        return self.generate_tests(smoke_data, random_data)

    def log_test_data(self):
        base_point = self.euc.random_point()
        point = self.euc.random_point()
        expected = point - base_point
        random_data = [
            dict(
                metric=self.new_euc_metric,
                point=point,
                base_point=base_point,
                expected=expected,
            )
        ]
        return self.generate_tests([], random_data)
示例#27
0
class HypersphereMetric(RiemannianMetric):
    """Class for the Hypersphere Metric."""
    def __init__(self, dimension):
        super(HypersphereMetric, self).__init__(dimension=dimension,
                                                signature=(dimension, 0, 0))
        self.embedding_metric = EuclideanMetric(dimension + 1)

    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """Compute the inner product of two tangent vectors at a base point.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[n_samples, dimension + 1]
                                    or shape=[1, dimension + 1]
        tangent_vec_b : array-like, shape=[n_samples, dimension + 1]
                                    or shape=[1, dimension + 1]
        base_point : array-like, shape=[n_samples, dimension + 1]
                                 or shape=[1, dimension + 1]

        Returns
        -------
        inner_prod : array-like, shape=[n_samples, 1]
                                 or shape=[1, 1]
        """
        inner_prod = self.embedding_metric.inner_product(
            tangent_vec_a, tangent_vec_b, base_point)

        return inner_prod

    def squared_norm(self, vector, base_point=None):
        """Compute squared norm of a vector.

        Squared norm of a vector associated to the inner product
        at the tangent space at a base point.

        Parameters
        ----------
        vector : array-like, shape=[n_samples, dimension + 1]
                             or shape=[1, dimension + 1]
        base_point : array-like, shape=[n_samples, dimension + 1]
                                 or shape=[1, dimension + 1]

        Returns
        -------
        sq_norm : array-like, shape=[n_samples, 1]
                              or shape=[1, 1]
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp(self, tangent_vec, base_point):
        """Riemannian exponential of a tangent vector wrt to a base point.

        Parameters
        ----------
        tangent_vec : array-like, shape=[n_samples, dimension + 1]
                                  or shape=[1, dimension + 1]
        base_point : array-like, shape=[n_samples, dimension + 1]
                                 or shape=[1, dimension + 1]

        Returns
        -------
        exp : array-like, shape=[n_samples, dimension + 1]
                          or shape=[1, dimension + 1]
        """
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

        # TODO(nina): Decide on metric.space or space.metric
        #  for the hypersphere
        # TODO(nina): Raise error when vector is not tangent
        n_base_points, extrinsic_dim = base_point.shape
        n_tangent_vecs, _ = tangent_vec.shape

        hypersphere = Hypersphere(dimension=extrinsic_dim - 1)
        proj_tangent_vec = hypersphere.projection_to_tangent_space(
            tangent_vec, base_point)
        norm_tangent_vec = self.embedding_metric.norm(proj_tangent_vec)

        mask_0 = gs.isclose(norm_tangent_vec, 0.)
        mask_non0 = ~mask_0

        coef_1 = gs.zeros((n_tangent_vecs, 1))
        coef_2 = gs.zeros((n_tangent_vecs, 1))
        norm2 = norm_tangent_vec[mask_0]**2
        norm4 = norm2**2
        norm6 = norm2**3
        coef_1[mask_0] = 1. - norm2 / 2. + norm4 / 24. - norm6 / 720.
        coef_2[mask_0] = 1. - norm2 / 6. + norm4 / 120. - norm6 / 5040.

        coef_1[mask_non0] = gs.cos(norm_tangent_vec[mask_non0])
        coef_2[mask_non0] = gs.sin(norm_tangent_vec[mask_non0]) / \
            norm_tangent_vec[mask_non0]

        n_coef_1 = n_tangent_vecs
        if n_coef_1 != n_base_points:
            if n_coef_1 == 1:
                coef_1 = gs.squeeze(coef_1, axis=0)
                einsum_str = 'i,nj->nj'
            elif n_base_points == 1:
                base_point = gs.squeeze(base_point, axis=0)
                einsum_str = 'ni,j->nj'
            else:
                raise ValueError('Shape mismatch in einsum.')
        else:
            einsum_str = 'ni,nj->nj'

        exp = (gs.einsum(einsum_str, coef_1, base_point) +
               gs.einsum('ni,nj->nj', coef_2, proj_tangent_vec))

        return exp

    def log(self, point, base_point):
        """Compute Riemannian logarithm of a point wrt a base point.

        Parameters
        ----------
        point : array-like, shape=[n_samples, dimension + 1]
                            or shape=[1, dimension + 1]
        base_point : array-like, shape=[n_samples, dimension + 1]
                                 or shape=[1, dimension + 1]

        Returns
        -------
        log : array-like, shape=[n_samples, dimension + 1]
                          or shape=[1, dimension + 1]
        """
        point = gs.to_ndarray(point, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

        norm_base_point = self.embedding_metric.norm(base_point)
        norm_point = self.embedding_metric.norm(point)
        inner_prod = self.embedding_metric.inner_product(base_point, point)
        cos_angle = inner_prod / (norm_base_point * norm_point)
        cos_angle = gs.clip(cos_angle, -1., 1.)

        angle = gs.arccos(cos_angle)
        angle = gs.to_ndarray(angle, to_ndim=1)
        angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

        mask_0 = gs.isclose(angle, 0.)
        mask_else = gs.equal(mask_0, gs.array(False))

        mask_0_float = gs.cast(mask_0, gs.float32)
        mask_else_float = gs.cast(mask_else, gs.float32)

        coef_1 = gs.zeros_like(angle)
        coef_2 = gs.zeros_like(angle)

        coef_1 += mask_0_float * (1. + INV_SIN_TAYLOR_COEFFS[1] * angle**2 +
                                  INV_SIN_TAYLOR_COEFFS[3] * angle**4 +
                                  INV_SIN_TAYLOR_COEFFS[5] * angle**6 +
                                  INV_SIN_TAYLOR_COEFFS[7] * angle**8)
        coef_2 += mask_0_float * (1. + INV_TAN_TAYLOR_COEFFS[1] * angle**2 +
                                  INV_TAN_TAYLOR_COEFFS[3] * angle**4 +
                                  INV_TAN_TAYLOR_COEFFS[5] * angle**6 +
                                  INV_TAN_TAYLOR_COEFFS[7] * angle**8)

        # This avoids division by 0.
        angle += mask_0_float * 1.

        coef_1 += mask_else_float * angle / gs.sin(angle)
        coef_2 += mask_else_float * angle / gs.tan(angle)

        log = (gs.einsum('ni,nj->nj', coef_1, point) -
               gs.einsum('ni,nj->nj', coef_2, base_point))

        mask_same_values = gs.isclose(point, base_point)

        mask_else = gs.equal(mask_same_values, gs.array(False))
        mask_else_float = gs.cast(mask_else, gs.float32)
        mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=1)
        mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=2)
        mask_not_same_points = gs.sum(mask_else_float, axis=1)
        mask_same_points = gs.isclose(mask_not_same_points, 0.)
        mask_same_points = gs.cast(mask_same_points, gs.float32)
        mask_same_points = gs.to_ndarray(mask_same_points, to_ndim=2, axis=1)

        mask_same_points_float = gs.cast(mask_same_points, gs.float32)

        log -= mask_same_points_float * log

        return log

    def dist(self, point_a, point_b):
        """Compute geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[n_samples, dimension + 1]
                              or shape=[1, dimension + 1]
        point_b : array-like, shape=[n_samples, dimension + 1]
                              or shape=[1, dimension + 1]

        Returns
        -------
        dist : array-like, shape=[n_samples, 1]
                           or shape=[1, 1]
        """
        norm_a = self.embedding_metric.norm(point_a)
        norm_b = self.embedding_metric.norm(point_b)
        inner_prod = self.embedding_metric.inner_product(point_a, point_b)

        cos_angle = inner_prod / (norm_a * norm_b)
        cos_angle = gs.clip(cos_angle, -1, 1)

        dist = gs.arccos(cos_angle)

        return dist

    def parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point):
        """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)

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[n_samples, dimension + 1]
        tangent_vec_b : array-like, shape=[n_samples, dimension + 1]
        base_point : array-like, shape=[n_samples, dimension + 1]

        Returns
        -------
        transported_tangent_vec: array-like, shape=[n_samples, dimension + 1]
        """
        tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2)
        tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)
        # TODO @nguigs: work around this condition
        assert len(base_point) == len(tangent_vec_a) == len(tangent_vec_b)
        theta = gs.linalg.norm(tangent_vec_b, axis=1)
        normalized_b = gs.einsum('n, ni->ni', 1 / theta, tangent_vec_b)
        pb = gs.einsum('ni,ni->n', tangent_vec_a, normalized_b)
        p_orth = tangent_vec_a - gs.einsum('n,ni->ni', pb, normalized_b)
        transported = - gs.einsum('n,ni->ni', gs.sin(theta) * pb, base_point)\
            + gs.einsum('n,ni->ni', gs.cos(theta) * pb, normalized_b)\
            + p_orth
        return transported

    def christoffels(self, point, point_type='spherical'):
        """Compute Christoffel symbols.

        Only implemented in dimension 2 and for spherical coordinates.

        Parameters
        ----------
        point : array-like, shape=[n_samples, dimension]

        point_type: str

        Returns
        -------
        christoffel : array-like, shape=[n_samples,
                                         contravariant index,
                                         first covariant index,
                                         second covariant index]
        """
        if self.dimension != 2 or point_type != 'spherical':
            raise NotImplementedError(
                'The Christoffel symbols are only implemented'
                ' for spherical coordinates in the 2-sphere')

        point = gs.to_ndarray(point, to_ndim=2)
        christoffel = []
        for sample in point:
            gamma_0 = gs.array([[0, 0],
                                [0, -gs.sin(sample[0]) * gs.cos(sample[0])]])
            gamma_1 = gs.array([[0, gs.cos(sample[0]) / gs.sin(sample[0])],
                                [gs.cos(sample[0]) / gs.sin(sample[0]), 0]])
            christoffel.append(gs.stack([gamma_0, gamma_1]))

        return gs.stack(christoffel)
示例#28
0
 def __init__(self, dim):
     super(HypersphereMetric, self).__init__(
         dim=dim,
         signature=(dim, 0))
     self.embedding_metric = EuclideanMetric(dim + 1)
     self._space = _Hypersphere(dim=dim)
示例#29
0
class TestConnectionMethods(geomstats.tests.TestCase):
    def setUp(self):
        warnings.simplefilter('ignore', category=UserWarning)

        self.dim = 4
        self.euc_metric = EuclideanMetric(dim=self.dim)

        self.connection = Connection(dim=2)
        self.hypersphere = Hypersphere(dim=2)

    def test_metric_matrix(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_matrix(base_point)
        expected = gs.eye(self.dim)

        self.assertAllClose(result, expected)

    def test_cometric_matrix(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_inverse_matrix(base_point)
        expected = gs.eye(self.dim)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_metric_derivative(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.inner_product_derivative_matrix(base_point)
        expected = gs.zeros((self.dim, ) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_only
    def test_christoffels(self):
        base_point = gs.array([0., 1., 0., 0.])

        result = self.euc_metric.christoffels(base_point)
        expected = gs.zeros((self.dim, ) * 3)

        self.assertAllClose(result, expected)

    @geomstats.tests.np_and_pytorch_only
    def test_parallel_transport(self):
        n_samples = 2
        for step in ['pole', 'schild']:
            n_steps = 1 if step == 'pole' else 100
            tol = 1e-6 if step == 'pole' else 1e-1
            base_point = self.hypersphere.random_uniform(n_samples)
            tan_vec_a = self.hypersphere.projection_to_tangent_space(
                gs.random.rand(n_samples, 3), base_point)
            tan_vec_b = self.hypersphere.projection_to_tangent_space(
                gs.random.rand(n_samples, 3), base_point)
            expected = self.hypersphere.metric.parallel_transport(
                tan_vec_a, tan_vec_b, base_point)
            ladder = self.hypersphere.metric.ladder_parallel_transport(
                tan_vec_a, tan_vec_b, base_point, step=step, n_steps=n_steps)
            result = ladder['transported_tangent_vec']

            self.assertAllClose(result, expected, rtol=tol, atol=tol)

    @geomstats.tests.np_and_pytorch_only
    def test_parallel_transport_trajectory(self):
        n_samples = 2
        for step in ['pole', 'schild']:
            n_steps = 1 if step == 'pole' else 100
            rtol = 1e-6 if step == 'pole' else 1e-1
            base_point = self.hypersphere.random_uniform(n_samples)
            tan_vec_a = self.hypersphere.projection_to_tangent_space(
                gs.random.rand(n_samples, 3), base_point)
            tan_vec_b = self.hypersphere.projection_to_tangent_space(
                gs.random.rand(n_samples, 3), base_point)
            expected = self.hypersphere.metric.parallel_transport(
                tan_vec_a, tan_vec_b, base_point)
            ladder = self.hypersphere.metric.ladder_parallel_transport(
                tan_vec_a,
                tan_vec_b,
                base_point,
                return_geodesics=True,
                step=step,
                n_steps=n_steps)
            result = ladder['transported_tangent_vec']

            self.assertAllClose(result, expected, rtol=rtol)

    @geomstats.tests.np_only
    def test_exp(self):
        point = gs.array([[gs.pi / 2, 0], [gs.pi / 6, gs.pi / 4]])
        vector = gs.array([[0.25, 0.5], [0.30, 0.2]])
        point_ext = self.hypersphere.spherical_to_extrinsic(point)
        vector_ext = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, point)
        self.connection.christoffels = self.hypersphere.metric.christoffels
        expected = self.hypersphere.metric.exp(vector_ext, point_ext)
        result_spherical = self.connection.exp(vector,
                                               point,
                                               n_steps=50,
                                               step='rk4')
        result = self.hypersphere.spherical_to_extrinsic(result_spherical)

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

    @geomstats.tests.np_only
    def test_log(self):
        base_point = gs.array([[gs.pi / 3, gs.pi / 4], [gs.pi / 2, gs.pi / 4]])
        point = gs.array([[1.0, gs.pi / 2], [gs.pi / 6, gs.pi / 3]])
        self.connection.christoffels = self.hypersphere.metric.christoffels
        vector = self.connection.log(point=point,
                                     base_point=base_point,
                                     n_steps=75,
                                     step='rk')
        result = self.hypersphere.tangent_spherical_to_extrinsic(
            vector, base_point)
        p_ext = self.hypersphere.spherical_to_extrinsic(base_point)
        q_ext = self.hypersphere.spherical_to_extrinsic(point)
        expected = self.hypersphere.metric.log(base_point=p_ext, point=q_ext)

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