예제 #1
0
    def rotation_vector_from_quaternion(self, quaternion):
        """Convert a unit quaternion into a rotation vector.

        Parameters
        ----------
        quaternion : array-like, shape=[..., 4]

        Returns
        -------
        rot_vec : array-like, shape=[..., 3]
        """
        cos_half_angle = quaternion[:, 0]
        cos_half_angle = gs.clip(cos_half_angle, -1, 1)
        half_angle = gs.arccos(cos_half_angle)

        half_angle = gs.to_ndarray(half_angle, to_ndim=2, axis=1)

        mask_0 = gs.isclose(half_angle, 0.)
        mask_not_0 = ~mask_0

        rotation_axis = gs.divide(
            quaternion[:, 1:],
            gs.sin(half_angle) * gs.cast(mask_not_0, gs.float32) +
            gs.cast(mask_0, gs.float32))
        rot_vec = gs.array(2 * half_angle * rotation_axis *
                           gs.cast(mask_not_0, gs.float32))

        rot_vec = self.regularize(rot_vec)
        return rot_vec
예제 #2
0
    def __init__(self,
                 group,
                 inner_product_mat_at_identity=None,
                 left_or_right='left'):
        if inner_product_mat_at_identity is None:
            inner_product_mat_at_identity = gs.eye(self.group.dimension)
        inner_product_mat_at_identity = gs.to_ndarray(
            inner_product_mat_at_identity, to_ndim=3)
        mat_shape = inner_product_mat_at_identity.shape
        assert mat_shape == (1, ) + (group.dimension, ) * 2, mat_shape

        assert left_or_right in ('left', 'right')
        eigenvalues = gs.linalg.eigvalsh(inner_product_mat_at_identity)
        mask_pos_eigval = gs.greater(eigenvalues, 0.)
        n_pos_eigval = gs.sum(gs.cast(mask_pos_eigval, gs.int32))
        mask_neg_eigval = gs.less(eigenvalues, 0.)
        n_neg_eigval = gs.sum(gs.cast(mask_neg_eigval, gs.int32))
        mask_null_eigval = gs.isclose(eigenvalues, 0.)
        n_null_eigval = gs.sum(gs.cast(mask_null_eigval, gs.int32))

        self.group = group
        if inner_product_mat_at_identity is None:
            inner_product_mat_at_identity = gs.eye(self.group.dimension)

        self.inner_product_mat_at_identity = inner_product_mat_at_identity
        self.left_or_right = left_or_right
        self.signature = (n_pos_eigval, n_null_eigval, n_neg_eigval)
예제 #3
0
    def __init__(self,
                 group,
                 algebra=None,
                 metric_mat_at_identity=None,
                 left_or_right='left',
                 **kwargs):
        super(InvariantMetric, self).__init__(dim=group.dim, **kwargs)

        self.group = group
        self.lie_algebra = algebra
        if metric_mat_at_identity is None:
            metric_mat_at_identity = gs.eye(self.group.dim)

        geomstats.errors.check_parameter_accepted_values(
            left_or_right, 'left_or_right', ['left', 'right'])

        eigenvalues = gs.linalg.eigvalsh(metric_mat_at_identity)
        mask_pos_eigval = gs.greater(eigenvalues, 0.)
        n_pos_eigval = gs.sum(gs.cast(mask_pos_eigval, gs.int32))
        mask_neg_eigval = gs.less(eigenvalues, 0.)
        n_neg_eigval = gs.sum(gs.cast(mask_neg_eigval, gs.int32))
        mask_null_eigval = gs.isclose(eigenvalues, 0.)
        n_null_eigval = gs.sum(gs.cast(mask_null_eigval, gs.int32))

        self.metric_mat_at_identity = metric_mat_at_identity
        self.left_or_right = left_or_right
        self.signature = (n_pos_eigval, n_null_eigval, n_neg_eigval)
예제 #4
0
 def test_random_von_mises_kappa(self):
     # check concentration parameter for dispersed distribution
     kappa = 1.0
     n_points = 100000
     for dim in [2, 9]:
         sphere = Hypersphere(dim)
         points = sphere.random_von_mises_fisher(kappa=kappa,
                                                 n_samples=n_points)
         sum_points = gs.sum(points, axis=0)
         mean_norm = gs.linalg.norm(sum_points) / n_points
         kappa_estimate = (mean_norm * (dim + 1.0 - mean_norm**2) /
                           (1.0 - mean_norm**2))
         kappa_estimate = gs.cast(kappa_estimate, gs.float64)
         p = dim + 1
         n_steps = 100
         for _ in range(n_steps):
             bessel_func_1 = scipy.special.iv(p / 2.0, kappa_estimate)
             bessel_func_2 = scipy.special.iv(p / 2.0 - 1.0, kappa_estimate)
             ratio = bessel_func_1 / bessel_func_2
             denominator = 1.0 - ratio**2 - (p -
                                             1.0) * ratio / kappa_estimate
             mean_norm = gs.cast(mean_norm, gs.float64)
             kappa_estimate = kappa_estimate - (ratio -
                                                mean_norm) / denominator
         result = kappa_estimate
         expected = kappa
         self.assertAllClose(result, expected, atol=KAPPA_ESTIMATION_TOL)
