Example #1
0
    def test_assignment_with_booleans_single_index(self):
        np_array = _np.array([[2., 5.]])
        gs_array = gs.array([[2., 5.]])
        np_mask = _np.array([True])
        gs_mask = gs.array([True])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * _np.ones_like(np_array[~np_mask])
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result,
                                  4 * gs.ones_like(gs_array[~gs_mask]),
                                  ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[2., 5.]])
        gs_array = gs.array([[2., 5.]])
        np_mask = _np.array([True])
        gs_mask = gs.array([True])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * np_array[~np_mask]
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result, 4 * gs_array[~gs_mask], ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[2., 5.]])
        gs_array = gs.array([[2., 5.]])
        np_mask = _np.array([False])
        gs_mask = gs.array([False])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * _np.ones_like(np_array[~np_mask])
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result,
                                  4 * gs.ones_like(gs_array[~gs_mask]),
                                  ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[2., 5.]])
        gs_array = gs.array([[2., 5.]])
        np_mask = _np.array([False])
        gs_mask = gs.array([False])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * np_array[~np_mask]
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result, 4 * gs_array[~gs_mask], ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)
Example #2
0
    def aux_differential_power(power, tangent_vec, base_point):
        """Compute the differential of the matrix power.

        Auxiliary function to the functions differential_power and
        inverse_differential_power.

        Parameters
        ----------
        power : float
            Power function to differentiate.
        tangent_vec : array_like, shape=[..., n, n]
            Tangent vector at base point.
        base_point : array_like, shape=[..., n, n]
            Base point.

        Returns
        -------
        eigvectors : array-like, shape=[..., n, n]
        transp_eigvectors : array-like, shape=[..., n, n]
        numerator : array-like, shape=[..., n, n]
        denominator : array-like, shape=[..., n, n]
        temp_result : array-like, shape=[..., n, n]
        """
        eigvalues, eigvectors = gs.linalg.eigh(base_point)

        if power == 0:
            powered_eigvalues = gs.log(eigvalues)
        elif power == math.inf:
            powered_eigvalues = gs.exp(eigvalues)
        else:
            powered_eigvalues = eigvalues ** power

        denominator = eigvalues[..., :, None] - eigvalues[..., None, :]
        numerator = powered_eigvalues[..., :, None] - powered_eigvalues[..., None, :]

        if power == 0:
            numerator = gs.where(denominator == 0, gs.ones_like(numerator), numerator)
            denominator = gs.where(
                denominator == 0, eigvalues[..., :, None], denominator
            )
        elif power == math.inf:
            numerator = gs.where(
                denominator == 0, powered_eigvalues[..., :, None], numerator
            )
            denominator = gs.where(
                denominator == 0, gs.ones_like(numerator), denominator
            )
        else:
            numerator = gs.where(
                denominator == 0, power * powered_eigvalues[..., :, None], numerator
            )
            denominator = gs.where(
                denominator == 0, eigvalues[..., :, None], denominator
            )

        transp_eigvectors = Matrices.transpose(eigvectors)
        temp_result = Matrices.mul(transp_eigvectors, tangent_vec, eigvectors)

        return (eigvectors, transp_eigvectors, numerator, denominator, temp_result)
