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 parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point): """Compute the parallel transport of a tangent vector. Closed-form solution for the parallel transport of a tangent vector a along the geodesic defined by exp_(base_point)(tangent_vec_b). Parameters ---------- tangent_vec_a : array-like, shape=[..., dim + 1] Tangent vector at base point to be transported. tangent_vec_b : array-like, shape=[..., dim + 1] Tangent vector at base point, along which the parallel transport is computed. base_point : array-like, shape=[..., dim + 1] Point on the hypersphere. Returns ------- transported_tangent_vec: array-like, shape=[..., dim + 1] Transported tangent vector at exp_(base_point)(tangent_vec_b). """ theta = self.embedding_metric.norm(tangent_vec_b) normalized_b = gs.einsum('...,...i->...i', 1 / theta, tangent_vec_b) pb = self.embedding_metric.inner_product(tangent_vec_a, normalized_b) p_orth = tangent_vec_a - gs.einsum('...,...i->...i', pb, normalized_b) transported = \ gs.einsum('...,...i->...i', gs.sinh(theta) * pb, base_point)\ + gs.einsum('...,...i->...i', gs.cosh(theta) * pb, normalized_b)\ + p_orth return transported
def test_inverse_differential_exp(self): base_point = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., -1.]]) x = gs.exp(1) y = gs.sinh(1) tangent_vec = gs.array([[x, x, y], [x, x, y], [y, y, 1 / x]]) result = self.space.inverse_differential_exp(tangent_vec, base_point) expected = gs.array([[[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]]) self.assertAllClose(result, expected)
def test_inverse_differential_exp(self): """Test of inverse_differential_exp method.""" base_point = gs.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]]) x = gs.exp(1.0) y = gs.sinh(1.0) tangent_vec = gs.array([[x, x, y], [x, x, y], [y, y, 1.0 / x]]) result = self.space.inverse_differential_exp(tangent_vec, base_point) expected = gs.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) self.assertAllClose(result, expected)
def test_differential_exp(self): """Test of differential_exp method.""" base_point = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., -1.]]) tangent_vec = gs.array([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]) result = self.space.differential_exp(tangent_vec, base_point) x = gs.exp(1.) y = gs.sinh(1.) expected = gs.array([[x, x, y], [x, x, y], [y, y, 1 / x]]) self.assertAllClose(result, expected)
def exp(self, tangent_vec, base_point): """ Riemannian exponential of a tangent vector wrt to a base point. Parameters ---------- tangent_vec : 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 ------- exp : array-like, shape=[n_samples, dimension + 1] or shape=[1, dimension + 1] """ 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 exp(self, tangent_vec, base_point): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[..., dim + 1] Tangent vector at a base point. base_point : array-like, shape=[..., dim + 1] Point in hyperbolic space. Returns ------- exp : array-like, shape=[..., dim + 1] Point in hyperbolic space equal to the Riemannian exponential of tangent_vec at the base point. """ sq_norm_tangent_vec = self.embedding_metric.squared_norm( tangent_vec) sq_norm_tangent_vec = gs.clip(sq_norm_tangent_vec, 0, math.inf) 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('...,...j->...j', coef_1, base_point) + gs.einsum('...,...j->...j', coef_2, tangent_vec)) hyperbolic_space = Hyperboloid(dim=self.dim) exp = hyperbolic_space.regularize(exp) return exp
def log(self, point, base_point): """ 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) 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 log(self, point, base_point): """Compute Riemannian logarithm of a point wrt a base point. If point_type = 'poincare' then base_point belongs to the Poincare ball and point is a vector in the Euclidean space of the same dimension as the ball. Parameters ---------- point : array-like, shape=[..., dim + 1] Point in hyperbolic space. base_point : array-like, shape=[..., dim + 1] Point in hyperbolic space. Returns ------- log : array-like, shape=[..., dim + 1] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ angle = self.dist(base_point, point) / self.scale angle = gs.to_ndarray(angle, to_ndim=1) 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_term_1 = gs.einsum('...,...j->...j', coef_1, point) log_term_2 = - gs.einsum('...,...j->...j', coef_2, base_point) log = log_term_1 + log_term_2 return log
def parallel_transport(self, tangent_vec, base_point, direction=None, end_point=None): r"""Compute the parallel transport of a tangent vector. Closed-form solution for the parallel transport of a tangent vector along the geodesic between two points `base_point` and `end_point` or alternatively defined by :math:`t\mapsto exp_(base_point)( t*direction)`. Parameters ---------- tangent_vec : array-like, shape=[..., dim + 1] Tangent vector at base point to be transported. base_point : array-like, shape=[..., dim + 1] Point on the hyperboloid. direction : array-like, shape=[..., dim + 1] Tangent vector at base point, along which the parallel transport is computed. Optional, default : None. end_point : array-like, shape=[..., dim + 1] Point on the hyperboloid. Point to transport to. Unused if `tangent_vec_b` is given. Optional, default : None. Returns ------- transported_tangent_vec: array-like, shape=[..., dim + 1] Transported tangent vector at exp_(base_point)(tangent_vec_b). """ if direction is None: if end_point is not None: direction = self.log(end_point, base_point) else: raise ValueError( "Either an end_point or a tangent_vec_b must be given to define the" " geodesic along which to transport.") theta = self.embedding_metric.norm(direction) eps = gs.where(theta == 0.0, 1.0, theta) normalized_b = gs.einsum("...,...i->...i", 1 / eps, direction) pb = self.embedding_metric.inner_product(tangent_vec, normalized_b) p_orth = tangent_vec - gs.einsum("...,...i->...i", pb, normalized_b) transported = (gs.einsum("...,...i->...i", gs.sinh(theta) * pb, base_point) + gs.einsum("...,...i->...i", gs.cosh(theta) * pb, normalized_b) + p_orth) return transported
def empirical_frechet_var_bubble(n_samples, theta, dim, n_expectation=1000): """Variance of the empirical Fréchet mean for a bubble distribution. Draw n_sampless from a bubble distribution, computes its empirical Fréchet mean and the square distance to the asymptotic mean. This is repeated n_expectation times to compute an approximation of its expectation (i.e. its variance) by sampling. The bubble distribution is an isotropic distributions on a Riemannian hyper sub-sphere of radius 0 < theta = around the north pole of the hyperbolic space of dimension dim. Parameters ---------- n_samples: number of samples to draw theta: radius of the bubble distribution dim: dimension of the hyperbolic space (embedded in R^{1,dim}) n_expectation: number of computations for approximating the expectation Returns ------- tuple (variance, std-dev on the computed variance) """ assert dim > 1, "Dim > 1 needed to draw a uniform sample on sub-sphere" var = [] hyperbole = Hyperbolic(dimension=dim) bubble = Hypersphere(dimension=dim - 1) origin = gs.zeros(dim + 1) origin[0] = 1.0 for k in range(n_expectation): # Sample n points from the uniform distribution on a sub-sphere # of radius theta (i.e cos(theta) in ambient space) data = gs.zeros((n_samples, dim + 1), dtype=gs.float64) directions = bubble.random_uniform(n_samples) for i in range(n_samples): for j in range(dim): data[i, j + 1] = gs.sinh(theta) * directions[i, j] data[i, 0] = gs.cosh(theta) current_mean = _adaptive_gradient_descent(data, metric=hyperbole.metric, n_max_iterations=64, init_points=[origin]) var.append(hyperbole.metric.squared_dist(origin, current_mean)) return np.mean(var), 2 * np.std(var) / np.sqrt(n_expectation)
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 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
"coefficients": INV_TANC_TAYLOR_COEFFS, } cosc_close_0 = { "function": lambda x: (1 - gs.cos(x)) / x ** 2, "coefficients": COSC_TAYLOR_COEFFS, } var_sinc_close_0 = { "function": lambda x: (x - gs.sin(x)) / x ** 3, "coefficients": [-k for k in SINC_TAYLOR_COEFFS[1:]], } var_inv_tanc_close_0 = { "function": lambda x: (1 - (x / gs.tan(x))) / x ** 2, "coefficients": VAR_INV_TAN_TAYLOR_COEFFS, } sinch_close_0 = { "function": lambda x: gs.sinh(x) / x, "coefficients": SINHC_TAYLOR_COEFFS, } cosh_close_0 = {"function": gs.cosh, "coefficients": COSH_TAYLOR_COEFFS} inv_sinch_close_0 = { "function": lambda x: x / gs.sinh(x), "coefficients": INV_SINHC_TAYLOR_COEFFS, } inv_tanh_close_0 = { "function": lambda x: x / gs.tanh(x), "coefficients": INV_TANH_TAYLOR_COEFFS, } arctanh_card_close_0 = { "function": lambda x: gs.arctanh(x) / x, "coefficients": ARCTANH_CARD_TAYLOR_COEFFS, }
'coefficients': INV_TANC_TAYLOR_COEFFS } cosc_close_0 = { 'function': lambda x: (1 - gs.cos(x)) / x**2, 'coefficients': COSC_TAYLOR_COEFFS } var_sinc_close_0 = { 'function': lambda x: (x - gs.sin(x)) / x**3, 'coefficients': [-k for k in SINC_TAYLOR_COEFFS[1:]] } var_inv_tanc_close_0 = { 'function': lambda x: (1 - (x / gs.tan(x))) / x**2, 'coefficients': VAR_INV_TAN_TAYLOR_COEFFS } sinch_close_0 = { 'function': lambda x: gs.sinh(x) / x, 'coefficients': SINHC_TAYLOR_COEFFS } cosh_close_0 = {'function': gs.cosh, 'coefficients': COSH_TAYLOR_COEFFS} inv_sinch_close_0 = { 'function': lambda x: x / gs.sinh(x), 'coefficients': INV_SINHC_TAYLOR_COEFFS } inv_tanh_close_0 = { 'function': lambda x: x / gs.tanh(x), 'coefficients': INV_TANH_TAYLOR_COEFFS } arctanh_card_close_0 = { 'function': lambda x: gs.arctanh(x) / x, 'coefficients': ARCTANH_CARD_TAYLOR_COEFFS }
def exp(self, tangent_vec, base_point): """Compute the Riemannian exponential of a tangent vector. Parameters ---------- tangent_vec : array-like, shape=[n_samples, dimension + 1] Tangent vector at a base point. base_point : array-like, shape=[n_samples, dimension + 1] Point in hyperbolic space. Returns ------- exp : array-like, shape=[n_samples, dimension + 1] Point in hyperbolic space equal to the Riemannian exponential of tangent_vec at the base point. """ if self.point_type == 'extrinsic': 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) sq_norm_tangent_vec = gs.clip(sq_norm_tangent_vec, 0, math.inf) 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 = Hyperbolic(dimension=self.dimension) exp = hyperbolic_space.regularize(exp) return exp elif self.point_type == 'ball': norm_base_point = gs.to_ndarray(gs.linalg.norm(base_point, -1), 2, -1) norm_base_point = gs.repeat(norm_base_point, base_point.shape[-1], -1) den = 1 - norm_base_point**2 norm_tan = gs.to_ndarray(gs.linalg.norm(tangent_vec, axis=-1), 2, -1) norm_tan = gs.repeat(norm_tan, base_point.shape[-1], -1) lambda_base_point = 1 / den direction = tangent_vec / norm_tan factor = gs.tanh(lambda_base_point * norm_tan) exp = self.mobius_add(base_point, direction * factor) return exp else: raise NotImplementedError( 'exp is only implemented for ball and extrinsic')
def log(self, point, base_point): """Compute Riemannian logarithm of a point wrt a base point. If point_type = 'poincare' then base_point belongs to the Poincare ball and point is a vector in the Euclidean space of the same dimension as the ball. Parameters ---------- point : array-like, shape=[n_samples, dimension + 1] Point in hyperbolic space. base_point : array-like, shape=[n_samples, dimension + 1] Point in hyperbolic space. Returns ------- log : array-like, shape=[n_samples, dimension + 1] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ if self.point_type == 'extrinsic': point = gs.to_ndarray(point, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) angle = self.dist(base_point, point) / self.scale 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 elif self.point_type == 'ball': add_base_point = self.mobius_add(-base_point, point) norm_add = gs.to_ndarray(gs.linalg.norm(add_base_point, axis=-1), 2, -1) norm_add = gs.repeat(norm_add, base_point.shape[-1], -1) norm_base_point = gs.to_ndarray( gs.linalg.norm(base_point, axis=-1), 2, -1) norm_base_point = gs.repeat(norm_base_point, base_point.shape[-1], -1) log = (1 - norm_base_point**2) * gs.arctanh(norm_add)\ * (add_base_point / norm_add) mask_0 = gs.all(gs.isclose(norm_add, 0.)) log[mask_0] = 0 return log else: raise NotImplementedError( 'log is only implemented for ball and extrinsic')