def __init__(self, n, p): dimension = int(p * n - (p * (p + 1) / 2)) super(StiefelCanonicalMetric, self).__init__(dimension=dimension, signature=(dimension, 0, 0)) self.embedding_metric = EuclideanMetric(n * p) self.n = n self.p = p
def __init__(self, n, p): assert isinstance(n, int) and isinstance(p, int) assert p <= n self.n = n self.p = p dimension = int(p * (n - p)) super(GrassmannianCanonicalMetric, self).__init__(dimension=dimension, signature=(dimension, 0, 0)) self.embedding_metric = EuclideanMetric(n * p)
def __init__(self, dimension): super(HypersphereMetric, self).__init__(dimension=dimension, signature=(dimension, 0, 0)) self.embedding_metric = EuclideanMetric(dimension + 1)
class HypersphereMetric(RiemannianMetric): """Class for the Hypersphere Metric.""" def __init__(self, dimension): super(HypersphereMetric, self).__init__(dimension=dimension, signature=(dimension, 0, 0)) self.embedding_metric = EuclideanMetric(dimension + 1) def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """Compute the inner product of two tangent vectors at a base point. Parameters ---------- tangent_vec_a : array-like, shape=[n_samples, dimension + 1] or shape=[1, dimension + 1] tangent_vec_b : 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 ------- inner_prod : array-like, shape=[n_samples, 1] or shape=[1, 1] """ 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): """Compute squared norm of a vector. Squared norm of a vector associated to the inner product at the tangent space at a base point. Parameters ---------- vector : 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 ------- sq_norm : array-like, shape=[n_samples, 1] or shape=[1, 1] """ 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. 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) # TODO(nina): Decide on metric.space or space.metric # for the hypersphere # TODO(nina): Raise error when vector is not tangent n_base_points, extrinsic_dim = base_point.shape n_tangent_vecs, _ = tangent_vec.shape hypersphere = Hypersphere(dimension=extrinsic_dim - 1) proj_tangent_vec = hypersphere.projection_to_tangent_space( tangent_vec, base_point) norm_tangent_vec = self.embedding_metric.norm(proj_tangent_vec) mask_0 = gs.isclose(norm_tangent_vec, 0.) mask_non0 = ~mask_0 coef_1 = gs.zeros((n_tangent_vecs, 1)) coef_2 = gs.zeros((n_tangent_vecs, 1)) norm2 = norm_tangent_vec[mask_0]**2 norm4 = norm2**2 norm6 = norm2**3 coef_1[mask_0] = 1. - norm2 / 2. + norm4 / 24. - norm6 / 720. coef_2[mask_0] = 1. - norm2 / 6. + norm4 / 120. - norm6 / 5040. coef_1[mask_non0] = gs.cos(norm_tangent_vec[mask_non0]) coef_2[mask_non0] = gs.sin(norm_tangent_vec[mask_non0]) / \ norm_tangent_vec[mask_non0] n_coef_1 = n_tangent_vecs if n_coef_1 != n_base_points: if n_coef_1 == 1: coef_1 = gs.squeeze(coef_1, axis=0) einsum_str = 'i,nj->nj' elif n_base_points == 1: base_point = gs.squeeze(base_point, axis=0) einsum_str = 'ni,j->nj' else: raise ValueError('Shape mismatch in einsum.') else: einsum_str = 'ni,nj->nj' exp = (gs.einsum(einsum_str, coef_1, base_point) + gs.einsum('ni,nj->nj', coef_2, proj_tangent_vec)) return exp def log(self, point, base_point): """Compute 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) 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 dist(self, point_a, point_b): """Compute geodesic distance between two points. 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 ------- dist : array-like, shape=[n_samples, 1] or shape=[1, 1] """ norm_a = self.embedding_metric.norm(point_a) norm_b = self.embedding_metric.norm(point_b) inner_prod = self.embedding_metric.inner_product(point_a, point_b) cos_angle = inner_prod / (norm_a * norm_b) cos_angle = gs.clip(cos_angle, -1, 1) dist = gs.arccos(cos_angle) return dist def parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point): 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) # TODO @nguigs: work around this condition assert len(base_point) == len(tangent_vec_a) == len(tangent_vec_b) theta = gs.linalg.norm(tangent_vec_b, axis=1) normalized_b = gs.einsum('n, ni->ni', 1 / theta, tangent_vec_b) pb = gs.einsum('ni,ni->n', tangent_vec_a, normalized_b) p_orth = tangent_vec_a - gs.einsum('n,ni->ni', pb, normalized_b) transported = - gs.einsum('n,ni->ni', gs.sin(theta) * pb, base_point)\ + gs.einsum('n,ni->ni', gs.cos(theta) * pb, normalized_b)\ + p_orth return transported def christoffels(self, point, point_type='spherical'): """Compute Christoffel symbols. Only implemented in dimension 2 and for spherical coordinates. Parameters ---------- point : array-like, shape=[n_samples, dimension] Returns ------- christoffel : array-like, shape=[n_samples, contravariant index, first covariant index, second covariant index] """ if self.dimension != 2 or point_type != 'spherical': raise NotImplementedError( 'The Christoffel symbols are only implemented' ' for spherical coordinates in the 2-sphere') point = gs.to_ndarray(point, to_ndim=2) n_samples = point.shape[0] christoffel = gs.zeros( (n_samples, self.dimension, self.dimension, self.dimension)) christoffel[:, 0, 1, 1] = -gs.sin(point[:, 0]) * gs.cos(point[:, 0]) christoffel[:, 1, 0, 1] = gs.cos(point[:, 0]) / gs.sin(point[:, 0]) christoffel[:, 1, 1, 0] = gs.cos(point[:, 0]) / gs.sin(point[:, 0]) return christoffel
def setUp(self): self.dimension = 4 self.metric = EuclideanMetric(dimension=self.dimension) self.connection = LeviCivitaConnection(self.metric)
class HypersphereMetric(RiemannianMetric): def __init__(self, dimension): super(HypersphereMetric, self).__init__(dimension=dimension, signature=(dimension, 0, 0)) self.embedding_metric = EuclideanMetric(dimension + 1) def inner_product(self, tangent_vec_a, tangent_vec_b, base_point=None): """ Inner product. Parameters ---------- tangent_vec_a : array-like, shape=[n_samples, dimension + 1] or shape=[1, dimension + 1] tangent_vec_b : 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 ------- inner_prod : array-like, shape=[n_samples, 1] or shape=[1, 1] """ 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 to the inner product at the tangent space at a base point. Parameters ---------- vector : 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 ------- sq_norm : array-like, shape=[n_samples, 1] or shape=[1, 1] """ 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. 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) # TODO(johmathe): Evaluate the bias introduced by this variable norm_tangent_vec = self.embedding_metric.norm(tangent_vec) + EPSILON coef_1 = gs.cos(norm_tangent_vec) coef_2 = gs.sin(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)) 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) 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 dist(self, point_a, point_b): """ Geodesic distance between two points. 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 ------- dist : array-like, shape=[n_samples, 1] or shape=[1, 1] """ norm_a = self.embedding_metric.norm(point_a) norm_b = self.embedding_metric.norm(point_b) inner_prod = self.embedding_metric.inner_product(point_a, point_b) cos_angle = inner_prod / (norm_a * norm_b) cos_angle = gs.clip(cos_angle, -1, 1) dist = gs.arccos(cos_angle) return dist