Example #3
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
Example #4
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
Example #5
0
    def test_assignment(self):
        gs_array_1 = gs.ones(3)
        self.assertRaises(ValueError, gs.assignment, gs_array_1, [.1, 2., 1.],
                          [0, 1])

        np_array_1 = _np.ones(3)
        gs_array_1 = gs.ones_like(gs.array(np_array_1))

        np_array_1[2] = 1.5
        gs_result = gs.assignment(gs_array_1, 1.5, 2)
        self.assertAllCloseToNp(gs_result, np_array_1)

        np_array_1_list = _np.ones(3)
        gs_array_1_list = gs.ones_like(gs.array(np_array_1_list))

        indices = [1, 2]
        np_array_1_list[indices] = 1.5
        gs_result = gs.assignment(gs_array_1_list, 1.5, indices)
        self.assertAllCloseToNp(gs_result, np_array_1_list)

        np_array_2 = _np.zeros((3, 2))
        gs_array_2 = gs.zeros_like(gs.array(np_array_2))

        np_array_2[0, :] = 1
        gs_result = gs.assignment(gs_array_2, 1, 0, axis=1)
        self.assertAllCloseToNp(gs_result, np_array_2)

        np_array_3 = _np.zeros((3, 3))
        gs_array_3 = gs.zeros_like(gs.array(np_array_3))

        np_array_3[0, 1] = 1
        gs_result = gs.assignment(gs_array_3, 1, (0, 1))
        self.assertAllCloseToNp(gs_result, np_array_3)

        np_array_4 = _np.zeros((3, 3, 2))
        gs_array_4 = gs.zeros_like(gs.array(np_array_4))

        np_array_4[0, :, 1] = 1
        gs_result = gs.assignment(gs_array_4, 1, (0, 1), axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4)

        gs_array_4_arr = gs.zeros_like(gs.array(np_array_4))

        gs_result = gs.assignment(gs_array_4_arr, 1, gs.array((0, 1)), axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4)

        np_array_4_list = _np.zeros((3, 3, 2))
        gs_array_4_list = gs.zeros_like(gs.array(np_array_4_list))

        np_array_4_list[(0, 1), :, (1, 1)] = 1
        gs_result = gs.assignment(gs_array_4_list, 1, [(0, 1), (1, 1)], axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4_list)
Example #6
0
    def test_assignment_by_sum(self):
        np_array_1 = _np.ones(3)
        gs_array_1 = gs.ones_like(gs.array(np_array_1))

        np_array_1[2] += 1.5
        gs_result = gs.assignment_by_sum(gs_array_1, 1.5, 2)
        self.assertAllCloseToNp(gs_result, np_array_1)

        np_array_1_list = _np.ones(3)
        gs_array_1_list = gs.ones_like(gs.array(np_array_1_list))

        indices = [1, 2]
        np_array_1_list[indices] += 1.5
        gs_result = gs.assignment_by_sum(gs_array_1_list, 1.5, indices)
        self.assertAllCloseToNp(gs_result, np_array_1)

        np_array_2 = _np.zeros((3, 2))
        gs_array_2 = gs.zeros_like(gs.array(np_array_2))

        np_array_2[0, :] += 1
        gs_result = gs.assignment_by_sum(gs_array_2, 1, 0, axis=0)
        self.assertAllCloseToNp(gs_result, np_array_2)

        np_array_3 = _np.zeros((3, 3))
        gs_array_3 = gs.zeros_like(gs.array(np_array_3))

        np_array_3[0, 1] += 1
        gs_result = gs.assignment_by_sum(gs_array_3, 1, (0, 1))
        self.assertAllCloseToNp(gs_result, np_array_3)

        np_array_4 = _np.zeros((3, 3, 2))
        gs_array_4 = gs.zeros_like(gs.array(np_array_4))

        np_array_4[0, :, 1] += 1
        gs_result = gs.assignment_by_sum(gs_array_4, 1, (0, 1), axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4)

        np_array_4_list = _np.zeros((3, 3, 2))
        gs_array_4_list = gs.zeros_like(gs.array(np_array_4_list))

        np_array_4_list[(0, 1), :, (1, 1)] += 1
        gs_result = gs.assignment_by_sum(
            gs_array_4_list, 1, [(0, 1), (1, 1)], axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4)
