Exemple #1
0
def _assess_dimension_(spectrum, rank, n_samples, n_features):
    """Compute the likelihood of a rank ``rank`` dataset

    The dataset is assumed to be embedded in gaussian noise of shape(n,
    dimf) having spectrum ``spectrum``.

    Parameters
    ----------
    spectrum : array of shape (n)
        Data spectrum.
    rank : int
        Tested rank value.
    n_samples : int
        Number of samples.
    n_features : int
        Number of features.

    Returns
    -------
    ll : float,
        The log-likelihood

    Notes
    -----
    This implements the method of `Thomas P. Minka:
    Automatic Choice of Dimensionality for PCA. NIPS 2000: 598-604`
    """
    if rank > len(spectrum):
        raise ValueError("The tested rank cannot exceed the rank of the"
                         " dataset")

    pu = -rank * log(2.)
    for i in range(rank):
        pu += (gammaln(
            (n_features - i) / 2.) - log(gs.pi) * (n_features - i) / 2.)

    pl = gs.sum(gs.log(spectrum[:rank]))
    pl = -pl * n_samples / 2.

    if rank == n_features:
        pv = 0
        v = 1
    else:
        v = gs.sum(spectrum[rank:]) / (n_features - rank)
        pv = -gs.log(v) * n_samples * (n_features - rank) / 2.

    m = n_features * rank - rank * (rank + 1.) / 2.
    pp = log(2. * gs.pi) * (m + rank + 1.) / 2.

    pa = 0.
    spectrum_ = spectrum.copy()
    spectrum_[rank:n_features] = v
    for i in range(rank):
        for j in range(i + 1, len(spectrum)):
            pa += log((spectrum[i] - spectrum[j]) *
                      (1. / spectrum_[j] - 1. / spectrum_[i])) + log(n_samples)

    ll = pu + pl + pv + pp - pa / 2. - rank * log(n_samples) / 2.

    return ll
    def squared_dist(self, point_a, point_b, **kwargs):
        """Compute the Cholesky Metric squared distance.

        Compute the Riemannian squared distance between point_a and point_b.

        Parameters
        ----------
        point_a : array-like, shape=[..., n, n]
            Point.
        point_b : array-like, shape=[..., n, n]
            Point.

        Returns
        -------
        _ : array-like, shape=[...]
            Riemannian squared distance.
        """
        log_diag_a = gs.log(Matrices.diagonal(point_a))
        log_diag_b = gs.log(Matrices.diagonal(point_b))
        diag_diff = log_diag_a - log_diag_b
        squared_dist_diag = gs.sum((diag_diff) ** 2, axis=-1)

        sl_a = Matrices.to_strictly_lower_triangular(point_a)
        sl_b = Matrices.to_strictly_lower_triangular(point_b)
        sl_diff = sl_a - sl_b
        squared_dist_sl = Matrices.frobenius_product(sl_diff, sl_diff)
        return squared_dist_sl + squared_dist_diag
Exemple #3
0
    def random_von_mises_fisher(self, kappa=10, n_samples=1):
        """Sample in the 2-sphere with the von Mises distribution.

        Sample in the 2-sphere with the von Mises distribution centered in the
        north pole.

        Parameters
        ----------
        kappa : int, optional
        n_samples : int, optional

        Returns
        -------
        point : array-like
        """
        if self.dimension != 2:
            raise NotImplementedError(
                'Sampling from the von Mises Fisher distribution'
                'is only implemented in dimension 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

        point = gs.hstack((coord_xy, coord_z))

        return point
 def test_differential_log(self):
     base_point = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 4.]])
     tangent_vec = gs.array([[1., 1., 3.], [1., 1., 3.], [3., 3., 4.]])
     result = self.space.differential_log(tangent_vec, base_point)
     x = 2 * gs.log(2)
     expected = gs.array([[[1., 1., x], [1., 1., x], [x, x, 1]]])
     self.assertAllClose(result, expected)
