def test_align(self): point, base_point = self.space.random_point(2) aligned = self.space.align(point, base_point) alignment = gs.matmul(Matrices.transpose(aligned), base_point) result = Matrices.is_symmetric(alignment) self.assertTrue(result)
def sylv_p(mat_b): """Solves Sylvester equation for vertical component.""" return gs.linalg.solve_sylvester(p_top_p, p_top_p, mat_b - Matrices.transpose(mat_b))
def __init__(self, n, **kwargs): super(SymmetricMatrices, self).__init__(dim=int(n * (n + 1) / 2), embedding_manifold=Matrices(n, n)) self.n = n
class TestVisualization(geomstats.tests.TestCase): def setup_method(self): self.n_samples = 10 self.SO3_GROUP = SpecialOrthogonal(n=3, point_type="vector") self.SE3_GROUP = SpecialEuclidean(n=3, point_type="vector") self.S1 = Hypersphere(dim=1) self.S2 = Hypersphere(dim=2) self.H2 = Hyperbolic(dim=2) self.H2_half_plane = PoincareHalfSpace(dim=2) self.M32 = Matrices(m=3, n=2) self.S32 = PreShapeSpace(k_landmarks=3, m_ambient=2) self.KS = visualization.KendallSphere() self.M33 = Matrices(m=3, n=3) self.S33 = PreShapeSpace(k_landmarks=3, m_ambient=3) self.KD = visualization.KendallDisk() self.spd = SPDMatrices(n=2) plt.figure() @staticmethod def test_tutorial_matplotlib(): visualization.tutorial_matplotlib() def test_plot_points_so3(self): points = self.SO3_GROUP.random_uniform(self.n_samples) visualization.plot(points, space="SO3_GROUP") def test_plot_points_se3(self): points = self.SE3_GROUP.random_point(self.n_samples) visualization.plot(points, space="SE3_GROUP") def test_draw_pre_shape_2d(self): self.KS.draw() def test_draw_points_pre_shape_2d(self): points = self.S32.random_point(self.n_samples) visualization.plot(points, space="S32") points = self.M32.random_point(self.n_samples) visualization.plot(points, space="M32") self.KS.clear_points() def test_draw_curve_pre_shape_2d(self): self.KS.draw() base_point = self.S32.random_point() vec = self.S32.random_point() tangent_vec = self.S32.to_tangent(vec, base_point) times = gs.linspace(0.0, 1.0, 1000) speeds = gs.array([-t * tangent_vec for t in times]) points = self.S32.ambient_metric.exp(speeds, base_point) self.KS.add_points(points) self.KS.draw_curve() self.KS.clear_points() def test_draw_vector_pre_shape_2d(self): self.KS.draw() base_point = self.S32.random_point() vec = self.S32.random_point() tangent_vec = self.S32.to_tangent(vec, base_point) self.KS.draw_vector(tangent_vec, base_point) def test_convert_to_spherical_coordinates_pre_shape_2d(self): points = self.S32.random_point(self.n_samples) coords = self.KS.convert_to_spherical_coordinates(points) x = coords[:, 0] y = coords[:, 1] z = coords[:, 2] result = x**2 + y**2 + z**2 expected = 0.25 * gs.ones(self.n_samples) self.assertAllClose(result, expected) def test_rotation_pre_shape_2d(self): theta = gs.random.rand(1)[0] phi = gs.random.rand(1)[0] rot = self.KS.rotation(theta, phi) result = _SpecialOrthogonalMatrices(3).belongs(rot) expected = True self.assertAllClose(result, expected) def test_draw_pre_shape_3d(self): self.KD.draw() def test_draw_points_pre_shape_3d(self): points = self.S33.random_point(self.n_samples) visualization.plot(points, space="S33") points = self.M33.random_point(self.n_samples) visualization.plot(points, space="M33") self.KD.clear_points() def test_draw_curve_pre_shape_3d(self): self.KD.draw() base_point = self.S33.random_point() vec = self.S33.random_point() tangent_vec = self.S33.to_tangent(vec, base_point) tangent_vec = 0.5 * tangent_vec / self.S33.ambient_metric.norm( tangent_vec) times = gs.linspace(0.0, 1.0, 1000) speeds = gs.array([-t * tangent_vec for t in times]) points = self.S33.ambient_metric.exp(speeds, base_point) self.KD.add_points(points) self.KD.draw_curve() self.KD.clear_points() def test_draw_vector_pre_shape_3d(self): self.KS.draw() base_point = self.S32.random_point() vec = self.S32.random_point() tangent_vec = self.S32.to_tangent(vec, base_point) self.KS.draw_vector(tangent_vec, base_point) def test_convert_to_planar_coordinates_pre_shape_3d(self): points = self.S33.random_point(self.n_samples) coords = self.KD.convert_to_planar_coordinates(points) x = coords[:, 0] y = coords[:, 1] radius = x**2 + y**2 result = [r <= 1.0 for r in radius] self.assertTrue(gs.all(result)) @geomstats.tests.np_autograd_and_torch_only def test_plot_points_s1(self): points = self.S1.random_uniform(self.n_samples) visualization.plot(points, space="S1") def test_plot_points_s2(self): points = self.S2.random_uniform(self.n_samples) visualization.plot(points, space="S2") def test_plot_points_h2_poincare_disk(self): points = self.H2.random_point(self.n_samples) visualization.plot(points, space="H2_poincare_disk") def test_plot_points_h2_poincare_half_plane_ext(self): points = self.H2.random_point(self.n_samples) visualization.plot(points, space="H2_poincare_half_plane", point_type="extrinsic") def test_plot_points_h2_poincare_half_plane_none(self): points = self.H2_half_plane.random_point(self.n_samples) visualization.plot(points, space="H2_poincare_half_plane") def test_plot_points_h2_poincare_half_plane_hs(self): points = self.H2_half_plane.random_point(self.n_samples) visualization.plot(points, space="H2_poincare_half_plane", point_type="half_space") def test_plot_points_h2_klein_disk(self): points = self.H2.random_point(self.n_samples) visualization.plot(points, space="H2_klein_disk") @staticmethod def test_plot_points_se2(): points = SpecialEuclidean(n=2, point_type="vector").random_point(4) visu = visualization.SpecialEuclidean2(points, point_type="vector") ax = visu.set_ax() visu.draw_points(ax) def test_plot_points_spd2(self): one_point = self.spd.random_point() visualization.plot(one_point, space="SPD2") points = self.spd.random_point(4) visualization.plot(points, space="SPD2") def test_compute_coordinates_spd2(self): point = gs.eye(2) ellipsis = visualization.Ellipses(n_sampling_points=4) x, y = ellipsis.compute_coordinates(point) self.assertAllClose(x, gs.array([1, 0, -1, 0, 1])) self.assertAllClose(y, gs.array([0, 1, 0, -1, 0])) @staticmethod def teardown_method(): plt.close()
def integrability_tensor_derivative_parallel(self, horizontal_vec_x, horizontal_vec_y, horizontal_vec_z, base_point): r"""Compute derivative of the integrability tensor A (special case). The horizontal covariant derivative :math:`\nabla_X (A_Y Z)` of the integrability tensor A may be computed more efficiently in the case of parallel vector fields in the quotient space. :math: `\nabla_X (A_Y Z)` and :math:`A_Y Z` are computed here for the Kendall shape space with quotient-parallel vector fields :math:`X, Y, Z` extending the values horizontal_vec_x, horizontal_vec_y and horizontal_vec_z by parallel transport in a neighborhood of the base-space. Such vector fields verify :math:`\nabla_X^X = A_X X = 0`, :math:`\nabla_X^Y = A_X Y` and similarly for Z. Parameters ---------- horizontal_vec_x : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. horizontal_vec_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. horizontal_vec_z : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point of the total space. Returns ------- nabla_x_a_y_z : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`\nabla_X (A_Y Z)` with `X = horizontal_vec_x`, `Y = horizontal_vec_y` and `Z = horizontal_vec_z`. a_y_z : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_Y Z` with `Y = horizontal_vec_y` and `Z = horizontal_vec_z`. References ---------- .. [Pennec] Pennec, Xavier. Computing the curvature and its gradient in Kendall shape spaces. Unpublished. """ # Vectors X and Y have to be horizontal. if not gs.all(self.is_centered(base_point)): raise ValueError("The base_point does not belong to the pre-shape" " space") if not gs.all(self.is_horizontal(horizontal_vec_x, base_point)): raise ValueError("Tangent vector x is not horizontal") if not gs.all(self.is_horizontal(horizontal_vec_y, base_point)): raise ValueError("Tangent vector y is not horizontal") if not gs.all(self.is_horizontal(horizontal_vec_z, base_point)): raise ValueError("Tangent vector z is not horizontal") p_top = Matrices.transpose(base_point) p_top_p = gs.matmul(p_top, base_point) def sylv_p(mat_b): """Solves Sylvester equation for vertical component.""" return gs.linalg.solve_sylvester(p_top_p, p_top_p, mat_b - Matrices.transpose(mat_b)) z_top = Matrices.transpose(horizontal_vec_z) y_top = Matrices.transpose(horizontal_vec_y) omega_yz = sylv_p(gs.matmul(z_top, horizontal_vec_y)) a_y_z = gs.matmul(base_point, omega_yz) omega_xy = sylv_p(gs.matmul(y_top, horizontal_vec_x)) omega_xz = sylv_p(gs.matmul(z_top, horizontal_vec_x)) omega_yz_x = gs.matmul(horizontal_vec_x, omega_yz) omega_xz_y = gs.matmul(horizontal_vec_y, omega_xz) omega_xy_z = gs.matmul(horizontal_vec_z, omega_xy) tangent_vec_f = 2.0 * omega_yz_x + omega_xz_y - omega_xy_z omega_fp = sylv_p(gs.matmul(p_top, tangent_vec_f)) omega_fp_p = gs.matmul(base_point, omega_fp) nabla_x_a_y_z = omega_yz_x - omega_fp_p return nabla_x_a_y_z, a_y_z
def test_to_matrix_type_is_matrix_type(self, m, n, matrix_type, mat): cls_mn = Matrices(m, n) to_function = getattr(cls_mn, "to_" + matrix_type) is_function = getattr(cls_mn, "is_" + matrix_type) self.assertAllClose(gs.all(is_function(to_function(gs.array(mat)))), True)
class MatricesMetricTestData(_RiemannianMetricTestData): m_list = random.sample(range(3, 5), 2) n_list = random.sample(range(3, 5), 2) metric_args_list = list(zip(m_list, n_list)) space_args_list = metric_args_list shape_list = space_args_list space_list = [Matrices(m, n) for m, n in metric_args_list] n_points_list = random.sample(range(1, 7), 2) n_tangent_vecs_list = random.sample(range(1, 7), 2) n_points_a_list = random.sample(range(1, 7), 2) n_points_b_list = [1] alpha_list = [1] * 2 n_rungs_list = [1] * 2 scheme_list = ["pole"] * 2 def inner_product_test_data(self): smoke_data = [ dict( m=2, n=2, tangent_vec_a=[[-3.0, 1.0], [-1.0, -2.0]], tangent_vec_b=[[-9.0, 0.0], [4.0, 2.0]], expected=19.0, ), dict( m=2, n=2, tangent_vec_a=[ [[-1.5, 0.0], [2.0, -3.0]], [[0.5, 7.0], [0.5, -2.0]], ], tangent_vec_b=[ [[2.0, 0.0], [2.0, -3.0]], [[-1.0, 0.0], [1.0, -2.0]], ], expected=[10.0, 4.0], ), ] return self.generate_tests(smoke_data) def norm_test_data(self): smoke_data = [ dict(m=2, n=2, vector=[[1.0, 0.0], [0.0, 1.0]], expected=SQRT_2), dict( m=2, n=2, vector=[[[3.0, 0.0], [4.0, 0.0]], [[-3.0, 0.0], [-4.0, 0.0]]], expected=[5.0, 5.0], ), ] return self.generate_tests(smoke_data) def inner_product_norm_test_data(self): smoke_data = [ dict(m=5, n=5, mat=Matrices(5, 5).random_point(100)), dict(m=10, n=10, mat=Matrices(5, 5).random_point(100)), ] return self.generate_tests(smoke_data) def exp_shape_test_data(self): return self._exp_shape_test_data( self.metric_args_list, self.space_list, self.shape_list, ) def log_shape_test_data(self): return self._log_shape_test_data( self.metric_args_list, self.space_list, ) def squared_dist_is_symmetric_test_data(self): return self._squared_dist_is_symmetric_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, atol=gs.atol * 1000, ) def exp_belongs_test_data(self): return self._exp_belongs_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, belongs_atol=gs.atol * 1000, ) def log_is_tangent_test_data(self): return self._log_is_tangent_test_data( self.metric_args_list, self.space_list, self.n_points_list, is_tangent_atol=gs.atol * 1000, ) def geodesic_ivp_belongs_test_data(self): return self._geodesic_ivp_belongs_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_points_list, belongs_atol=gs.atol * 1000, ) def geodesic_bvp_belongs_test_data(self): return self._geodesic_bvp_belongs_test_data( self.metric_args_list, self.space_list, self.n_points_list, belongs_atol=gs.atol * 1000, ) def log_then_exp_test_data(self): return self._log_then_exp_test_data( self.metric_args_list, self.space_list, self.n_points_list, rtol=gs.rtol * 100, atol=gs.atol * 10000, ) def exp_then_log_test_data(self): return self._exp_then_log_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, rtol=gs.rtol * 100, atol=gs.atol * 10000, ) def exp_ladder_parallel_transport_test_data(self): return self._exp_ladder_parallel_transport_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, self.n_rungs_list, self.alpha_list, self.scheme_list, ) def exp_geodesic_ivp_test_data(self): return self._exp_geodesic_ivp_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, self.n_points_list, rtol=gs.rtol * 100000, atol=gs.atol * 100000, ) def parallel_transport_ivp_is_isometry_test_data(self): return self._parallel_transport_ivp_is_isometry_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, is_tangent_atol=gs.atol * 1000, atol=gs.atol * 1000, ) def parallel_transport_bvp_is_isometry_test_data(self): return self._parallel_transport_bvp_is_isometry_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, is_tangent_atol=gs.atol * 1000, atol=gs.atol * 1000, ) def dist_is_symmetric_test_data(self): return self._dist_is_symmetric_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def dist_is_positive_test_data(self): return self._dist_is_positive_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def squared_dist_is_positive_test_data(self): return self._squared_dist_is_positive_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def dist_is_norm_of_log_test_data(self): return self._dist_is_norm_of_log_test_data( self.metric_args_list, self.space_list, self.n_points_a_list, self.n_points_b_list, ) def dist_point_to_itself_is_zero_test_data(self): return self._dist_point_to_itself_is_zero_test_data( self.metric_args_list, self.space_list, self.n_points_list) def inner_product_is_symmetric_test_data(self): return self._inner_product_is_symmetric_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, ) def retraction_lifting_test_data(self): return self._exp_then_log_test_data( self.metric_args_list, self.space_list, self.shape_list, self.n_tangent_vecs_list, rtol=gs.rtol * 100, atol=gs.atol * 10000, )
def test_frobenius_product(self, mat_a, mat_b, expected): self.assertAllClose( Matrices.frobenius_product(gs.array(mat_a), gs.array(mat_b)), gs.array(expected), )
def test_trace_product(self, mat_a, mat_b, expected): self.assertAllClose( Matrices.trace_product(gs.array(mat_a), gs.array(mat_b)), gs.array(expected))
def test_bracket(self, mat_a, mat_b, expected): self.assertAllClose(Matrices.bracket(gs.array(mat_a), gs.array(mat_b)), gs.array(expected))
def test_congruent(self, mat_a, mat_b, expected): self.assertAllClose( Matrices.congruent(gs.array(mat_a), gs.array(mat_b)), gs.array(expected))
def test_mul(self, mat, expected): self.assertAllClose(Matrices.mul(*mat), gs.array(expected))
def test_equal(self, m, n, mat1, mat2, expected): self.assertAllClose( Matrices(m, n).equal(gs.array(mat1), gs.array(mat2)), gs.array(expected))
def test_belongs(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).belongs(gs.array(mat)), gs.array(expected))
def test_to_lower_triangular_diagonal_scaled(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).to_lower_triangular_diagonal_scaled(gs.array(mat)), gs.array(expected), )
def test_flatten(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).flatten(gs.array(mat)), gs.array(expected))
def test_flatten_reshape(self, m, n, mat): cls_mn = Matrices(m, n) self.assertAllClose(cls_mn.reshape(cls_mn.flatten(gs.array(mat))), gs.array(mat))
def test_transpose(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).transpose(gs.array(mat)), gs.array(expected))
def test_basis(self, m, n, expected): result = Matrices(m, n).basis self.assertAllClose(result, expected)
def test_is_spd(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).is_spd(gs.array(mat)), gs.array(expected))
def inner_product_norm_test_data(self): smoke_data = [ dict(m=5, n=5, mat=Matrices(5, 5).random_point(100)), dict(m=10, n=10, mat=Matrices(5, 5).random_point(100)), ] return self.generate_tests(smoke_data)
def test_is_lower_triangular(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).is_lower_triangular(gs.array(mat)), gs.array(expected))
def integrability_tensor_derivative( self, horizontal_vec_x, horizontal_vec_y, nabla_x_y, tangent_vec_e, nabla_x_e, base_point, ): r"""Compute the covariant derivative of the integrability tensor A. The horizontal covariant derivative :math:`\nabla_X (A_Y E)` is necessary to compute the covariant derivative of the curvature in a submersion. The components :math:`\nabla_X (A_Y E)` and :math:`A_Y E` are computed here for the Kendall shape space at base-point :math:`P = base\_point` for horizontal vector fields fields :math: `X, Y` extending the values :math:`X|_P = horizontal\_vec\_x`, :math:`Y|_P = horizontal\_vec\_y` and a general vector field :math:`E` extending :math:`E|_P = tangent\_vec\_e` in a neighborhood of the base-point P with covariant derivatives :math:`\nabla_X Y |_P = nabla_x_y` and :math:`\nabla_X E |_P = nabla_x_e`. Parameters ---------- horizontal_vec_x : array-like, shape=[..., k_landmarks, m_ambient] Horizontal tangent vector at `base_point`. horizontal_vec_y : array-like, shape=[..., k_landmarks, m_ambient] Horizontal tangent vector at `base_point`. nabla_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. tangent_vec_e : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. nabla_x_e : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point of the total space. Returns ------- nabla_x_a_y_e : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`\nabla_X^S (A_Y E)`. a_y_e : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_Y E`. References ---------- .. [Pennec] Pennec, Xavier. Computing the curvature and its gradient in Kendall shape spaces. Unpublished. """ if not gs.all(self.belongs(base_point)): raise ValueError("The base_point does not belong to the pre-shape" " space") if not gs.all(self.is_horizontal(horizontal_vec_x, base_point)): raise ValueError("Tangent vector x is not horizontal") if not gs.all(self.is_horizontal(horizontal_vec_y, base_point)): raise ValueError("Tangent vector y is not horizontal") if not gs.all(self.is_tangent(nabla_x_y, base_point)): raise ValueError("Vector nabla_x_y is not tangent") a_x_y = self.integrability_tensor(horizontal_vec_x, horizontal_vec_y, base_point) if not gs.all(self.is_horizontal(nabla_x_y - a_x_y, base_point)): raise ValueError("Tangent vector nabla_x_y is not the gradient " "of a horizontal distrinbution") if not gs.all(self.is_tangent(tangent_vec_e, base_point)): raise ValueError("Tangent vector e is not tangent") if not gs.all(self.is_tangent(nabla_x_e, base_point)): raise ValueError("Vector nabla_x_e is not tangent") p_top = Matrices.transpose(base_point) p_top_p = gs.matmul(p_top, base_point) e_top = Matrices.transpose(tangent_vec_e) x_top = Matrices.transpose(horizontal_vec_x) y_top = Matrices.transpose(horizontal_vec_y) def sylv_p(mat_b): """Solves Sylvester equation for vertical component.""" return gs.linalg.solve_sylvester(p_top_p, p_top_p, mat_b - Matrices.transpose(mat_b)) omega_ep = sylv_p(gs.matmul(p_top, tangent_vec_e)) omega_ye = sylv_p(gs.matmul(e_top, horizontal_vec_y)) tangent_vec_b = gs.matmul(horizontal_vec_x, omega_ye) tangent_vec_e_sym = tangent_vec_e - 2.0 * gs.matmul( base_point, omega_ep) a_y_e = gs.matmul(base_point, omega_ye) + gs.matmul( horizontal_vec_y, omega_ep) tmp_tangent_vec_p = (gs.matmul(e_top, nabla_x_y) - gs.matmul(y_top, nabla_x_e) - 2.0 * gs.matmul(p_top, tangent_vec_b)) tmp_tangent_vec_y = gs.matmul(p_top, nabla_x_e) + gs.matmul( x_top, tangent_vec_e_sym) scal_x_a_y_e = self.ambient_metric.inner_product( horizontal_vec_x, a_y_e, base_point) nabla_x_a_y_e = ( gs.matmul(base_point, sylv_p(tmp_tangent_vec_p)) + gs.matmul(horizontal_vec_y, sylv_p(tmp_tangent_vec_y)) + gs.matmul(nabla_x_y, omega_ep) + tangent_vec_b + gs.einsum("...,...ij->...ij", scal_x_a_y_e, base_point)) return nabla_x_a_y_e, a_y_e
def test_to_diagonal(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).to_diagonal(gs.array(mat)), gs.array(expected))
def iterated_integrability_tensor_derivative_parallel( self, horizontal_vec_x, horizontal_vec_y, base_point): r"""Compute iterated derivatives of the integrability tensor A. The iterated horizontal covariant derivative :math:`\nabla_X (A_Y A_X Y)` (where :math:`X` and :math:`Y` are horizontal vector fields) is a key ingredient in the computation of the covariant derivative of the directional curvature in a submersion. The components :math:`\nabla_X (A_Y A_X Y)`, :math:`A_X A_Y A_X Y`, :math:`\nabla_X (A_X Y)`, and intermediate computations :math:`A_Y A_X Y` and :math:`A_X Y` are computed here for the Kendall shape space in the special case of quotient-parallel vector fields :math:`X, Y` extending the values horizontal_vec_x and horizontal_vec_y by parallel transport in a neighborhood. Such vector fields verify :math:`\nabla_X^X = A_X X` and :math: `\nabla_X^Y = A_X Y`. Parameters ---------- horizontal_vec_x : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. horizontal_vec_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point of the total space. Returns ------- nabla_x_a_y_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`\nabla_X^S (A_Y A_X Y)` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. a_x_a_y_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_X A_Y A_X Y` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. nabla_x_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`\nabla_X^S (A_X Y)` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. a_y_a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_Y A_X Y` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. a_x_y : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector at `base_point`, result of :math:`A_X Y` with `X = horizontal_vec_x` and `Y = horizontal_vec_y`. References ---------- .. [Pennec] Pennec, Xavier. Computing the curvature and its gradient in Kendall shape spaces. Unpublished. """ if not gs.all(self.is_centered(base_point)): raise ValueError("The base_point does not belong to the pre-shape" " space") if not gs.all(self.is_horizontal(horizontal_vec_x, base_point)): raise ValueError("Tangent vector x is not horizontal") if not gs.all(self.is_horizontal(horizontal_vec_y, base_point)): raise ValueError("Tangent vector y is not horizontal") p_top = Matrices.transpose(base_point) p_top_p = gs.matmul(p_top, base_point) def sylv_p(mat_b): """Solves Sylvester equation for vertical component.""" return gs.linalg.solve_sylvester(p_top_p, p_top_p, mat_b - Matrices.transpose(mat_b)) y_top = Matrices.transpose(horizontal_vec_y) x_top = Matrices.transpose(horizontal_vec_x) x_y_top = gs.matmul(y_top, horizontal_vec_x) omega_xy = sylv_p(x_y_top) vertical_vec_v = gs.matmul(base_point, omega_xy) omega_xy_x = gs.matmul(horizontal_vec_x, omega_xy) omega_xy_y = gs.matmul(horizontal_vec_y, omega_xy) v_top = Matrices.transpose(vertical_vec_v) x_v_top = gs.matmul(v_top, horizontal_vec_x) omega_xv = sylv_p(x_v_top) omega_xv_p = gs.matmul(base_point, omega_xv) y_v_top = gs.matmul(v_top, horizontal_vec_y) omega_yv = sylv_p(y_v_top) omega_yv_p = gs.matmul(base_point, omega_yv) nabla_x_v = 3.0 * omega_xv_p + omega_xy_x a_y_a_x_y = omega_yv_p + omega_xy_y tmp_mat = gs.matmul(x_top, a_y_a_x_y) a_x_a_y_a_x_y = -gs.matmul(base_point, sylv_p(tmp_mat)) omega_xv_y = gs.matmul(horizontal_vec_y, omega_xv) omega_yv_x = gs.matmul(horizontal_vec_x, omega_yv) omega_xy_v = gs.matmul(vertical_vec_v, omega_xy) norms = Matrices.frobenius_product(vertical_vec_v, vertical_vec_v) sq_norm_v_p = gs.einsum("...,...ij->...ij", norms, base_point) tmp_mat = gs.matmul(p_top, 3.0 * omega_xv_y + 2.0 * omega_yv_x) + gs.matmul(y_top, omega_xy_x) nabla_x_a_y_v = (3.0 * omega_xv_y + omega_yv_x + omega_xy_v - gs.matmul(base_point, sylv_p(tmp_mat)) + sq_norm_v_p) return nabla_x_a_y_v, a_x_a_y_a_x_y, nabla_x_v, a_y_a_x_y, vertical_vec_v
def test_to_symmetric(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).to_symmetric(gs.array(mat)), gs.array(expected))
def __init__(self, n): super(FullRankCorrelationMatrices, self).__init__( dim=int(n * (n - 1) / 2), embedding_space=SPDMatrices(n=n), submersion=Matrices.diagonal, value=gs.ones(n), tangent_submersion=lambda v, x: Matrices.diagonal(v)) self.n = n
def test_to_strictly_upper_triangular(self, m, n, mat, expected): self.assertAllClose( Matrices(m, n).to_strictly_upper_triangular(gs.array(mat)), gs.array(expected), )
class TestMatrices(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.n = 3 self.space = Matrices(m=self.n, n=self.n) self.metric = self.space.metric self.n_samples = 2 @geomstats.tests.np_only def test_mul(self): a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) c = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 0.]]) d = gs.array([[0., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) result = self.space.mul([a, b], [b, a]) expected = gs.array([c, d]) self.assertAllClose(result, expected) result = self.space.mul(a, [a, b]) expected = gs.array([gs.eye(3, 3, 2), c]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_bracket(self): x = gs.array([[0., 0., 0.], [0., 0., -1.], [0., 1., 0.]]) y = gs.array([[0., 0., 1.], [0., 0., 0.], [-1., 0., 0.]]) z = gs.array([[0., -1., 0.], [1., 0., 0.], [0., 0., 0.]]) result = self.space.bracket([x, y], [y, z]) expected = gs.array([z, x]) self.assertAllClose(result, expected) result = self.space.bracket(x, [x, y, z]) expected = gs.array([gs.zeros((3, 3)), z, -y]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_transpose(self): tr = self.space.transpose ar = gs.array a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) self.assertAllClose(tr(a), b) self.assertAllClose(tr(ar([a, b])), ar([b, a])) @geomstats.tests.np_only def test_is_symmetric(self): sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.is_symmetric(sym_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_is_skew_symmetric(self): skew_mat = gs.array([[0, -2.], [2., 0]]) result = self.space.is_skew_symmetric(skew_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_skew_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_is_symmetric_vectorization(self): points = gs.array([[[1., 2.], [2., 1.]], [[3., 4.], [4., 5.]], [[1., 2.], [3., 4.]]]) result = self.space.is_symmetric(points) expected = [True, True, False] self.assertAllClose(result, expected) @geomstats.tests.np_and_pytorch_only def test_make_symmetric(self): sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.to_symmetric(sym_mat) expected = sym_mat self.assertAllClose(result, expected) mat = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.to_symmetric(mat) expected = gs.array([[1., 1., 3.], [1., 0., 0.5], [3., 0.5, 1.]]) self.assertAllClose(result, expected) mat = gs.array([[1e100, 1e-100, 1e100], [1e100, 1e-100, 1e100], [1e-100, 1e-100, 1e100]]) result = self.space.to_symmetric(mat) res = 0.5 * (1e100 + 1e-100) expected = gs.array([[1e100, res, res], [res, 1e-100, res], [res, res, 1e100]]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_make_symmetric_and_is_symmetric_vectorization(self): points = gs.array([[[1., 2.], [3., 4.]], [[5., 6.], [4., 9.]]]) sym_points = self.space.to_symmetric(points) result = gs.all(self.space.is_symmetric(sym_points)) expected = True self.assertAllClose(result, expected) def test_inner_product(self): base_point = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector_1 = gs.array([[1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) tangent_vector_2 = gs.array([[1., 4., 3.], [5., 0., 0.], [3., 1., 1.]]) result = self.metric.inner_product(tangent_vector_1, tangent_vector_2, base_point=base_point) expected = gs.trace( gs.matmul(gs.transpose(tangent_vector_1), tangent_vector_2)) self.assertAllClose(result, expected) def test_cong(self): base_point = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector = gs.array([[1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) result = self.space.congruent(tangent_vector, base_point) expected = gs.matmul(tangent_vector, gs.transpose(base_point)) expected = gs.matmul(base_point, expected) self.assertAllClose(result, expected) def test_belongs(self): base_point = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.belongs(base_point) expected = True self.assertAllClose(result, expected)
def log(self, point, base_point, max_iter=30, tol=1e-6): """Compute the Riemannian logarithm of a point. Based on [ZR2017]_. References ---------- .. [ZR2017] Zimmermann, Ralf. "A Matrix-Algebraic Algorithm for the Riemannian Logarithm on the Stiefel Manifold under the Canonical Metric" SIAM J. Matrix Anal. & Appl., 38(2), 322–342, 2017. https://arxiv.org/pdf/1604.05054.pdf Parameters ---------- point : array-like, shape=[..., n, p] Point in the Stiefel manifold. base_point : array-like, shape=[..., n, p] Point in the Stiefel manifold. max_iter: int Maximum number of iterations to perform during the algorithm. Optional, default: 30. tol: float Tolerance to reach convergence. The matrix 2-norm is used as criterion. Optional, default: 1e-6. Returns ------- log : array-like, shape=[..., dim + 1] Tangent vector at the base point equal to the Riemannian logarithm of point at the base point. """ p = base_point.shape[-1] transpose_base_point = Matrices.transpose(base_point) matrix_m = gs.matmul(transpose_base_point, point) matrix_q, matrix_n = StiefelCanonicalMetric._normal_component_qr( point, base_point, matrix_m) matrix_v = StiefelCanonicalMetric._orthogonal_completion( matrix_m, matrix_n) matrix_v = StiefelCanonicalMetric._procrustes_preprocessing( p, matrix_v, matrix_m, matrix_n) for _ in range(max_iter): matrix_lv = gs.linalg.logm(matrix_v) matrix_c = matrix_lv[:, p:2 * p, p:2 * p] norm_matrix_c = gs.linalg.norm(matrix_c) if gs.less_equal(norm_matrix_c, tol): break matrix_phi = gs.linalg.expm(-matrix_c) aux_matrix = gs.matmul( matrix_v[:, :, p:2 * p], matrix_phi) matrix_v = gs.concatenate( [matrix_v[:, :, 0:p], aux_matrix], axis=2) matrix_xv = gs.matmul(base_point, matrix_lv[:, 0:p, 0:p]) matrix_qv = gs.matmul(matrix_q, matrix_lv[:, p:2 * p, 0:p]) return matrix_xv + matrix_qv