def retraction(tangent_vec, base_point): """Compute the retraction of a tangent vector. This computation is based on the QR-decomposition. e.g. :math:`P_x(V) = qf(X + V)`. Parameters ---------- tangent_vec : array-like, shape=[..., n, p] Tangent vector at a base point. base_point : array-like, shape=[..., n, p] Point in the Stiefel manifold. Returns ------- exp : array-like, shape=[..., n, p] Point in the Stiefel manifold equal to the retraction of tangent_vec at the base point. """ n_tangent_vecs, _, _ = tangent_vec.shape n_base_points, _, _ = base_point.shape if not (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1): raise NotImplementedError matrix_q, matrix_r = gs.linalg.qr(base_point + tangent_vec) diagonal = gs.diagonal(matrix_r, axis1=1, axis2=2) sign = gs.sign(gs.sign(diagonal) + 0.5) diag = algebra_utils.from_vector_to_diagonal_matrix(sign) result = gs.einsum('nij,njk->nik', matrix_q, diag) return result
def retraction(tangent_vec, base_point): """Compute the retraction of a tangent vector. This computation is based on the QR-decomposition. e.g. :math:`P_x(V) = qf(X + V)`. Parameters ---------- tangent_vec : array-like, shape=[..., n, p] Tangent vector at a base point. base_point : array-like, shape=[..., n, p] Point in the Stiefel manifold. Returns ------- exp : array-like, shape=[..., n, p] Point in the Stiefel manifold equal to the retraction of tangent_vec at the base point. """ matrix_q, matrix_r = gs.linalg.qr(base_point + tangent_vec) diagonal = gs.diagonal(matrix_r, axis1=-2, axis2=-1) sign = gs.sign(gs.sign(diagonal) + 0.5) diag = algebra_utils.from_vector_to_diagonal_matrix(sign) result = Matrices.mul(matrix_q, diag) return result
def retraction(self, tangent_vec, base_point): """ Retraction map, based on QR-decomposion: P_x(V) = qf(X + V) """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, n, p = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) matrix_q, matrix_r = gs.linalg.qr(base_point+tangent_vec) diagonal = gs.diagonal(matrix_r, axis1=1, axis2=2) sign = gs.sign(gs.sign(diagonal) + 0.5) diag = gs.diag(sign) result = gs.einsum('nij,njk->nik', matrix_q, diag) return result
def align_matrices(cls, point, base_point): """Align matrices. Find the optimal rotation R in SO(m) such that the base point and R.point are well positioned. Parameters ---------- point : array-like, shape=[..., m, n] Point on the manifold. base_point : array-like, shape=[..., m, n] Point on the manifold. Returns ------- aligned : array-like, shape=[..., m, n] R.point. """ mat = gs.matmul(cls.transpose(point), base_point) left, singular_values, right = gs.linalg.svd(mat) det = gs.linalg.det(mat) conditioning = ( singular_values[..., -2] + gs.sign(det) * singular_values[..., -1] ) / singular_values[..., 0] if gs.any(conditioning < gs.atol): logging.warning( f"Singularity close, ill-conditioned matrix " f"encountered: " f"{conditioning[conditioning < 1e-10]}" ) if gs.any(gs.isclose(conditioning, 0.0)): logging.warning("Alignment matrix is not unique.") flipped = flip_determinant(cls.transpose(right), det) return Matrices.mul(point, left, cls.transpose(flipped))
def align(self, point, base_point, **kwargs): """Align point to base_point. Find the optimal rotation R in SO(m) such that the base point and R.point are well positioned. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. Returns ------- aligned : array-like, shape=[..., k_landmarks, m_ambient] R.point. """ mat = gs.matmul(Matrices.transpose(point), base_point) left, singular_values, right = gs.linalg.svd(mat) det = gs.linalg.det(mat) conditioning = ((singular_values[..., -2] + gs.sign(det) * singular_values[..., -1]) / singular_values[..., 0]) if gs.any(conditioning < 5e-4): logging.warning(f'Singularity close, ill-conditioned matrix ' f'encountered: {conditioning}') if gs.any(gs.isclose(conditioning, 0.)): logging.warning("Alignment matrix is not unique.") flipped = flip_determinant(Matrices.transpose(right), det) return Matrices.mul(point, left, Matrices.transpose(flipped))
def retraction(self, tangent_vec, base_point): """Compute the retraction of a tangent vector. This computation is based on the QR-decomposition. e.g. :math:`P_x(V) = qf(X + V)`. Parameters ---------- tangent_vec : array-like, shape=[n_samples, n, p] Tangent vector at a base point. base_point : array-like, shape=[n_samples, n, p] Point in the Stiefel manifold. Returns ------- exp : array-like, shape=[n_samples, n, p] Point in the Stiefel manifold equal to the retraction of tangent_vec at the base point. """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=3) n_tangent_vecs, _, _ = tangent_vec.shape base_point = gs.to_ndarray(base_point, to_ndim=3) n_base_points, n, p = base_point.shape assert (n_tangent_vecs == n_base_points or n_tangent_vecs == 1 or n_base_points == 1) if n_base_points == 1: base_point = gs.tile(base_point, (n_tangent_vecs, 1, 1)) if n_tangent_vecs == 1: tangent_vec = gs.tile(tangent_vec, (n_base_points, 1, 1)) matrix_q, matrix_r = gs.linalg.qr(base_point + tangent_vec) diagonal = gs.diagonal(matrix_r, axis1=1, axis2=2) sign = gs.sign(gs.sign(diagonal) + 0.5) diag = gs.diag(sign) result = gs.einsum('nij,njk->nik', matrix_q, diag) return result
def align(self, point, base_point, **kwargs): """Align point to base_point. Find the optimal rotation R in SO(m) such that the base point and R.point are well positioned. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. Returns ------- aligned : array-like, shape=[..., k_landmarks, m_ambient] R.point. """ mat = gs.matmul(Matrices.transpose(point), base_point) left, singular_values, right = gs.linalg.svd(mat) det = gs.linalg.det(mat) conditioning = ( (singular_values[..., -2] + gs.sign(det) * singular_values[..., -1]) / singular_values[..., 0]) if gs.any(conditioning < 5e-4): logging.warning(f'Singularity close, ill-conditioned matrix ' f'encountered: {conditioning}') if gs.any(gs.isclose(conditioning, 0.)): logging.warning("Alignment matrix is not unique.") if gs.any(det < 0): ones = gs.ones(self.m_ambient) reflection_vec = gs.concatenate( [ones[:-1], gs.array([-1.])], axis=0) mask = gs.cast(det < 0, gs.float32) sign = (mask[..., None] * reflection_vec + (1. - mask)[..., None] * ones) j_matrix = from_vector_to_diagonal_matrix(sign) rotation = Matrices.mul( Matrices.transpose(right), j_matrix, Matrices.transpose(left)) else: rotation = gs.matmul( Matrices.transpose(right), Matrices.transpose(left)) return gs.matmul(point, Matrices.transpose(rotation))
return solver.solve(problem) if __name__ == '__main__': if os.environ.get('GEOMSTATS_BACKEND') != 'numpy': raise SystemExit( 'This example currently only supports the numpy backend') ambient_dim = 128 mat = gs.random.normal(size=(ambient_dim, ambient_dim)) mat = 0.5 * (mat + mat.T) eigenvalues, eigenvectors = gs.linalg.eig(mat) dominant_eigenvector = eigenvectors[:, gs.argmax(eigenvalues)] dominant_eigenvector_estimate = estimate_dominant_eigenvector(mat) if (gs.sign(dominant_eigenvector[0]) != gs.sign(dominant_eigenvector_estimate[0])): dominant_eigenvector_estimate = -dominant_eigenvector_estimate logging.info( 'l2-norm of dominant eigenvector: %s', gs.linalg.norm(dominant_eigenvector)) logging.info( 'l2-norm of dominant eigenvector estimate: %s', gs.linalg.norm(dominant_eigenvector_estimate)) error_norm = gs.linalg.norm( dominant_eigenvector - dominant_eigenvector_estimate) logging.info('l2-norm of difference vector: %s', error_norm) logging.info('solution found: %s', gs.isclose(error_norm, 0.0, atol=1e-3))
def rotation_vector_from_matrix(self, rot_mat): r"""Convert rotation matrix (in 3D) to rotation vector (axis-angle). Get the angle through the trace of the rotation matrix: The eigenvalues are: :math:`\{1, \cos(angle) + i \sin(angle), \cos(angle) - i \sin(angle)\}` so that: :math:`trace = 1 + 2 \cos(angle), \{-1 \leq trace \leq 3\}` Get the rotation vector through the formula: :math:`S_r = \frac{angle}{(2 * \sin(angle) ) (R - R^T)}` For the edge case where the angle is close to pi, the formulation is derived by using the following equality (see the Axis-angle representation on Wikipedia): :math:`outer(r, r) = \frac{1}{2} (R + I_3)` In nD, the rotation vector stores the :math:`n(n-1)/2` values of the skew-symmetric matrix representing the rotation. Parameters ---------- rot_mat : array-like, shape=[..., n, n] Returns ------- regularized_rot_vec : array-like, shape=[..., 3] """ n_rot_mats, _, _ = rot_mat.shape trace = gs.trace(rot_mat, axis1=1, axis2=2) trace = gs.to_ndarray(trace, to_ndim=2, axis=1) trace_num = gs.clip(trace, -1, 3) angle = gs.arccos(0.5 * (trace_num - 1)) rot_mat_transpose = gs.transpose(rot_mat, axes=(0, 2, 1)) rot_vec_not_pi = self.vector_from_skew_matrix(rot_mat - rot_mat_transpose) mask_0 = gs.cast(gs.isclose(angle, 0.), gs.float32) mask_pi = gs.cast(gs.isclose(angle, gs.pi, atol=1e-2), gs.float32) mask_else = (1 - mask_0) * (1 - mask_pi) numerator = 0.5 * mask_0 + angle * mask_else denominator = (1 - angle**2 / 6) * mask_0 + 2 * gs.sin(angle) * mask_else + mask_pi rot_vec_not_pi = rot_vec_not_pi * numerator / denominator vector_outer = 0.5 * (gs.eye(3) + rot_mat) gs.set_diag( vector_outer, gs.maximum(0., gs.diagonal(vector_outer, axis1=1, axis2=2))) squared_diag_comp = gs.diagonal(vector_outer, axis1=1, axis2=2) diag_comp = gs.sqrt(squared_diag_comp) norm_line = gs.linalg.norm(vector_outer, axis=2) max_line_index = gs.argmax(norm_line, axis=1) selected_line = gs.get_slice(vector_outer, (range(n_rot_mats), max_line_index)) signs = gs.sign(selected_line) rot_vec_pi = angle * signs * diag_comp rot_vec = rot_vec_not_pi + mask_pi * rot_vec_pi return self.regularize(rot_vec)