def __init__(self, dimension): self.dimension = dimension self.metric = HyperbolicMetric(self.dimension) self.embedding_metric = MinkowskiMetric(self.dimension + 1)
def __init__(self, dimension): self.dimension = dimension self.signature = (dimension, 0, 0) self.embedding_metric = MinkowskiMetric(dimension + 1)
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
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
def __init__(self, dimension): super(HyperbolicMetric, self).__init__(dimension=dimension, signature=(dimension, 0, 0)) self.embedding_metric = MinkowskiMetric(dimension + 1)
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
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
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