Example #7
0
    def draw(self, n_theta=25, n_phi=13, scale=0.05, elev=60.0, azim=0.0):
        """Draw the sphere regularly sampled with corresponding triangles."""
        self.set_ax()
        self.set_view(elev=elev, azim=azim)
        self.ax.set_axis_off()
        plt.tight_layout()

        coords_theta = gs.linspace(0.0, 2.0 * gs.pi, n_theta)
        coords_phi = gs.linspace(0.0, gs.pi, n_phi)

        coords_x = gs.to_numpy(0.5 * gs.outer(gs.sin(coords_phi), gs.cos(coords_theta)))
        coords_y = gs.to_numpy(0.5 * gs.outer(gs.sin(coords_phi), gs.sin(coords_theta)))
        coords_z = gs.to_numpy(
            0.5 * gs.outer(gs.cos(coords_phi), gs.ones_like(coords_theta))
        )

        self.ax.plot_surface(
            coords_x,
            coords_y,
            coords_z,
            rstride=1,
            cstride=1,
            color="grey",
            linewidth=0,
            alpha=0.1,
            zorder=-1,
        )
        self.ax.plot_wireframe(
            coords_x,
            coords_y,
            coords_z,
            linewidths=0.6,
            color="grey",
            alpha=0.6,
            zorder=-1,
        )

        def lim(theta):
            return (
                gs.pi
                - self.elev
                + (2.0 * self.elev - gs.pi) / gs.pi * abs(self.azim - theta)
            )

        for theta in gs.linspace(0.0, 2.0 * gs.pi, n_theta // 2 + 1):
            for phi in gs.linspace(0.0, gs.pi, n_phi):
                if theta <= self.azim + gs.pi and phi <= lim(theta):
                    self.draw_triangle(theta, phi, scale)
                if theta > self.azim + gs.pi and phi < lim(
                    2.0 * self.azim + 2.0 * gs.pi - theta
                ):
                    self.draw_triangle(theta, phi, scale)
Example #8
0
def homogeneous_representation(rotation,
                               translation,
                               output_shape,
                               constant=1.0):
    r"""Embed rotation, translation couples into n+1 square matrices.

    Construct a block matrix of size :math: `n + 1 \times n + 1` of the form
    .. math::
        \matvec{cc}{R & t\\
                    0&c}

    where :math: `R` is a square matrix, :math: `t` a vector of size
    :math: `n`, and :math: `c` a constant (either 0 or 1 should be used).

    Parameters
    ----------
    rotation : array-like, shape=[..., n, n]
        Square Matrix.
    translation : array-like, shape=[..., n]
        Vector.
    output_shape : tuple of int
        Desired output shape. This is need for vectorization.
    constant : float or array-like of shape [...]
        Constant to use at the last line and column of the square matrix.
        Optional, default: 1.

    Returns
    -------
    mat: array-like, shape=[..., n + 1, n + 1]
        Square Matrix of size n + 1. It can represent an element of the
        special euclidean group or its Lie algebra.
    """
    mat = gs.concatenate((rotation, translation[..., None]), axis=-1)
    last_line = gs.zeros(output_shape)[..., -1]
    if isinstance(constant, float):
        last_col = constant * gs.ones_like(translation)[..., None, -1]
    else:
        last_col = constant[..., None]
    last_line = gs.concatenate([last_line[..., :-1], last_col], axis=-1)
    mat = gs.concatenate((mat, last_line[..., None, :]), axis=-2)
    return mat
    def to_tangent(self, vector, base_point):
        """Project a matrix to the tangent space at a base point.

        The tangent space to the space of correlation matrices is the space of
        symmetric matrices with null diagonal.

        Parameters
        ----------
        vector : array-like, shape=[..., n, n]
            Matrix to project
        base_point : array-like, shape=[..., n, n]
            Correlation matrix.

        Returns
        -------
        tangent_vec : array-like, shape=[..., n, n]
            Symmetric matrix with 0 diagonal.
        """
        sym = self.embedding_space.to_tangent(vector, base_point)
        mask_diag = gs.ones_like(vector) - gs.eye(self.n)
        return sym * mask_diag
Example #10
0
    def exp_from_identity(self, tangent_vec, point_type=None):
        """Compute group exponential of the tangent vector at the identity.

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

        Returns
        -------
        group_exp: array-like, shape=[n_samples, {dim, [n + 1, n + 1]}]
            the group exponential of the tangent vectors calculated
            at the identity
        """
        if point_type == 'vector':
            rotations = self.rotations
            dim_rotations = rotations.dim

            rot_vec = tangent_vec[:, :dim_rotations]
            rot_vec = self.rotations.regularize(rot_vec, point_type=point_type)
            translation = tangent_vec[:, dim_rotations:]

            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)

            angle += mask_0_float * gs.ones_like(angle)

            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)

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

            n_tangent_vecs, _ = tangent_vec.shape
            exp_translation = gs.zeros((n_tangent_vecs, self.n))
            for i in range(n_tangent_vecs):
                translation_i = translation[i]
                term_1_i = coef_1[i] * gs.dot(translation_i,
                                              gs.transpose(skew_mat[i]))
                term_2_i = coef_2[i] * gs.dot(translation_i,
                                              gs.transpose(sq_skew_mat[i]))
                mask_i_float = gs.get_mask_i_float(i, n_tangent_vecs)
                exp_translation += gs.outer(
                    mask_i_float, translation_i + term_1_i + term_2_i)

            group_exp = gs.concatenate([rot_vec, exp_translation], axis=1)

            group_exp = self.regularize(group_exp, point_type=point_type)
            return group_exp

        if point_type == 'matrix':
            return GeneralLinear.exp(tangent_vec)

        raise ValueError('Invalid point_type, expected \'vector\' or '
                         '\'matrix\'.')