예제 #5
0
    def _log_translation_transform(self, rot_vec):
        rot_vec = gs.to_ndarray(rot_vec, to_ndim=2, axis=1)
        exp_transform = self._exp_translation_transform(rot_vec)

        if rot_vec.dtype == gs.float32:
            mask_close_0 = gs.isclose(rot_vec, 0., atol=1e-6)
        else:
            mask_close_0 = gs.isclose(rot_vec, 0.)
        mask_else = ~mask_close_0

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

        inv_determinant = gs.zeros_like(rot_vec)

        inv_determinant += 0.5 * mask_close_0_float / (
            TAYLOR_COEFFS_1_AT_0[0]
            + TAYLOR_COEFFS_1_AT_0[1] * rot_vec ** 2
            + TAYLOR_COEFFS_1_AT_0[2] * rot_vec ** 6)

        rot_vec = rot_vec + 1e-3 * mask_close_0_float
        inv_determinant += mask_else_float * (
            rot_vec ** 2 / (2 * (1 - gs.cos(rot_vec))))
        transform = gs.einsum(
            'il, ijk -> ijk', inv_determinant,
            gs.transpose(exp_transform, axes=[0, 2, 1]))

        return transform
예제 #6
0
    def log(self, point, base_point):
        """
        Riemannian logarithm of a point wrt a base point.
        """
        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
예제 #7
0
    def quaternion_from_rotation_vector(self, rot_vec):
        """Convert a rotation vector into a unit quaternion.

        Parameters
        ----------
        rot_vec : array-like, shape=[..., 3]

        Returns
        -------
        quaternion : array-like, shape=[..., 4]
        """
        rot_vec = self.regularize(rot_vec)

        angle = gs.linalg.norm(rot_vec, axis=1)
        angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

        mask_0 = gs.isclose(angle, 0.)
        mask_not_0 = ~mask_0

        rotation_axis = gs.divide(
            rot_vec,
            angle * gs.cast(mask_not_0, gs.float32) +
            gs.cast(mask_0, gs.float32))

        quaternion = gs.concatenate(
            (gs.cos(angle / 2), gs.sin(angle / 2) * rotation_axis[:]), axis=1)

        return quaternion
