def exp(self, tangent_vec, base_point, n_steps=N_STEPS, step='euler'): """Exponential map associated to the affine connection. Exponential map at base_point of tangent_vec computed by integration of the geodesic equation (initial value problem), using the christoffel symbols Parameters ---------- tangent_vec : array-like, shape=[n_samples, dimension] Tangent vector at the base point. base_point : array-like, shape=[n_samples, dimension] Point on the manifold. n_steps : int The number of discrete time steps to take in the integration. step : str, {'euler', 'rk4'} The numerical scheme to use for integration. Returns ------- exp : array-like, shape=[n_samples, dimension] Point on the manifold. """ tangent_vec = gs.to_ndarray(tangent_vec, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) initial_state = (base_point, tangent_vec) flow, _ = integrate(self.geodesic_equation, initial_state, n_steps=n_steps, step=step) return flow[-1]
def exp(self, tangent_vec, base_point, n_steps=N_STEPS, step='euler', point_type=None): """Exponential map associated to the affine connection. Exponential map at base_point of tangent_vec computed by integration of the geodesic equation (initial value problem), using the christoffel symbols Parameters ---------- tangent_vec : array-like, shape=[..., dim] Tangent vector at the base point. base_point : array-like, shape=[..., dim] Point on the manifold. n_steps : int Number of discrete time steps to take in the integration. Optional, default: N_STEPS. step : str, {'euler', 'rk4'} The numerical scheme to use for integration. Optional, default: 'euler'. point_type : str, {'vector', 'matrix'} Type of representation used for points. Optional, default: None. Returns ------- exp : array-like, shape=[..., dim] Point on the manifold. """ initial_state = (base_point, tangent_vec) flow, _ = integrate( self.geodesic_equation, initial_state, n_steps=n_steps, step=step) exp = flow[-1] return exp
def test_integrator(self): initial_state = self.euclidean.random_point(2) def function(_, velocity): return velocity, gs.zeros_like(velocity) for step in ['euler', 'rk4']: flow, _ = integrator.integrate(function, initial_state, step=step) result = flow[-1] expected = initial_state[0] + initial_state[1] self.assertAllClose(result, expected)
def test_integrator(self): initial_state = self.euclidean.random_point(2) def function(state, _time): _, velocity = state return gs.stack([velocity, gs.zeros_like(velocity)]) for step in ["euler", "rk2", "rk4"]: flow = integrator.integrate(function, initial_state, step=step) result = flow[-1][0] expected = initial_state[0] + initial_state[1] self.assertAllClose(result, expected)
def parallel_transport( self, tangent_vec_a, base_point, tangent_vec_b=None, end_point=None, n_steps=10, step="rk4", ): r"""Compute the parallel transport of a tangent vec along a geodesic. Approximation of the solution of the parallel transport of a tangent vector a along the geodesic defined by :math:`t \mapsto exp_{( base\_point)}(t* tangent\_vec\_b)`. The parallel transport equation is formulated in this case in [TP2021]_. Parameters ---------- tangent_vec_a : array-like, shape=[..., n, n] Tangent vector at `base_point` to transport. tangent_vec_b : array-like, shape=[..., n, n] Tangent vector ar `base_point`, initial velocity of the geodesic to transport along. base_point : array-like, shape=[..., n, n] Initial point of the geodesic. end_point : array-like, shape=[..., n, n] Point to transport to. Optional, default: None. n_steps : int Number of steps to use to approximate the solution of the ordinary differential equation. Optional, default: 100 step : str, {'euler', 'rk2', 'rk4'} Scheme to use in the integration scheme. Optional, default: 'rk4'. Returns ------- transported : array-like, shape=[..., n, n] Transported tangent vector at `exp_(base_point)(tangent_vec_b)`. References ---------- .. [TP2021] Yann Thanwerdas, Xavier Pennec. O(n)-invariant Riemannian metrics on SPD matrices. 2021. ⟨hal-03338601v2⟩ See Also -------- Integration module: geomstats.integrator """ if end_point is None: end_point = self.exp(tangent_vec_b, base_point) horizontal_lift_a = gs.linalg.solve_sylvester(base_point, base_point, tangent_vec_a) square_root_bp, inverse_square_root_bp = SymmetricMatrices.powerm( base_point, [0.5, -0.5]) end_point_lift = Matrices.mul(square_root_bp, end_point, square_root_bp) square_root_lift = SymmetricMatrices.powerm(end_point_lift, 0.5) horizontal_velocity = gs.matmul(inverse_square_root_bp, square_root_lift) partial_horizontal_velocity = Matrices.mul(horizontal_velocity, square_root_bp) partial_horizontal_velocity += Matrices.transpose( partial_horizontal_velocity) def force(state, time): horizontal_geodesic_t = ( 1 - time) * square_root_bp + time * horizontal_velocity geodesic_t = ((1 - time)**2 * base_point + time * (1 - time) * partial_horizontal_velocity + time**2 * end_point) align = Matrices.mul( horizontal_geodesic_t, Matrices.transpose(horizontal_velocity - square_root_bp), state, ) right = align + Matrices.transpose(align) return gs.linalg.solve_sylvester(geodesic_t, geodesic_t, -right) flow = integrate(force, horizontal_lift_a, n_steps=n_steps, step=step) final_align = Matrices.mul(end_point, flow[-1]) return final_align + Matrices.transpose(final_align)
def exp(self, tangent_vec, base_point=None, n_steps=10, step='rk4', **kwargs): r"""Compute Riemannian exponential of tan. vector wrt to base point. If :math: `\gamma` is a geodesic, then it satisfies the Euler-Poincare equation [Kolev]_: .. math: \dot{\gamma}(t) = (dL_{\gamma(t)}) X(t) \dot{X}(t) = ad^*_{X(t)}X(t) where :math: `ad^*` is the dual adjoint map with respect to the metric. For a right-invariant metric, :math: `dR` is used instead of :math: `dL` and :math: `ad^*` is replaced by :math: `-ad^*`. The exponential map is approximated by numerical integration of this equation, with initial conditions :math: `\dot{\gamma}(0)` given by the argument `tangent_vec` and :math: `\gamma(0)` by `base_point`. A Runge-Kutta scheme of order 2 or 4 is used for integration. Parameters ---------- tangent_vec : array-like, shape=[..., n, n] Tangent vector at a base point. base_point : array-like, shape=[..., n, n] Point in the group. Optional, defaults to identity if None. n_steps : int, Number of integration steps. Optional, default : 15. step : str, {'euler', 'rk2', 'rk4'} Scheme to use in the integration. Optional, default : 'rk4'. Returns ------- exp : array-like, shape=[..., n, n] Point in the group equal to the Riemannian exponential of tangent_vec at the base point. References ---------- https://en.wikipedia.org/wiki/Runge–Kutta_methods .. [Kolev] Kolev, Boris. “Lie Groups and Mechanics: An Introduction.” Journal of Nonlinear Mathematical Physics 11, no. 4, 2004: 480–98. https://doi.org/10.2991/jnmp.2004.11.4.5. """ group = self.group basis = self.orthonormal_basis(self.lie_algebra.basis) sign = 1. if self.left_or_right == 'left' else -1. def lie_acceleration(point, vector): """Compute the right-hand side of the geodesic equation.""" velocity = self.group.tangent_translation_map( point, left_or_right=self.left_or_right)(vector) coefficients = gs.array([self.structure_constant( vector, basis_vector, vector) for basis_vector in basis]) acceleration = gs.einsum('i...,ijk->...jk', coefficients, basis) return velocity, sign * acceleration if base_point is None: base_point = group.identity left_angular_vel = tangent_vec else: left_angular_vel = self.group.tangent_translation_map( base_point, left_or_right=self.left_or_right, inverse=True)(tangent_vec) initial_state = (base_point, group.regularize(left_angular_vel)) flow, _ = integrate(lie_acceleration, initial_state, n_steps=n_steps, step=step, **kwargs) return flow[-1]
def parallel_transport( self, tangent_vec, base_point, direction=None, end_point=None, n_steps=100, step="rk4", ): r"""Compute the parallel transport of a tangent vec along a geodesic. Approximation of the solution of the parallel transport of a tangent vector a 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=[..., k_landmarks, m_ambient] Tangent vector at `base_point` to transport. base_point : array-like, shape=[..., k_landmarks, m_ambient] Initial point of the geodesic to transport along. direction : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector ar `base_point`, initial velocity of the geodesic to transport along. Optional, default: None. end_point : array-like, shape=[..., k_landmarks, m_ambient] Point to transport to. Unused if `tangent_vec_b` is given. Optional, default: None. n_steps : int Number of steps to use to approximate the solution of the ordinary differential equation. Optional, default: 100. step : str, {'euler', 'rk2', 'rk4'} Scheme to use in the integration scheme. Optional, default: 'rk4'. Returns ------- transported : array-like, shape=[..., k_landmarks, m_ambient] Transported tangent vector at `exp_(base_point)(tangent_vec_b)`. References ---------- .. [GMTP21] Guigui, Nicolas, Elodie Maignant, Alain Trouvé, and Xavier Pennec. “Parallel Transport on Kendall Shape Spaces.” 5th conference on Geometric Science of Information, Paris 2021. Lecture Notes in Computer Science. Springer, 2021. https://hal.inria.fr/hal-03160677. See Also -------- Integration module: geomstats.integrator """ if direction is None: if end_point is not None: direction = 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.") horizontal_a = self.fiber_bundle.horizontal_projection( tangent_vec, base_point) horizontal_b = self.fiber_bundle.horizontal_projection( direction, base_point) def force(state, time): gamma_t = self.ambient_metric.exp(time * horizontal_b, base_point) speed = self.ambient_metric.parallel_transport( horizontal_b, base_point, time * horizontal_b) coef = self.inner_product(speed, state, gamma_t) normal = gs.einsum("...,...ij->...ij", coef, gamma_t) align = gs.matmul(Matrices.transpose(speed), state) right = align - Matrices.transpose(align) left = gs.matmul(Matrices.transpose(gamma_t), gamma_t) skew_ = gs.linalg.solve_sylvester(left, left, right) vertical_ = -gs.matmul(gamma_t, skew_) return vertical_ - normal flow = integrate(force, horizontal_a, n_steps=n_steps, step=step) return flow[-1]
def parallel_transport(self, tangent_vec_a, tangent_vec_b, base_point, n_steps=100, step="rk4"): r"""Compute the parallel transport of a tangent vec along a geodesic. Approximation of the solution of 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=[..., k, m] Tangent vector at `base_point` to transport. tangent_vec_b : array-like, shape=[..., k, m] Tangent vector ar `base_point`, initial velocity of the geodesic to transport along. base_point : array-like, shape=[..., k, m] Initial point of the geodesic. n_steps : int Number of steps to use to approximate the solution of the ordinary differential equation. Optional, default: 100 step : str, {'euler', 'rk2', 'rk4'} Scheme to use in the integration scheme. Optional, default: 'rk4'. Returns ------- transported : array-like, shape=[..., k, m] Transported tangent vector at `exp_(base_point)(tangent_vec_b)`. References ---------- [GMTP21]_ Guigui, Nicolas, Elodie Maignant, Alain Trouvé, and Xavier Pennec. “Parallel Transport on Kendall Shape Spaces.” 5th conference on Geometric Science of Information, Paris 2021. Lecture Notes in Computer Science. Springer, 2021. https://hal.inria.fr/hal-03160677. See Also -------- Integration module: geomstats.integrator """ horizontal_a = self.fiber_bundle.horizontal_projection( tangent_vec_a, base_point) horizontal_b = self.fiber_bundle.horizontal_projection( tangent_vec_b, base_point) def force(state, time): gamma_t = self.ambient_metric.exp(time * horizontal_b, base_point) speed = self.ambient_metric.parallel_transport( horizontal_b, time * horizontal_b, base_point) coef = self.inner_product(speed, state, gamma_t) normal = gs.einsum("...,...ij->...ij", coef, gamma_t) align = gs.matmul(Matrices.transpose(speed), state) right = align - Matrices.transpose(align) left = gs.matmul(Matrices.transpose(gamma_t), gamma_t) skew_ = gs.linalg.solve_sylvester(left, left, right) vertical_ = -gs.matmul(gamma_t, skew_) return vertical_ - normal flow = integrate(force, horizontal_a, n_steps=n_steps, step=step) return flow[-1]
def parallel_transport( self, tangent_vec, base_point, direction=None, end_point=None, n_steps=10, step="rk4", return_endpoint=False, ): r"""Compute the parallel transport of a tangent vec along a geodesic. Approximate solution for the parallel transport of a tangent vector a along the geodesic between two points `base_point` and `end_point` or alternatively defined by :math:`t\mapsto exp_(base_point)( t*direction)`. The parallel transport equation is written entirely in the Lie algebra and solved with an integration scheme. 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 manifold. direction : 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 manifold. Point to transport to. Unused if `tangent_vec_b` is given Optional, default: None n_steps : int Number of integration steps to take. Optional, default : 10. step : str, {'euler', 'rk2', 'rk4'} Scheme to use for the approximation of the solution of the ODE Optional, default : rk4 return_endpoint : bool Whether the end-point of the geodesic should be returned. Optional, default : False. Returns ------- transported_tangent_vec: array-like, shape=[..., n, n] Transported tangent vector at `exp_(base_point)(tangent_vec_b)`. end_point : array-like, shape=[..., n, n] `exp_(base_point)(tangent_vec_b)`, only returned if `return_endpoint` is set to `True`. See Also -------- geomstats.integrator References ---------- [GP21]_ Guigui, Nicolas, and Xavier Pennec. “A Reduced Parallel Transport Equation on Lie Groups with a Left-Invariant Metric.” 5th conference on Geometric Science of Information, Paris 2021. Springer. Lecture Notes in Computer Science. https://hal.inria.fr/hal-03154318. """ if direction 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." ) else: tangent_vec_b_ = direction group = self.group translation_map = group.tangent_translation_map( base_point, left_or_right=self.left_or_right, inverse=True ) left_angular_vel_a = group.to_tangent(translation_map(tangent_vec)) left_angular_vel_b = group.to_tangent(translation_map(tangent_vec_b_)) def acceleration(state, time): """Compute the right-hand-side of the parallel transport eq.""" omega, zeta = state[1:] gam_dot, omega_dot = self.geodesic_equation(state[:2], time) zeta_dot = -self.connection_at_identity(omega, zeta) return gs.stack([gam_dot, omega_dot, zeta_dot]) if (base_point.ndim == 2 or base_point.shape[0] == 1) and ( 3 in (tangent_vec.ndim, tangent_vec_b_.ndim) ): n_sample = ( tangent_vec.shape[0] if tangent_vec.ndim == 3 else tangent_vec_b_.shape[0] ) base_point = gs.stack([base_point] * n_sample) initial_state = gs.stack([base_point, left_angular_vel_b, left_angular_vel_a]) flow = integrate(acceleration, initial_state, n_steps=n_steps, step=step) gamma, _, zeta_t = flow[-1] transported = group.tangent_translation_map( gamma, left_or_right=self.left_or_right, inverse=False )(zeta_t) return (transported, gamma) if return_endpoint else transported