Example #11
0
    def group_log_from_identity(self, point):
        """
        Compute the group logarithm of the point at the identity.
        """
        assert self.belongs(point)
        point = self.regularize(point)

        rotations = self.rotations
        dim_rotations = rotations.dimension

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

        translation = point[:, dim_rotations:]

        group_log = gs.zeros_like(point)
        group_log[:, :dim_rotations] = rot_vec
        skew_rot_vec = so_group.skew_matrix_from_vector(rot_vec)
        sq_skew_rot_vec = gs.matmul(skew_rot_vec, skew_rot_vec)

        mask_close_0 = gs.isclose(angle, 0)
        mask_close_0 = gs.squeeze(mask_close_0, axis=1)

        mask_close_pi = gs.isclose(angle, gs.pi)
        mask_close_pi = gs.squeeze(mask_close_pi, axis=1)

        mask_else = ~mask_close_0 & ~mask_close_pi

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

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

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

        psi = (0.5 * angle[mask_else]
               * gs.sin(angle[mask_else]) / (1 - gs.cos(angle[mask_else])))
        coef_2[mask_else] = (1 - psi) / (angle[mask_else] ** 2)

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

        group_log[:, dim_rotations:] = group_log_translation

        assert group_log.ndim == 2

        return group_log
Example #12
0
    def test_assignment_by_sum(self):
        gs_array_1 = gs.ones(3)
        self.assertRaises(ValueError, gs.assignment_by_sum, gs_array_1,
                          [.1, 2., 1.], [0, 1])

        np_array_1 = _np.ones(3)
        gs_array_1 = gs.ones_like(gs.array(np_array_1))

        np_array_1[2] += 1.5
        gs_result = gs.assignment_by_sum(gs_array_1, 1.5, 2)
        self.assertAllCloseToNp(gs_result, np_array_1)

        gs_result_list = gs.assignment_by_sum(gs_array_1, [2., 1.5], [0, 2])
        np_array_1[0] += 2.
        self.assertAllCloseToNp(gs_result_list, np_array_1)

        np_array_1_list = _np.ones(3)
        gs_array_1_list = gs.ones_like(gs.array(np_array_1_list))

        indices = [1, 2]
        np_array_1_list[indices] += 1.5
        gs_result = gs.assignment_by_sum(gs_array_1_list, 1.5, indices)
        self.assertAllCloseToNp(gs_result, np_array_1_list)

        np_array_2 = _np.zeros((3, 2))
        gs_array_2 = gs.zeros_like(gs.array(np_array_2))

        np_array_2[0, :] += 1
        gs_result = gs.assignment_by_sum(gs_array_2, 1, 0, axis=1)
        self.assertAllCloseToNp(gs_result, np_array_2)

        np_array_3 = _np.zeros((3, 3))
        gs_array_3 = gs.zeros_like(gs.array(np_array_3))

        np_array_3[0, 1] += 1
        gs_result = gs.assignment_by_sum(gs_array_3, 1, (0, 1))
        self.assertAllCloseToNp(gs_result, np_array_3)

        np_array_4 = _np.zeros((3, 3, 2))
        gs_array_4 = gs.zeros_like(gs.array(np_array_4))

        np_array_4[0, :, 1] += 1
        gs_result = gs.assignment_by_sum(gs_array_4, 1, (0, 1), axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4)

        np_array_4_list = _np.zeros((3, 3, 2))
        gs_array_4_list = gs.zeros_like(gs.array(np_array_4_list))

        np_array_4_list[(0, 1), :, (1, 1)] += 1
        gs_result = gs.assignment_by_sum(gs_array_4_list,
                                         1, [(0, 1), (1, 1)],
                                         axis=1)
        self.assertAllCloseToNp(gs_result, np_array_4_list)

        n_samples = 3
        theta = _np.array([0.1, 0.2, 0.3, 0.4, 5.5])
        phi = _np.array([0.11, 0.22, 0.33, 0.44, -.55])
        np_array = _np.ones((n_samples, 5, 4))
        gs_array = gs.array(np_array)

        gs_array = gs.assignment_by_sum(gs_array,
                                        gs.cos(theta) * gs.cos(phi), (0, 0),
                                        axis=1)
        gs_array = gs.assignment_by_sum(gs_array,
                                        -gs.sin(theta) * gs.sin(phi), (0, 1),
                                        axis=1)

        np_array[0, :, 0] += _np.cos(theta) * _np.cos(phi)
        np_array[0, :, 1] -= _np.sin(theta) * _np.sin(phi)

        # TODO (ninamiolane): This test fails 15% of the time,
        # when gs and _np computations are in the reverse order.
        # We should investigate this.
        self.assertAllCloseToNp(gs_array, np_array)

        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])
        np_mask = _np.array([[False, False], [False, True], [True, True]])
        gs_mask = gs.array([[False, False], [False, True], [True, True]])

        np_array[np_mask] += _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] += 4 * np_array[~np_mask]
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment_by_sum(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment_by_sum(gs_result, 4 * gs_array[~gs_mask],
                                         ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)
Example #13
0
    def exp_from_identity(self, tangent_vec):
        """Compute group exponential of the tangent vector at the identity.

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

        Returns
        -------
        group_exp: array-like, shape=[..., 3]
            The group exponential of the tangent vectors calculated
            at the identity.
        """
        rotations = self.rotations
        dim_rotations = rotations.dim

        rot_vec = tangent_vec[..., :dim_rotations]
        rot_vec = self.rotations.regularize(rot_vec)
        translation = tangent_vec[..., dim_rotations:]

        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)

        angle += mask_0_float * gs.ones_like(angle)

        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)

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

        n_tangent_vecs, _ = tangent_vec.shape
        exp_translation = gs.zeros((n_tangent_vecs, self.n))
        for i in range(n_tangent_vecs):
            translation_i = translation[i]
            term_1_i = coef_1[i] * gs.dot(translation_i,
                                          gs.transpose(skew_mat[i]))
            term_2_i = coef_2[i] * gs.dot(translation_i,
                                          gs.transpose(sq_skew_mat[i]))
            mask_i_float = gs.get_mask_i_float(i, n_tangent_vecs)
            exp_translation += gs.outer(mask_i_float,
                                        translation_i + term_1_i + term_2_i)

        group_exp = gs.concatenate([rot_vec, exp_translation], axis=1)

        group_exp = self.regularize(group_exp)
        return group_exp
