def test_exp(self, n, power_affine, tangent_vec, base_point, expected):
     metric = SPDMetricAffine(n, power_affine)
     self.assertAllClose(
         metric.exp(gs.array(tangent_vec), gs.array(base_point)), gs.array(expected)
     )
Exemple #2
0
 def __init__(self, points=None):
     self.center = gs.array([0., 0.])
     self.points = []
     if points is not None:
         self.add_points(points)
Exemple #3
0
    def test_random_uniform_and_belongs(self):
        point = self.space.random_uniform()
        result = self.space.belongs(point)
        expected = gs.array([[True]])

        self.assertAllClose(result, expected)
Exemple #4
0
    def test_squared_norm_vectorization(self):
        n_samples = 3
        n_points = gs.array([[-1., 0.], [1., 0.], [2., math.sqrt(3)]])

        result = self.metric.squared_norm(n_points)
        self.assertAllClose(gs.shape(result), (n_samples, 1))
Exemple #5
0
    def test_inner_product_matrix(self):
        result = self.metric.inner_product_matrix()

        expected = gs.array([[-1., 0.], [0., 1.]])
        self.assertAllClose(result, expected)
Exemple #6
0
 def test_belongs(self, n, mat, expected):
     space = self.space(n)
     self.assertAllClose(space.belongs(gs.array(mat)), gs.array(expected))
Exemple #7
0
 def test_random_and_belongs_vector(self):
     n_samples = 5
     data = self.space_vector.random_uniform(n_samples)
     result = self.space_vector.belongs(data)
     expected = gs.array([[True] * n_samples]).transpose(1, 0)
     self.assertAllClose(result, expected)
 def test_log(self, n, power_euclidean, point, base_point, expected):
     metric = SPDMetricEuclidean(n)
     result = metric.log(gs.array(point), gs.array(base_point))
     self.assertAllClose(result, gs.array(expected))
 def test_log_exp_composition(self, n, power_euclidean, point, base_point):
     metric = SPDMetricEuclidean(n, power_euclidean)
     log = metric.log(gs.array(point), base_point=gs.array(base_point))
     result = metric.exp(tangent_vec=log, base_point=gs.array(base_point))
     self.assertAllClose(result, point, atol=gs.atol * 1000)
 def test_log(self, n, point, base_point, expected):
     metric = SPDMetricBuresWasserstein(n)
     result = metric.log(gs.array(point), gs.array(base_point))
     self.assertAllClose(result, expected)
 def test_exp_domain(self, n, power_euclidean, tangent_vec, base_point, expected):
     metric = SPDMetricEuclidean(n, power_euclidean)
     result = metric.exp_domain(
         gs.array(tangent_vec), gs.array(base_point), expected
     )
     self.assertAllClose(result, gs.array(expected))
 def test_exp(self, n, tangent_vec, base_point, expected):
     metric = SPDMetricBuresWasserstein(n)
     result = metric.exp(gs.array(tangent_vec), gs.array(base_point))
     self.assertAllClose(result, gs.array(expected))
 def test_inner_product(self, n, tangent_vec_a, tangent_vec_b, base_point, expected):
     metric = SPDMetricBuresWasserstein(n)
     result = metric.inner_product(
         gs.array(tangent_vec_a), gs.array(tangent_vec_b), gs.array(base_point)
     )
     self.assertAllClose(result, gs.array(expected))
 def test_log(self, n, power_affine, point, base_point, expected):
     metric = SPDMetricAffine(n, power_affine)
     self.assertAllClose(
         metric.log(gs.array(point), gs.array(base_point)), gs.array(expected)
     )
 def test_ball_extrinsic_ball(self):
     x = gs.array([[0.5, 0.2]])
     x_e = self.ball_manifold.to_coordinates(x, to_point_type='extrinsic')
     x2 = self.extrinsic_manifold.to_coordinates(x_e, to_point_type='ball')
     self.assertAllClose(x, x2, atol=1e-10)
    def test_belongs(self):
        result = self.space_landmarks_in_sphere_2d.belongs(self.landmarks_a)
        expected = gs.array([[True]])

        self.assertAllClose(result, expected)
 def test_belongs_ball(self):
     x = gs.array([[0.5, 0.2]])
     belong = self.ball_manifold.belongs(x)
     assert (belong[0])
 def test_compute_coordinates_spd2(self):
     point = gs.eye(2)
     ellipsis = visualization.Ellipses(n_sampling_points=4)
     x, y = ellipsis.compute_coordinates(point)
     self.assertAllClose(x, gs.array([1, 0, -1, 0, 1]))
     self.assertAllClose(y, gs.array([0, 1, 0, -1, 0]))
