예제 #1
0
    def injectivity_radius(self, base_point):
        """Compute the radius of the injectivity domain.

        This is is the supremum of radii r for which the exponential map is a
        diffeomorphism from the open ball of radius r centered at the base point onto
        its image.
        In this case, it does not depend on the base point. If the rotation part is
        null, then the radius is infinite, otherwise it is the same as the special
        orthonormal group.

        Parameters
        ----------
        base_point : array-like, shape=[..., n + 1, n + 1]
            Point on the manifold.

        Returns
        -------
        radius : float
            Injectivity radius.
        """
        rotation = base_point[..., :self.n, :self.n]
        rotation_radius = gs.pi * (self.dim - self.n)**0.5
        radius = gs.where(
            gs.sum(rotation, axis=(-2, -1)) == 0, math.inf, rotation_radius)
        return radius
예제 #2
0
def parabolic_radial_kernel(distance, bandwidth=1.0):
    """Parabolic radial kernel.

    Parameters
    ----------
    distance : array-like
        Array of non-negative real values.
    bandwidth : float, optional (default=1.0)
        Positive scale parameter of the kernel.

    Returns
    -------
    weight : array-like
        Array of non-negative real values of the same shape than
        parameter 'distance'.

    References
    ----------
    https://en.wikipedia.org/wiki/Kernel_(statistics)
    """
    distance = _check_distance(distance)
    bandwidth = _check_bandwidth(bandwidth)
    scaled_distance = distance / bandwidth
    weight = gs.where(
        scaled_distance < 1,
        1 - scaled_distance**2,
        gs.zeros(distance.shape, dtype=float),
    )
    return weight
예제 #3
0
    def exp(self, tangent_vec, base_point):
        """Compute the Riemannian exponential of a tangent vector.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim]
            Tangent vector at a base point.
        base_point : array-like, shape=[..., dim]
            Point in the Poincare ball.

        Returns
        -------
        exp : array-like, shape=[..., dim]
            Point in the Poincare ball equal to the Riemannian exponential
            of tangent_vec at the base point.
        """
        squared_norm_bp = gs.sum(base_point**2, axis=-1)
        norm_tan = gs.linalg.norm(tangent_vec, axis=-1)
        lambda_base_point = 1 / (1 - squared_norm_bp)

        # This avoids dividing by 0
        norm_tan_eps = gs.where(gs.isclose(norm_tan, 0.), EPSILON, norm_tan)
        direction = gs.einsum('...i,...->...i', tangent_vec, 1 / norm_tan_eps)

        factor = gs.tanh(lambda_base_point * norm_tan)

        exp = self.mobius_add(base_point,
                              gs.einsum('...,...i->...i', factor, direction))

        return exp
예제 #4
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
예제 #5
0
    def compute_coordinates(self, point):
        """Compute the ellipse coordinates of a 2D SPD matrix.

        Parameters
        ----------
        point : array-like, shape=[2, 2]
            SPD matrix.

        Returns
        -------
        x_coords : array-like, shape=[n_sampling_points,]
            x_coords coordinates of the sampling points on the discretized ellipse.
        Y: array-like, shape = [n_sampling_points,]
            y coordinates of the sampling points on the discretized ellipse.
        """
        eigvalues, eigvectors = gs.linalg.eigh(point)
        eigvalues = gs.where(eigvalues < gs.atol, gs.atol, eigvalues)

        [eigvalue1, eigvalue2] = eigvalues

        rot_sin = eigvectors[1, 0]
        rot_cos = eigvectors[0, 0]
        thetas = gs.linspace(0.0, 2 * gs.pi, self.n_sampling_points + 1)

        x_coords = eigvalue1 * gs.cos(thetas) * rot_cos
        x_coords -= rot_sin * eigvalue2 * gs.sin(thetas)
        y_coords = eigvalue1 * gs.cos(thetas) * rot_sin
        y_coords += rot_cos * eigvalue2 * gs.sin(thetas)
        return x_coords, y_coords
예제 #6
0
    def projection(self, point):
        r"""Project a matrix to the set of full rank matrices.

        As the space of full rank matrices is dense in the space of matrices,
        this is not a projection per se, but a regularization if the matrix input X
        is not already full rank: :math:`X + \epsilon [I_{rank}, 0]` is returned
        where :math:`\epsilon=gs.atol`

        Parameters
        ----------
        point : array-like, shape=[..., n, k]
            Point in embedding manifold.

        Returns
        -------
        projected : array-like, shape=[..., n, k]
            Projected point.
        """
        belongs = self.belongs(point)
        regularization = gs.einsum(
            "...,ij->...ij",
            gs.where(~belongs, gs.atol, 0.0),
            gs.eye(self.ambient_space.shape[0], self.ambient_space.shape[1]),
        )
        projected = point + regularization
        return projected