Example #14
0
    def test_assignment_with_booleans_many_indices(self):
        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])

        np_mask = _np.array([True, False, True])
        gs_mask = gs.array([True, False, True])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * _np.ones_like(np_array[~np_mask])
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result,
                                  4 * gs.ones_like(gs_array[~gs_mask]),
                                  ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])

        np_mask = _np.array([False, True, True])
        gs_mask = gs.array([False, True, True])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * _np.ones_like(np_array[~np_mask])
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result,
                                  4 * gs.ones_like(gs_array[~gs_mask]),
                                  ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])
        np_mask = _np.array([True, True, True])
        gs_mask = gs.array([True, True, True])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * _np.ones_like(np_array[~np_mask])
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result,
                                  4 * gs.ones_like(gs_array[~gs_mask]),
                                  ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])
        np_mask = _np.array([True, True, True])
        gs_mask = gs.array([True, True, True])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * np_array[~np_mask]
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result, 4 * gs_array[~gs_mask], ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])
        np_mask = _np.array([False, False, False])
        gs_mask = gs.array([False, False, False])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * np_array[~np_mask]
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result, 4 * gs_array[~gs_mask], ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)

        np_array = _np.array([[22., 55.], [33., 88.], [77., 99.]])
        gs_array = gs.array([[22., 55.], [33., 88.], [77., 99.]])
        np_mask = _np.array([[False, False], [False, True], [True, True]])
        gs_mask = gs.array([[False, False], [False, True], [True, True]])

        np_array[np_mask] = _np.zeros_like(np_array[np_mask])
        np_array[~np_mask] = 4 * np_array[~np_mask]
        np_result = np_array

        values_mask = gs.zeros_like(gs_array[gs_mask])
        gs_result = gs.assignment(gs_array, values_mask, gs_mask)
        gs_result = gs.assignment(gs_result, 4 * gs_array[~gs_mask], ~gs_mask)
        self.assertAllCloseToNp(gs_result, np_result)