Exemple #19
0
 def test_permute(self, n, graph, permutation, expected):
     space = self.space(n)
     result = space.permute(gs.array(graph), permutation)
     self.assertAllClose(result, expected)
Exemple #20
0
    def random_von_mises_fisher(
            self, mu=None, kappa=10, n_samples=1, max_iter=100):
        """Sample with the von Mises-Fisher distribution.

        This distribution corresponds to the maximum entropy distribution
        given a mean. In dimension 2, a closed form expression is available.
        In larger dimension, rejection sampling is used according to [Wood94]_

        References
        ----------
        https://en.wikipedia.org/wiki/Von_Mises-Fisher_distribution

        .. [Wood94]   Wood, Andrew T. A. “Simulation of the von Mises Fisher
                      Distribution.” Communications in Statistics - Simulation
                      and Computation, June 27, 2007.
                      https://doi.org/10.1080/03610919408813161.

        Parameters
        ----------
        mu : array-like, shape=[dim]
            Mean parameter of the distribution.
        kappa : float
            Kappa parameter of the von Mises distribution.
            Optional, default: 10.
        n_samples : int
            Number of samples.
            Optional, default: 1.

        Returns
        -------
        point : array-like, shape=[..., 3]
            Points sampled on the sphere in extrinsic coordinates
            in Euclidean space of dimension 3.
        """
        dim = self.dim

        if dim == 2:
            angle = 2. * gs.pi * gs.random.rand(n_samples)
            angle = gs.to_ndarray(angle, to_ndim=2, axis=1)
            unit_vector = gs.hstack((gs.cos(angle), gs.sin(angle)))
            scalar = gs.random.rand(n_samples)

            coord_z = 1. + 1. / kappa * gs.log(
                scalar + (1. - scalar) * gs.exp(gs.array(-2. * kappa)))
            coord_z = gs.to_ndarray(coord_z, to_ndim=2, axis=1)
            coord_xy = gs.sqrt(1. - coord_z ** 2) * unit_vector
            sample = gs.hstack((coord_xy, coord_z))

            if mu is not None:
                rot_vec = gs.cross(
                    gs.array([0., 0., 1.]), mu)
                rot_vec *= gs.arccos(mu[-1]) / gs.linalg.norm(rot_vec)
                rot = SpecialOrthogonal(
                    3, 'vector').matrix_from_rotation_vector(rot_vec)
                sample = gs.matmul(sample, gs.transpose(rot))
        else:
            if mu is None:
                mu = gs.array([0.] * dim + [1.])
            # rejection sampling in the general case
            sqrt = gs.sqrt(4 * kappa ** 2. + dim ** 2)
            envelop_param = (-2 * kappa + sqrt) / dim
            node = (1. - envelop_param) / (1. + envelop_param)
            correction = kappa * node + dim * gs.log(1. - node ** 2)

            n_accepted, n_iter = 0, 0
            result = []
            while (n_accepted < n_samples) and (n_iter < max_iter):
                sym_beta = beta.rvs(
                    dim / 2, dim / 2, size=n_samples - n_accepted)
                coord_z = (1 - (1 + envelop_param) * sym_beta) / (
                    1 - (1 - envelop_param) * sym_beta)
                accept_tol = gs.random.rand(n_samples - n_accepted)
                criterion = (
                    kappa * coord_z
                    + dim * gs.log(1 - node * coord_z)
                    - correction) > gs.log(accept_tol)
                result.append(coord_z[criterion])
                n_accepted += gs.sum(criterion)
                n_iter += 1
            if n_accepted < n_samples:
                logging.warning(
                    'Maximum number of iteration reached in rejection '
                    'sampling before n_samples were accepted.')
            coord_z = gs.concatenate(result)
            coord_rest = self.random_uniform(n_accepted)
            coord_rest = self.to_tangent(coord_rest, mu)
            coord_rest = self.projection(coord_rest)
            coord_rest = gs.einsum(
                '...,...i->...i', gs.sqrt(1 - coord_z ** 2), coord_rest)
            sample = coord_rest + coord_z[:, None] * mu[None, :]

        return sample if n_samples > 1 else sample[0]