Exemple #5
0
    def dist(self, point_a, point_b):
        """Compute the geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[..., dim]
            First point in the Poincare ball.
        point_b : array-like, shape=[..., dim]
            Second point in the Poincare ball.

        Returns
        -------
        dist : array-like, shape=[...,]
            Geodesic distance between the two points.
        """
        point_a_norm = gs.clip(gs.sum(point_a**2, -1), 0.0, 1 - EPSILON)
        point_b_norm = gs.clip(gs.sum(point_b**2, -1), 0.0, 1 - EPSILON)

        diff_norm = gs.sum((point_a - point_b)**2, -1)
        norm_function = 1 + 2 * diff_norm / ((1 - point_a_norm) *
                                             (1 - point_b_norm))

        dist = gs.log(norm_function + gs.sqrt(norm_function**2 - 1))
        dist *= self.scale
        return dist
    def log(self, point, base_point, **kwargs):
        """Compute the Cholesky logarithm map.

        Compute the Riemannian logarithm at point base_point,
        of point wrt the Cholesky metric.
        This gives a tangent vector at point base_point.

        Parameters
        ----------
        point : array-like, shape=[..., n, n]
            Point.
        base_point : array-like, shape=[..., n, n]
            Base point.

        Returns
        -------
        log : array-like, shape=[..., n, n]
            Riemannian logarithm.
        """
        sl_base_point = Matrices.to_strictly_lower_triangular(base_point)
        sl_point = Matrices.to_strictly_lower_triangular(point)
        diag_base_point = Matrices.diagonal(base_point)
        diag_point = Matrices.diagonal(point)
        diag_product_logm = gs.log(gs.divide(diag_point, diag_base_point))

        sl_log = sl_point - sl_base_point
        diag_log = gs.vec_to_diag(diag_base_point * diag_product_logm)
        log = sl_log + diag_log
        return log
Exemple #7
0
    def dist(self, point_a, point_b):
        """Compute the geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[n_samples, dim]
            First point in hyperbolic space.
        point_b : array-like, shape=[n_samples, dim]
            Second point in hyperbolic space.

        Returns
        -------
        dist : array-like, shape=[n_samples, 1]
            Geodesic distance between the two points.
        """
        point_a = gs.to_ndarray(point_a, to_ndim=2)
        point_b = gs.to_ndarray(point_b, to_ndim=2)

        point_a_norm = gs.clip(gs.sum(point_a**2, -1), 0., 1 - EPSILON)
        point_b_norm = gs.clip(gs.sum(point_b**2, -1), 0., 1 - EPSILON)

        diff_norm = gs.sum((point_a - point_b)**2, -1)
        norm_function = 1 + 2 * \
            diff_norm / ((1 - point_a_norm) * (1 - point_b_norm))

        dist = gs.log(norm_function + gs.sqrt(norm_function**2 - 1))
        dist = gs.to_ndarray(dist, to_ndim=1)
        dist = gs.to_ndarray(dist, to_ndim=2, axis=1)
        dist *= self.scale
        return dist
    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)
 def test_inverse_differential_log(self):
     """Test of inverse_differential_log method."""
     base_point = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 4.]])
     x = 2 * gs.log(2.)
     tangent_vec = gs.array([[1., 1., x], [1., 1., x], [x, x, 1]])
     result = self.space.inverse_differential_log(tangent_vec, base_point)
     expected = gs.array([[1., 1., 3.], [1., 1., 3.], [3., 3., 4.]])
     self.assertAllClose(result, expected)