Example #15
0
    def log_from_identity(self, point):
        """Compute the group logarithm of the point at the identity.

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

        Returns
        -------
        group_log: array-like, shape=[..., 3]
            the group logarithm in the Lie algbra
        """
        point = self.regularize(point)

        rotations = self.rotations
        dim_rotations = rotations.dim

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

        translation = point[:, dim_rotations:]

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

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

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

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

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

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

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

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

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

        return gs.concatenate([rot_vec, log_translation], axis=1)
Example #16
0
    def group_exp_from_identity(self, tangent_vec, point_type=None):
        """
        Compute the group exponential of the tangent vector at the identity.
        """
        if point_type is None:
            point_type = self.default_point_type

        if point_type == 'vector':
            tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2)

            rotations = self.rotations
            dim_rotations = rotations.dimension

            rot_vec = tangent_vec[:, :dim_rotations]
            rot_vec = self.rotations.regularize(rot_vec, point_type=point_type)
            translation = tangent_vec[:, dim_rotations:]

            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)

            angle += mask_0_float * gs.ones_like(angle)

            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)

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

            n_tangent_vecs, _ = tangent_vec.shape
            group_exp_translation = gs.zeros((n_tangent_vecs, self.n))
            for i in range(n_tangent_vecs):
                translation_i = translation[i]
                term_1_i = coef_1[i] * gs.dot(translation_i,
                                              gs.transpose(skew_mat[i]))
                term_2_i = coef_2[i] * gs.dot(translation_i,
                                              gs.transpose(sq_skew_mat[i]))
                mask_i_float = gs.get_mask_i_float(i, n_tangent_vecs)
                group_exp_translation += mask_i_float * (translation_i +
                                                         term_1_i + term_2_i)

            group_exp = gs.concatenate([rot_vec, group_exp_translation],
                                       axis=1)

            group_exp = self.regularize(group_exp, point_type=point_type)
            return group_exp
        elif point_type == 'matrix':
            raise NotImplementedError()
    def fit(self, x, y=None):
        """Fit centers in all the input points.

        Parameters
        ----------
        x : array-like, shape=[..., n_features]
            Clusters of points.
        """
        @joblib.delayed
        @joblib.wrap_non_picklable_objects
        def pickable_mean(points, weights):
            """Frechet Mean of all points weighted by weights.

            Parameters
            ----------
            points : array-like, shape=[..., n_features]
                Clusters of points.
            weights : array-like,
                Weight associated with each point in cluster.
            """
            return self.mean.fit(points, weights=weights).estimate_

        if self.init_centers == "from_points":
            n_points = x.shape[0]
            centers = x[gs.random.randint(n_points, size=(
                self.n_centers, )), :]
        elif self.init_centers == "random_uniform":
            centers = self.manifold.random_uniform(n_samples=self.n_centers)

        for _ in range(self.max_iter):
            dists = self.dist_intersets(centers, x)

            if self.kernel == "flat":
                weights = gs.ones_like(dists)

            weights[dists > self.bandwidth] = 0.0
            weights = weights / gs.sum(weights, axis=1, keepdims=True)

            points_to_average, nonzero_weights = [], []

            for j in range(self.n_centers):
                indexes = gs.where(weights[j] > 0)
                nonzero_weights += [
                    weights[j][indexes],
                ]
                points_to_average += [
                    x[indexes],
                ]

            pool = joblib.Parallel(n_jobs=self.n_jobs)
            out = pool(
                pickable_mean(points_to_average[j], nonzero_weights[j])
                for j in range(self.n_centers))

            new_centers = gs.array(out)

            displacements = [self.metric.dist(centers, new_centers)]
            centers = new_centers

            if (gs.array(displacements) < self.tol).all():
                break

        self.centers = centers