예제 #8
0
    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """
        Inner product defined by the Riemannian metric at point base_point
        between tangent vectors tangent_vec_a and 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)
        n_tangent_vec_a, _ = tangent_vec_a.shape
        n_tangent_vec_b, _ = tangent_vec_b.shape

        inner_prod_mat = self.inner_product_matrix(base_point)
        inner_prod_mat = gs.to_ndarray(inner_prod_mat, to_ndim=3)
        n_mats, _, _ = inner_prod_mat.shape

        n_inner_prod = gs.maximum(n_tangent_vec_a, n_tangent_vec_b)
        n_inner_prod = gs.maximum(n_inner_prod, n_mats)

        n_tiles_a = gs.divide(n_inner_prod, n_tangent_vec_a)
        n_tiles_a = gs.cast(n_tiles_a, gs.int32)
        tangent_vec_a = gs.tile(tangent_vec_a, [n_tiles_a, 1])
        n_tiles_b = gs.divide(n_inner_prod, n_tangent_vec_b)
        n_tiles_b = gs.cast(n_tiles_b, gs.int32)
        tangent_vec_b = gs.tile(tangent_vec_b, [n_tiles_b, 1])

        n_tiles_mat = gs.divide(n_inner_prod, n_mats)
        n_tiles_mat = gs.cast(n_tiles_mat, gs.int32)
        inner_prod_mat = gs.tile(inner_prod_mat, [n_tiles_mat, 1, 1])

        aux = gs.einsum('nj,njk->nk', tangent_vec_a, inner_prod_mat)
        inner_prod = gs.einsum('nk,nk->n', aux, tangent_vec_b)
        inner_prod = gs.to_ndarray(inner_prod, to_ndim=2, axis=1)

        assert gs.ndim(inner_prod) == 2, inner_prod.shape
        return inner_prod
    def _fit_extrinsic(self, X, y, weights=None, compute_training_score=False):
        """Estimate the parameters using the extrinsic gradient descent.

        Estimate the intercept and the coefficient defining the
        geodesic regression model, using the extrinsic gradient.

        Parameters
        ----------
        X : {array-like, sparse matrix}, shape=[...,}]
            Training input samples.
        y : array-like, shape=[..., {dim, [n,n]}]
            Training target values.
        weights : array-like, shape=[...,]
            Weights associated to the points.
            Optional, default: None.
        compute_training_score : bool
            Whether to compute R^2.
            Optional, default: False.

        Returns
        -------
        self : object
            Returns self.
        """
        shape = (
            y.shape[-1:] if self.space.default_point_type == "vector" else y.shape[-2:]
        )

        intercept_init, coef_init = self.initialize_parameters(y)
        intercept_hat = self.space.projection(intercept_init)
        coef_hat = self.space.to_tangent(coef_init, intercept_hat)
        initial_guess = gs.vstack([gs.flatten(intercept_hat), gs.flatten(coef_hat)])

        objective_with_grad = gs.autodiff.value_and_grad(
            lambda param: self._loss(X, y, param, shape, weights), to_numpy=True
        )

        res = minimize(
            objective_with_grad,
            initial_guess,
            method="CG",
            jac=True,
            options={"disp": self.verbose, "maxiter": self.max_iter},
            tol=self.tol,
        )

        intercept_hat, coef_hat = gs.split(gs.array(res.x), 2)
        intercept_hat = gs.reshape(intercept_hat, shape)
        intercept_hat = gs.cast(intercept_hat, dtype=y.dtype)
        coef_hat = gs.reshape(coef_hat, shape)
        coef_hat = gs.cast(coef_hat, dtype=y.dtype)

        self.intercept_ = self.space.projection(intercept_hat)
        self.coef_ = self.space.to_tangent(coef_hat, self.intercept_)

        if compute_training_score:
            variance = gs.sum(self.metric.squared_dist(y, self.intercept_))
            self.training_score_ = 1 - 2 * res.fun / variance

        return self
예제 #10
0
 def test_log(self, n, point, base_point, expected):
     group = self.space(n)
     expected = gs.cast(gs.array(expected), gs.float64)
     point = gs.cast(gs.array(point), gs.float64)
     base_point = (None if base_point is None else gs.cast(
         gs.array(base_point), gs.float64))
     self.assertAllClose(group.log(point, base_point), expected)
예제 #11
0
    def test_exp_vectorization(self):
        point = gs.array(
            [
                [[2.0, 0.0, 0.0], [0.0, 3.0, 0.0], [0.0, 0.0, 4.0]],
                [[1.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 0.0, 6.0]],
            ]
        )

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

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

        result = self.group.exp(point)
        self.assertAllClose(result, expected)
예제 #12
0
    def projection(self, point, atol=gs.atol):
        """Project a point in ambient space to the parameter set.

        The parameter is floored to `gs.atol` if it is negative
        and to '1-gs.atol' if it is greater than 1.

        Parameters
        ----------
        point : array-like, shape=[...,]
            Point in ambient space.
        atol : float
            Tolerance to evaluate positivity.

        Returns
        -------
        projected : array-like, shape=[...,]
            Projected point.
        """
        point = gs.cast(gs.array(point), dtype=gs.float32)
        projected = gs.where(
            gs.logical_or(point < atol, point > 1 - atol),
            (1 - atol) * gs.cast(
                (point > 1 - atol), gs.float32) + atol * gs.cast(
                    (point < atol), gs.float32),
            point,
        )
        return gs.squeeze(projected)
예제 #13
0
    def _exp_translation_transform(self, rot_vec):
        n_samples = rot_vec.shape[0]
        base_1 = gs.array([gs.eye(2)] * n_samples)
        base_2 = -gs.array(
            [self.rotations.skew_matrix_from_vector(gs.ones(1))] * n_samples)

        mask_close_0 = gs.isclose(rot_vec, 0.)
        mask_else = ~mask_close_0

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

        cos_coef = gs.zeros_like(rot_vec)
        sin_coef = gs.zeros_like(rot_vec)

        cos_coef += mask_close_0_float * (
            TAYLOR_COEFFS_1_AT_0[0] * rot_vec
            + TAYLOR_COEFFS_1_AT_0[1] * rot_vec ** 3
            + TAYLOR_COEFFS_1_AT_0[2] * rot_vec ** 5)
        sin_coef += mask_close_0_float * (
            1
            - TAYLOR_COEFFS_2_AT_0[0] * rot_vec ** 2
            - TAYLOR_COEFFS_2_AT_0[2] * rot_vec ** 4)

        rot_vec = rot_vec + mask_close_0_float * 1e-6
        cos_coef += mask_else_float * ((1. - gs.cos(rot_vec)) / rot_vec)
        sin_coef += mask_else_float * (gs.sin(rot_vec) / rot_vec)

        sin_term = gs.einsum('...i,...jk->...jk', sin_coef, base_1)
        cos_term = gs.einsum('...i,...jk->...jk', cos_coef, base_2)
        transform = sin_term + cos_term

        return transform
예제 #14
0
    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """
        Inner product between two tangent vectors at a base point.
        """
        tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2)
        tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2)
        n_tangent_vec_a = gs.shape(tangent_vec_a)[0]
        n_tangent_vec_b = gs.shape(tangent_vec_b)[0]

        inner_prod_mat = self.inner_product_matrix(base_point)
        inner_prod_mat = gs.to_ndarray(inner_prod_mat, to_ndim=3)
        n_mats = gs.shape(inner_prod_mat)[0]

        n_inner_prod = gs.maximum(n_tangent_vec_a, n_tangent_vec_b)
        n_inner_prod = gs.maximum(n_inner_prod, n_mats)

        n_tiles_a = gs.divide(n_inner_prod, n_tangent_vec_a)
        n_tiles_a = gs.cast(n_tiles_a, gs.int32)
        tangent_vec_a = gs.tile(tangent_vec_a, [n_tiles_a, 1])

        n_tiles_b = gs.divide(n_inner_prod, n_tangent_vec_b)
        n_tiles_b = gs.cast(n_tiles_b, gs.int32)
        tangent_vec_b = gs.tile(tangent_vec_b, [n_tiles_b, 1])

        n_tiles_mat = gs.divide(n_inner_prod, n_mats)
        n_tiles_mat = gs.cast(n_tiles_mat, gs.int32)
        inner_prod_mat = gs.tile(inner_prod_mat, [n_tiles_mat, 1, 1])

        aux = gs.einsum('nj,njk->nk', tangent_vec_a, inner_prod_mat)
        inner_prod = gs.einsum('nk,nk->n', aux, tangent_vec_b)
        inner_prod = gs.to_ndarray(inner_prod, to_ndim=2, axis=1)

        assert gs.ndim(inner_prod) == 2, inner_prod.shape
        return inner_prod
예제 #15
0
 def test_exp(self, n, tangent_vec, base_point, expected):
     group = self.space(n)
     expected = gs.cast(gs.array(expected), gs.float64)
     tangent_vec = gs.cast(gs.array(tangent_vec), gs.float64)
     base_point = (None if base_point is None else gs.cast(
         gs.array(base_point), gs.float64))
     self.assertAllClose(group.exp(tangent_vec, base_point),
                         gs.array(expected))
예제 #16
0
    def _log_translation_transform(self, rot_vec):
        """Compute matrix associated to rot_vec for the translation part in log.

        Parameters
        ----------
        rot_vec : array-like, shape=[..., 3]

        Returns
        -------
        transform : array-like, shape=[..., 3, 3]
        Matrix to be applied to the translation part in log
        """
        n_samples = rot_vec.shape[0]
        angle = gs.linalg.norm(rot_vec, axis=1)
        angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

        skew_mat = self.rotations.skew_matrix_from_vector(rot_vec)
        sq_skew_mat = gs.matmul(skew_mat, skew_mat)

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

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

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

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

        coef_2 += mask_close_0_float * (1.0 / 12.0 + angle**2 / 720.0 +
                                        angle**4 / 30240.0 +
                                        angle**6 / 1209600.0)

        delta_angle = angle - gs.pi
        coef_2 += mask_close_pi_float * (
            1.0 / PI2 + (PI2 - 8.0) * delta_angle / (4.0 * PI3) -
            ((PI2 - 12.0) * delta_angle**2 / (4.0 * PI4)) +
            ((-192.0 + 12.0 * PI2 + PI4) * delta_angle**3 / (48.0 * PI5)) -
            ((-240.0 + 12.0 * PI2 + PI4) * delta_angle**4 / (48.0 * PI6)) +
            ((-2880.0 + 120.0 * PI2 + 10.0 * PI4 + PI6) * delta_angle**5 /
             (480.0 * PI7)) -
            ((-3360 + 120.0 * PI2 + 10.0 * PI4 + PI6) * delta_angle**6 /
             (480.0 * PI8)))

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

        term_1 = gs.einsum("...i,...ij->...ij", coef_1, skew_mat)
        term_2 = gs.einsum("...i,...ij->...ij", coef_2, sq_skew_mat)
        term_id = gs.array([gs.eye(3)] * n_samples)
        transform = term_id + term_1 + term_2

        return transform
예제 #17
0
    def regularize_tangent_vec_at_identity(
            self, tangent_vec, metric=None):
        """Regularize a tangent vector at the identify.

        In 3D, regularize a tangent_vector by getting its norm at the identity,
        determined by the metric, to be less than pi.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., 3]]
        metric : RiemannianMetric, optional
            default: self.left_canonical_metric

        Returns
        -------
        regularized_vec : array-like, shape=[..., 3]]
        """
        if metric is None:
            metric = self.left_canonical_metric
        tangent_vec_metric_norm = metric.norm(tangent_vec)
        tangent_vec_canonical_norm = gs.linalg.norm(tangent_vec, axis=-1)

        mask_norm_0 = gs.isclose(tangent_vec_metric_norm, 0.)
        mask_canonical_norm_0 = gs.isclose(tangent_vec_canonical_norm, 0.)

        mask_0 = mask_norm_0 | mask_canonical_norm_0
        mask_else = ~mask_0

        # This avoids division by 0.
        mask_0_float = gs.cast(mask_0, gs.float32) + self.epsilon
        mask_else_float = gs.cast(mask_else, gs.float32) + self.epsilon

        regularized_vec = gs.zeros_like(tangent_vec)
        regularized_vec += gs.einsum(
            '...,...i->...i', mask_0_float, tangent_vec)

        tangent_vec_canonical_norm += mask_0_float

        coef = gs.zeros_like(tangent_vec_metric_norm)
        coef += mask_else_float * (
            tangent_vec_metric_norm
            / tangent_vec_canonical_norm)

        coef_tangent_vec = gs.einsum(
            '...,...i->...i', coef, tangent_vec)
        regularized_vec += gs.einsum(
            '...,...i->...i',
            mask_else_float,
            self.regularize(coef_tangent_vec))

        coef += mask_0_float
        regularized_vec = gs.einsum(
            '...,...i->...i', 1. / coef, regularized_vec)
        regularized_vec = gs.einsum(
            '...,...i->...i', mask_else_float, regularized_vec)
        return regularized_vec
예제 #18
0
    def _exp_translation_transform(self, rot_vec):
        """Compute matrix associated to rot_vec for the translation part in exp.

        Parameters
        ----------
        rot_vec : array-like, shape=[..., 3]

        Returns
        -------
        transform : array-like, shape=[..., 3, 3]
            Matrix to be applied to the translation part in exp.
        """
        n_samples = rot_vec.shape[0]

        angle = gs.linalg.norm(rot_vec, axis=-1)
        angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

        skew_mat = self.rotations.skew_matrix_from_vector(rot_vec)
        sq_skew_mat = gs.matmul(skew_mat, skew_mat)

        mask_0 = gs.equal(angle, 0.)
        mask_close_0 = gs.isclose(angle, 0.) & ~mask_0
        mask_else = ~mask_0 & ~mask_close_0

        mask_0_float = gs.cast(mask_0, gs.float32)
        mask_close_0_float = gs.cast(mask_close_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. / 2. * gs.ones_like(angle)
        coef_2 += mask_0_float * 1. / 6. * gs.ones_like(angle)

        coef_1 += mask_close_0_float * (
            TAYLOR_COEFFS_1_AT_0[0]
            + TAYLOR_COEFFS_1_AT_0[2] * angle ** 2
            + TAYLOR_COEFFS_1_AT_0[4] * angle ** 4
            + TAYLOR_COEFFS_1_AT_0[6] * angle ** 6)
        coef_2 += mask_close_0_float * (
            TAYLOR_COEFFS_2_AT_0[0]
            + TAYLOR_COEFFS_2_AT_0[2] * angle ** 2
            + TAYLOR_COEFFS_2_AT_0[4] * angle ** 4
            + TAYLOR_COEFFS_2_AT_0[6] * angle ** 6)

        angle += mask_0_float * 1.

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

        term_1 = gs.einsum('...i,...ij->...ij', coef_1, skew_mat)
        term_2 = gs.einsum('...i,...ij->...ij', coef_2, sq_skew_mat)
        term_id = gs.array([gs.eye(3)] * n_samples)
        transform = term_id + term_1 + term_2

        return transform
예제 #19
0
    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)

        sq_norm_tangent_vec = self.embedding_metric.squared_norm(
                tangent_vec)
        norm_tangent_vec = gs.sqrt(sq_norm_tangent_vec)

        mask_0 = gs.isclose(sq_norm_tangent_vec, 0.)
        mask_0 = gs.to_ndarray(mask_0, to_ndim=1)
        mask_else = ~mask_0
        mask_else = gs.to_ndarray(mask_else, to_ndim=1)
        mask_0_float = gs.cast(mask_0, gs.float32)
        mask_else_float = gs.cast(mask_else, gs.float32)

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

        coef_1 += mask_0_float * (
                  1. + COSH_TAYLOR_COEFFS[2] * norm_tangent_vec ** 2
                  + COSH_TAYLOR_COEFFS[4] * norm_tangent_vec ** 4
                  + COSH_TAYLOR_COEFFS[6] * norm_tangent_vec ** 6
                  + COSH_TAYLOR_COEFFS[8] * norm_tangent_vec ** 8)
        coef_2 += mask_0_float * (
                  1. + SINH_TAYLOR_COEFFS[3] * norm_tangent_vec ** 2
                  + SINH_TAYLOR_COEFFS[5] * norm_tangent_vec ** 4
                  + SINH_TAYLOR_COEFFS[7] * norm_tangent_vec ** 6
                  + SINH_TAYLOR_COEFFS[9] * norm_tangent_vec ** 8)
        # This avoids dividing by 0.
        norm_tangent_vec += mask_0_float * 1.0
        coef_1 += mask_else_float * (gs.cosh(norm_tangent_vec))
        coef_2 += mask_else_float * (
            (gs.sinh(norm_tangent_vec) / (norm_tangent_vec)))

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

        hyperbolic_space = HyperbolicSpace(dimension=self.dimension)
        exp = hyperbolic_space.regularize(exp)
        return exp
예제 #20
0
    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 in hyperbolic space.

        Returns
        -------
        exp : array-like, shape=[..., dim + 1]
            Point in hyperbolic space equal to the Riemannian exponential
            of tangent_vec at the base point.
        """
        sq_norm_tangent_vec = self.embedding_metric.squared_norm(
            tangent_vec)
        sq_norm_tangent_vec = gs.clip(sq_norm_tangent_vec, 0, math.inf)
        norm_tangent_vec = gs.sqrt(sq_norm_tangent_vec)

        mask_0 = gs.isclose(sq_norm_tangent_vec, 0.)
        mask_0 = gs.to_ndarray(mask_0, to_ndim=1)
        mask_else = ~mask_0
        mask_else = gs.to_ndarray(mask_else, to_ndim=1)
        mask_0_float = gs.cast(mask_0, gs.float32)
        mask_else_float = gs.cast(mask_else, gs.float32)

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

        coef_1 += mask_0_float * (
            1. + COSH_TAYLOR_COEFFS[2] * norm_tangent_vec ** 2
            + COSH_TAYLOR_COEFFS[4] * norm_tangent_vec ** 4
            + COSH_TAYLOR_COEFFS[6] * norm_tangent_vec ** 6
            + COSH_TAYLOR_COEFFS[8] * norm_tangent_vec ** 8)
        coef_2 += mask_0_float * (
            1. + SINH_TAYLOR_COEFFS[3] * norm_tangent_vec ** 2
            + SINH_TAYLOR_COEFFS[5] * norm_tangent_vec ** 4
            + SINH_TAYLOR_COEFFS[7] * norm_tangent_vec ** 6
            + SINH_TAYLOR_COEFFS[9] * norm_tangent_vec ** 8)
        # This avoids dividing by 0.
        norm_tangent_vec += mask_0_float * 1.0
        coef_1 += mask_else_float * (gs.cosh(norm_tangent_vec))
        coef_2 += mask_else_float * (
            (gs.sinh(norm_tangent_vec) / (norm_tangent_vec)))

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

        hyperbolic_space = Hyperboloid(dim=self.dim)
        exp = hyperbolic_space.regularize(exp)
        return exp