Exemple #10
0
    def test_differential_log(self):
        """Test of differential_log method."""
        base_point = gs.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 4.0]])
        tangent_vec = gs.array([[1.0, 1.0, 3.0], [1.0, 1.0, 3.0], [3.0, 3.0, 4.0]])
        result = self.space.differential_log(tangent_vec, base_point)
        x = 2 * gs.log(2.0)
        expected = gs.array([[1.0, 1.0, x], [1.0, 1.0, x], [x, x, 1]])

        self.assertAllClose(result, expected)
    def test_log_euclidean_inner_product(self):
        base_point = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 4.]])
        tangent_vec = gs.array([[1., 1., 3.], [1., 1., 3.], [3., 3., 4.]])
        metric = self.metric_logeuclidean
        result = metric.inner_product(tangent_vec, tangent_vec, base_point)
        x = 2 * gs.log(2)
        expected = 5. + 4. * x**2

        self.assertAllClose(result, expected)
Exemple #12
0
    def dist_broadcast(self, point_a, point_b):
        """Compute the geodesic distance between points.

        If n_samples_a == n_samples_b then dist is the element-wise
        distance result of a point in points_a with the point from
        points_b of the same index. If n_samples_a not equal to
        n_samples_b then dist is the result of applying geodesic
        distance for each point from points_a to all points from
        points_b.

        Parameters
        ----------
        point_a : array-like, shape=[n_samples_a, dim]
            Set of points in hyperbolic space.
        point_b : array-like, shape=[n_samples_b, dim]
            Second set of points in hyperbolic space.

        Returns
        -------
        dist : array-like,
            shape=[n_samples_a, dim] or [n_samples_a, n_samples_b, dim]
            Geodesic distance between the two points.
        """
        if point_a.shape[-1] != point_b.shape[-1]:
            raise ValueError('Manifold dimensions not equal')

        if point_a.shape[0] != point_b.shape[0]:

            point_a_broadcast, point_b_broadcast = gs.broadcast_arrays(
                point_a[:, None], point_b[None, ...])

            point_a_flatten = gs.reshape(point_a_broadcast,
                                         (-1, point_a_broadcast.shape[-1]))
            point_b_flatten = gs.reshape(point_b_broadcast,
                                         (-1, point_b_broadcast.shape[-1]))

            point_a_norm = gs.clip(gs.sum(point_a_flatten**2, -1), 0.,
                                   1 - EPSILON)
            point_b_norm = gs.clip(gs.sum(point_b_flatten**2, -1), 0.,
                                   1 - EPSILON)

            square_diff = (point_a_flatten - point_b_flatten)**2

            diff_norm = gs.sum(square_diff, -1)
            norm_function = 1 + 2 * \
                diff_norm / ((1 - point_a_norm) * (1 - point_b_norm))

            dist = gs.log(norm_function + gs.sqrt(norm_function**2 - 1))
            dist *= self.scale
            dist = gs.reshape(dist, (point_a.shape[0], point_b.shape[0]))
            dist = gs.squeeze(dist)

        elif point_a.shape == point_b.shape:
            dist = self.dist(point_a, point_b)

        return dist
Exemple #13
0
    def test_log_euclidean_inner_product(self):
        """Test of SPDMetricLogEuclidean.inner_product method."""
        base_point = gs.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 4.0]])
        tangent_vec = gs.array([[1.0, 1.0, 3.0], [1.0, 1.0, 3.0], [3.0, 3.0, 4.0]])
        metric = self.metric_logeuclidean
        result = metric.inner_product(tangent_vec, tangent_vec, base_point)
        x = 2 * gs.log(2.0)
        expected = 5.0 + 4.0 * x ** 2

        self.assertAllClose(result, expected)
Exemple #14
0
    def test_euclidean_frechet_mean(self):
        """Test if the frechet mean of the samples is close to mean"""
        mean = gs.zeros(self.n)
        cov = gs.eye(self.n)
        data = LogNormal(self.euclidean, mean, cov).sample(5000)
        log_data = gs.log(data)
        fm = gs.mean(log_data, axis=0)

        expected = mean
        result = fm
        self.assertAllClose(result, expected, atol=5 * 1e-2)
Exemple #15
0
    def log_sigmoid(vector):
        """Logsigmoid function.

        Apply log sigmoid function.

        Parameters
        ----------
        vector : array-like, shape=[n_samples, dim]

        Returns
        -------
        result : array-like, shape=[n_samples, dim]
        """
        return gs.log((1 / (1 + gs.exp(-vector))))
