def test_exp_np(self): vec = Matrices.bracket(pi_2 * r_y, gs.array([p_xy, p_yz])) result = self.metric.exp(vec, gs.array([p_xy, p_yz])) expected = gs.array([p_yz, p_xy]) self.assertAllClose(result, expected) vec = Matrices.bracket(pi_2 * gs.array([r_y, r_z]), gs.array([p_xy, p_yz])) result = self.metric.exp(vec, gs.array([p_xy, p_yz])) expected = gs.array([p_yz, p_xz]) self.assertAllClose(result, expected)
def curvature_at_identity(self, tangent_vec_a, tangent_vec_b, tangent_vec_c): r"""Compute the curvature at identity. For three tangent vectors at identity :math: `x,y,z`, the curvature is defined by :math: `R(x, y)z = \nabla_{[x,y]}z - \nabla_x\nabla_y z + \nabla_y\nabla_x z`. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at identity. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at identity. tangent_vec_c : array-like, shape=[..., n, n] Tangent vector at identity. Returns ------- curvature : array-like, shape=[..., n, n] Tangent vector at identity. """ bracket = Matrices.bracket(tangent_vec_a, tangent_vec_b) bracket_term = self.connection_at_identity(bracket, tangent_vec_c) left_term = self.connection_at_identity( tangent_vec_a, self.connection_at_identity(tangent_vec_b, tangent_vec_c)) right_term = self.connection_at_identity( tangent_vec_b, self.connection_at_identity(tangent_vec_a, tangent_vec_c)) return bracket_term - left_term + right_term
def connection_at_identity(self, tangent_vec_a, tangent_vec_b): r"""Compute the Levi-Civita connection at identity. For two tangent vectors at identity :math: `x,y`, one can associate left (respectively right) invariant vector fields :math: `\tilde{x}, \tilde{y}`. Then the vector :math: `(\nabla_\tilde{x}(\tilde{x}))_{ Id}` is computed using the lie bracket and the dual adjoint map. This is a bilinear map that characterizes the connection [Gallier]_. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at identity. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at identity. Returns ------- nabla : array-like, shape=[..., n, n] Tangent vector at identity. References ---------- .. [Gallier] Gallier, Jean, and Jocelyn Quaintance. Differential Geometry and Lie Groups: A Computational Perspective. Geonger International Publishing, 2020. https://doi.org/10.1007/978-3-030-46040-2. """ sign = 1. if self.left_or_right == 'left' else -1. return sign / 2 * (Matrices.bracket(tangent_vec_a, tangent_vec_b) - self.dual_adjoint(tangent_vec_a, tangent_vec_b) - self.dual_adjoint(tangent_vec_b, tangent_vec_a))
def parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point): r"""Compute the parallel transport of a tangent vector. Closed-form solution for the parallel transport of a tangent vector a along the geodesic defined by :math: `t \mapsto exp_(base_point)(t* tangent_vec_b)`. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at base point to be transported. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at base point, along which the parallel transport is computed. base_point : array-like, shape=[..., n, n] Point on the Grassmann manifold. Returns ------- transported_tangent_vec: array-like, shape=[..., n, n] Transported tangent vector at `exp_(base_point)(tangent_vec_b)`. References ---------- .. [BZA20] Bendokat, Thomas, Ralf Zimmermann, and P.-A. Absil. “A Grassmann Manifold Handbook: Basic Geometry and Computational Aspects.” ArXiv:2011.13699 [Cs, Math], November 27, 2020. http://arxiv.org/abs/2011.13699. """ expm = gs.linalg.expm mul = Matrices.mul rot = Matrices.bracket(base_point, -tangent_vec_b) return mul(expm(rot), tangent_vec_a, expm(-rot))
def to_tangent(self, vector, base_point): """Project a vector to a tangent space of the manifold. Compute the bracket (commutator) of the base_point with the skew-symmetric part of vector. Parameters ---------- vector : array-like, shape=[..., n, n] Vector. base_point : array-like, shape=[..., n, n] Point on the manifold. Returns ------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point. """ sym = Matrices.to_symmetric(vector) return Matrices.bracket(base_point, Matrices.bracket(base_point, sym))
def exp_test_data(self): smoke_data = [ dict( n=3, k=2, tangent_vec=Matrices.bracket(pi_2 * r_y, gs.array([p_xy, p_yz])), base_point=gs.array([p_xy, p_yz]), expected=gs.array([p_yz, p_xy]), ), dict( n=3, k=2, tangent_vec=Matrices.bracket(pi_2 * gs.array([r_y, r_z]), gs.array([p_xy, p_yz])), base_point=gs.array([p_xy, p_yz]), expected=gs.array([p_yz, p_xz]), ), ] return self.generate_tests(smoke_data)
def parallel_transport(self, tangent_vec, base_point, tangent_vec_b=None, end_point=None): r"""Compute the parallel transport of a tangent vector. Closed-form solution for the parallel transport of a tangent vector along the geodesic between two points `base_point` and `end_point` or alternatively defined by :math:`t\mapsto exp_(base_point)( t*direction)`. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point to be transported. base_point : array-like, shape=[..., n, n] Point on the Grassmann manifold. Point to transport from. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at base point, along which the parallel transport is computed. Optional, default: None end_point : array-like, shape=[..., n, n] Point on the Grassmann manifold to transport to. Unused if `tangent_vec_b` is given. Optional, default: None Returns ------- transported_tangent_vec: array-like, shape=[..., n, n] Transported tangent vector at `exp_(base_point)(tangent_vec_b)`. References ---------- .. [BZA20] Bendokat, Thomas, Ralf Zimmermann, and P.-A. Absil. “A Grassmann Manifold Handbook: Basic Geometry and Computational Aspects.” ArXiv:2011.13699 [Cs, Math], November 27, 2020. https://arxiv.org/abs/2011.13699. """ if tangent_vec_b is None: if end_point is not None: tangent_vec_b = self.log(end_point, base_point) else: raise ValueError( "Either an end_point or a tangent_vec_b must be given to define the" " geodesic along which to transport.") expm = gs.linalg.expm mul = Matrices.mul rot = -Matrices.bracket(base_point, tangent_vec_b) return mul(expm(rot), tangent_vec, expm(-rot))
def exp(self, tangent_vec, base_point, **kwargs): """Exponentiate the invariant vector field v from base point p. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at base point. `tangent_vec` is the bracket of a skew-symmetric matrix with the base_point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- exp : array-like, shape=[..., n, n] Riemannian exponential. """ expm = gs.linalg.expm mul = Matrices.mul rot = Matrices.bracket(base_point, -tangent_vec) return mul(expm(rot), base_point, expm(-rot))
def structure_constant(self, tangent_vec_a, tangent_vec_b, tangent_vec_c): r"""Compute the structure constant of the metric. For three tangent vectors :math: `x, y, z` at identity, compute :math: `<[x,y], z>`. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at identity. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector at identity. tangent_vec_c : array-like, shape=[..., n, n] Tangent vector at identity. Returns ------- structure_constant : array-like, shape=[...,] """ bracket = Matrices.bracket(tangent_vec_a, tangent_vec_b) return self.inner_product_at_identity(bracket, tangent_vec_c)
def log(self, point, base_point, **kwargs): r"""Compute the Riemannian logarithm of point w.r.t. base_point. Given :math:`P, P'` in Gr(n, k) the logarithm from :math:`P` to :math:`P` is induced by the infinitesimal rotation [Batzies2015]_: .. math:: Y = \frac 1 2 \log \big((2 P' - 1)(2 P - 1)\big) The tangent vector :math:`X` at :math:`P` is then recovered by :math:`X = [Y, P]`. Parameters ---------- point : array-like, shape=[..., n, n] Point. base_point : array-like, shape=[..., n, n] Base point. Returns ------- tangent_vec : array-like, shape=[..., n, n] Riemannian logarithm, a tangent vector at `base_point`. References ---------- .. [Batzies2015] Batzies, Hüper, Machado, Leite. "Geometric Mean and Geodesic Regression on Grassmannians" Linear Algebra and its Applications, 466, 83-101, 2015. """ GLn = GeneralLinear(self.n) id_n = GLn.identity id_n, point, base_point = gs.convert_to_wider_dtype( [id_n, point, base_point]) sym2 = 2 * point - id_n sym1 = 2 * base_point - id_n rot = GLn.compose(sym2, sym1) return Matrices.bracket(GLn.log(rot) / 2, base_point)
class TestMatrices(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.m = 2 self.n = 3 self.space = Matrices(m=self.n, n=self.n) self.space_nonsquare = Matrices(m=self.m, 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])) def test_is_symmetric(self): not_squared = gs.array([[1., 2.], [2., 1.], [3., 1.]]) result = self.space.is_symmetric(not_squared) expected = False self.assertAllClose(result, expected) 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_square = gs.zeros((self.n, self.n)) base_point_nonsquare = gs.zeros((self.m, self.n)) result = self.space.belongs(base_point_square) expected = True self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_square) expected = False self.assertAllClose(result, expected) result = self.space.belongs(base_point_nonsquare) expected = False self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_nonsquare) expected = True self.assertAllClose(result, expected) result = self.space.belongs(gs.zeros((2, 2, 3))) self.assertFalse(gs.all(result)) result = self.space.belongs(gs.zeros((2, 3, 3))) self.assertTrue(gs.all(result)) def test_is_diagonal(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.is_diagonal(base_point) expected = False self.assertAllClose(result, expected) diagonal = gs.eye(3) result = self.space.is_diagonal(diagonal) self.assertTrue(result) base_point = gs.stack([base_point, diagonal]) result = self.space.is_diagonal(base_point) expected = gs.array([False, True]) self.assertAllClose(result, expected) base_point = gs.stack([diagonal] * 2) result = self.space.is_diagonal(base_point) self.assertTrue(gs.all(result)) base_point = gs.reshape(gs.arange(6), (2, 3)) result = self.space.is_diagonal(base_point) self.assertTrue(~result) def test_norm(self): for n_samples in [1, 2]: mat = self.space.random_point(n_samples) result = self.metric.norm(mat) expected = self.space.frobenius_product(mat, mat) ** .5 self.assertAllClose(result, expected)
class TestMatricesMethods(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_and_tf_only def test_is_symmetric_vectorization(self): points = gs.array([[[1., 2.], [2., 1.]], [[3., 4.], [4., 5.]]]) result = gs.all(self.space.is_symmetric(points)) expected = True 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.make_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.make_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.make_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.make_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)) expected = helper.to_scalar(expected) self.assertAllClose(result, 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_log(self): expected = Matrices.bracket(pi_4 * r_y, p_xy) result = self.metric.log(self.metric.exp(expected, p_xy), p_xy) self.assertTrue(self.space.is_tangent(result, p_xy)) self.assertAllClose(result, expected)