Ejemplo n.º 1
0
class HyperbolicSpace(Manifold):
    """
    Hyperbolic space embedded in Minkowski space.
    Note: points are parameterized by the extrinsic
    coordinates by defaults.
    """
    def __init__(self, dimension):
        self.dimension = dimension
        self.metric = HyperbolicMetric(self.dimension)
        self.embedding_metric = MinkowskiMetric(self.dimension + 1)

    def belongs(self, point, tolerance=TOLERANCE):
        """
        By definition, a point on the Hyperbolic space
        has Minkowski squared norm -1.

        Note: point must be given in extrinsic coordinates.
        """
        point = vectorization.expand_dims(point, to_ndim=2)
        _, point_dim = point.shape

        if point_dim is not self.dimension + 1:
            if point_dim is self.dimension:
                logging.warning('Use the extrinsic coordinates to '
                                'represent points on the hypersphere.')
            return False

        sq_norm = self.embedding_metric.squared_norm(point)
        diff = np.abs(sq_norm + 1)

        return diff < tolerance

    def intrinsic_to_extrinsic_coords(self, point_intrinsic):
        """
        From the intrinsic coordinates in the hyperbolic space,
        to the extrinsic coordinates in Minkowski space.
        """
        point_intrinsic = vectorization.expand_dims(point_intrinsic, to_ndim=2)
        n_points, _ = point_intrinsic.shape

        dimension = self.dimension
        point_extrinsic = np.zeros((n_points, dimension + 1))
        point_extrinsic[:, 1:dimension + 1] = point_intrinsic[:, 0:dimension]
        point_extrinsic[:, 0] = np.sqrt(
            1. + np.linalg.norm(point_intrinsic, axis=1)**2)
        assert np.all(self.belongs(point_extrinsic))

        assert point_extrinsic.ndim == 2
        return point_extrinsic

    def extrinsic_to_intrinsic_coords(self, point_extrinsic):
        """
        From the extrinsic coordinates in Minkowski space,
        to the extrinsic coordinates in Hyperbolic space.
        """
        point_extrinsic = vectorization.expand_dims(point_extrinsic, to_ndim=2)
        assert np.all(self.belongs(point_extrinsic))

        point_intrinsic = point_extrinsic[:, 1:]
        assert point_intrinsic.ndim == 2
        return point_intrinsic

    def projection_to_tangent_space(self, vector, base_point):
        """
         Project the vector vector onto the tangent space at base_point
         T_{base_point}H
                = { w s.t. embedding_inner_product(base_point, w) = 0 }
        """
        assert self.belongs(base_point)

        inner_prod = self.embedding_metric.inner_product(base_point, vector)
        sq_norm_base_point = self.embedding_metric.squared_norm(base_point)

        tangent_vec = vector - inner_prod * base_point / sq_norm_base_point
        return tangent_vec

    def random_uniform(self, n_samples=1, max_norm=1):
        """
        Generate random elements on the hyperbolic space.
        """
        point = (np.random.rand(n_samples, self.dimension) - .5) * max_norm
        point = self.intrinsic_to_extrinsic_coords(point)
        assert np.all(self.belongs(point))

        assert point.ndim == 2
        return point