Exemple #16
0
    def dist(self, point_a, point_b):
        """Compute the geodesic distance between two points.

        Parameters
        ----------
        point_a : array-like, shape=[n_samples, dimension + 1]
            First point in hyperbolic space.
        point_b : array-like, shape=[n_samples, dimension + 1]
            Second point in hyperbolic space.

        Returns
        -------
        dist : array-like, shape=[n_samples, 1]
            Geodesic distance between the two points.
        """
        if self.point_type == 'extrinsic':

            sq_norm_a = self.embedding_metric.squared_norm(point_a)
            sq_norm_b = self.embedding_metric.squared_norm(point_b)
            inner_prod = self.embedding_metric.inner_product(point_a, point_b)

            cosh_angle = -inner_prod / gs.sqrt(sq_norm_a * sq_norm_b)
            cosh_angle = gs.clip(cosh_angle, 1.0, 1e24)

            dist = gs.arccosh(cosh_angle)

            return self.scale * dist

        elif self.point_type == 'ball':

            point_a_norm = gs.clip(gs.sum(point_a**2, -1), 0., 1 - EPSILON)

            point_b_norm = gs.clip(gs.sum(point_b**2, -1), 0., 1 - EPSILON)

            diff_norm = gs.sum((point_a - point_b)**2, -1)

            norm_function = 1 + 2 * \
                diff_norm / ((1 - point_a_norm) * (1 - point_b_norm))

            dist = gs.log(norm_function + gs.sqrt(norm_function**2 - 1))
            dist = gs.to_ndarray(dist, to_ndim=1)
            dist = gs.to_ndarray(dist, to_ndim=2, axis=1)

            return self.scale * dist

        else:
            raise NotImplementedError(
                'dist is only implemented for ball and extrinsic')
Exemple #17
0
    def squared_dist(self, point_a, point_b, **kwargs):
        """Compute squared distance associated with the exponential Fisher Rao metric.

        Parameters
        ----------
        point_a : array-like, shape=[...,]
            Point representing an exponential distribution (scale parameter).
        point_b : array-like, shape=[...,] (same shape as point_a)
            Point representing a exponential distribution (scale parameter).

        Returns
        -------
        squared_dist : array-like, shape=[...,]
            Squared distance between points point_a and point_b.
        """
        return gs.log(point_a / point_b)**2
Exemple #18
0
    def random_von_mises_fisher(self, kappa=10, n_samples=1):
        """
        Sample in the 2-sphere with the von Mises distribution
        centered in the north pole.
        """
        if self.dimension != 2:
            raise NotImplementedError(
                    'Sampling from the von Mises Fisher distribution'
                    'is only implemented in dimension 2.')
        angle = 2 * gs.pi * gs.random.rand(n_samples)
        unit_vector = gs.vstack((gs.cos(angle), gs.sin(angle)))
        scalar = gs.random.rand(n_samples)
        coord_z = 1 + 1/kappa*gs.log(scalar + (1-scalar)*gs.exp(-2*kappa))
        coord_xy = gs.sqrt(1 - coord_z**2) * unit_vector
        point = gs.vstack((coord_xy, coord_z))

        return point.T