예제 #21
0
 def test_powerm(self):
     """Test of powerm method."""
     sym_n = SymmetricMatrices(self.n)
     expected = gs.array(
         [[[1, 1. / 4., 0.], [1. / 4, 2., 0.], [0., 0., 1.]]])
     expected = gs.cast(expected, gs.float64)
     power = gs.array(1. / 2)
     power = gs.cast(power, gs.float64)
     result = sym_n.powerm(expected, power)
     result = gs.matmul(result, gs.transpose(result, (0, 2, 1)))
     self.assertAllClose(result, expected)
예제 #22
0
    def log(self, point, base_point):
        """
        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)

        angle = self.dist(base_point, point)
        angle = gs.to_ndarray(angle, to_ndim=1)
        angle = gs.to_ndarray(angle, to_ndim=2)

        mask_0 = gs.isclose(angle, 0.)
        mask_else = ~mask_0

        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_SINH_TAYLOR_COEFFS[1] * angle ** 2
                  + INV_SINH_TAYLOR_COEFFS[3] * angle ** 4
                  + INV_SINH_TAYLOR_COEFFS[5] * angle ** 6
                  + INV_SINH_TAYLOR_COEFFS[7] * angle ** 8)
        coef_2 += mask_0_float * (
                  1. + INV_TANH_TAYLOR_COEFFS[1] * angle ** 2
                  + INV_TANH_TAYLOR_COEFFS[3] * angle ** 4
                  + INV_TANH_TAYLOR_COEFFS[5] * angle ** 6
                  + INV_TANH_TAYLOR_COEFFS[7] * angle ** 8)

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

        coef_1 += mask_else_float * (angle / gs.sinh(angle))
        coef_2 += mask_else_float * (angle / gs.tanh(angle))

        log = (gs.einsum('ni,nj->nj', coef_1, point)
               - gs.einsum('ni,nj->nj', coef_2, base_point))
        return log
예제 #23
0
    def log(self, point, base_point):
        """Compute Riemannian logarithm of a point wrt a base point.

        If point_type = 'poincare' then base_point belongs
        to the Poincare ball and point is a vector in the Euclidean
        space of the same dimension as the ball.

        Parameters
        ----------
        point : array-like, shape=[..., dim + 1]
            Point in hyperbolic space.
        base_point : array-like, shape=[..., dim + 1]
            Point in hyperbolic space.

        Returns
        -------
        log : array-like, shape=[..., dim + 1]
            Tangent vector at the base point equal to the Riemannian logarithm
            of point at the base point.
        """
        angle = self.dist(base_point, point) / self.scale
        angle = gs.to_ndarray(angle, to_ndim=1)

        mask_0 = gs.isclose(angle, 0.)
        mask_else = ~mask_0

        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_SINH_TAYLOR_COEFFS[1] * angle ** 2
            + INV_SINH_TAYLOR_COEFFS[3] * angle ** 4
            + INV_SINH_TAYLOR_COEFFS[5] * angle ** 6
            + INV_SINH_TAYLOR_COEFFS[7] * angle ** 8)
        coef_2 += mask_0_float * (
            1. + INV_TANH_TAYLOR_COEFFS[1] * angle ** 2
            + INV_TANH_TAYLOR_COEFFS[3] * angle ** 4
            + INV_TANH_TAYLOR_COEFFS[5] * angle ** 6
            + INV_TANH_TAYLOR_COEFFS[7] * angle ** 8)

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

        coef_1 += mask_else_float * (angle / gs.sinh(angle))
        coef_2 += mask_else_float * (angle / gs.tanh(angle))
        log_term_1 = gs.einsum('...,...j->...j', coef_1, point)
        log_term_2 = - gs.einsum('...,...j->...j', coef_2, base_point)
        log = log_term_1 + log_term_2
        return log
예제 #24
0
    def matrix_from_rotation_vector(self, rot_vec):
        """Convert rotation vector to rotation matrix.

        Parameters
        ----------
        rot_vec: array-like, shape=[..., 3]

        Returns
        -------
        rot_mat: array-like, shape=[..., 3]
        """
        rot_vec = self.regularize(rot_vec)

        angle = gs.linalg.norm(rot_vec, axis=1)
        angle = gs.to_ndarray(angle, to_ndim=2, axis=1)

        skew_rot_vec = self.skew_matrix_from_vector(rot_vec)

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

        # This avoids dividing by 0.
        mask_0 = gs.isclose(angle, 0.)
        mask_0_float = gs.cast(mask_0, gs.float32) + self.epsilon

        coef_1 += mask_0_float * (1. - (angle ** 2) / 6.)
        coef_2 += mask_0_float * (1. / 2. - angle ** 2)

        # This avoids dividing by 0.
        mask_else = ~mask_0
        mask_else_float = gs.cast(mask_else, gs.float32) + self.epsilon

        angle += mask_0_float

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

        coef_1 = gs.squeeze(coef_1, axis=1)
        coef_2 = gs.squeeze(coef_2, axis=1)
        term_1 = (gs.eye(self.dim)
                  + gs.einsum('n,njk->njk', coef_1, skew_rot_vec))

        squared_skew_rot_vec = gs.einsum(
            'nij,njk->nik', skew_rot_vec, skew_rot_vec)

        term_2 = gs.einsum('n,njk->njk', coef_2, squared_skew_rot_vec)

        return term_1 + term_2
예제 #25
0
    def test_log_and_exp_general_case_general_dim(self):
        """
        Test that the Riemannian exponential
        and the Riemannian logarithm are inverse.

        Expect their composition to give the identity function.
        """
        # Riemannian Log then Riemannian Exp
        dim = 5
        n_samples = self.n_samples

        h5 = Hyperboloid(dim=dim)
        h5_metric = h5.metric

        base_point = h5.random_point()
        point = h5.random_point()
        point = gs.cast(point, gs.float64)
        base_point = gs.cast(base_point, gs.float64)
        one_log = h5_metric.log(point=point, base_point=base_point)

        result = h5_metric.exp(tangent_vec=one_log, base_point=base_point)
        expected = point
        self.assertAllClose(result, expected)

        # Test vectorization of log
        base_point = gs.stack([base_point] * n_samples, axis=0)
        point = gs.stack([point] * n_samples, axis=0)
        expected = gs.stack([one_log] * n_samples, axis=0)

        log = h5_metric.log(point=point, base_point=base_point)
        result = log

        self.assertAllClose(gs.shape(result), (n_samples, dim + 1))
        self.assertAllClose(result, expected)

        result = h5_metric.exp(tangent_vec=log, base_point=base_point)
        expected = point

        self.assertAllClose(gs.shape(result), (n_samples, dim + 1))
        self.assertAllClose(result, expected)

        # Test vectorization of exp
        tangent_vec = gs.stack([one_log] * n_samples, axis=0)
        exp = h5_metric.exp(tangent_vec=tangent_vec, base_point=base_point)
        result = exp

        expected = point
        self.assertAllClose(gs.shape(result), (n_samples, dim + 1))
        self.assertAllClose(result, expected)
예제 #26
0
    def test_squared_dist_bureswasserstein_vectorization(self):
        """Test of SPDMetricBuresWasserstein.squared_dist method."""
        point_a = self.space.random_point(2)
        point_b = gs.array([[9., 0., 0.], [0., 5., 0.], [0., 0., 1.]])

        point_a = gs.cast(point_a, gs.float64)
        point_b = gs.cast(point_b, gs.float64)

        metric = self.metric_bureswasserstein
        result = metric.squared_dist(point_a, point_b)

        log = metric.log(point=point_b, base_point=point_a)
        expected = metric.squared_norm(vector=log, base_point=point_a)

        self.assertAllClose(result, expected)
예제 #27
0
    def regularize(self, point):
        """Regularize a point to be in accordance with convention.

        In 3D, regularize the norm of the rotation vector,
        to be between 0 and pi, following the axis-angle
        representation's convention.

        If the angle angle is between pi and 2pi,
        the function computes its complementary in 2pi and
        inverts the direction of the rotation axis.

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

        Returns
        -------
        regularized_point : array-like, shape=[..., 3]
        """
        regularized_point = point
        angle = gs.linalg.norm(regularized_point, axis=-1)

        mask_0 = gs.isclose(angle, 0.)
        mask_not_0 = ~mask_0
        mask_pi = gs.isclose(angle, gs.pi)

        # This avoids division by 0.
        mask_0_float = gs.cast(mask_0, gs.float32) + self.epsilon
        mask_not_0_float = (
            gs.cast(mask_not_0, gs.float32)
            + self.epsilon)
        mask_pi_float = gs.cast(mask_pi, gs.float32) + self.epsilon

        k = gs.floor(angle / (2 * gs.pi) + .5)
        angle += mask_0_float

        norms_ratio = gs.zeros_like(angle)
        norms_ratio += mask_not_0_float * (
            1. - 2. * gs.pi * k / angle)
        norms_ratio += mask_0_float
        norms_ratio += mask_pi_float * (
            gs.pi / angle
            - (1. - 2. * gs.pi * k / angle))

        regularized_point = gs.einsum(
            '...,...i->...i', norms_ratio, regularized_point)

        return regularized_point
예제 #28
0
    def square_root_velocity(self, curve):
        """Compute the square root velocity representation of a curve.

        The velocity is computed using the log map. The case of several curves
        is handled through vectorization. In that case, an index selection
        procedure allows to get rid of the log between the end point of
        curve[k, :, :] and the starting point of curve[k + 1, :, :].

        Parameters
        ----------
        curve :

        Returns
        -------
        srv :
        """
        curve = gs.to_ndarray(curve, to_ndim=3)
        n_curves, n_sampling_points, n_coords = curve.shape
        srv_shape = (n_curves, n_sampling_points - 1, n_coords)

        curve = gs.reshape(curve, (n_curves * n_sampling_points, n_coords))
        coef = gs.cast(gs.array(n_sampling_points - 1), gs.float32)
        velocity = coef * self.ambient_metric.log(point=curve[1:, :],
                                                  base_point=curve[:-1, :])
        velocity_norm = self.ambient_metric.norm(velocity, curve[:-1, :])
        srv = velocity / gs.sqrt(velocity_norm)

        index = gs.arange(n_curves * n_sampling_points - 1)
        mask = ~gs.equal((index + 1) % n_sampling_points, 0)
        index_select = gs.gather(index, gs.squeeze(gs.where(mask)))
        srv = gs.reshape(gs.gather(srv, index_select), srv_shape)

        return srv
예제 #29
0
    def apply_func_to_eigvals(x, function, check_positive=False):
        """
        Apply function to eigenvalues and reconstruct the matrix.

        Parameters
        ----------
        x : array_like, shape=[..., n, n]
            Symmetric matrix.
        function : callable
            Function to apply to eigenvalues.

        Returns
        -------
        x : array_like, shape=[..., n, n]
            Symmetric matrix.
        """
        eigvals, eigvecs = gs.linalg.eigh(x)
        if check_positive:
            if gs.any(gs.cast(eigvals, gs.float32) < 0.):
                logging.warning('Negative eigenvalue encountered in'
                                ' {}'.format(function.__name__))
        eigvals = function(eigvals)
        eigvals = algebra_utils.from_vector_to_diagonal_matrix(eigvals)
        transp_eigvecs = Matrices.transpose(eigvecs)
        reconstuction = gs.matmul(eigvecs, eigvals)
        reconstuction = gs.matmul(reconstuction, transp_eigvecs)
        return reconstuction
예제 #30
0
def flip_determinant(matrix, det):
    """Change sign of the determinant if it is negative.

    For a batch of matrices, multiply the matrices which have negative
    determinant by a diagonal matrix :math: `diag(1,...,1,-1) from the right.
    This changes the sign of the last column of the matrix.

    Parameters
    ----------
    matrix : array-like, shape=[...,n ,m]
        Matrix to transform.

    det : array-like, shape=[...]
        Determinant of matrix, or any other scalar to use as threshold to
        determine whether to change the sign of the last column of matrix.

    Returns
    -------
    matrix_flipped : array-like, shape=[..., n, m]
        Matrix with the sign of last column changed if det < 0.
    """
    if gs.any(det < 0):
        ones = gs.ones(matrix.shape[-1])
        reflection_vec = gs.concatenate([ones[:-1], gs.array([-1.0])], axis=0)
        mask = gs.cast(det < 0, matrix.dtype)
        sign = mask[..., None] * reflection_vec + (1.0 - mask)[..., None] * ones
        return gs.einsum("...ij,...j->...ij", matrix, sign)
    return matrix