Ejemplo n.º 2
0
class HyperbolicMetric(RiemannianMetric):
    def __init__(self, dimension):
        self.dimension = dimension
        self.signature = (dimension, 0, 0)
        self.embedding_metric = MinkowskiMetric(dimension + 1)

    def squared_norm(self, vector, base_point=None):
        """
        Squared norm associated to the Hyperbolic Metric.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp_basis(self, tangent_vec, base_point):
        """
        Compute the Riemannian exponential at point base_point
        of tangent vector tangent_vec wrt the metric obtained by
        embedding of the hyperbolic space in the Minkowski space.

        This gives a point on the hyperbolic space.

        :param base_point: a point on the hyperbolic space
        :param vector: vector
        :returns riem_exp: a point on the hyperbolic space
        """
        sq_norm_tangent_vec = self.embedding_metric.squared_norm(tangent_vec)
        # TODO(nina): Fix, value error on this squared norm
        norm_tangent_vec = np.sqrt(sq_norm_tangent_vec)

        if np.isclose(norm_tangent_vec, 0):
            coef_1 = (1. + COSH_TAYLOR_COEFFS[2] * norm_tangent_vec**2 +
                      COSH_TAYLOR_COEFFS[4] * norm_tangent_vec**4 +
                      COSH_TAYLOR_COEFFS[6] * norm_tangent_vec**6 +
                      COSH_TAYLOR_COEFFS[8] * norm_tangent_vec**8)
            coef_2 = (1. + SINH_TAYLOR_COEFFS[3] * norm_tangent_vec**2 +
                      SINH_TAYLOR_COEFFS[5] * norm_tangent_vec**4 +
                      SINH_TAYLOR_COEFFS[7] * norm_tangent_vec**6 +
                      SINH_TAYLOR_COEFFS[9] * norm_tangent_vec**8)
        else:
            coef_1 = np.cosh(norm_tangent_vec)
            coef_2 = np.sinh(norm_tangent_vec) / norm_tangent_vec

        riem_exp = coef_1 * base_point + coef_2 * tangent_vec

        return riem_exp

    def log_basis(self, point, base_point):
        """
        Compute the Riemannian logarithm at point base_point,
        of point wrt the metric obtained by
        embedding of the hyperbolic space in the Minkowski space.

        This gives a tangent vector at point base_point.

        :param base_point: point on the hyperbolic space
        :param point: point on the hyperbolic space
        :returns riem_log: tangent vector at base_point
        """
        angle = self.dist(base_point, point)
        if np.isclose(angle, 0):
            coef_1 = (1. + INV_SINH_TAYLOR_COEFFS[1] * angle**2 +
                      INV_SINH_TAYLOR_COEFFS[3] * angle**4 +
                      INV_SINH_TAYLOR_COEFFS[5] * angle**6 +
                      INV_SINH_TAYLOR_COEFFS[7] * angle**8)
            coef_2 = (1. + INV_TANH_TAYLOR_COEFFS[1] * angle**2 +
                      INV_TANH_TAYLOR_COEFFS[3] * angle**4 +
                      INV_TANH_TAYLOR_COEFFS[5] * angle**6 +
                      INV_TANH_TAYLOR_COEFFS[7] * angle**8)
        else:
            coef_1 = angle / np.sinh(angle)
            coef_2 = angle / np.tanh(angle)
        return coef_1 * point - coef_2 * base_point

    def dist(self, point_a, point_b):
        """
        Compute the distance induced on the hyperbolic
        space, from its embedding in the Minkowski space.
        """
        if np.all(point_a == point_b):
            return 0.
        point_a = vectorization.expand_dims(point_a, to_ndim=2)
        point_b = vectorization.expand_dims(point_b, to_ndim=2)

        n_points_a, _ = point_a.shape
        n_points_b, _ = point_b.shape

        assert (n_points_a == n_points_b or n_points_a == 1 or n_points_b == 1)

        n_dists = np.maximum(n_points_a, n_points_b)
        dist = np.zeros((n_dists, 1))
        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 / np.sqrt(sq_norm_a * sq_norm_b)

        mask_cosh_less_1 = np.less_equal(cosh_angle, 1.)
        mask_cosh_greater_1 = ~mask_cosh_less_1

        dist[mask_cosh_less_1] = 0.
        dist[mask_cosh_greater_1] = np.arccosh(cosh_angle[mask_cosh_greater_1])

        return dist
Ejemplo n.º 3
0
class HyperbolicMetric(RiemannianMetric):
    def __init__(self, dimension):
        self.dimension = dimension
        self.signature = (dimension, 0, 0)
        self.embedding_metric = MinkowskiMetric(dimension + 1)

    def squared_norm(self, vector, base_point=None):
        """
        Squared norm associated to the Hyperbolic Metric.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp(self, tangent_vec, base_point):
        """
        Compute the Riemannian exponential at point base_point
        of tangent vector tangent_vec wrt the metric obtained by
        embedding of the hyperbolic space in the Minkowski space.

        This gives a point on the hyperbolic space.

        :param base_point: a point on the hyperbolic space
        :param vector: vector
        :returns riem_exp: a point on the hyperbolic space
        """
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

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

        mask_0 = gs.isclose(sq_norm_tangent_vec, 0)
        mask_0 = gs.to_ndarray(mask_0, to_ndim=1)
        mask_else = ~mask_0
        mask_else = gs.to_ndarray(mask_else, to_ndim=1)

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

        coef_1[mask_0] = (1. +
                          COSH_TAYLOR_COEFFS[2] * norm_tangent_vec[mask_0]**2 +
                          COSH_TAYLOR_COEFFS[4] * norm_tangent_vec[mask_0]**4 +
                          COSH_TAYLOR_COEFFS[6] * norm_tangent_vec[mask_0]**6 +
                          COSH_TAYLOR_COEFFS[8] * norm_tangent_vec[mask_0]**8)
        coef_2[mask_0] = (1. +
                          SINH_TAYLOR_COEFFS[3] * norm_tangent_vec[mask_0]**2 +
                          SINH_TAYLOR_COEFFS[5] * norm_tangent_vec[mask_0]**4 +
                          SINH_TAYLOR_COEFFS[7] * norm_tangent_vec[mask_0]**6 +
                          SINH_TAYLOR_COEFFS[9] * norm_tangent_vec[mask_0]**8)

        coef_1[mask_else] = gs.cosh(norm_tangent_vec[mask_else])
        coef_2[mask_else] = (gs.sinh(norm_tangent_vec[mask_else]) /
                             norm_tangent_vec[mask_else])

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

        hyperbolic_space = HyperbolicSpace(dimension=self.dimension)
        exp = hyperbolic_space.regularize(exp)
        return exp

    def log(self, point, base_point):
        """
        Compute the Riemannian logarithm at point base_point,
        of point wrt the metric obtained by
        embedding of the hyperbolic space in the Minkowski space.

        This gives a tangent vector at point base_point.

        :param base_point: point on the hyperbolic space
        :param point: point on the hyperbolic space
        :returns riem_log: tangent vector at base_point
        """
        point = gs.to_ndarray(point, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

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

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

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

        coef_1[mask_0] = (1. + INV_SINH_TAYLOR_COEFFS[1] * angle[mask_0]**2 +
                          INV_SINH_TAYLOR_COEFFS[3] * angle[mask_0]**4 +
                          INV_SINH_TAYLOR_COEFFS[5] * angle[mask_0]**6 +
                          INV_SINH_TAYLOR_COEFFS[7] * angle[mask_0]**8)
        coef_2[mask_0] = (1. + INV_TANH_TAYLOR_COEFFS[1] * angle[mask_0]**2 +
                          INV_TANH_TAYLOR_COEFFS[3] * angle[mask_0]**4 +
                          INV_TANH_TAYLOR_COEFFS[5] * angle[mask_0]**6 +
                          INV_TANH_TAYLOR_COEFFS[7] * angle[mask_0]**8)

        coef_1[mask_else] = angle[mask_else] / gs.sinh(angle[mask_else])
        coef_2[mask_else] = angle[mask_else] / gs.tanh(angle[mask_else])

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

    def dist(self, point_a, point_b):
        """
        Compute the distance induced on the hyperbolic
        space, from its embedding in the Minkowski space.
        """
        if gs.all(gs.equal(point_a, point_b)):
            return 0.

        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, None)

        dist = gs.arccosh(cosh_angle)

        return dist