Exemple #19
0
    def random_von_mises_fisher(self, kappa=10, n_samples=1):
        """Sample in the 2-sphere with the von Mises distribution.

        Sample in the 2-sphere with the von Mises distribution centered at the
        north pole.

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

        Parameters
        ----------
        kappa : int
            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.
        """
        if self.dim != 2:
            raise NotImplementedError(
                'Sampling from the von Mises Fisher distribution'
                'is only implemented in dimension 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

        point = gs.hstack((coord_xy, coord_z))

        if n_samples == 1:
            point = gs.squeeze(point, axis=0)
        return point
Exemple #20
0
def group_log(sym_mat):
    """
    Group logarithm of the Lie group of
    all invertible matrices has a straight-forward
    computation for symmetric positive definite matrices.
    """
    sym_mat = gs.to_ndarray(sym_mat, to_ndim=3)
    n_sym_mats, mat_dim, _ = sym_mat.shape

    assert gs.all(is_symmetric(sym_mat))
    sym_mat = make_symmetric(sym_mat)
    [eigenvalues, vectors] = gs.linalg.eigh(sym_mat)
    assert gs.all(eigenvalues > 0)

    log_eigenvalues = gs.log(eigenvalues)

    aux = gs.einsum('ijk,ik->ijk', vectors, log_eigenvalues)
    log_mat = gs.einsum('ijk,ilk->ijl', aux, vectors)

    log_mat = gs.to_ndarray(log_mat, to_ndim=3)
    return log_mat
    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.
        max_iter : int
            Maximum number of trials in the rejection algorithm. In case it
            is reached, the current number of samples < n_samples is returned.
            Optional, default: 100.

        Returns
        -------
        point : array-like, shape=[n_samples, dim + 1]
            Points sampled on the sphere in extrinsic coordinates
            in Euclidean space of dimension dim + 1.
        """
        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_x = 1. + 1. / kappa * gs.log(
                scalar + (1. - scalar) * gs.exp(gs.array(-2. * kappa)))
            coord_x = gs.to_ndarray(coord_x, to_ndim=2, axis=1)
            coord_yz = gs.sqrt(1. - coord_x ** 2) * unit_vector
            sample = gs.hstack((coord_x, coord_yz))

        else:
            # 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)
                sym_beta = gs.cast(sym_beta, node.dtype)
                coord_x = (1 - (1 + envelop_param) * sym_beta) / (
                    1 - (1 - envelop_param) * sym_beta)
                accept_tol = gs.random.rand(n_samples - n_accepted)
                criterion = (
                    kappa * coord_x
                    + dim * gs.log(1 - node * coord_x)
                    - correction) > gs.log(accept_tol)
                result.append(coord_x[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_x = gs.concatenate(result)
            coord_rest = _Hypersphere(dim - 1).random_uniform(n_accepted)
            coord_rest = gs.einsum(
                '...,...i->...i', gs.sqrt(1 - coord_x ** 2), coord_rest)
            sample = gs.concatenate([coord_x[..., None], coord_rest], axis=1)

        if mu is not None:
            sample = utils.rotate_points(sample, mu)

        return sample if (n_samples > 1) else sample[0]
Exemple #22
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]
        """
        n_tangent_vecs, _, _ = tangent_vec.shape
        n_base_points, _, n = base_point.shape

        eigvalues, eigvectors = gs.linalg.eigh(base_point)
        eigvalues = gs.to_ndarray(eigvalues, to_ndim=3, axis=1)
        transp_eigvalues = gs.transpose(eigvalues, (0, 2, 1))

        if power == 0:
            powered_eigvalues = gs.log(eigvalues)
        elif power == math.inf:
            powered_eigvalues = gs.exp(eigvalues)
        else:
            powered_eigvalues = eigvalues**power
        transp_powered_eigvalues = gs.transpose(powered_eigvalues, (0, 2, 1))
        ones = gs.ones((n_base_points, 1, n))
        transp_ones = gs.transpose(ones, (0, 2, 1))

        vertical_index = gs.matmul(transp_eigvalues, ones)
        horizontal_index = gs.matmul(transp_ones, eigvalues)
        one_matrix = gs.matmul(transp_ones, ones)
        vertical_index_power = gs.matmul(transp_powered_eigvalues, ones)
        horizontal_index_power = gs.matmul(transp_ones, powered_eigvalues)
        denominator = vertical_index - horizontal_index
        numerator = vertical_index_power - horizontal_index_power

        if power == 0:
            numerator = gs.where(denominator == 0, one_matrix, numerator)
            denominator = gs.where(denominator == 0, vertical_index,
                                   denominator)
        elif power == math.inf:
            numerator = gs.where(denominator == 0, vertical_index_power,
                                 numerator)
            denominator = gs.where(denominator == 0, one_matrix, denominator)
        else:
            numerator = gs.where(denominator == 0,
                                 power * vertical_index_power, numerator)
            denominator = gs.where(denominator == 0, vertical_index,
                                   denominator)

        transp_eigvectors = gs.transpose(eigvectors, (0, 2, 1))
        temp_result = gs.matmul(transp_eigvectors, tangent_vec)
        temp_result = gs.matmul(temp_result, eigvectors)

        if n_base_points == n_tangent_vecs == 1:
            transp_eigvectors = gs.squeeze(transp_eigvectors, axis=0)
            eigvectors = gs.squeeze(eigvectors, axis=0)
            temp_result = gs.squeeze(temp_result, axis=0)
            numerator = gs.squeeze(numerator, axis=0)
            denominator = gs.squeeze(denominator, axis=0)

        return (eigvectors, transp_eigvectors, numerator, denominator,
                temp_result)
Exemple #23
0
    def aux_differential_power(self, 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 : int
        tangent_vec : array_like, shape=[n_samples, n, n]
            Tangent vectors.
        base_point : array_like, shape=[n_samples, n, n]
            Base points.

        Returns
        -------
        eigvectors : array-like, shape=[n_samples, n, n]
        transp_eigvectors : array-like, shape=[n_samples, n, n]
        numerator : array-like, shape=[n_samples, n, n]
        denominator : array-like, shape=[n_samples, n, n]
        temp_result : array-like, shape=[n_samples, n, n]
        """
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3)
        n_tangent_vecs, _, _ = tangent_vec.shape
        base_point = gs.to_ndarray(base_point, to_ndim=3)
        n_base_points, _, _ = base_point.shape

        assert (n_tangent_vecs == n_base_points or n_base_points == 1
                or n_tangent_vecs == 1)

        eigvalues, eigvectors = gs.linalg.eigh(base_point)
        eigvalues = gs.to_ndarray(eigvalues, to_ndim=3, axis=1)
        transp_eigvalues = gs.transpose(eigvalues, (0, 2, 1))

        if power == 0:
            powered_eigvalues = gs.log(eigvalues)
        elif power == math.inf:
            powered_eigvalues = gs.exp(eigvalues)
        else:
            powered_eigvalues = eigvalues**power
        transp_powered_eigvalues = gs.transpose(powered_eigvalues, (0, 2, 1))
        ones = gs.ones((n_base_points, 1, self.n))
        transp_ones = gs.transpose(ones, (0, 2, 1))

        vertical_index = gs.matmul(transp_eigvalues, ones)
        horizontal_index = gs.matmul(transp_ones, eigvalues)
        one_matrix = gs.matmul(transp_ones, ones)
        vertical_index_power = gs.matmul(transp_powered_eigvalues, ones)
        horizontal_index_power = gs.matmul(transp_ones, powered_eigvalues)
        denominator = vertical_index - horizontal_index
        numerator = vertical_index_power - horizontal_index_power

        if power == 0:
            numerator = gs.where(denominator == 0, one_matrix, numerator)
            denominator = gs.where(denominator == 0, vertical_index,
                                   denominator)
        elif power == math.inf:
            numerator = gs.where(denominator == 0, vertical_index_power,
                                 numerator)
            denominator = gs.where(denominator == 0, one_matrix, denominator)
        else:
            numerator = gs.where(denominator == 0,
                                 power * vertical_index_power, numerator)
            denominator = gs.where(denominator == 0, vertical_index,
                                   denominator)

        transp_eigvectors = gs.transpose(eigvectors, (0, 2, 1))
        temp_result = gs.matmul(transp_eigvectors, tangent_vec)
        temp_result = gs.matmul(temp_result, eigvectors)
        return (eigvectors, transp_eigvectors, numerator, denominator,
                temp_result)
Exemple #24
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]