def _sphere_metric_matrix(base_point):
    """Return sphere's metric in spherical coordinates."""
    theta = base_point[..., 0]
    mat = gs.array([[1.0, 0.0], [0.0, gs.sin(theta)**2]])
    return mat
 def test_symmetric_matrix_from_vector(self):
     vector_2 = gs.array([1, 2, 3, 4, 5, 6])
     result = self.space.from_vector(vector_2)
     expected = gs.array([[1., 2., 3.], [2., 4., 5.], [3., 5., 6.]])
     self.assertAllClose(result, expected)
Exemple #23
0
    def test_belongs(self):
        point = gs.array([-1., 3.])
        result = self.space.belongs(point)
        expected = gs.array([[True]])

        self.assertAllClose(result, expected)
Exemple #24
0
    def log(self, point, base_point):
        """Compute the Riemannian logarithm of a point.

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

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

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

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

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

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

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

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

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

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

        mask_same_values = gs.isclose(point, base_point)

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

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

        log -= mask_same_points_float * log

        return log
Exemple #25
0
    def test_squared_norm(self):
        point = gs.array([-2., 4.])

        result = self.metric.squared_norm(point)
        expected = gs.array([[12.]])
        self.assertAllClose(result, expected)
Exemple #26
0
def online_kmeans(X, metric, n_clusters, n_repetitions=20,
                  tolerance=1e-5, max_iter=5e4):
    """Perform online K-means clustering.

    Perform online version of k-means algorithm on data contained in X.
    The data points are treated sequentially and the cluster centers are
    updated one at a time. This version of k-means avoids computing the
    mean of each cluster at each iteration and is therefore less
    computationally intensive than the offline version.

    In the setting of quantization of probability distributions, this
    algorithm is also known as Competitive Learning Riemannian Quantization.
    It computes the closest approximation of the empirical distribution of
    data by a discrete distribution supported by a smaller number of points
    with respect to the Wasserstein distance. This smaller number of points
    is n_clusters.

    Parameters
    ----------
    X : array-like, shape=[..., n_features]
        Input data. It is treated sequentially by the algorithm, i.e.
        one datum is chosen randomly at each iteration.
    metric : object
        Metric of the space in which the data lives. At each iteration,
        one of the cluster centers is moved in the direction of the new
        datum, according the exponential map of the underlying space, which
        is a method of metric.
    n_clusters : int
        Number of clusters of the k-means clustering, or number of desired
        atoms of the quantized distribution.
    n_repetitions : int, default=20
        The cluster centers are updated using decreasing step sizes, each
        of which stays constant for n_repetitions iterations to allow a better
        exploration of the data points.
    max_iter : int, default=5e4
        Maximum number of iterations. If it is reached, the
        quantization may be inacurate.

    Returns
    -------
    cluster_centers : array, shape=[n_clusters, n_features]
        Coordinates of cluster centers.
    labels : array, shape=[n_samples]
        Cluster labels for each point.
    """
    n_samples = X.shape[0]

    random_indices = gs.random.randint(low=0, high=n_samples,
                                       size=(n_clusters,))
    cluster_centers = gs.get_slice(X, gs.cast(random_indices, gs.int32))

    gap = 1.0
    iteration = 0

    while iteration < max_iter:
        iteration += 1
        step_size = gs.floor(gs.array(iteration / n_repetitions)) + 1

        random_index = gs.random.randint(low=0, high=n_samples, size=(1,))
        point = gs.get_slice(X, gs.cast(random_index, gs.int32))

        index_to_update = metric.closest_neighbor_index(point, cluster_centers)
        center_to_update = gs.copy(
            gs.get_slice(cluster_centers, index_to_update))

        tangent_vec_update = metric.log(
            point=point, base_point=center_to_update
        ) / (step_size + 1)
        new_center = metric.exp(
            tangent_vec=tangent_vec_update,
            base_point=center_to_update
        )
        gap = metric.dist(center_to_update, new_center)
        if gap == 0 and iteration == 1:
            gap = gs.array(1.0)

        cluster_centers[index_to_update, :] = new_center

        if gs.isclose(gap, 0, atol=tolerance):
            break

    if iteration == max_iter - 1:
        logging.warning(
            'Maximum number of iterations {} reached. The'
            'clustering may be inaccurate'.format(max_iter))

    labels = gs.zeros(n_samples)
    for i in range(n_samples):
        labels[i] = int(metric.closest_neighbor_index(X[i], cluster_centers))

    return cluster_centers, labels
Exemple #27
0
    def log(self, point, base_point):
        """Compute Riemannian logarithm of a point wrt a base point.

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

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

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

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

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

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

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

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

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

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

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

        mask_same_values = gs.isclose(point, base_point)

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

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

        log -= mask_same_points_float * log

        return log