Ejemplo n.º 4
0
class HyperbolicMetric(RiemannianMetric):
    def __init__(self, dimension):
        super(HyperbolicMetric, self).__init__(dimension=dimension,
                                               signature=(dimension, 0, 0))
        self.embedding_metric = MinkowskiMetric(dimension + 1)

    def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None):
        """
        Inner product.
        """
        inner_prod = self.embedding_metric.inner_product(
            tangent_vec_a, tangent_vec_b, base_point)
        return inner_prod

    def squared_norm(self, vector, base_point=None):
        """
        Squared norm of a vector associated with the inner product
        at the tangent space at a base point.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp(self, tangent_vec, base_point):
        """
        Riemannian exponential of a tangent vector wrt to a base point.
        """
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

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

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

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

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

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

        hyperbolic_space = HyperbolicSpace(dimension=self.dimension)
        exp = hyperbolic_space.regularize(exp)
        return exp

    def log(self, point, base_point):
        """
        Riemannian logarithm of a point wrt a base point.
        """
        point = gs.to_ndarray(point, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

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

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

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

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

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

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

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

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

    def dist(self, point_a, point_b):
        """
        Geodesic distance between two points.
        """
        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 dist
Ejemplo n.º 5
0
class HyperbolicMetric(RiemannianMetric):
    def __init__(self, dimension):
        self.dimension = dimension
        self.signature = (dimension, 0, 0)
        self.embedding_metric = MinkowskiMetric(dimension + 1)

    def squared_norm(self, vector, base_point=None):
        """
        Squared norm of a vector associated with the inner product
        at the tangent space at a base point.
        """
        sq_norm = self.embedding_metric.squared_norm(vector)
        return sq_norm

    def exp(self, tangent_vec, base_point):
        """
        Riemannian exponential of a tangent vector wrt to a base point.
        """
        tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

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

        mask_0 = gs.isclose(sq_norm_tangent_vec, 0)
        mask_0 = gs.to_ndarray(mask_0, to_ndim=1)
        mask_else = ~mask_0
        mask_else = gs.to_ndarray(mask_else, to_ndim=1)

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

        coef_1[mask_0] = (1. +
                          COSH_TAYLOR_COEFFS[2] * norm_tangent_vec[mask_0]**2 +
                          COSH_TAYLOR_COEFFS[4] * norm_tangent_vec[mask_0]**4 +
                          COSH_TAYLOR_COEFFS[6] * norm_tangent_vec[mask_0]**6 +
                          COSH_TAYLOR_COEFFS[8] * norm_tangent_vec[mask_0]**8)
        coef_2[mask_0] = (1. +
                          SINH_TAYLOR_COEFFS[3] * norm_tangent_vec[mask_0]**2 +
                          SINH_TAYLOR_COEFFS[5] * norm_tangent_vec[mask_0]**4 +
                          SINH_TAYLOR_COEFFS[7] * norm_tangent_vec[mask_0]**6 +
                          SINH_TAYLOR_COEFFS[9] * norm_tangent_vec[mask_0]**8)

        coef_1[mask_else] = gs.cosh(norm_tangent_vec[mask_else])
        coef_2[mask_else] = (gs.sinh(norm_tangent_vec[mask_else]) /
                             norm_tangent_vec[mask_else])

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

        hyperbolic_space = HyperbolicSpace(dimension=self.dimension)
        exp = hyperbolic_space.regularize(exp)
        return exp

    def log(self, point, base_point):
        """
        Riemannian logarithm of a point wrt a base point.
        """
        point = gs.to_ndarray(point, to_ndim=2)
        base_point = gs.to_ndarray(base_point, to_ndim=2)

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

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

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

        coef_1[mask_0] = (1. + INV_SINH_TAYLOR_COEFFS[1] * angle[mask_0]**2 +
                          INV_SINH_TAYLOR_COEFFS[3] * angle[mask_0]**4 +
                          INV_SINH_TAYLOR_COEFFS[5] * angle[mask_0]**6 +
                          INV_SINH_TAYLOR_COEFFS[7] * angle[mask_0]**8)
        coef_2[mask_0] = (1. + INV_TANH_TAYLOR_COEFFS[1] * angle[mask_0]**2 +
                          INV_TANH_TAYLOR_COEFFS[3] * angle[mask_0]**4 +
                          INV_TANH_TAYLOR_COEFFS[5] * angle[mask_0]**6 +
                          INV_TANH_TAYLOR_COEFFS[7] * angle[mask_0]**8)

        coef_1[mask_else] = angle[mask_else] / gs.sinh(angle[mask_else])
        coef_2[mask_else] = angle[mask_else] / gs.tanh(angle[mask_else])

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

    def dist(self, point_a, point_b):
        """
        Geodesic distance between two points.
        """
        if gs.all(gs.equal(point_a, point_b)):
            return 0.

        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, None)

        dist = gs.arccosh(cosh_angle)

        return dist