def bump_radial_kernel(distance, bandwidth=1.0):
    """Bump radial kernel.

    Parameters
    ----------
    distance : array-like
        Array of non-negative real values.
    bandwidth : float, optional (default=1.0)
        Positive scale parameter of the kernel.

    Returns
    -------
    weight : array-like
        Array of non-negative real values of the same shape than
        parameter 'distance'.

    References
    ----------
    https://en.wikipedia.org/wiki/Radial_basis_function
    """
    distance = _check_distance(distance)
    bandwidth = _check_bandwidth(bandwidth)
    scaled_distance = distance / bandwidth
    weight = gs.where(scaled_distance < 1,
                      gs.exp(-1 / (1 - scaled_distance**2)),
                      gs.zeros(distance.shape, dtype=float))
    return weight
예제 #8
0
    def projection(self, point):
        r"""Project a matrix to the general linear group.

        As GL(n) is dense in the space of matrices, this is not a projection
        per se, but a regularization if the matrix is not already invertible:
        :math:`X + \epsilon I_n` is returned where :math:`\epsilon=gs.atol`
        is returned for an input X.

        Parameters
        ----------
        point : array-like, shape=[..., dim_embedding]
            Point in embedding manifold.

        Returns
        -------
        projected : array-like, shape=[..., dim_embedding]
            Projected point.
        """
        belongs = self.belongs(point)
        regularization = gs.einsum("...,ij->...ij",
                                   gs.where(~belongs, gs.atol, 0.0),
                                   self.identity)
        projected = point + regularization
        if self.positive_det:
            det = gs.linalg.det(point)
            return utils.flip_determinant(projected, det)
        return projected
