def log_test_data(self): theta = gs.pi / 5.0 rot_vec_base_point = theta / gs.sqrt(3.0) * gs.array([1.0, 1.0, 1.0]) # Note: the rotation vector for the reference point # needs to be regularized. # The Logarithm of a point at itself gives 0. expected = gs.array([0.0, 0.0, 0.0]) # General case: this is the inverse test of test 1 for Riemannian exp expected = gs.pi / 4 * gs.array([1.0, 0.0, 0.0]) phi = (gs.pi / 10) / (gs.tan(gs.array(gs.pi / 10))) skew = gs.array([[0.0, -1.0, 1.0], [1.0, 0.0, -1.0], [-1.0, 1.0, 0.0]]) jacobian = (phi * gs.eye(3) + (1 - phi) / 3 * gs.ones([3, 3]) + gs.pi / (10 * gs.sqrt(3.0)) * skew) inv_jacobian = gs.linalg.inv(jacobian) aux = gs.dot(inv_jacobian, expected) rot_vec_2 = SpecialOrthogonal(3, "vector").compose( rot_vec_base_point, aux) smoke_data = [ dict( point=rot_vec_base_point, base_point=rot_vec_base_point, expected=gs.array([0.0, 0.0, 0.0]), ), dict( point=rot_vec_2, base_point=rot_vec_base_point, expected=expected, ), ] return self.generate_tests(smoke_data)
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) norm_base_point = self.embedding_metric.norm(base_point) norm_point = self.embedding_metric.norm(point) inner_prod = self.embedding_metric.inner_product(base_point, point) cos_angle = inner_prod / (norm_base_point * norm_point) cos_angle = gs.clip(cos_angle, -1., 1.) angle = gs.arccos(cos_angle) angle = gs.to_ndarray(angle, to_ndim=1) angle = gs.to_ndarray(angle, to_ndim=2, axis=1) mask_0 = gs.isclose(angle, 0.) mask_else = gs.equal(mask_0, gs.array(False)) 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_SIN_TAYLOR_COEFFS[1] * angle**2 + INV_SIN_TAYLOR_COEFFS[3] * angle**4 + INV_SIN_TAYLOR_COEFFS[5] * angle**6 + INV_SIN_TAYLOR_COEFFS[7] * angle**8) coef_2 += mask_0_float * (1. + INV_TAN_TAYLOR_COEFFS[1] * angle**2 + INV_TAN_TAYLOR_COEFFS[3] * angle**4 + INV_TAN_TAYLOR_COEFFS[5] * angle**6 + INV_TAN_TAYLOR_COEFFS[7] * angle**8) # This avoids division by 0. angle += mask_0_float * 1. coef_1 += mask_else_float * angle / gs.sin(angle) coef_2 += mask_else_float * angle / gs.tan(angle) log = (gs.einsum('ni,nj->nj', coef_1, point) - gs.einsum('ni,nj->nj', coef_2, base_point)) mask_same_values = gs.isclose(point, base_point) mask_else = gs.equal(mask_same_values, gs.array(False)) mask_else_float = gs.cast(mask_else, gs.float32) mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=1) mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=2) mask_not_same_points = gs.sum(mask_else_float, axis=1) mask_same_points = gs.isclose(mask_not_same_points, 0.) mask_same_points = gs.cast(mask_same_points, gs.float32) mask_same_points = gs.to_ndarray(mask_same_points, to_ndim=2, axis=1) mask_same_points_float = gs.cast(mask_same_points, gs.float32) log -= mask_same_points_float * log return log
def exp_test_data(self): theta = gs.pi / 5.0 rot_vec_base_point = theta / gs.sqrt(3.0) * gs.array([1.0, 1.0, 1.0]) rot_vec_2 = gs.pi / 4 * gs.array([1.0, 0.0, 0.0]) phi = (gs.pi / 10) / (gs.tan(gs.array(gs.pi / 10))) skew = gs.array([[0.0, -1.0, 1.0], [1.0, 0.0, -1.0], [-1.0, 1.0, 0.0]]) jacobian = (phi * gs.eye(3) + (1 - phi) / 3 * gs.ones([3, 3]) + gs.pi / (10 * gs.sqrt(3.0)) * skew) inv_jacobian = gs.linalg.inv(jacobian) expected = SpecialOrthogonal(3, "vector").compose( (gs.pi / 5.0) / gs.sqrt(3.0) * gs.array([1.0, 1.0, 1.0]), gs.dot(inv_jacobian, rot_vec_2), ) smoke_data = [ dict( tangent_vec=gs.array([0.0, 0.0, 0.0]), base_point=rot_vec_base_point, expected=rot_vec_base_point, ), dict( tangent_vec=rot_vec_2, base_point=rot_vec_base_point, expected=expected, ), ] return self.generate_tests(smoke_data)
def asymptotic_modulation(dim, theta): """Compute the asymptotic modulation factor. Parameters ---------- dim: dimension of the sphere (embedded in R^{dim+1}) theta: radius of the bubble distribution Returns ------- tuple (modulation factor, std-dev on the modulation factor) """ gamma = 1.0 / dim + (1.0 - 1.0 / dim) * theta / gs.tan(theta) return (1.0 / gamma)**2
def log(self, point, base_point): """ Compute the Riemannian logarithm at point base_point, of point wrt the metric obtained by embedding of the n-dimensional sphere in the (n+1)-dimensional euclidean space. This gives a tangent vector at point base_point. :param base_point: point on the n-dimensional sphere :param point: point on the n-dimensional sphere :return log: tangent vector at base_point """ point = gs.to_ndarray(point, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) norm_base_point = self.embedding_metric.norm(base_point) norm_point = self.embedding_metric.norm(point) inner_prod = self.embedding_metric.inner_product(base_point, point) cos_angle = inner_prod / (norm_base_point * norm_point) cos_angle = gs.clip(cos_angle, -1.0, 1.0) angle = gs.arccos(cos_angle) mask_0 = gs.isclose(angle, 0.0) mask_else = gs.equal(mask_0, False) coef_1 = gs.zeros_like(angle) coef_2 = gs.zeros_like(angle) coef_1[mask_0] = ( 1. + INV_SIN_TAYLOR_COEFFS[1] * angle[mask_0] ** 2 + INV_SIN_TAYLOR_COEFFS[3] * angle[mask_0] ** 4 + INV_SIN_TAYLOR_COEFFS[5] * angle[mask_0] ** 6 + INV_SIN_TAYLOR_COEFFS[7] * angle[mask_0] ** 8) coef_2[mask_0] = ( 1. + INV_TAN_TAYLOR_COEFFS[1] * angle[mask_0] ** 2 + INV_TAN_TAYLOR_COEFFS[3] * angle[mask_0] ** 4 + INV_TAN_TAYLOR_COEFFS[5] * angle[mask_0] ** 6 + INV_TAN_TAYLOR_COEFFS[7] * angle[mask_0] ** 8) coef_1[mask_else] = angle[mask_else] / gs.sin(angle[mask_else]) coef_2[mask_else] = angle[mask_else] / gs.tan(angle[mask_else]) log = (gs.einsum('ni,nj->nj', coef_1, point) - gs.einsum('ni,nj->nj', coef_2, base_point)) return log
def msd_hbar_s2(location, data): """compute the mean-square deviation from the location to the points of the dataset and the mean Hessian of square distance at the location""" sphere = Hypersphere(2) num_sample = len(data) assert num_sample > 0, "Dataset needs to have at least one data" var = 0.0 hbar = 0.0 for item in data: sq_dist = sphere.metric.squared_dist(location, item)[0, 0] var = var + sq_dist # hbar = E(h(dist ^ 2)) with h(t) = sqrt(t) cot( sqrt(t) ) for kappa=1 if sq_dist > 1e-4: d = gs.sqrt(sq_dist) h = d / gs.tan(d) else: h = 1.0 - sq_dist / 3.0 - sq_dist ** 2 / 45.0 - 2 / 945 * \ sq_dist ** 3 - sq_dist ** 4 / 4725 hbar = hbar + h return var / num_sample, hbar / num_sample
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) norm_base_point = self.embedding_metric.norm(base_point) norm_point = self.embedding_metric.norm(point) inner_prod = self.embedding_metric.inner_product(base_point, point) cos_angle = inner_prod / (norm_base_point * norm_point) cos_angle = gs.clip(cos_angle, -1.0, 1.0) angle = gs.arccos(cos_angle) mask_0 = gs.isclose(angle, 0.0) mask_else = gs.equal(mask_0, gs.cast(gs.array(False), gs.int8)) coef_1 = gs.zeros_like(angle) coef_2 = gs.zeros_like(angle) coef_1[mask_0] = ( 1. + INV_SIN_TAYLOR_COEFFS[1] * angle[mask_0] ** 2 + INV_SIN_TAYLOR_COEFFS[3] * angle[mask_0] ** 4 + INV_SIN_TAYLOR_COEFFS[5] * angle[mask_0] ** 6 + INV_SIN_TAYLOR_COEFFS[7] * angle[mask_0] ** 8) coef_2[mask_0] = ( 1. + INV_TAN_TAYLOR_COEFFS[1] * angle[mask_0] ** 2 + INV_TAN_TAYLOR_COEFFS[3] * angle[mask_0] ** 4 + INV_TAN_TAYLOR_COEFFS[5] * angle[mask_0] ** 6 + INV_TAN_TAYLOR_COEFFS[7] * angle[mask_0] ** 8) coef_1[mask_else] = angle[mask_else] / gs.sin(angle[mask_else]) coef_2[mask_else] = angle[mask_else] / gs.tan(angle[mask_else]) log = (gs.einsum('ni,nj->nj', coef_1, point) - gs.einsum('ni,nj->nj', coef_2, base_point)) return log
7.0 / 360.0, -31.0 / 15120.0, 127.0 / 604800.0, ] INV_TANH_TAYLOR_COEFFS = [1.0, 1.0 / 3.0, -1.0 / 45.0, 2.0 / 945.0, -1.0 / 4725.0] ARCTANH_CARD_TAYLOR_COEFFS = [1.0, 1.0 / 3.0, 1.0 / 5.0, 1 / 7.0, 1.0 / 9] cos_close_0 = {"function": gs.cos, "coefficients": COS_TAYLOR_COEFFS} sinc_close_0 = {"function": lambda x: gs.sin(x) / x, "coefficients": SINC_TAYLOR_COEFFS} inv_sinc_close_0 = { "function": lambda x: x / gs.sin(x), "coefficients": INV_SINC_TAYLOR_COEFFS, } inv_tanc_close_0 = { "function": lambda x: x / gs.tan(x), "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 = {
1., -1. / 6., 7. / 360., -31. / 15120., 127. / 604800. ] INV_TANH_TAYLOR_COEFFS = [1., 1. / 3., -1. / 45., 2. / 945., -1. / 4725.] ARCTANH_CARD_TAYLOR_COEFFS = [1., 1. / 3., 1. / 5., 1 / 7., 1. / 9] cos_close_0 = {'function': gs.cos, 'coefficients': COS_TAYLOR_COEFFS} sinc_close_0 = { 'function': lambda x: gs.sin(x) / x, 'coefficients': SINC_TAYLOR_COEFFS } inv_sinc_close_0 = { 'function': lambda x: x / gs.sin(x), 'coefficients': INV_SINC_TAYLOR_COEFFS } inv_tanc_close_0 = { 'function': lambda x: x / gs.tan(x), '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 = {
def jacobian_translation(self, point, left_or_right='left'): """ Compute the jacobian matrix of the differential of the left/right translations from the identity to point in SO(n). """ assert self.belongs(point) assert left_or_right in ('left', 'right') if self.n == 3: point = self.regularize(point) n_points, _ = point.shape angle = gs.linalg.norm(point, axis=1) angle = gs.expand_dims(angle, axis=1) coef_1 = gs.zeros([n_points, 1]) coef_2 = gs.zeros([n_points, 1]) mask_0 = gs.isclose(angle, 0) mask_0 = gs.squeeze(mask_0, axis=1) coef_1[mask_0] = (1 - angle[mask_0]**2 / 12 - angle[mask_0]**4 / 720 - angle[mask_0]**6 / 30240) coef_2[mask_0] = (1 / 12 + angle[mask_0]**2 / 720 + angle[mask_0]**4 / 30240 + angle[mask_0]**6 / 1209600) mask_pi = gs.isclose(angle, gs.pi) mask_pi = gs.squeeze(mask_pi, axis=1) delta_angle = angle[mask_pi] - gs.pi coef_1[mask_pi] = (-gs.pi * delta_angle / 4 - delta_angle**2 / 4 - gs.pi * delta_angle**3 / 48 - delta_angle**4 / 48 - gs.pi * delta_angle**5 / 480 - delta_angle**6 / 480) coef_2[mask_pi] = (1 - coef_1[mask_pi]) / angle[mask_pi]**2 mask_else = ~mask_0 & ~mask_pi coef_1[mask_else] = ((angle[mask_else] / 2) / gs.tan(angle[mask_else] / 2)) coef_2[mask_else] = (1 - coef_1[mask_else]) / angle[mask_else]**2 jacobian = gs.zeros((n_points, self.dimension, self.dimension)) for i in range(n_points): if left_or_right == 'left': jacobian[i] = (coef_1[i] * gs.identity(self.dimension) + coef_2[i] * gs.outer(point[i], point[i]) + skew_matrix_from_vector(point[i]) / 2) else: jacobian[i] = (coef_1[i] * gs.identity(self.dimension) + coef_2[i] * gs.outer(point[i], point[i]) - skew_matrix_from_vector(point[i]) / 2) else: if left_or_right == 'right': raise NotImplementedError( 'The jacobian of the right translation' ' is not implemented.') jacobian = self.matrix_from_rotation_vector(point) assert jacobian.ndim == 3 return jacobian
def log(self, point, base_point): """Compute the Riemannian logarithm of a point. Parameters ---------- point : array-like, shape=[..., dim + 1] Point on the hypersphere. base_point : array-like, shape=[..., dim + 1] Point on the hypersphere. Returns ------- log : array-like, shape=[..., dim + 1] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ norm_base_point = self.embedding_metric.norm(base_point) norm_point = self.embedding_metric.norm(point) inner_prod = self.embedding_metric.inner_product(base_point, point) cos_angle = inner_prod / (norm_base_point * norm_point) cos_angle = gs.clip(cos_angle, -1., 1.) angle = gs.arccos(cos_angle) angle = gs.to_ndarray(angle, to_ndim=1) angle = gs.to_ndarray(angle, to_ndim=2, axis=1) mask_0 = gs.isclose(angle, 0.) mask_else = gs.equal(mask_0, gs.array(False)) 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_SIN_TAYLOR_COEFFS[1] * angle**2 + INV_SIN_TAYLOR_COEFFS[3] * angle**4 + INV_SIN_TAYLOR_COEFFS[5] * angle**6 + INV_SIN_TAYLOR_COEFFS[7] * angle**8) coef_2 += mask_0_float * (1. + INV_TAN_TAYLOR_COEFFS[1] * angle**2 + INV_TAN_TAYLOR_COEFFS[3] * angle**4 + INV_TAN_TAYLOR_COEFFS[5] * angle**6 + INV_TAN_TAYLOR_COEFFS[7] * angle**8) # This avoids division by 0. angle += mask_0_float * 1. coef_1 += mask_else_float * angle / gs.sin(angle) coef_2 += mask_else_float * angle / gs.tan(angle) log = (gs.einsum('...i,...j->...j', coef_1, point) - gs.einsum('...i,...j->...j', coef_2, base_point)) mask_same_values = gs.isclose(point, base_point) mask_else = gs.equal(mask_same_values, gs.array(False)) mask_else_float = gs.cast(mask_else, gs.float32) mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=1) mask_else_float = gs.to_ndarray(mask_else_float, to_ndim=2) mask_not_same_points = gs.sum(mask_else_float, axis=1) mask_same_points = gs.isclose(mask_not_same_points, 0.) mask_same_points = gs.cast(mask_same_points, gs.float32) mask_same_points = gs.to_ndarray(mask_same_points, to_ndim=2, axis=1) mask_same_points_float = gs.cast(mask_same_points, gs.float32) log -= mask_same_points_float * log return log
def jacobian_translation(self, point, left_or_right='left'): """Compute the jacobian matrix corresponding to translation. Compute the jacobian matrix of the differential of the left/right translations from the identity to point in SO(3). Parameters ---------- point : array-like, shape=[..., 3] left_or_right : str, {'left', 'right'}, optional default: 'left' point_type : str, {'vector', 'matrix'}, optional default: self.default_point_type Returns ------- jacobian : array-like, shape=[..., 3, 3] """ geomstats.error.check_parameter_accepted_values( left_or_right, 'left_or_right', ['left', 'right']) point = self.regularize(point) n_points, _ = point.shape angle = gs.linalg.norm(point, axis=-1) angle = gs.expand_dims(angle, axis=-1) coef_1 = gs.zeros([n_points, 1]) coef_2 = gs.zeros([n_points, 1]) # This avoids dividing by 0. mask_0 = gs.isclose(angle, 0.) mask_0_float = gs.cast(mask_0, gs.float32) + self.epsilon coef_1 += mask_0_float * (TAYLOR_COEFFS_1_AT_0[0] + TAYLOR_COEFFS_1_AT_0[2] * angle**2 + TAYLOR_COEFFS_1_AT_0[4] * angle**4 + TAYLOR_COEFFS_1_AT_0[6] * angle**6) coef_2 += mask_0_float * (TAYLOR_COEFFS_2_AT_0[0] + TAYLOR_COEFFS_2_AT_0[2] * angle**2 + TAYLOR_COEFFS_2_AT_0[4] * angle**4 + TAYLOR_COEFFS_2_AT_0[6] * angle**6) # This avoids dividing by 0. mask_pi = gs.isclose(angle, gs.pi) mask_pi_float = gs.cast(mask_pi, gs.float32) + self.epsilon delta_angle = angle - gs.pi coef_1 += mask_pi_float * (TAYLOR_COEFFS_1_AT_PI[1] * delta_angle + TAYLOR_COEFFS_1_AT_PI[2] * delta_angle**2 + TAYLOR_COEFFS_1_AT_PI[3] * delta_angle**3 + TAYLOR_COEFFS_1_AT_PI[4] * delta_angle**4 + TAYLOR_COEFFS_1_AT_PI[5] * delta_angle**5 + TAYLOR_COEFFS_1_AT_PI[6] * delta_angle**6) angle += mask_0_float coef_2 += mask_pi_float * ((1 - coef_1) / angle**2) # This avoids dividing by 0. mask_else = ~mask_0 & ~mask_pi mask_else_float = gs.cast(mask_else, gs.float32) + self.epsilon # This avoids division by 0. angle += mask_pi_float coef_1 += mask_else_float * ((angle / 2) / gs.tan(angle / 2)) coef_2 += mask_else_float * ((1 - coef_1) / angle**2) jacobian = gs.zeros((n_points, self.dim, self.dim)) n_points_tensor = gs.array(n_points) for i in range(n_points): # This avoids dividing by 0. mask_i_float = (gs.get_mask_i_float(i, n_points_tensor) + self.epsilon) sign = -1. if left_or_right == 'left': sign = +1. jacobian_i = (coef_1[i] * gs.eye(self.dim) + coef_2[i] * gs.outer(point[i], point[i]) + sign * self.skew_matrix_from_vector(point[i]) / 2.) jacobian += gs.einsum('n,ij->nij', mask_i_float, jacobian_i) return jacobian
def jacobian_translation(self, point, left_or_right='left', point_type=None): """ Compute the jacobian matrix of the differential of the left/right translations from the identity to point in SO(n). """ assert left_or_right in ('left', 'right') if point_type is None: point_type = self.default_point_type assert self.belongs(point, point_type) if point_type == 'vector': if self.n == 3: point = self.regularize(point, point_type=point_type) n_points, _ = point.shape angle = gs.linalg.norm(point, axis=1) angle = gs.expand_dims(angle, axis=1) coef_1 = gs.zeros([n_points, 1]) coef_2 = gs.zeros([n_points, 1]) mask_0 = gs.isclose(angle, 0) mask_0 = gs.squeeze(mask_0, axis=1) coef_1[mask_0] = (TAYLOR_COEFFS_1_AT_0[0] + TAYLOR_COEFFS_1_AT_0[2] * angle[mask_0]**2 + TAYLOR_COEFFS_1_AT_0[4] * angle[mask_0]**4 + TAYLOR_COEFFS_1_AT_0[6] * angle[mask_0]**6) coef_2[mask_0] = (TAYLOR_COEFFS_2_AT_0[0] + TAYLOR_COEFFS_2_AT_0[2] * angle[mask_0]**2 + TAYLOR_COEFFS_2_AT_0[4] * angle[mask_0]**4 + TAYLOR_COEFFS_2_AT_0[6] * angle[mask_0]**6) mask_pi = gs.isclose(angle, gs.pi) mask_pi = gs.squeeze(mask_pi, axis=1) delta_angle = angle[mask_pi] - gs.pi coef_1[mask_pi] = (TAYLOR_COEFFS_1_AT_PI[1] * delta_angle + TAYLOR_COEFFS_1_AT_PI[2] * delta_angle**2 + TAYLOR_COEFFS_1_AT_PI[3] * delta_angle**3 + TAYLOR_COEFFS_1_AT_PI[4] * delta_angle**4 + TAYLOR_COEFFS_1_AT_PI[5] * delta_angle**5 + TAYLOR_COEFFS_1_AT_PI[6] * delta_angle**6) coef_2[mask_pi] = (1 - coef_1[mask_pi]) / angle[mask_pi]**2 mask_else = ~mask_0 & ~mask_pi coef_1[mask_else] = ((angle[mask_else] / 2) / gs.tan(angle[mask_else] / 2)) coef_2[mask_else] = ((1 - coef_1[mask_else]) / angle[mask_else]**2) jacobian = gs.zeros((n_points, self.dimension, self.dimension)) for i in range(n_points): sign = -1 if left_or_right == 'left': sign = +1 jacobian[i] = ( coef_1[i] * gs.identity(self.dimension) + coef_2[i] * gs.outer(point[i], point[i]) + sign * self.skew_matrix_from_vector(point[i]) / 2) else: if left_or_right == 'right': raise NotImplementedError( 'The jacobian of the right translation' ' is not implemented.') jacobian = self.matrix_from_rotation_vector(point) assert gs.ndim(jacobian) == 3 elif point_type == 'matrix': raise NotImplementedError() return jacobian