Exemple #28
0
    def geodesic(self,
                 initial_point,
                 end_point=None,
                 initial_tangent_vec=None,
                 point_type='vector'):
        """Generate parameterized function for the geodesic curve.

        Geodesic curve defined by either:
        - an initial point and an initial tangent vector,
        - an initial point and an end point.

        Parameters
        ----------
        initial_point : array-like, shape=[n_samples, dimension]
            Point on the manifold, initial point of the geodesic.
        end_point : array-like, shape=[n_samples, dimension], optional
            Point on the manifold, end point of the geodesic. If None,
            an initial tangent vector must be given.
        initial_tangent_vec : array-like, shape=[n_samples, dimension],
            optional
            Tangent vector at base point, the initial speed of the geodesics.
            If None, an end point must be given and a logarithm is computed.
        point_type : str, {'vector', 'matrix'}
            The type of point.

        Returns
        -------
        path : callable
            The time parameterized geodesic curve.
        """
        point_ndim = 1
        if point_type == 'matrix':
            point_ndim = 2

        initial_point = gs.to_ndarray(initial_point, to_ndim=point_ndim + 1)

        if end_point is None and initial_tangent_vec is None:
            raise ValueError('Specify an end point or an initial tangent '
                             'vector to define the geodesic.')
        if end_point is not None:
            end_point = gs.to_ndarray(end_point, to_ndim=point_ndim + 1)
            shooting_tangent_vec = self.log(point=end_point,
                                            base_point=initial_point)
            if initial_tangent_vec is not None:
                assert gs.allclose(shooting_tangent_vec, initial_tangent_vec)
            initial_tangent_vec = shooting_tangent_vec
        initial_tangent_vec = gs.array(initial_tangent_vec)
        initial_tangent_vec = gs.to_ndarray(initial_tangent_vec,
                                            to_ndim=point_ndim + 1)

        def path(t):
            """Generate parameterized function for geodesic curve.

            Parameters
            ----------
            t : array-like, shape=[n_points,]
                Times at which to compute points of the geodesics.
            """
            t = gs.array(t)
            t = gs.cast(t, gs.float32)
            t = gs.to_ndarray(t, to_ndim=1)
            t = gs.to_ndarray(t, to_ndim=2, axis=1)
            new_initial_point = gs.to_ndarray(initial_point,
                                              to_ndim=point_ndim + 1)
            new_initial_tangent_vec = gs.to_ndarray(initial_tangent_vec,
                                                    to_ndim=point_ndim + 1)

            if point_type == 'vector':
                tangent_vecs = gs.einsum('il,nk->ik', t,
                                         new_initial_tangent_vec)
            elif point_type == 'matrix':
                tangent_vecs = gs.einsum('il,nkm->ikm', t,
                                         new_initial_tangent_vec)

            point_at_time_t = self.exp(tangent_vec=tangent_vecs,
                                       base_point=new_initial_point)
            return point_at_time_t

        return path
 def test_is_vector_vectorized(self):
     vector = gs.array([1.3, 3.3])
     result = self.is_vector_vectorized(vector)
     expected = True
     self.assertAllClose(result, expected)
 def test_cholesky_factor_belongs(self, n, mat):
     result = SPDMatrices(n).cholesky_factor(gs.array(mat))
     self.assertAllClose(
         gs.all(PositiveLowerTriangularMatrices(n).belongs(result)), True
     )