def regularize(self, point): """Regularize a point to the canonical representation. Regularize a point to the canonical representation chosen for the Hyperbolic space, to avoid numerical issues. Parameters ---------- point : array-like, shape=[n_samples, dimension + 1] Input points. TODO: confusing: singular or plural Returns ------- projected_point : array-like, shape=[n_samples, dimension + 1] """ point = gs.to_ndarray(point, to_ndim=2) sq_norm = self.embedding_metric.squared_norm(point) real_norm = gs.sqrt(gs.abs(sq_norm)) mask_0 = gs.isclose(real_norm, 0.) mask_not_0 = ~mask_0 mask_not_0_float = gs.cast(mask_not_0, gs.float32) projected_point = point projected_point = mask_not_0_float * (point / real_norm) return projected_point
def reshape_metric_matrix(self, metric_mat): """Reshape diagonal metric matrix to a symmetric matrix of size n. Reshape a diagonal metric matrix of size `dim x dim` into a symmetric matrix of size `n x n` where :math: `dim= n (n -1) / 2` is the dimension of the space of skew symmetric matrices. The non-diagonal coefficients in the output matrix correspond to the basis matrices of this space. The diagonal is filled with ones. This useful to compute a matrix inner product. Parameters ---------- metric_mat : array-like, shape=[dim, dim] Diagonal metric matrix. Returns ------- symmetric_matrix : array-like, shape=[n, n] Symmetric matrix. """ if Matrices.is_diagonal(metric_mat): metric_coeffs = gs.diagonal(metric_mat) metric_mat = gs.abs(self.matrix_representation(metric_coeffs)) return metric_mat raise ValueError('This is only possible for a diagonal matrix')
def belongs(self, point, tolerance=TOLERANCE): """Test if a point belongs to the hyperbolic space. Test if a point belongs to the hyperbolic space in its hyperboloid representation. Parameters ---------- point : array-like, shape=[..., dim] Point to be tested. tolerance : float, optional Tolerance at which to evaluate how close the squared norm is to the reference value. Returns ------- belongs : array-like, shape=[..., 1] Array of booleans indicating whether the corresponding points belong to the hyperbolic space. """ point_dim = point.shape[-1] if point_dim is not self.dim + 1: belongs = False if point_dim is self.dim and self.coords_type == 'intrinsic': belongs = True if gs.ndim(point) == 2: belongs = gs.tile([belongs], (point.shape[0], )) return belongs sq_norm = self.embedding_metric.squared_norm(point) euclidean_sq_norm = gs.linalg.norm(point, axis=-1)**2 diff = gs.abs(sq_norm + 1) belongs = diff < tolerance * euclidean_sq_norm return belongs
def regularize(self, point): """Regularize a point to the canonical representation. Regularize a point to the canonical representation chosen for the hyperbolic space, to avoid numerical issues. Parameters ---------- point : array-like, shape=[..., dim + 1] Point. Returns ------- projected_point : array-like, shape=[..., dim + 1] Point in hyperbolic space in canonical representation in extrinsic coordinates. """ if self.coords_type == 'intrinsic': point = self.intrinsic_to_extrinsic_coords(point) point = gs.to_ndarray(point, to_ndim=2) sq_norm = self.embedding_metric.squared_norm(point) real_norm = gs.sqrt(gs.abs(sq_norm)) mask_0 = gs.isclose(real_norm, 0.) mask_not_0 = ~mask_0 mask_not_0_float = gs.cast(mask_not_0, gs.float32) projected_point = point normalized_point = gs.einsum('...,...i->...i', 1. / real_norm, point) projected_point = gs.einsum('...,...i->...i', mask_not_0_float, normalized_point) return projected_point
def regularize(self, point): """Regularize a point to the canonical representation. Regularize a point to the canonical representation chosen for the hyperbolic space, to avoid numerical issues. Parameters ---------- point : array-like, shape=[..., dim + 1] Point. Returns ------- projected_point : array-like, shape=[..., dim + 1] Point in hyperbolic space in canonical representation in extrinsic coordinates. """ if self.coords_type == 'intrinsic': point = self.intrinsic_to_extrinsic_coords(point) sq_norm = self.embedding_metric.squared_norm(point) if not gs.all(sq_norm): raise ValueError('Cannot project a vector of norm 0. in the ' 'Minkowski space to the hyperboloid') real_norm = gs.sqrt(gs.abs(sq_norm)) projected_point = gs.einsum('...i,...->...i', point, 1. / real_norm) return projected_point
def belongs(self, point, tolerance=TOLERANCE): """Test if a point belongs to the hypersphere. This tests whether the point's squared norm in Euclidean space is 1. Parameters ---------- point : array-like, shape=[n_samples, dimension + 1] Points in Euclidean space. tolerance : float, optional Tolerance at which to evaluate norm == 1 (default: TOLERANCE). Returns ------- belongs : array-like, shape=[n_samples, 1] Array of booleans evaluating if each point belongs to the hypersphere. """ point = gs.asarray(point) point_dim = point.shape[-1] if point_dim != self.dimension + 1: if point_dim is self.dimension: logging.warning('Use the extrinsic coordinates to ' 'represent points on the hypersphere.') return gs.array([[False]]) sq_norm = self.embedding_metric.squared_norm(point) diff = gs.abs(sq_norm - 1) return gs.less_equal(diff, tolerance)
def belongs(self, point, tolerance=TOLERANCE): """Test if a point belongs to the hypersphere. This tests whether the point's squared norm in Euclidean space is 1. Parameters ---------- point : array-like, shape=[..., dim + 1] Point in Euclidean space. tolerance : float Tolerance at which to evaluate norm == 1. Optional, default: 1e-6. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the hypersphere. """ point_dim = gs.shape(point)[-1] if point_dim != self.dim + 1: if point_dim is self.dim: logging.warning('Use the extrinsic coordinates to ' 'represent points on the hypersphere.') belongs = False if gs.ndim(point) == 2: belongs = gs.tile([belongs], (point.shape[0], )) return belongs sq_norm = self.embedding_metric.squared_norm(point) diff = gs.abs(sq_norm - 1) return gs.less_equal(diff, tolerance)
def find_normalization_factor(variances, variances_range, normalization_factor_var): """Find the normalization factor given some variances. Parameters ---------- variances : array-like, shape=[n_gaussians,] Array of standard deviations for each component of some GMM. variances_range : array-like, shape=[n_variances,] Array of standard deviations. normalization_factor_var : array-like, shape=[n_variances,] Array of computed normalization factor. Returns ------- norm_factor : array-like, shape=[n_gaussians,] Array of normalization factors for the given variances. """ n_gaussians, precision = variances.shape[0], variances_range.shape[0] ref = gs.expand_dims(variances_range, 0) ref = gs.repeat(ref, n_gaussians, axis=0) val = gs.expand_dims(variances, 1) val = gs.repeat(val, precision, axis=1) difference = gs.abs(ref - val) index = gs.argmin(difference, axis=-1) norm_factor = normalization_factor_var[index] return norm_factor
def random_gaussian_rotation_orbit_noisy(self, mean_spd=None, eigensummary=None, var_rotations=1., var_eigenvalues=None, n_samples=1): r""" Define a Gaussian-like random sample of SPD matrices. Formally speaking, sample an orbit for given rotations in the SPD manifold. For all means and purposes, it looks rather Gaussian. Parameters ---------- mean_spd : array-like, shape = [n, n] Mean SPD matrix. var_rotations : float Variance in rotation. var_eigenvalues : array-like, shape = [n,] Additional variance in eigenvalues. eigensummary : EigenSummary Represents the mean SPD matrix decomposed in eigenspace and eigenvalues. Notes ----- :math:'mean_spd' is the mean SPD matrix; :math:'var_rotations' is the scalar variance by which the mean is rotated: :math:'\Sigma_{mid} \sim \mathcal{N}(\Sigma_{in}, \sigma_{eig}'; :math:'X_{out} = R \Sigma_{in} R^T'. mean_spd and eigensummary are mutually exclusive; an error is thrown if both are not None, or if both are None. """ n = self.n if var_eigenvalues is None: var_eigenvalues = gs.ones(n) if mean_spd is not None and eigensummary is not None: raise NotImplementedError if mean_spd is None and eigensummary is None: raise NotImplementedError if eigensummary is None: eigenvalues, eigenspace = gs.linalg.eigh(mean_spd) eigenvalues = gs.diag(eigenvalues) if mean_spd is None: eigenvalues, eigenspace\ = eigensummary.eigenvalues, eigensummary.eigenspace rotations = SpecialOrthogonal(n).random_gaussian( eigenspace, var_rotations, n_samples=n_samples) eigenvalues =\ gs.abs(gs.diag(gs.random.multivariate_normal( gs.diag(eigenvalues), gs.diag(var_eigenvalues)))) spd_mat = Matrices.mul(rotations, eigenvalues, Matrices.transpose( rotations)) return spd_mat
def plot(self, points, ax=None, space=None, **point_draw_kwargs): if space == "SE3_GROUP": ax_s = AX_SCALE * gs.amax(gs.abs(points[:, 3:6])) elif space == "SO3_GROUP": ax_s = AX_SCALE * gs.amax(gs.abs(points[:, :3])) ax_s = float(ax_s) bounds = (-ax_s, ax_s) plt.setp( ax, xlim=bounds, ylim=bounds, zlim=bounds, xlabel="X", ylabel="Y", zlabel="Z", ) trihedrons = convert_to_trihedron(points, space=space) for t in trihedrons: t.draw(ax, **point_draw_kwargs)
def regularize(self, point): assert gs.all(self.belongs(point)) point = gs.to_ndarray(point, to_ndim=2) sq_norm = self.embedding_metric.squared_norm(point) real_norm = gs.sqrt(gs.abs(sq_norm)) mask_0 = gs.isclose(real_norm, 0) mask_0 = gs.squeeze(mask_0, axis=1) mask_not_0 = ~mask_0 projected_point = point projected_point[mask_not_0] = (point[mask_not_0] / real_norm[mask_not_0]) return projected_point
def belongs(self, point, tolerance=TOLERANCE): """ Evaluate if a point belongs to the Hypersphere, i.e. evaluate if its squared norm in the Euclidean space is 1. """ point = gs.asarray(point) point_dim = point.shape[-1] if point_dim != self.dimension + 1: if point_dim is self.dimension: logging.warning('Use the extrinsic coordinates to ' 'represent points on the hypersphere.') return gs.array([[False]]) sq_norm = self.embedding_metric.squared_norm(point) diff = gs.abs(sq_norm - 1) return gs.less_equal(diff, tolerance)
def belongs(self, point, tolerance=TOLERANCE): """ By definition, a point on the Hypersphere has squared norm 1 in the embedding Euclidean space. Note: point must be given in extrinsic coordinates. """ point = gs.asarray(point) point_dim = point.shape[-1] if point_dim != 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 = gs.abs(sq_norm - 1) return gs.less_equal(diff, tolerance)
def plot(points, ax=None, space=None, **point_draw_kwargs): """Plot trihedrons.""" ax_s = AX_SCALE * gs.amax(gs.abs(points[:, :3])) ax_s = float(ax_s) bounds = (-ax_s, ax_s) plt.setp( ax, xlim=bounds, ylim=bounds, zlim=bounds, xlabel="X", ylabel="Y", zlabel="Z", ) trihedrons = convert_to_trihedron(points, space=space) for t in trihedrons: t.draw(ax, **point_draw_kwargs)
def regularize(self, point): """ Regularize a point to the canonical representation chosen for the Hyperbolic space, to avoid numerical issues. """ point = gs.to_ndarray(point, to_ndim=2) sq_norm = self.embedding_metric.squared_norm(point) real_norm = gs.sqrt(gs.abs(sq_norm)) mask_0 = gs.isclose(real_norm, 0.) mask_not_0 = ~mask_0 mask_not_0_float = gs.cast(mask_not_0, gs.float32) projected_point = point projected_point = mask_not_0_float * (point / real_norm) return projected_point
def regularize(self, point): """ Regularize a point to the canonical representation chosen for the Hyperbolic space, to avoid numerical issues. """ assert gs.all(self.belongs(point)) point = gs.to_ndarray(point, to_ndim=2) sq_norm = self.embedding_metric.squared_norm(point) real_norm = gs.sqrt(gs.abs(sq_norm)) mask_0 = gs.isclose(real_norm, 0) mask_0 = gs.squeeze(mask_0, axis=1) mask_not_0 = ~mask_0 projected_point = point projected_point[mask_not_0] = (point[mask_not_0] / real_norm[mask_not_0]) return projected_point
def find_variance_from_index(weighted_distances, variances_range, phi_inv_var): r"""Return the variance given weighted distances. Parameters ---------- weighted_distances : array-like, shape=[n_gaussians,] Mean of the weighted distances between training data and current barycentres. The weights of each data sample corresponds to the probability of belonging to a component of the Gaussian mixture model. variances_range : array-like, shape=[n_variances,] Array of standard deviations. phi_inv_var : array-like, shape=[n_variances,] Array of the computed inverse of a function phi whose expression is closed-form :math:`\sigma\mapsto \sigma^3 \times \frac{d } {\mathstrut d\sigma}\log \zeta_m(\sigma)' where :math:'\sigma' denotes the variance and :math:'\zeta' the normalization coefficient and :math:'m' the dimension. Returns ------- var : array-like, shape=[n_gaussians,] Estimated variances for each component of the GMM. """ n_gaussians, precision = \ weighted_distances.shape[0], variances_range.shape[0] ref = gs.expand_dims(phi_inv_var, 0) ref = gs.repeat(ref, n_gaussians, axis=0) val = gs.expand_dims(weighted_distances, 1) val = gs.repeat(val, precision, axis=1) abs_difference = gs.abs(ref - val) index = gs.argmin(abs_difference, -1) var = variances_range[index] return var
def belongs(self, point, tolerance=TOLERANCE): """ Evaluate if a point belongs to the Hyperbolic space, i.e. evaluate if its squared norm in the Minkowski space is -1. """ point = gs.to_ndarray(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 hyperbolic space.') return gs.array([[False]]) sq_norm = self.embedding_metric.squared_norm(point) euclidean_sq_norm = gs.linalg.norm(point, axis=-1)**2 euclidean_sq_norm = gs.to_ndarray(euclidean_sq_norm, to_ndim=2, axis=1) diff = gs.abs(sq_norm + 1) belongs = diff < tolerance * euclidean_sq_norm return belongs
def gradient_descent( start, loss, grad, manifold, lr=0.01, max_iter=256, precision=1e-5 ): """Operate a gradient descent on a given manifold. Until either max_iter or a given precision is reached. """ x = start for i in range(max_iter): x_prev = x euclidean_grad = -lr * grad(x) tangent_vec = manifold.to_tangent(vector=euclidean_grad, base_point=x) x = manifold.metric.exp(base_point=x, tangent_vec=tangent_vec) if gs.abs(loss(x, use_gs=True) - loss(x_prev, use_gs=True)) <= precision: logging.info("x: %s", x) logging.info("reached precision %s", precision) logging.info("iterations: %d", i) break yield x, loss(x)
def belongs(self, point, atol=gs.atol): """Check if a matrix is invertible and of the right shape. Parameters ---------- point : array-like, shape=[..., n, n] Matrix to be checked. atol : float Tolerance threshold for the determinant. Returns ------- belongs : array-like, shape=[...,] Boolean denoting if point is in GL(n). """ has_right_size = self.ambient_space.belongs(point) if gs.all(has_right_size): det = gs.linalg.det(point) return det > atol if self.positive_det else gs.abs(det) > atol return has_right_size
def belongs(self, point, tolerance=TOLERANCE): """Test if a point belongs to the hyperbolic space. Test if a point belongs to the hyperbolic space according to the current representation. Parameters ---------- point : array-like, shape=[n_samples, dimension] or shape=[n_samples, dimension + 1] Point. tolerance : float, optional Tolerance at which to evaluate how close is the squared norm compared to the reference value. Returns ------- belongs : array-like, shape=[n_samples, 1] Array of booleans evaluating if the corresponding points belong to the hyperbolic space. """ if self.point_type == 'ball': return self.belongs_to[self.point_type](point, tolerance=tolerance) else: point = gs.to_ndarray(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 in the hyperbolic space.') return gs.array([[False]]) sq_norm = self.embedding_metric.squared_norm(point) euclidean_sq_norm = gs.linalg.norm(point, axis=-1)**2 euclidean_sq_norm = gs.to_ndarray(euclidean_sq_norm, to_ndim=2, axis=1) diff = gs.abs(sq_norm + 1) belongs = diff < tolerance * euclidean_sq_norm return belongs
def projection(self, point): """Project a matrix to the Cholesksy space. First it is projected to space lower triangular matrices and then diagonal elements are exponentiated to make it positive. Parameters ---------- point : array-like, shape=[..., n, n] Matrix to project. Returns ------- projected: array-like, shape=[..., n, n] SPD matrix. """ vec_diag = gs.abs(Matrices.diagonal(point) - 0.1) + 0.1 diag = gs.vec_to_diag(vec_diag) strictly_lower_triangular = Matrices.to_lower_triangular(point) projection = diag + strictly_lower_triangular return projection
def belongs(self, point, tolerance=TOLERANCE): """Evaluate if a point belongs to the Hyperbolic space. Evaluate if a point belongs to the Hyperbolic space according to the current representation Parameters ---------- point : array-like, shape=[n_samples, dimension] or shape=[n_samples, dimension + 1] for extrinsic coordinates Input points. tolerance : float, optional Returns ------- belongs : array-like, shape=[n_samples, 1] """ if self.point_type == 'ball': return self.belongs_to[self.point_type](point, tolerance=tolerance) else: point = gs.to_ndarray(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 hyperbolic space.') return gs.array([[False]]) sq_norm = self.embedding_metric.squared_norm(point) euclidean_sq_norm = gs.linalg.norm(point, axis=-1)**2 euclidean_sq_norm = gs.to_ndarray(euclidean_sq_norm, to_ndim=2, axis=1) diff = gs.abs(sq_norm + 1) belongs = diff < tolerance * euclidean_sq_norm return belongs
def gradient_descent(start, loss, grad, manifold, lr=0.01, max_iter=256, precision=1e-5): """Operate a gradient descent on a given manifold until either max_iter or a given precision is reached.""" x = start for i in range(max_iter): x_prev = x euclidean_grad = - lr * grad(x) tangent_vec = manifold.projection_to_tangent_space( vector=euclidean_grad, base_point=x) x = manifold.metric.exp(base_point=x, tangent_vec=tangent_vec)[0] if (gs.abs(loss(x, use_gs=True) - loss(x_prev, use_gs=True)) <= precision): print('x: %s' % x) print('reached precision %s' % precision) print('iterations: %d' % i) break yield x, loss(x)
def random_point(self, n_samples=1): r"""Compute a random point of the spider set. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. Returns ------- samples : list of SpiderPoint, shape=[...] List of SpiderPoints randomly sampled from the Spider. """ if self.n_rays != 0: s = gs.random.randint(low=0, high=self.n_rays, size=n_samples) x = gs.abs(gs.random.normal(loc=10, scale=1, size=n_samples)) x[s == 0] = 0 return [ SpiderPoint(stratum=s[k], stratum_coord=x[k]) for k in range(n_samples) ] return [SpiderPoint(stratum=0, stratum_coord=0)] * n_samples
def from_vector_to_diagonal_matrix(vector, num_diag=0): """Create diagonal matrices from rows of a matrix. Parameters ---------- vector : array-like, shape=[m, n] num_diag : int number of diagonal in result matrix. If 0, the result matrix is a diagonal matrix; if positive, the result matrix has an upper-right non-zero diagonal; if negative, the result matrix has a lower-left non-zero diagonal. Optional, Default: 0. Returns ------- diagonals : array-like, shape=[m, n, n] 3-dimensional array where the `i`-th n-by-n array `diagonals[i, :, :]` is a diagonal matrix containing the `i`-th row of `vector`. """ num_columns = gs.shape(vector)[-1] identity = gs.eye(num_columns) identity = gs.cast(identity, vector.dtype) diagonals = gs.einsum("...i,ij->...ij", vector, identity) diagonals = gs.to_ndarray(diagonals, to_ndim=3) num_lines = diagonals.shape[0] if num_diag > 0: left_zeros = gs.zeros((num_lines, num_columns, num_diag)) lower_zeros = gs.zeros((num_lines, num_diag, num_columns + num_diag)) diagonals = gs.concatenate((left_zeros, diagonals), axis=2) diagonals = gs.concatenate((diagonals, lower_zeros), axis=1) elif num_diag < 0: num_diag = gs.abs(num_diag) right_zeros = gs.zeros((num_lines, num_columns, num_diag)) upper_zeros = gs.zeros((num_lines, num_diag, num_columns + num_diag)) diagonals = gs.concatenate((diagonals, right_zeros), axis=2) diagonals = gs.concatenate((upper_zeros, diagonals), axis=1) return gs.squeeze(diagonals) if gs.ndim(vector) == 1 else diagonals
def belongs(self, point, tolerance=TOLERANCE): """ By definition, a point on the Hyperbolic space has Minkowski squared norm -1. We use a tolerance relative to the Euclidean norm of the point. Note: point must be given in extrinsic coordinates. """ point = gs.to_ndarray(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 hyperbolic space.') return False sq_norm = self.embedding_metric.squared_norm(point) euclidean_sq_norm = gs.linalg.norm(point, axis=-1)**2 euclidean_sq_norm = gs.to_ndarray(euclidean_sq_norm, to_ndim=2, axis=1) diff = gs.abs(sq_norm + 1) belongs = diff < tolerance * euclidean_sq_norm return belongs
def belongs(self, point, atol=TOLERANCE): """Test if a point belongs to the pre-shape space. This tests whether the point is centered and whether the point's Frobenius norm is 1. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point in Matrices space. atol : float Tolerance at which to evaluate norm == 1 and mean == 0. Optional, default: 1e-6. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the pre-shape space. """ shape = point.shape[-2:] == (self.k_landmarks, self.m_ambient) frob_norm = self.ambient_metric.norm(point) diff = gs.abs(frob_norm - 1) is_centered = gs.logical_and(self.is_centered(point, atol), shape) return gs.logical_and(gs.less_equal(diff, atol), is_centered)
def plot(points, ax=None, space=None, point_type='extrinsic', **point_draw_kwargs): """Plot points in the 3D Special Euclidean Group. Plot points in the 3D Special Euclidean Group, by showing them as trihedrons. """ if space not in IMPLEMENTED: raise NotImplementedError( 'The plot function is not implemented' ' for space {}. The spaces available for visualization' ' are: {}.'.format(space, IMPLEMENTED)) if points is None: raise ValueError("No points given for plotting.") points = gs.to_ndarray(points, to_ndim=2) if space in ('SO3_GROUP', 'SE3_GROUP'): if ax is None: ax = plt.subplot(111, projection='3d') if space == 'SE3_GROUP': ax_s = AX_SCALE * gs.amax(gs.abs(points[:, 3:6])) elif space == 'SO3_GROUP': ax_s = AX_SCALE * gs.amax(gs.abs(points[:, :3])) plt.setp(ax, xlim=(-ax_s, ax_s), ylim=(-ax_s, ax_s), zlim=(-ax_s, ax_s), xlabel='X', ylabel='Y', zlabel='Z') trihedrons = convert_to_trihedron(points, space=space) for t in trihedrons: t.draw(ax, **point_draw_kwargs) elif space == 'S1': circle = Circle() ax = circle.set_ax(ax=ax) circle.add_points(points) circle.draw(ax, **point_draw_kwargs) elif space == 'S2': sphere = Sphere() ax = sphere.set_ax(ax=ax) sphere.add_points(points) sphere.draw(ax, **point_draw_kwargs) elif space == 'H2_poincare_disk': poincare_disk = PoincareDisk(point_type=point_type) ax = poincare_disk.set_ax(ax=ax) poincare_disk.add_points(points) poincare_disk.draw(ax, **point_draw_kwargs) elif space == 'poincare_polydisk': n_disks = points.shape[1] poincare_poly_disk = PoincarePolyDisk(point_type=point_type, n_disks=n_disks) n_columns = gs.ceil(n_disks**0.5) n_rows = gs.ceil(n_disks / n_columns) axis_list = [] for i_disk in range(n_disks): axis_list.append(ax.add_subplot(n_rows, n_columns, i_disk + 1)) for i_disk, ax in enumerate(axis_list): ax = poincare_poly_disk.set_ax(ax=ax) poincare_poly_disk.clear_points() poincare_poly_disk.add_points(points[:, i_disk, ...]) poincare_poly_disk.draw(ax, **point_draw_kwargs) elif space == 'H2_poincare_half_plane': poincare_half_plane = PoincareHalfPlane() ax = poincare_half_plane.set_ax(ax=ax) poincare_half_plane.add_points(points) poincare_half_plane.draw(ax, **point_draw_kwargs) elif space == 'H2_klein_disk': klein_disk = KleinDisk() ax = klein_disk.set_ax(ax=ax) klein_disk.add_points(points) klein_disk.draw(ax, **point_draw_kwargs) return ax
def plot(points, ax=None, space=None, point_type=None, **point_draw_kwargs): """Plot points in one of the implemented manifolds. The implemented manifolds are: - the special orthogonal group SO(3) - the special Euclidean group SE(3) - the circle S1 and the sphere S2 - the hyperbolic plane (the Poincare disk, the Poincare half plane and the Klein disk) - the Poincare polydisk - the Kendall shape space of 2D triangles - the Kendall shape space of 3D triangles Parameters ---------- points : array-like, shape=[..., dim] Points to be plotted. space: str, optional, {'SO3_GROUP', 'SE3_GROUP', 'S1', 'S2', 'H2_poincare_disk', 'H2_poincare_half_plane', 'H2_klein_disk', 'poincare_polydisk', 'S32', 'M32', 'S33', 'M33'} point_type: str, optional, {'extrinsic', 'ball', 'half-space', 'pre-shape'} """ if space not in IMPLEMENTED: raise NotImplementedError( 'The plot function is not implemented' ' for space {}. The spaces available for visualization' ' are: {}.'.format(space, IMPLEMENTED)) if points is None: raise ValueError("No points given for plotting.") if points.ndim < 2: points = gs.expand_dims(points, 0) if space in ('SO3_GROUP', 'SE3_GROUP'): if ax is None: ax = plt.subplot(111, projection='3d') if space == 'SE3_GROUP': ax_s = AX_SCALE * gs.amax(gs.abs(points[:, 3:6])) elif space == 'SO3_GROUP': ax_s = AX_SCALE * gs.amax(gs.abs(points[:, :3])) ax_s = float(ax_s) bounds = (-ax_s, ax_s) plt.setp(ax, xlim=bounds, ylim=bounds, zlim=bounds, xlabel='X', ylabel='Y', zlabel='Z') trihedrons = convert_to_trihedron(points, space=space) for t in trihedrons: t.draw(ax, **point_draw_kwargs) elif space == 'S1': circle = Circle() ax = circle.set_ax(ax=ax) circle.add_points(points) circle.draw(ax, **point_draw_kwargs) elif space == 'S2': sphere = Sphere() ax = sphere.set_ax(ax=ax) sphere.add_points(points) sphere.draw(ax, **point_draw_kwargs) elif space == 'H2_poincare_disk': if point_type is None: point_type = 'extrinsic' poincare_disk = PoincareDisk(point_type=point_type) ax = poincare_disk.set_ax(ax=ax) poincare_disk.add_points(points) poincare_disk.draw(ax, **point_draw_kwargs) plt.axis('off') elif space == 'poincare_polydisk': if point_type is None: point_type = 'extrinsic' n_disks = points.shape[1] poincare_poly_disk = PoincarePolyDisk(point_type=point_type, n_disks=n_disks) n_columns = int(gs.ceil(n_disks**0.5)) n_rows = int(gs.ceil(n_disks / n_columns)) axis_list = [] for i_disk in range(n_disks): axis_list.append(ax.add_subplot(n_rows, n_columns, i_disk + 1)) for i_disk, one_ax in enumerate(axis_list): ax = poincare_poly_disk.set_ax(ax=one_ax) poincare_poly_disk.clear_points() poincare_poly_disk.add_points(points[:, i_disk, ...]) poincare_poly_disk.draw(ax, **point_draw_kwargs) elif space == 'H2_poincare_half_plane': if point_type is None: point_type = 'half-space' poincare_half_plane = PoincareHalfPlane(point_type=point_type) ax = poincare_half_plane.set_ax(points=points, ax=ax) poincare_half_plane.add_points(points) poincare_half_plane.draw(ax, **point_draw_kwargs) elif space == 'H2_klein_disk': klein_disk = KleinDisk() ax = klein_disk.set_ax(ax=ax) klein_disk.add_points(points) klein_disk.draw(ax, **point_draw_kwargs) elif space == 'SE2_GROUP': plane = SpecialEuclidean2() ax = plane.set_ax(ax=ax) plane.add_points(points) plane.draw(ax, **point_draw_kwargs) elif space == 'S32': sphere = KendallSphere() sphere.add_points(points) sphere.draw() sphere.draw_points() ax = sphere.ax elif space == 'M32': sphere = KendallSphere(point_type='extrinsic') sphere.add_points(points) sphere.draw() sphere.draw_points() ax = sphere.ax elif space == 'S33': disk = KendallDisk() disk.add_points(points) disk.draw() disk.draw_points() ax = disk.ax elif space == 'M33': disk = KendallDisk(point_type='extrinsic') disk.add_points(points) disk.draw() disk.draw_points() ax = disk.ax return ax