def __init__(self, metrics): self.n_metrics = gs.len(metrics) dimensions = [metric.dimension for metric in metrics] signatures = [metric.signature for metric in metrics] self.metrics = metrics self.dimensions = dimensions self.signatures = signatures super(ProductRiemannianMetric, self).__init__(dimension=gs.sum(dimensions), signature=map(sum, signatures))
def test_estimate_default_gradient_descent_so3(self): points = self.so3.random_uniform(2) mean_vec = FrechetMean(metric=self.so3.bi_invariant_metric, method='default') mean_vec.fit(points) logs = self.so3.bi_invariant_metric.log(points, mean_vec.estimate_) result = gs.sum(logs, axis=0) expected = gs.zeros_like(points[0]) self.assertAllClose(result, expected)
def test_sample_belong(self): """Test that sample samples in the simplex. Check that samples belong to the simplex, i.e. that their components sum up to one. """ points = self.dirichlet.random_point(self.n_points) samples = self.dirichlet.sample(points, self.n_samples) result = gs.sum(samples, -1) expected = gs.ones((self.n_points, self.n_samples)) self.assertAllClose(expected, result)
def test_weighted_frechet_mean(self): """Test for weighted mean.""" data = gs.array([[0.1, 0.2], [0.25, 0.35]]) weights = gs.array([3.0, 1.0]) mean_o = FrechetMean(metric=self.metric, point_type="vector", lr=1.0) mean_o.fit(data, weights=weights) result = mean_o.estimate_ expected = self.metric.exp( weights[1] / gs.sum(weights) * self.metric.log(data[1], data[0]), data[0]) self.assertAllClose(result, expected)
def test_to_tangent(self): """Test to_tangent. Test that the result belongs to the tangent space to the simplex. """ vectors = -5 + 2 * gs.random.rand(self.n_points, self.dim + 1) projected_vectors = self.categorical.to_tangent(vectors) result = gs.sum(projected_vectors, -1) expected = gs.zeros(self.n_points) self.assertAllClose(expected, result, atol=1e-05)
def mean(self, points, weights=None): """ The Frechet mean of (weighted) points computed with the Euclidean metric is the weighted average of the points in the Euclidean space. """ if isinstance(points, list): points = gs.vstack(points) points = gs.to_ndarray(points, to_ndim=2) n_points = gs.shape(points)[0] if isinstance(weights, list): weights = gs.vstack(weights) elif weights is None: weights = gs.ones((n_points, )) weighted_points = gs.einsum('n,nj->nj', weights, points) mean = (gs.sum(weighted_points, axis=0) / gs.sum(weights)) mean = gs.to_ndarray(mean, to_ndim=2) return mean
def test_sample_von_mises_fisher(self): """ Check that the maximum likelihood estimates of the mean and concentration parameter are close to the real values. A first estimation of the concentration parameter is obtained by a closed-form expression and improved through the Newton method. """ dim = 2 n_points = 1000000 sphere = Hypersphere(dim) # check mean value for concentrated distribution kappa = 10000000 points = sphere.random_von_mises_fisher(kappa, n_points) sum_points = gs.sum(points, axis=0) mean = gs.array([0., 0., 1.]) mean_estimate = sum_points / gs.linalg.norm(sum_points) expected = mean result = mean_estimate self.assertTrue(gs.allclose(result, expected, atol=MEAN_ESTIMATION_TOL)) # check concentration parameter for dispersed distribution kappa = 1. points = sphere.random_von_mises_fisher(kappa, n_points) sum_points = gs.sum(points, axis=0) mean_norm = gs.linalg.norm(sum_points) / n_points kappa_estimate = (mean_norm * (dim + 1. - mean_norm**2) / (1. - mean_norm**2)) kappa_estimate = gs.cast(kappa_estimate, gs.float64) p = dim + 1 n_steps = 100 for _ in range(n_steps): bessel_func_1 = scipy.special.iv(p / 2., kappa_estimate) bessel_func_2 = scipy.special.iv(p / 2. - 1., kappa_estimate) ratio = bessel_func_1 / bessel_func_2 denominator = 1. - ratio**2 - (p - 1.) * ratio / kappa_estimate mean_norm = gs.cast(mean_norm, gs.float64) kappa_estimate = kappa_estimate - (ratio - mean_norm) / denominator result = kappa_estimate expected = kappa self.assertAllClose(result, expected, atol=KAPPA_ESTIMATION_TOL)
def linear_mean(points, weights=None, point_type='vector'): """Compute the weighted linear mean. The linear mean is the Frechet mean when points: - lie in a Euclidean space with Euclidean metric, - lie in a Minkowski space with Minkowski metric. Parameters ---------- points : array-like, shape=[n_samples, dim] Points to be averaged. weights : array-like, shape=[n_samples, 1], optional Weights associated to the points. Returns ------- mean : array-like, shape=[1, dim] Weighted linear mean of the points. """ # TODO(ninamiolane): Factorize this code to handle lists # in the whole codebase if isinstance(points, list): points = gs.stack(points, axis=0) if isinstance(weights, list): weights = gs.stack(weights, axis=0) n_points = geomstats.vectorization.get_n_points( points, point_type) if weights is None: weights = gs.ones((n_points,)) sum_weights = gs.sum(weights) einsum_str = '...,...j->...j' if point_type == 'matrix': einsum_str = '...,...jk->...jk' weighted_points = gs.einsum(einsum_str, weights, points) mean = gs.sum(weighted_points, axis=0) / sum_weights return mean
def test_closest_neighbor_index(self): """Check that the closest neighbor is one of neighbors.""" n_samples = 10 points = self.space.random_uniform(n_samples=n_samples) point = points[0, :] neighbors = points[1:, :] index = self.metric.closest_neighbor_index(point, neighbors) closest_neighbor = points[index, :] test = gs.sum(gs.all(points == closest_neighbor, axis=1)) result = test > 0 self.assertTrue(result)
def linear_mean(points, weights=None, point_type='vector'): """Compute the weighted linear mean. The linear mean is the Frechet mean when points: - lie in a Euclidean space with Euclidean metric, - lie in a Minkowski space with Minkowski metric. Parameters ---------- points : array-like, shape=[..., dim] Points to be averaged. weights : array-like, shape=[...,] Weights associated to the points. Optional, default: None. Returns ------- mean : array-like, shape=[dim,] Weighted linear mean of the points. """ if isinstance(points, list): points = gs.stack(points, axis=0) if isinstance(weights, list): weights = gs.stack(weights, axis=0) n_points = geomstats.vectorization.get_n_points( points, point_type) if weights is None: weights = gs.ones((n_points,)) sum_weights = gs.sum(weights) einsum_str = '...,...j->...j' if point_type == 'matrix': einsum_str = '...,...jk->...jk' weighted_points = gs.einsum(einsum_str, weights, points) mean = gs.sum(weighted_points, axis=0) / sum_weights return mean
def inner_product( self, tangent_vec_a, tangent_vec_b, base_point=None, point_type=None): """Compute the inner-product of two tangent vectors at a base point. Inner product defined by the Riemannian metric at point `base_point` between tangent vectors `tangent_vec_a` and `tangent_vec_b`. Parameters ---------- tangent_vec_a : array-like, shape=[n_samples, dimension + 1] First tangent vector at base point. tangent_vec_b : array-like, shape=[n_samples, dimension + 1] Second tangent vector at base point. base_point : array-like, shape=[n_samples, dimension + 1], optional Point on the manifold. point_type : str, {'vector', 'matrix'} Type of representation used for points. Returns ------- inner_prod : array-like, shape=[n_samples, 1] Inner-product of the two tangent vectors. """ if base_point is None: base_point = [None, ] * self.n_metrics if point_type is None: point_type = self.default_point_type if point_type == 'vector': tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=2) tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) intrinsic = self._is_intrinsic(tangent_vec_b) args = {'tangent_vec_a': tangent_vec_a, 'tangent_vec_b': tangent_vec_b, 'base_point': base_point} inner_prod = self._iterate_over_metrics( 'inner_product', args, intrinsic) return gs.sum(gs.hstack(inner_prod), axis=1) elif point_type == 'matrix': tangent_vec_a = gs.to_ndarray(tangent_vec_a, to_ndim=3) tangent_vec_b = gs.to_ndarray(tangent_vec_b, to_ndim=3) base_point = gs.to_ndarray(base_point, to_ndim=3) inner_products = [metric.inner_product(tangent_vec_a[:, i], tangent_vec_b[:, i], base_point[:, i]) for i, metric in enumerate(self.metrics)] return sum(inner_products) else: raise ValueError('invalid point_type argument: {}, expected ' 'either matrix of vector'.format(point_type))
def log(self, point, base_point, n_steps=N_STEPS, step='euler'): """Compute logarithm map associated to the affine connection. Solve the boundary value problem associated to the geodesic equation using the Christoffel symbols and conjugate gradient descent. Parameters ---------- point : array-like, shape=[n_samples, dim] Point on the manifold. base_point : array-like, shape=[n_samples, dim] Point on the manifold. n_steps : int The number of discrete time steps to take in the integration. step : str, {'euler', 'rk4'} The numerical scheme to use for integration. Returns ------- tangent_vec : array-like, shape=[n_samples, dim] Tangent vector at the base point. """ point = gs.to_ndarray(point, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) n_samples = len(base_point) def objective(velocity): """Define the objective function.""" velocity = velocity.reshape(base_point.shape) delta = self.exp(velocity, base_point, n_steps, step) - point loss = 1. / self.dim * gs.sum(delta**2, axis=1) return 1. / n_samples * gs.sum(loss) objective_grad = autograd.elementwise_grad(objective) def objective_with_grad(velocity): """Create helpful objective func wrapper for autograd comp.""" return objective(velocity), objective_grad(velocity) tangent_vec = gs.random.rand(gs.sum(gs.shape(base_point))) res = minimize(objective_with_grad, tangent_vec, method='L-BFGS-B', jac=True, options={ 'disp': False, 'maxiter': 25 }) tangent_vec = res.x tangent_vec = gs.reshape(tangent_vec, base_point.shape) return tangent_vec
def cost_fun(param): """Compute the energy of the polynomial curve defined by param. Parameters ---------- param : array-like, shape=(degree - 1, dim) Parameters of the curve coordinates' polynomial functions of time. Returns ------- energy : float Energy of the polynomial approximation of the geodesic. length : float Length of the polynomial approximation of the geodesic. curve : array-like, shape=(n_times, dim) Polynomial approximation of the geodesic. velocity : array-like, shape=(n_times, dim) Velocity of the polynomial approximation of the geodesic. """ last_coef = end_point - initial_point - gs.sum(param, axis=0) coef = gs.vstack((initial_point, param, last_coef)) t = gs.linspace(0.0, 1.0, n_times) t_curve = [t**i for i in range(degree + 1)] t_curve = gs.stack(t_curve) curve = gs.einsum("ij,ik->kj", coef, t_curve) t_velocity = [i * t**(i - 1) for i in range(1, degree + 1)] t_velocity = gs.stack(t_velocity) velocity = gs.einsum("ij,ik->kj", coef[1:], t_velocity) if curve.min() < 0: return np.inf, np.inf, curve, np.nan velocity_sqnorm = self.squared_norm(vector=velocity, base_point=curve) length = gs.sum(velocity_sqnorm**(1 / 2)) / n_times energy = gs.sum(velocity_sqnorm) / n_times return energy, length, curve, velocity
def test_sample_von_mises_fisher_mean(self, dim, mean, kappa, n_points): """ Check that the maximum likelihood estimates of the mean and concentration parameter are close to the real values. A first estimation of the concentration parameter is obtained by a closed-form expression and improved through the Newton method. """ space = self.space(dim) points = space.random_von_mises_fisher(mu=mean, kappa=kappa, n_samples=n_points) sum_points = gs.sum(points, axis=0) result = sum_points / gs.linalg.norm(sum_points) expected = mean self.assertAllClose(result, expected, atol=MEAN_ESTIMATION_TOL)
def test_sample(self): """ Test that the sample method samples variates from beta distributions with the specified parameters, using the law of large numbers """ n_samples = self.n_samples tol = (n_samples * 10)**(-0.5) point = self.beta.random_uniform(n_samples) samples = self.beta.sample(point, n_samples * 10) result = gs.mean(samples, axis=1) expected = point[:, 0] / gs.sum(point, axis=1) self.assertAllClose(result, expected, rtol=tol, atol=tol)
def ball_to_half_space_tangent(tangent_vec, base_point): """Convert ball to half-space tangent coordinates. Convert the parameterization of a tangent vector to the hyperbolic space from its Poincare ball coordinates, to the Poinare half-space coordinates. Parameters ---------- tangent_vec : array-like, shape=[..., dim] Tangent vector at the base point in the Poincare ball. base_point : array-like, shape=[..., dim] Point in the Poincare ball. Returns ------- tangent_vec_half_spacel : array-like, shape=[..., dim] Tangent vector in the Poincare half-space. """ sq_norm = gs.sum(base_point ** 2, axis=-1) den = 1 + sq_norm - 2 * base_point[..., -1] scalar_prod = gs.sum(base_point * tangent_vec, -1) component_1 = gs.einsum( "...i,...->...i", tangent_vec[..., :-1], 2.0 / den ) - 4.0 * gs.einsum( "...i,...->...i", base_point[..., :-1], (scalar_prod - tangent_vec[..., -1]) / den ** 2, ) component_2 = ( -2.0 * scalar_prod / den - 2 * (1.0 - sq_norm) * (scalar_prod - tangent_vec[..., -1]) / den ** 2 ) tangent_vec_half_space = gs.concatenate( [component_1, component_2[..., None]], axis=-1 ) return tangent_vec_half_space
def test_random_von_mises_general_dim_mean(self): for dim in [2, 9]: sphere = Hypersphere(dim) n_points = 100000 # check mean value for concentrated distribution kappa = 10 points = sphere.random_von_mises_fisher(kappa=kappa, n_samples=n_points) sum_points = gs.sum(points, axis=0) expected = gs.array([1.0] + [0.0] * dim) result = sum_points / gs.linalg.norm(sum_points) self.assertAllClose(result, expected, atol=KAPPA_ESTIMATION_TOL)
def _loss(self, X, y, param, shape, weights=None): """Compute the loss associated to the geodesic regression. Parameters ---------- X : {array-like, sparse matrix}, shape=[...,}] Training input samples. y : array-like, shape=[..., {dim, [n,n]}] Training target values. param : array-like, shape=[2, {dim, [n,n]}] Parameters intercept and coef of the geodesic regression, vertically stacked. weights : array-like, shape=[...,] Weights associated to the points. Optional, default: None. Returns ------- _ : float Loss. """ intercept, coef = gs.split(param, 2) intercept = gs.reshape(intercept, shape) coef = gs.reshape(coef, shape) intercept = gs.cast(intercept, dtype=y.dtype) coef = gs.cast(coef, dtype=y.dtype) if self.method == "extrinsic": base_point = self.space.projection(intercept) penalty = self.regularization * gs.sum((base_point - intercept)**2) else: base_point = intercept penalty = 0 tangent_vec = self.space.to_tangent(coef, base_point) distances = self.metric.squared_dist( self._model(X, tangent_vec, base_point), y) if weights is None: weights = 1.0 return 1.0 / 2.0 * gs.sum(weights * distances) + penalty
def mobius_add(self, point_a, point_b): """Compute the mobius addition of two points. Mobius addition is necessary for computation of the log and exp using the 'poincare' representation set as point_type. Parameters ---------- point_a : array-like, shape=[n_samples, dimension + 1] or shape=[1, dimension + 1] point_b : array-like, shape=[n_samples, dimension + 1] or shape=[1, dimension + 1] Returns ------- mobius_add : array-like, shape=[n_samples, 1] or shape=[1, 1] """ norm_point_a = gs.sum(point_a**2, axis=-1, keepdims=True) # to redefine to use autograd norm_point_a = gs.repeat(norm_point_a, point_a.shape[-1], -1) norm_point_b = gs.sum(point_b**2, axis=-1, keepdims=True) norm_point_b = gs.repeat(norm_point_b, point_a.shape[-1], -1) sum_prod_a_b = (point_a * point_b).sum(-1, keepdims=True) sum_prod_a_b = gs.repeat(sum_prod_a_b, point_a.shape[-1], -1) add_nominator = ((1 + 2 * sum_prod_a_b + norm_point_b) * point_a + (1 - norm_point_a) * point_b) add_denominator = (1 + 2 * sum_prod_a_b + norm_point_a * norm_point_b) mobius_add = add_nominator / add_denominator return mobius_add
def log(self, point, base_point, **kwargs): """Compute Riemannian logarithm of a point wrt a base point. Parameters ---------- point : array-like, shape=[..., dim] Point in the Poincare ball. base_point : array-like, shape=[..., dim] Point in the Poincare ball. Returns ------- log : array-like, shape=[..., dim] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ mobius_addition = self.mobius_add(-base_point, point) squared_norm_add = gs.sum(mobius_addition**2, axis=-1) squared_norm_bp = gs.sum(base_point**2, axis=-1) coef = (1 - squared_norm_bp) * utils.taylor_exp_even_func( squared_norm_add, utils.arctanh_card_close_0) log = gs.einsum("...,...j->...j", coef, mobius_addition) return log
def coefficients(ind_k): param_k = base_point[..., ind_k] param_sum = gs.sum(base_point, -1) c1 = 1 / gs.polygamma(1, param_k) / ( 1 / gs.polygamma(1, param_sum) - gs.sum(1 / gs.polygamma(1, base_point), -1)) c2 = - c1 * gs.polygamma(2, param_sum) / gs.polygamma(1, param_sum) mat_ones = gs.ones((n_points, self.dim, self.dim)) mat_diag = from_vector_to_diagonal_matrix( - gs.polygamma(2, base_point) / gs.polygamma(1, base_point)) arrays = [gs.zeros((1, ind_k)), gs.ones((1, 1)), gs.zeros((1, self.dim - ind_k - 1))] vec_k = gs.tile(gs.hstack(arrays), (n_points, 1)) val_k = gs.polygamma(2, param_k) / gs.polygamma(1, param_k) vec_k = gs.einsum('i,ij->ij', val_k, vec_k) mat_k = from_vector_to_diagonal_matrix(vec_k) mat = gs.einsum('i,ijk->ijk', c2, mat_ones)\ - gs.einsum('i,ijk->ijk', c1, mat_diag) + mat_k return 1 / 2 * mat
def test_estimate_default_gradient_descent_so_matrix(self): points = self.so_matrix.random_uniform(2) mean_vec = FrechetMean( metric=self.so_matrix.bi_invariant_metric, method="default", init_step_size=1.0, ) mean_vec.fit(points) logs = self.so_matrix.bi_invariant_metric.log(points, mean_vec.estimate_) result = gs.sum(logs, axis=0) expected = gs.zeros_like(points[0]) self.assertAllClose(result, expected, atol=1e-5)
def __init__(self, group, inner_product_mat_at_identity=None, left_or_right='left', **kwargs): super(InvariantMetric, self).__init__(dim=group.dim, **kwargs) self.group = group if inner_product_mat_at_identity is None: inner_product_mat_at_identity = gs.eye(self.group.dim) geomstats.errors.check_parameter_accepted_values( left_or_right, 'left_or_right', ['left', 'right']) eigenvalues = gs.linalg.eigvalsh(inner_product_mat_at_identity) mask_pos_eigval = gs.greater(eigenvalues, 0.) n_pos_eigval = gs.sum(gs.cast(mask_pos_eigval, gs.int32)) mask_neg_eigval = gs.less(eigenvalues, 0.) n_neg_eigval = gs.sum(gs.cast(mask_neg_eigval, gs.int32)) mask_null_eigval = gs.isclose(eigenvalues, 0.) n_null_eigval = gs.sum(gs.cast(mask_null_eigval, gs.int32)) self.inner_product_mat_at_identity = inner_product_mat_at_identity self.left_or_right = left_or_right self.signature = (n_pos_eigval, n_null_eigval, n_neg_eigval)
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 baker_campbell_hausdorff(self, matrix_a, matrix_b, order=2): """Calculate the Baker-Campbell-Hausdorff approximation of given order. The implementation is based on [CM2009a]_ with the pre-computed constants taken from [CM2009b]_. Our coefficients are truncated to enable us to calculate BCH up to order 15. This represents Z = log(exp(X)exp(Y)) as an infinite linear combination of the form Z = sum z_i e_i where z_i are rational numbers and e_i are iterated Lie brackets starting with e_1 = X, e_2 = Y, each e_i is given by some i',i'': e_i = [e_i', e_i'']. Parameters ---------- matrix_a, matrix_b : array-like, shape=[..., n, n] Matrices. order : int The order to which the approximation is calculated. Note that this is NOT the same as using only e_i with i < order. Optional, default 2. References ---------- .. [CM2009a] F. Casas and A. Murua. An efficient algorithm for computing the Baker–Campbell–Hausdorff series and some of its applications. Journal of Mathematical Physics 50, 2009 .. [CM2009b] http://www.ehu.eus/ccwmuura/research/bchHall20.dat """ if order > 15: raise NotImplementedError("BCH is not implemented for order > 15.") number_of_hom_degree = gs.array( [2, 1, 2, 3, 6, 9, 18, 30, 56, 99, 186, 335, 630, 1161, 2182] ) n_terms = gs.sum(number_of_hom_degree[:order]) el = [matrix_a, matrix_b] result = matrix_a + matrix_b for i in gs.arange(2, n_terms): i_p = BCH_COEFFICIENTS[i, 1] - 1 i_pp = BCH_COEFFICIENTS[i, 2] - 1 el.append(self.bracket(el[i_p], el[i_pp])) result += ( float(BCH_COEFFICIENTS[i, 3]) / float(BCH_COEFFICIENTS[i, 4]) * el[i] ) return result
def test_srv_metric_dist_and_geod(self): """Test that the length of the geodesic gives the distance. N.B: Here curve_a and curve_b are seen as curves in R3 and not S2. """ geod = self.srv_metric_r3.geodesic( initial_curve=self.curve_a, end_curve=self.curve_b ) geod = geod(self.times) srv = self.srv_metric_r3.srv_transform(geod) srv_derivative = self.n_discretized_curves * (srv[1:, :] - srv[:-1, :]) norms = self.srv_metric_r3.l2_metric.norm(srv_derivative) result = gs.sum(norms, 0) / self.n_discretized_curves expected = self.srv_metric_r3.dist(self.curve_a, self.curve_b) self.assertAllClose(result, expected)
def test_srv_norm(self, curve_a, curve_b, times): l2_metric_s2 = L2CurvesMetric(ambient_manifold=s2) srv_metric_r3 = SRVMetric(ambient_manifold=r3) curves_ab = l2_metric_s2.geodesic(curve_a, curve_b) curves_ab = curves_ab(times) srvs_ab = srv_metric_r3.srv_transform(curves_ab) result = srv_metric_r3.l2_curves_metric.norm(srvs_ab) products = srvs_ab * srvs_ab sums = [gs.sum(product) for product in products] squared_norm = gs.array(sums) / (srvs_ab.shape[-2] + 1) expected = gs.sqrt(squared_norm) self.assertAllClose(result, expected) result = result.shape expected = [srvs_ab.shape[0]] self.assertAllClose(result, expected)
def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """ Inner product defined by the Riemannian metric at point base_point between tangent vectors tangent_vec_a and tangent_vec_b. """ if base_point is None: base_point = [ None, ] * self.n_metrics inner_products = [ self.metrics[i].inner_product(tangent_vec_a[i], tangent_vec_b[i], base_point[i]) for i in range(self.n_metrics) ] inner_product = gs.sum(inner_products) return inner_product
def baker_campbell_hausdorff(self, matrix_a, matrix_b, order=2): """Calculate the Baker Campbell Hausdorff approximation of given order. We use the algorithm published by Casas / Murua in their paper "An efficient algorithm for computing the Baker–Campbell–Hausdorff series and some of its applications" in J.o.Math.Physics 50 (2009) as well as their computed constants from http://www.ehu.eus/ccwmuura/research/bchHall20.dat. Our file is truncated to enable us to calculate BCH up to order 15 This represents Z =log(exp(X)exp(Y)) as an infinite linear combination of the form Z = sum z_i e_i where z_i are rational numbers and e_i are iterated Lie brackets starting with e_1 = X, e_2 = Y, each e_i is given by some i',i'': e_i = [e_i', e_i'']. Parameters ---------- matrix_a: array-like, shape=[n_sample, n, n] matrix_b: array-like, shape=[n_sample, n, n] order: int the order to which the approximation is calculated. Note that this is NOT the same as using only e_i with i < order """ if order > 15: raise NotImplementedError("BCH is not implemented for order > 15.") number_of_hom_degree = gs.array( [2, 1, 2, 3, 6, 9, 18, 30, 56, 99, 186, 335, 630, 1161, 2182]) n_terms = gs.sum(number_of_hom_degree[:order]) ei = gs.zeros((n_terms, self.n, self.n)) ei[0] = matrix_a ei[1] = matrix_b result = matrix_a + matrix_b for i in gs.arange(2, n_terms): i_p = BCH_INFO[i, 1] - 1 i_pp = BCH_INFO[i, 2] - 1 ei[i] = self.lie_bracket(ei[i_p], ei[i_pp]) result = result + BCH_INFO[i, 3] / float(BCH_INFO[i, 4]) * ei[i] return result
def _belongs_ball(point, tolerance=TOLERANCE): """Evaluate if a point belongs to the Hyperbolic space (poin. ball). Evaluate if a point belongs to the Hyperbolic space based on the poincare ball representation, i.e. evaluate if its squared norm is lower than one. Parameters ---------- point : array-like, shape=[n_samples, dimension] Input points. tolerance : float, optional Returns ------- belongs : array-like, shape=[n_samples, 1] """ return gs.sum(point**2, -1) < (1 + tolerance)