예제 #9
0
    def parallel_transport(self, 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).
        """
        theta = self.embedding_metric.norm(tangent_vec_b)
        eps = gs.where(theta == 0., 1., theta)
        normalized_b = gs.einsum('...,...i->...i', 1 / eps, tangent_vec_b)
        pb = self.embedding_metric.inner_product(tangent_vec_a, normalized_b)
        p_orth = tangent_vec_a - gs.einsum('...,...i->...i', pb, normalized_b)
        transported = \
            gs.einsum('...,...i->...i', gs.sinh(theta) * pb, base_point)\
            + gs.einsum('...,...i->...i', gs.cosh(theta) * pb, normalized_b)\
            + p_orth
        return transported
예제 #10
0
    def extrinsic_to_spherical(self, point_extrinsic):
        """Convert point from extrinsic to spherical coordinates.

        Convert from the extrinsic coordinates, i.e. embedded in Euclidean
        space of dim 3 to spherical coordinates in the hypersphere.
        Spherical coordinates are defined from the north pole, i.e.
        angles [0., 0.] correspond to point [0., 0., 1.].
        Only implemented in dimension 2.

        Parameters
        ----------
        point_extrinsic : array-like, shape=[..., dim]
            Point on the sphere, in extrinsic coordinates.

        Returns
        -------
        point_spherical : array_like, shape=[..., dim + 1]
            Point on the sphere, in spherical coordinates relative to the
            north pole.
        """
        if self.dim != 2:
            raise NotImplementedError(
                "The conversion from to extrinsic coordinates "
                "spherical coordinates is implemented"
                " only in dimension 2.")

        theta = gs.arccos(point_extrinsic[..., -1])
        x = point_extrinsic[..., 0]
        y = point_extrinsic[..., 1]
        phi = gs.arctan2(y, x)
        phi = gs.where(phi < 0, phi + 2 * gs.pi, phi)
        return gs.stack([theta, phi], axis=-1)
예제 #11
0
    def maximum_likelihood_fit(data, loc=0, scale=1):
        """Estimate parameters from samples.

        This a wrapper around scipy's maximum likelihood estimator to
        estimate the parameters of a beta distribution from samples.

        Parameters
        ----------
        data : array-like, shape=[..., n_samples]
            Data to estimate parameters from. Arrays of
            different length may be passed.
        loc : float
            Location parameter of the distribution to estimate parameters
            from. It is kept fixed during optimization.
            Optional, default: 0.
        scale : float
            Scale parameter of the distribution to estimate parameters
            from. It is kept fixed during optimization.
            Optional, default: 1.

        Returns
        -------
        parameter : array-like, shape=[..., 2]
            Estimate of parameter obtained by maximum likelihood.
        """
        data = gs.cast(data, gs.float32)
        data = gs.to_ndarray(gs.where(data == 1., 1. - EPSILON, data),
                             to_ndim=2)
        parameters = []
        for sample in data:
            param_a, param_b, _, _ = beta.fit(sample, floc=loc, fscale=scale)
            parameters.append(gs.array([param_a, param_b]))
        return parameters[0] if len(data) == 1 else gs.stack(parameters)
예제 #12
0
    def exp_domain(self, tangent_vec, base_point):
        base_point = gs.to_ndarray(base_point, to_ndim=3)
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3)
        invsqrt_base_point = gs.linalg.powerm(base_point, -.5)
        reduced_vec = gs.matmul(invsqrt_base_point, tangent_vec)
        reduced_vec = gs.matmul(reduced_vec, invsqrt_base_point)
        eigvals = gs.linalg.eigvalsh(reduced_vec)
        min_eig = gs.amin(eigvals, axis=1)
        max_eig = gs.amax(eigvals, axis=1)
        inf_value = gs.where(max_eig <= 0, -math.inf, -1 / max_eig)
        inf_value = gs.to_ndarray(inf_value, to_ndim=2)
        sup_value = gs.where(min_eig >= 0, math.inf, -1 / min_eig)
        sup_value = gs.to_ndarray(sup_value, to_ndim=2)
        domain = gs.concatenate((inf_value, sup_value), axis=1)

        return domain
예제 #13
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)
예제 #14
0
    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)
        eps = gs.where(theta == 0., 1., 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
예제 #15
0
    def maximum_likelihood_fit(self, data, loc=0, scale=1):
        """Estimate parameters from samples.

        This a wrapper around scipy's maximum likelihood estimator to
        estimate the parameters of a beta distribution from samples.

        Parameters
        ----------
        data : array-like, shape=[n_distributions, n_samples]
            the data to estimate parameters from. Arrays of
            different length may be passed.
        loc : float, optional
            the location parameter of the distribution to estimate parameters
            from. It is kept fixed during optimization
            default: 0
        scale : float, optional
            the scale parameter of the distribution to estimate parameters
            from. It is kept fixed during optimization
            default: 1
        Returns
        -------
        parameter : array-like, shape=[n_samples, 2]
        """
        data = gs.to_ndarray(
            gs.where(data == 1., 1 - EPSILON, data), to_ndim=2)
        parameters = []
        for sample in data:
            param_a, param_b, _, _ = beta.fit(sample, floc=loc, fscale=scale)
            parameters.append(gs.array([param_a, param_b]))
        return parameters[0] if len(data) == 1 else gs.stack(parameters)
예제 #16
0
 def belongs(self, point):
     """Test if a matrix is invertible and of the right size."""
     point = gs.to_ndarray(point, to_ndim=3)
     _, mat_dim_1, mat_dim_2 = point.shape
     det = gs.linalg.det(point)
     return gs.logical_and(
         mat_dim_1 == self.n and mat_dim_2 == self.n,
         gs.where(det != 0., gs.array(True), gs.array(False)))
예제 #17
0
        def path(t):
            """Generate parameterized function for geodesic curve.

            Parameters
            ----------
            t : array-like, shape=[n_times,]
                Times at which to compute points of the geodesics.

            Returns
            -------
            geodesic : array-like, shape=[..., n_times, dim]
                Values of the geodesic at times t.
            """
            t = gs.to_ndarray(t, to_ndim=1)
            geod = []

            def initialize(point_0, point_1):
                """Initialize the solution of the boundary value problem."""
                if init == "polynomial":
                    _, curve, velocity = self._approx_geodesic_bvp(
                        point_0, point_1, n_times=n_steps
                    )
                    return gs.vstack((curve.T, velocity.T))

                lin_init = gs.zeros([2 * self.dim, n_steps])
                lin_init[: self.dim, :] = gs.transpose(
                    gs.linspace(point_0, point_1, n_steps)
                )
                lin_init[self.dim :, :-1] = n_steps * (
                    lin_init[: self.dim, 1:] - lin_init[: self.dim, :-1]
                )
                lin_init[self.dim :, -1] = lin_init[self.dim :, -2]
                return lin_init

            t_int = gs.linspace(0.0, 1.0, n_steps)
            fun_jac = jac if jacobian else None

            for ip, ep in zip(initial_point, end_point):

                def bc(y0, y1, ip=ip, ep=ep):
                    return boundary_cond(y0, y1, ip, ep)

                solution = solve_bvp(
                    bvp, bc, t_int, initialize(ip, ep), fun_jac=fun_jac
                )
                if solution.status == 1:
                    logging.warning(
                        "The maximum number of mesh nodes for solving the  "
                        "geodesic boundary value problem is exceeded. "
                        "Result may be inaccurate."
                    )
                solution_at_t = solution.sol(t)
                geodesic = solution_at_t[: self.dim, :]
                geod.append(gs.squeeze(gs.transpose(geodesic)))

            geod = geod[0] if len(initial_point) == 1 else gs.stack(geod)
            return gs.where(geod < gs.atol, gs.atol, geod)
예제 #18
0
def _circle_variances(mean, var, n_samples, points):
    """Compute the minimizer of the variance functional.

    Parameters
    ----------
    mean : float
        Mean angle.
    var : float
        Variance of the angles.
    n_samples : int
        Number of samples.
    points : array-like, shape=[n,]
        Data set of ordered angles.

    References
    ---------
    ..[HH15]     Hotz, T. and S. F. Huckemann (2015), "Intrinsic means on the circle:
                 Uniqueness, locus and asymptotics", Annals of the Institute of
                 Statistical Mathematics 67 (1), 177–193.
                 https://arxiv.org/abs/1108.2141
    """
    means = (mean + gs.linspace(0.0, 2 * gs.pi, n_samples + 1)[:-1]) % (2 * gs.pi)
    means = gs.where(means >= gs.pi, means - 2 * gs.pi, means)
    parts = gs.array([sum(points) / n_samples if means[0] < 0 else 0])
    m_plus = means >= 0
    left_sums = gs.cumsum(points)
    right_sums = left_sums[-1] - left_sums
    i = gs.arange(n_samples, dtype=right_sums.dtype)
    j = i[1:]
    parts2 = right_sums[:-1] / (n_samples - j)
    first_term = parts2[:1]
    parts2 = gs.where(m_plus[1:], left_sums[:-1] / j, parts2)
    parts = gs.concatenate([parts, first_term, parts2[1:]])

    # Formula (6) from [HH15]_
    plus_vec = (4 * gs.pi * i / n_samples) * (gs.pi + parts - mean) - (
        2 * gs.pi * i / n_samples
    ) ** 2
    minus_vec = (4 * gs.pi * (n_samples - i) / n_samples) * (gs.pi - parts + mean) - (
        2 * gs.pi * (n_samples - i) / n_samples
    ) ** 2
    minus_vec = gs.where(m_plus, plus_vec, minus_vec)
    means = gs.transpose(gs.vstack([means, var + minus_vec]))
    return means
예제 #19
0
 def _to_lie_algebra(self, tangent_vec):
     """Project vector rotation part onto skew-symmetric matrices."""
     translation_mask = gs.hstack(
         [gs.ones((self.n, ) * 2), 2 * gs.ones((self.n, 1))])
     translation_mask = gs.concatenate(
         [translation_mask, gs.zeros((1, self.n + 1))], axis=0)
     tangent_vec = tangent_vec * gs.where(translation_mask != 0.,
                                          gs.array(1.), gs.array(0.))
     tangent_vec = (tangent_vec - GeneralLinear.transpose(tangent_vec)) / 2.
     return tangent_vec * translation_mask
예제 #20
0
    def sectional_curvature(self,
                            tangent_vec_a,
                            tangent_vec_b,
                            base_point=None):
        r"""Compute the sectional curvature.

        For two orthonormal tangent vectors :math:`x,y` at a base point,
        the sectional curvature is defined by :math:`<R(x, y)x, y> =
        <R_x(y), y>`. For non-orthonormal vectors vectors, it is
        :math:`<R(x, y)x, y> / \\|x \\wedge y\\|^2`.

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

        Returns
        -------
        sectional_curvature : array-like, shape=[...,]
            Sectional curvature at `base_point`.

        See Also
        --------
        https://en.wikipedia.org/wiki/Sectional_curvature
        """
        curvature = self.curvature(tangent_vec_a, tangent_vec_b, tangent_vec_a,
                                   base_point)
        sectional = self.inner_product(curvature, tangent_vec_b, base_point)
        norm_a = self.squared_norm(tangent_vec_a, base_point)
        norm_b = self.squared_norm(tangent_vec_b, base_point)
        inner_ab = self.inner_product(tangent_vec_a, tangent_vec_b, base_point)
        normalization_factor = norm_a * norm_b - inner_ab**2

        condition = gs.isclose(normalization_factor, 0.0)
        normalization_factor = gs.where(condition, EPSILON,
                                        normalization_factor)
        return gs.where(~condition, sectional / normalization_factor, 0.0)
예제 #21
0
    def _create_batches(self):
        """Create the batches used to compute covariance matrices.

        If margin != 0, we add an index margin at each label change
        to get stationary signal corresponding to each label.
        """
        start_ids = gs.where(np.diff(self.data['y']) != 0)[0]
        end_ids = np.append(start_ids[1:], len(self.data)) - self.margin
        start_ids += self.margin
        batches_list = [range(start_id, end_id - self.n_steps, self.n_steps)
                        for start_id, end_id in zip(start_ids, end_ids)]
        self.batches = np.int_(gs.concatenate(batches_list))
예제 #22
0
    def projection(self, point):
        """Project a point in space on the hyperboloid.

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

        Returns
        -------
        projected_point : array-like, shape=[..., dim + 1]
            Point projected on the hyperboloid.
        """
        belongs = self.belongs(point)

        # avoid dividing by 0
        factor = gs.where(point[..., 0] == 0.0, 1.0, point[..., 0] + gs.atol)

        first_coord = gs.where(belongs, 1.0, 1.0 / factor)
        intrinsic = gs.einsum("...,...i->...i", first_coord, point)[..., 1:]
        return self.intrinsic_to_extrinsic_coords(intrinsic)
예제 #23
0
 def test_closest_neighbor_index(self):
     """
     Check that the closest neighbor is one of neighbors.
     """
     n_samples = 10
     points = self.space.random_uniform(n_samples=n_samples)
     point = points[0, :]
     neighbors = points[1:, :]
     index = self.metric.closest_neighbor_index(point, neighbors)
     closest_neighbor = points[index, :]
     test = gs.where((points == closest_neighbor).all(axis=1))
     result = test[0].size > 0
     self.assertTrue(result)
예제 #24
0
    def inverse(cls, point):
        """Return the inverse of a point.

        Parameters
        ----------
        point : array-like, shape=[..., n, n]
            Point to be inverted.
        """
        n = point.shape[-1] - 1
        translation_mask = gs.hstack([gs.ones((n, ) * 2), 2 * gs.ones((n, 1))])
        translation_mask = gs.concatenate(
            [translation_mask, gs.zeros((1, n + 1))], axis=0)
        embedded_rotations = point * gs.where(translation_mask == 1,
                                              translation_mask, gs.eye(n + 1))
        transposed_rot = cls.transpose(embedded_rotations)
        translation = point[..., :, -1]
        translation = gs.einsum('...ij,...j->...i', transposed_rot,
                                translation)
        translation *= gs.where(translation_mask[..., -1] == 2,
                                -translation_mask[..., -1] / 2, gs.ones(n + 1))
        return gs.concatenate(
            [transposed_rot[..., :, :-1], translation[..., None]], axis=-1)
예제 #25
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)
예제 #26
0
    def sectional_curvature_at_identity(self, tangent_vec_a, tangent_vec_b):
        """Compute the sectional curvature at identity.

        For two orthonormal tangent vectors at identity :math: `x,y`,
        the sectional curvature is defined by :math: `< R(x, y)x,
        y>`. Non-orthonormal vectors can be given.

        Parameters
        ----------
        tangent_vec_a : array-like, shape=[..., n, n]
            Tangent vector at identity.
        tangent_vec_b : array-like, shape=[..., n, n]
            Tangent vector at identity.

        Returns
        -------
        sectional_curvature : array-like, shape=[...,]
            Sectional curvature at identity.

        References
        ----------
        https://en.wikipedia.org/wiki/Sectional_curvature

        .. [Milnor]    Milnor, John. “Curvatures of Left Invariant Metrics on
                       Lie Groups.” Advances in Mathematics 21, no. 3, 1976:
                       293–329. https://doi.org/10.1016/S0001-8708(76)80002-3.
        """
        curvature = self.curvature_at_identity(
            tangent_vec_a, tangent_vec_b, tangent_vec_a
        )
        num = self.inner_product(tangent_vec_b, curvature)
        denom = (
            self.squared_norm(tangent_vec_a) * self.squared_norm(tangent_vec_a)
            - self.inner_product(tangent_vec_a, tangent_vec_b) ** 2
        )
        condition = gs.isclose(denom, 0.0)
        denom = gs.where(condition, EPSILON, denom)
        return gs.where(~condition, num / denom, 0.0)
예제 #27
0
    def tangent_spherical_to_extrinsic(
        self, tangent_vec_spherical, base_point_spherical
    ):
        """Convert tangent vector from spherical to extrinsic coordinates.

        Convert from the spherical coordinates in the hypersphere
        to the extrinsic coordinates in Euclidean space for a tangent
        vector. Only implemented in dimension 2.

        Parameters
        ----------
        tangent_vec_spherical : array-like, shape=[..., dim]
            Tangent vector to the sphere, in spherical coordinates.
        base_point_spherical : array-like, shape=[..., dim]
            Point on the sphere, in spherical coordinates.

        Returns
        -------
        tangent_vec_extrinsic : array-like, shape=[..., dim + 1]
            Tangent vector to the sphere, at base point,
            in extrinsic coordinates in Euclidean space.
        """
        if self.dim != 2:
            raise NotImplementedError(
                "The conversion from spherical coordinates"
                " to extrinsic coordinates is implemented"
                " only in dimension 2."
            )

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

        zeros = gs.zeros_like(theta)

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

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

        return tangent_vec_extrinsic
예제 #28
0
    def parallel_transport(self,
                           tangent_vec,
                           base_point,
                           direction=None,
                           end_point=None):
        r"""Compute the parallel transport of a tangent vector.

        Closed-form solution for the parallel transport of a tangent vector
        along the geodesic between two points `base_point` and `end_point`
        or alternatively defined by :math:`t\mapsto exp_(base_point)(
        t*direction)`.

        Parameters
        ----------
        tangent_vec : array-like, shape=[..., dim + 1]
            Tangent vector at base point to be transported.
        base_point : array-like, shape=[..., dim + 1]
            Point on the hyperboloid.
        direction : array-like, shape=[..., dim + 1]
            Tangent vector at base point, along which the parallel transport
            is computed.
            Optional, default : None.
        end_point : array-like, shape=[..., dim + 1]
            Point on the hyperboloid. Point to transport to. Unused if `tangent_vec_b`
            is given.
            Optional, default : None.

        Returns
        -------
        transported_tangent_vec: array-like, shape=[..., dim + 1]
            Transported tangent vector at exp_(base_point)(tangent_vec_b).
        """
        if direction is None:
            if end_point is not None:
                direction = self.log(end_point, base_point)
            else:
                raise ValueError(
                    "Either an end_point or a tangent_vec_b must be given to define the"
                    " geodesic along which to transport.")
        theta = self.embedding_metric.norm(direction)
        eps = gs.where(theta == 0.0, 1.0, theta)
        normalized_b = gs.einsum("...,...i->...i", 1 / eps, direction)
        pb = self.embedding_metric.inner_product(tangent_vec, normalized_b)
        p_orth = tangent_vec - gs.einsum("...,...i->...i", pb, normalized_b)
        transported = (gs.einsum("...,...i->...i",
                                 gs.sinh(theta) * pb, base_point) +
                       gs.einsum("...,...i->...i",
                                 gs.cosh(theta) * pb, normalized_b) + p_orth)
        return transported
예제 #29
0
    def belongs(self, point, atol=gs.atol):
        r"""Check if the matrix belongs to `math:`R_*^{m\times n}`.

        Parameters
        ----------
        point : array-like, shape=[..., m, n]
            Matrix to be checked.

        Returns
        -------
        belongs : Boolean denoting if point is in `math:`R_*^{m\times n}`
        """
        has_right_size = self.ambient_space.belongs(point)
        has_right_rank = gs.where(
            gs.linalg.matrix_rank(point) == self.rank, True, False)
        belongs = gs.logical_and(gs.array(has_right_size), has_right_rank)
        return belongs
예제 #30
0
def get_label_at_index(i, labels):
    """Get the label of data point indexed by 'i'.

    Parameters
    ----------
    i : int
        Index of data point.
    labels : array-like, shape = [n_samples * n_classes, n_features]
        All labels.

    Returns
    -------
    label_i : int
        Class index.
    """
    label_i = gs.where(labels[i])[0][0]
    return label_i