Example #18
0
    def group_log_from_identity(self, point, point_type=None):
        """
        Compute the group logarithm of the point at the identity.
        """
        if point_type is None:
            point_type = self.default_point_type

        point = self.regularize(point, point_type=point_type)

        rotations = self.rotations
        dim_rotations = rotations.dimension

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

            translation = point[:, dim_rotations:]

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

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

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

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

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

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

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

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

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

            group_log = gs.concatenate([rot_vec, group_log_translation],
                                       axis=1)

            assert gs.ndim(group_log) == 2

        elif point_type == 'matrix':
            raise NotImplementedError()

        return group_log
Example #19
0
    def tangent_extrinsic_to_spherical(self,
                                       tangent_vec,
                                       base_point=None,
                                       base_point_spherical=None):
        """Convert tangent vector from extrinsic to spherical coordinates.

        Convert a tangent vector from the extrinsic coordinates in Euclidean
        space to the spherical coordinates in the hypersphere for.
        Spherical coordinates are considered from the north pole [0., 0.,
        1.]. This method is only implemented in dimension 2.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim]
            Tangent vector to the sphere, in spherical coordinates.
        base_point : array-like, shape=[..., dim]
            Point on the sphere. Unused if `base_point_spherical` is given.
            Optional, default : None.
        base_point_spherical : array-like, shape=[..., dim]
            Point on the sphere, in spherical coordinates. Either
            `base_point` or `base_point_spherical` must be given.
            Optional, default : None.

        Returns
        -------
        tangent_vec_spherical : array-like, shape=[..., dim + 1]
            Tangent vector to the sphere, at base point,
            in spherical coordinates relative to the north pole [0., 0., 1.].
        """
        if self.dim != 2:
            raise NotImplementedError(
                "The conversion from to extrinsic coordinates "
                "spherical coordinates is implemented"
                " only in dimension 2.")
        if base_point is None and base_point_spherical is None:
            raise ValueError('A base point must be given, either in '
                             'extrinsic or in spherical coordinates.')
        if base_point_spherical is None and base_point is not None:
            base_point_spherical = self.extrinsic_to_spherical(base_point)

        axes = (2, 0, 1) if base_point_spherical.ndim == 2 else (0, 1)
        theta = base_point_spherical[..., 0]
        phi = base_point_spherical[..., 1]

        theta_safe = gs.where(gs.abs(theta) < gs.atol, gs.atol, theta)
        zeros = gs.zeros_like(theta)
        jac_close_0 = gs.array([[gs.ones_like(theta), zeros, zeros],
                                [zeros, gs.ones_like(theta), zeros]])

        jac = gs.array([[
            gs.cos(theta) * gs.cos(phi),
            gs.cos(theta) * gs.sin(phi), -gs.sin(theta)
        ],
                        [
                            -gs.sin(phi) / gs.sin(theta_safe),
                            gs.cos(phi) / gs.sin(theta_safe), zeros
                        ]])

        jac = gs.transpose(jac, axes)
        jac_close_0 = gs.transpose(jac_close_0, axes)
        theta_criterion = gs.einsum('...,...ij->...ij', theta,
                                    gs.ones_like(jac))
        jac = gs.where(gs.abs(theta_criterion) < gs.atol, jac_close_0, jac)

        tangent_vec_spherical = gs.einsum("...ij,...j->...i", jac, tangent_vec)

        return tangent_vec_spherical