use crate::error::{IntegrateResult, IntegrateResult as Result};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
pub trait LieAlgebra: Clone {
fn dim(&self) -> usize;
fn bracket(&self, other: &Self) -> Self;
fn from_vector(v: &ArrayView1<f64>) -> Self;
fn to_vector(&self) -> Array1<f64>;
fn norm(&self) -> f64;
}
pub trait ExponentialMap: Sized {
type Algebra: LieAlgebra;
fn exp(algebra: &Self::Algebra) -> Self;
fn log(&self) -> Self::Algebra;
fn multiply(&self, other: &Self) -> Self;
fn inverse(&self) -> Self;
fn identity(&self) -> Self;
}
pub struct LieGroupIntegrator<G: ExponentialMap> {
pub dt: f64,
pub method: LieGroupMethod,
_phantom: std::marker::PhantomData<G>,
}
#[derive(Debug, Clone, Copy)]
pub enum LieGroupMethod {
LieEuler,
LieMidpoint,
LieTrapezoidal,
RKMK4,
CrouchGrossman,
}
impl<G: ExponentialMap> LieGroupIntegrator<G> {
pub fn new(dt: f64, method: LieGroupMethod) -> Self {
Self {
dt,
method,
_phantom: std::marker::PhantomData,
}
}
pub fn step<F>(&self, g: &G, f: F) -> IntegrateResult<G>
where
F: Fn(&G) -> G::Algebra,
{
match self.method {
LieGroupMethod::LieEuler => self.lie_euler_step(g, f),
LieGroupMethod::LieMidpoint => self.lie_midpoint_step(g, f),
LieGroupMethod::LieTrapezoidal => self.lie_trapezoidal_step(g, f),
LieGroupMethod::RKMK4 => self.rkmk4_step(g, f),
LieGroupMethod::CrouchGrossman => self.crouch_grossman_step(g, f),
}
}
fn lie_euler_step<F>(&self, g: &G, f: F) -> IntegrateResult<G>
where
F: Fn(&G) -> G::Algebra,
{
let xi = f(g);
let mut scaled_xi = xi.to_vector();
scaled_xi *= self.dt;
let scaledalgebra = G::Algebra::from_vector(&scaled_xi.view());
Ok(g.multiply(&G::exp(&scaledalgebra)))
}
fn lie_midpoint_step<F>(&self, g: &G, f: F) -> IntegrateResult<G>
where
F: Fn(&G) -> G::Algebra,
{
let xi1 = f(g);
let mut half_xi = xi1.to_vector();
half_xi *= self.dt / 2.0;
let halfalgebra = G::Algebra::from_vector(&half_xi.view());
let g_mid = g.multiply(&G::exp(&halfalgebra));
let xi_mid = f(&g_mid);
let mut full_xi = xi_mid.to_vector();
full_xi *= self.dt;
let fullalgebra = G::Algebra::from_vector(&full_xi.view());
Ok(g.multiply(&G::exp(&fullalgebra)))
}
fn lie_trapezoidal_step<F>(&self, g: &G, f: F) -> IntegrateResult<G>
where
F: Fn(&G) -> G::Algebra,
{
let xi1 = f(g);
let mut full_xi = xi1.to_vector();
full_xi *= self.dt;
let fullalgebra = G::Algebra::from_vector(&full_xi.view());
let g_euler = g.multiply(&G::exp(&fullalgebra));
let xi2 = f(&g_euler);
let mut avg_xi = (xi1.to_vector() + xi2.to_vector()) / 2.0;
avg_xi *= self.dt;
let avgalgebra = G::Algebra::from_vector(&avg_xi.view());
Ok(g.multiply(&G::exp(&avgalgebra)))
}
fn rkmk4_step<F>(&self, g: &G, f: F) -> IntegrateResult<G>
where
F: Fn(&G) -> G::Algebra,
{
let k1 = f(g);
let mut exp_arg = k1.to_vector() * (self.dt / 2.0);
let exp_k1_2 = G::exp(&G::Algebra::from_vector(&exp_arg.view()));
let g2 = g.multiply(&exp_k1_2);
let k2 = f(&g2);
exp_arg = k2.to_vector() * (self.dt / 2.0);
let exp_k2_2 = G::exp(&G::Algebra::from_vector(&exp_arg.view()));
let g3 = g.multiply(&exp_k2_2);
let k3 = f(&g3);
exp_arg = k3.to_vector() * self.dt;
let exp_k3 = G::exp(&G::Algebra::from_vector(&exp_arg.view()));
let g4 = g.multiply(&exp_k3);
let k4 = f(&g4);
let combined =
(k1.to_vector() + k2.to_vector() * 2.0 + k3.to_vector() * 2.0 + k4.to_vector()) / 6.0;
let mut final_xi = combined * self.dt;
let comm = k1.bracket(&k2);
final_xi = final_xi + comm.to_vector() * (self.dt * self.dt / 12.0);
let finalalgebra = G::Algebra::from_vector(&final_xi.view());
Ok(g.multiply(&G::exp(&finalalgebra)))
}
fn crouch_grossman_step<F>(&self, g: &G, f: F) -> IntegrateResult<G>
where
F: Fn(&G) -> G::Algebra,
{
let c2 = 0.5;
let c3 = 1.0;
let b1 = 1.0 / 6.0;
let b2 = 2.0 / 3.0;
let b3 = 1.0 / 6.0;
let k1 = f(g);
let mut exp_arg = k1.to_vector() * (c2 * self.dt);
let y2 = g.multiply(&G::exp(&G::Algebra::from_vector(&exp_arg.view())));
let k2 = f(&y2);
exp_arg = k2.to_vector() * (c3 * self.dt);
let y3 = g.multiply(&G::exp(&G::Algebra::from_vector(&exp_arg.view())));
let k3 = f(&y3);
let combined = k1.to_vector() * b1 + k2.to_vector() * b2 + k3.to_vector() * b3;
exp_arg = combined * self.dt;
Ok(g.multiply(&G::exp(&G::Algebra::from_vector(&exp_arg.view()))))
}
}
#[derive(Debug, Clone)]
pub struct So3 {
pub omega: Array1<f64>,
}
impl LieAlgebra for So3 {
fn dim(&self) -> usize {
3
}
fn bracket(&self, other: &Self) -> Self {
let omega = Array1::from_vec(vec![
self.omega[1] * other.omega[2] - self.omega[2] * other.omega[1],
self.omega[2] * other.omega[0] - self.omega[0] * other.omega[2],
self.omega[0] * other.omega[1] - self.omega[1] * other.omega[0],
]);
So3 { omega }
}
fn from_vector(v: &ArrayView1<f64>) -> Self {
So3 {
omega: v.to_owned(),
}
}
fn to_vector(&self) -> Array1<f64> {
self.omega.clone()
}
fn norm(&self) -> f64 {
self.omega.dot(&self.omega).sqrt()
}
}
impl So3 {
pub fn to_matrix(&self) -> Array2<f64> {
let mut mat = Array2::zeros((3, 3));
mat[[0, 1]] = -self.omega[2];
mat[[0, 2]] = self.omega[1];
mat[[1, 0]] = self.omega[2];
mat[[1, 2]] = -self.omega[0];
mat[[2, 0]] = -self.omega[1];
mat[[2, 1]] = self.omega[0];
mat
}
}
#[derive(Debug, Clone)]
pub struct SO3 {
pub matrix: Array2<f64>,
}
impl ExponentialMap for SO3 {
type Algebra = So3;
fn exp(algebra: &Self::Algebra) -> Self {
let theta = algebra.norm();
if theta < 1e-10 {
SO3 {
matrix: Array2::eye(3) + algebra.to_matrix(),
}
} else {
let k = algebra.to_matrix() / theta;
let k2 = k.dot(&k);
SO3 {
matrix: Array2::eye(3) + k * theta.sin() + k2 * (1.0 - theta.cos()),
}
}
}
fn log(&self) -> Self::Algebra {
let trace = self.matrix[[0, 0]] + self.matrix[[1, 1]] + self.matrix[[2, 2]];
let theta = ((trace - 1.0) / 2.0).clamp(-1.0, 1.0).acos();
if theta.abs() < 1e-10 {
So3 {
omega: Array1::from_vec(vec![
self.matrix[[2, 1]] - self.matrix[[1, 2]],
self.matrix[[0, 2]] - self.matrix[[2, 0]],
self.matrix[[1, 0]] - self.matrix[[0, 1]],
]) / 2.0,
}
} else {
let factor = theta / (2.0 * theta.sin());
So3 {
omega: Array1::from_vec(vec![
self.matrix[[2, 1]] - self.matrix[[1, 2]],
self.matrix[[0, 2]] - self.matrix[[2, 0]],
self.matrix[[1, 0]] - self.matrix[[0, 1]],
]) * factor,
}
}
}
fn multiply(&self, other: &Self) -> Self {
SO3 {
matrix: self.matrix.dot(&other.matrix),
}
}
fn inverse(&self) -> Self {
SO3 {
matrix: self.matrix.t().to_owned(),
}
}
fn identity(&self) -> Self {
SO3 {
matrix: Array2::eye(3),
}
}
}
pub struct SO3Integrator {
base: LieGroupIntegrator<SO3>,
}
impl SO3Integrator {
pub fn new(dt: f64, method: LieGroupMethod) -> Self {
Self {
base: LieGroupIntegrator::new(dt, method),
}
}
pub fn integrate_rigid_body(
&self,
orientation: &SO3,
angular_velocity: &Array1<f64>,
inertia_tensor: &Array2<f64>,
external_torque: &Array1<f64>,
n_steps: usize,
) -> IntegrateResult<Vec<(SO3, Array1<f64>)>> {
let mut states = vec![(orientation.clone(), angular_velocity.clone())];
let mut current_orientation = orientation.clone();
let mut current_omega = angular_velocity.clone();
let inertia_inv = SO3Integrator::invertinertia(inertia_tensor)?;
for _ in 0..n_steps {
let omega_cross_i_omega =
SO3Integrator::cross_product(¤t_omega, &inertia_tensor.dot(¤t_omega));
let angular_accel = inertia_inv.dot(&(external_torque - &omega_cross_i_omega));
current_omega = ¤t_omega + &angular_accel * self.base.dt;
let omegaalgebra = So3 {
omega: current_omega.clone(),
};
current_orientation = self
.base
.step(¤t_orientation, |_| omegaalgebra.clone())?;
states.push((current_orientation.clone(), current_omega.clone()));
}
Ok(states)
}
fn cross_product(a: &Array1<f64>, b: &Array1<f64>) -> Array1<f64> {
Array1::from_vec(vec![
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
])
}
fn invertinertia(inertia: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
let det = inertia[[0, 0]]
* (inertia[[1, 1]] * inertia[[2, 2]] - inertia[[1, 2]] * inertia[[2, 1]])
- inertia[[0, 1]]
* (inertia[[1, 0]] * inertia[[2, 2]] - inertia[[1, 2]] * inertia[[2, 0]])
+ inertia[[0, 2]]
* (inertia[[1, 0]] * inertia[[2, 1]] - inertia[[1, 1]] * inertia[[2, 0]]);
if det.abs() < 1e-10 {
return Err(crate::error::IntegrateError::ValueError(
"Singular inertia tensor".to_string(),
));
}
let mut inv = Array2::zeros((3, 3));
inv[[0, 0]] = (inertia[[1, 1]] * inertia[[2, 2]] - inertia[[1, 2]] * inertia[[2, 1]]) / det;
inv[[0, 1]] = (inertia[[0, 2]] * inertia[[2, 1]] - inertia[[0, 1]] * inertia[[2, 2]]) / det;
inv[[0, 2]] = (inertia[[0, 1]] * inertia[[1, 2]] - inertia[[0, 2]] * inertia[[1, 1]]) / det;
inv[[1, 0]] = (inertia[[1, 2]] * inertia[[2, 0]] - inertia[[1, 0]] * inertia[[2, 2]]) / det;
inv[[1, 1]] = (inertia[[0, 0]] * inertia[[2, 2]] - inertia[[0, 2]] * inertia[[2, 0]]) / det;
inv[[1, 2]] = (inertia[[0, 2]] * inertia[[1, 0]] - inertia[[0, 0]] * inertia[[1, 2]]) / det;
inv[[2, 0]] = (inertia[[1, 0]] * inertia[[2, 1]] - inertia[[1, 1]] * inertia[[2, 0]]) / det;
inv[[2, 1]] = (inertia[[0, 1]] * inertia[[2, 0]] - inertia[[0, 0]] * inertia[[2, 1]]) / det;
inv[[2, 2]] = (inertia[[0, 0]] * inertia[[1, 1]] - inertia[[0, 1]] * inertia[[1, 0]]) / det;
Ok(inv)
}
}
#[derive(Debug, Clone)]
pub struct Se3 {
pub v: Array1<f64>,
pub omega: Array1<f64>,
}
impl LieAlgebra for Se3 {
fn dim(&self) -> usize {
6
}
fn bracket(&self, other: &Self) -> Self {
let omega_bracket = So3 {
omega: self.omega.clone(),
}
.bracket(&So3 {
omega: other.omega.clone(),
});
let v_bracket = self.cross_3d(&self.omega, &other.v) - self.cross_3d(&other.omega, &self.v);
Se3 {
v: v_bracket,
omega: omega_bracket.omega,
}
}
fn from_vector(v: &ArrayView1<f64>) -> Self {
Se3 {
v: v.slice(scirs2_core::ndarray::s![0..3]).to_owned(),
omega: v.slice(scirs2_core::ndarray::s![3..6]).to_owned(),
}
}
fn to_vector(&self) -> Array1<f64> {
let mut vec = Array1::zeros(6);
vec.slice_mut(scirs2_core::ndarray::s![0..3])
.assign(&self.v);
vec.slice_mut(scirs2_core::ndarray::s![3..6])
.assign(&self.omega);
vec
}
fn norm(&self) -> f64 {
(self.v.dot(&self.v) + self.omega.dot(&self.omega)).sqrt()
}
}
impl Se3 {
fn cross_3d(&self, a: &Array1<f64>, b: &Array1<f64>) -> Array1<f64> {
Array1::from_vec(vec![
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
])
}
}
#[derive(Debug, Clone)]
pub struct SE3 {
pub rotation: SO3,
pub translation: Array1<f64>,
}
impl ExponentialMap for SE3 {
type Algebra = Se3;
fn exp(algebra: &Self::Algebra) -> Self {
let omega_norm = algebra.omega.dot(&algebra.omega).sqrt();
let rotation = SO3::exp(&So3 {
omega: algebra.omega.clone(),
});
let translation = if omega_norm < 1e-10 {
algebra.v.clone()
} else {
let axis = &algebra.omega / omega_norm;
let axis_cross = So3 {
omega: axis.clone(),
}
.to_matrix();
let axis_cross2 = axis_cross.dot(&axis_cross);
let v_matrix = Array2::eye(3)
+ axis_cross * ((1.0 - omega_norm.cos()) / omega_norm)
+ axis_cross2 * ((omega_norm - omega_norm.sin()) / omega_norm);
v_matrix.dot(&algebra.v)
};
SE3 {
rotation,
translation,
}
}
fn log(&self) -> Self::Algebra {
let omegaalgebra = self.rotation.log();
let omega = &omegaalgebra.omega;
let omega_norm = omega.dot(omega).sqrt();
let v = if omega_norm < 1e-10 {
self.translation.clone()
} else {
let axis = omega / omega_norm;
let axis_cross = So3 {
omega: axis.clone(),
}
.to_matrix();
let axis_cross2 = axis_cross.dot(&axis_cross);
let cot_half = 1.0 / (omega_norm / 2.0).tan();
let v_inv = Array2::eye(3) - axis_cross / 2.0
+ axis_cross2 * (1.0 / omega_norm.powi(2)) * (1.0 - omega_norm / 2.0 * cot_half);
v_inv.dot(&self.translation)
};
Se3 {
v,
omega: omegaalgebra.omega,
}
}
fn multiply(&self, other: &Self) -> Self {
SE3 {
rotation: self.rotation.multiply(&other.rotation),
translation: &self.translation + &self.rotation.matrix.dot(&other.translation),
}
}
fn inverse(&self) -> Self {
let rotation_inv = self.rotation.inverse();
SE3 {
rotation: rotation_inv.clone(),
translation: -rotation_inv.matrix.dot(&self.translation),
}
}
fn identity(&self) -> Self {
SE3 {
rotation: SO3 {
matrix: Array2::eye(3),
},
translation: Array1::zeros(3),
}
}
}
pub struct SE3Integrator {
base: LieGroupIntegrator<SE3>,
}
impl SE3Integrator {
pub fn new(dt: f64, method: LieGroupMethod) -> Self {
Self {
base: LieGroupIntegrator::new(dt, method),
}
}
pub fn integrate_rigid_body_6dof(
&self,
pose: &SE3,
velocity: &Se3,
mass: f64,
inertia: &Array2<f64>,
forces: &Array1<f64>,
torques: &Array1<f64>,
n_steps: usize,
) -> IntegrateResult<Vec<(SE3, Se3)>> {
let mut states = vec![(pose.clone(), velocity.clone())];
let mut current_pose = pose.clone();
let mut current_velocity = velocity.clone();
for _ in 0..n_steps {
let linear_accel = forces / mass;
let angular_accel =
self.compute_angular_acceleration(¤t_velocity.omega, inertia, torques)?;
current_velocity.v = ¤t_velocity.v + &linear_accel * self.base.dt;
current_velocity.omega = ¤t_velocity.omega + &angular_accel * self.base.dt;
current_pose = self
.base
.step(¤t_pose, |_| current_velocity.clone())?;
states.push((current_pose.clone(), current_velocity.clone()));
}
Ok(states)
}
fn compute_angular_acceleration(
&self,
omega: &Array1<f64>,
inertia: &Array2<f64>,
torque: &Array1<f64>,
) -> IntegrateResult<Array1<f64>> {
let i_omega = inertia.dot(omega);
let omega_cross_i_omega = Array1::from_vec(vec![
omega[1] * i_omega[2] - omega[2] * i_omega[1],
omega[2] * i_omega[0] - omega[0] * i_omega[2],
omega[0] * i_omega[1] - omega[1] * i_omega[0],
]);
let inertia_inv = SO3Integrator::invertinertia(inertia)?;
Ok(inertia_inv.dot(&(torque - &omega_cross_i_omega)))
}
}
#[derive(Debug, Clone)]
pub struct Sln {
pub n: usize,
pub matrix: Array2<f64>,
}
impl LieAlgebra for Sln {
fn dim(&self) -> usize {
0 }
fn bracket(&self, other: &Self) -> Self {
let ab = self.matrix.dot(&other.matrix);
let ba = other.matrix.dot(&self.matrix);
Sln {
n: self.n,
matrix: ab - ba,
}
}
fn from_vector(v: &ArrayView1<f64>) -> Self {
let n = 2;
let mut matrix = Array2::zeros((n, n));
matrix[[0, 0]] = v[2];
matrix[[0, 1]] = v[0] - v[1];
matrix[[1, 0]] = v[0] + v[1];
matrix[[1, 1]] = -v[2];
Sln { n, matrix }
}
fn to_vector(&self) -> Array1<f64> {
if self.n == 2 {
Array1::from_vec(vec![
(self.matrix[[1, 0]] + self.matrix[[0, 1]]) / 2.0,
(self.matrix[[1, 0]] - self.matrix[[0, 1]]) / 2.0,
self.matrix[[0, 0]],
])
} else {
let mut v = Vec::new();
for i in 0..self.n {
for j in 0..self.n {
if i != self.n - 1 || j != self.n - 1 {
v.push(self.matrix[[i, j]]);
}
}
}
Array1::from_vec(v)
}
}
fn norm(&self) -> f64 {
self.matrix.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
}
#[derive(Debug, Clone)]
pub struct SLn {
pub n: usize,
pub matrix: Array2<f64>,
}
impl ExponentialMap for SLn {
type Algebra = Sln;
fn exp(algebra: &Self::Algebra) -> Self {
let n = algebra.n;
let a = &algebra.matrix;
let norm = algebra.norm();
let s = (norm.log2().ceil() as i32).max(0);
let a_scaled = a / 2.0f64.powi(s);
let a2 = a_scaled.dot(&a_scaled);
let a4 = a2.dot(&a2);
let a6 = a4.dot(&a2);
let mut u = Array2::eye(n) * 1.0;
u = u + &a_scaled * 1.0;
u = u + &a2 * (1.0 / 2.0);
u = u + &a4 * (1.0 / 24.0);
u = u + &a6 * (1.0 / 720.0);
let mut v = Array2::eye(n) * 1.0;
v = v - &a_scaled * 1.0;
v = v + &a2 * (1.0 / 2.0);
v = v - &a4 * (1.0 / 24.0);
v = v + &a6 * (1.0 / 720.0);
let exp_a = Self::solve_linear_system(&v, &u).unwrap_or(Array2::eye(n));
let mut result = exp_a;
for _ in 0..s {
result = result.dot(&result);
}
SLn { n, matrix: result }
}
fn log(&self) -> Self::Algebra {
let n = self.n;
let i = Array2::<f64>::eye(n);
let a = &self.matrix - &i;
let mut log_m = a.clone();
let mut a_power = a.clone();
for k in 2..10 {
a_power = a_power.dot(&a);
if k % 2 == 0 {
log_m = log_m - &a_power / (k as f64);
} else {
log_m = log_m + &a_power / (k as f64);
}
}
Sln { n, matrix: log_m }
}
fn multiply(&self, other: &Self) -> Self {
SLn {
n: self.n,
matrix: self.matrix.dot(&other.matrix),
}
}
fn inverse(&self) -> Self {
SLn {
n: self.n,
matrix: Self::matrix_inverse(&self.matrix).unwrap_or(Array2::eye(self.n)),
}
}
fn identity(&self) -> Self {
let n = 2; SLn {
n,
matrix: Array2::eye(n),
}
}
}
impl SLn {
fn solve_linear_system(a: &Array2<f64>, b: &Array2<f64>) -> Option<Array2<f64>> {
let n = a.nrows();
let mut aug = Array2::zeros((n, 2 * n));
for i in 0..n {
for j in 0..n {
aug[[i, j]] = a[[i, j]];
aug[[i, n + j]] = b[[i, j]];
}
}
for k in 0..n {
let mut max_row = k;
for i in (k + 1)..n {
if aug[[i, k]].abs() > aug[[max_row, k]].abs() {
max_row = i;
}
}
for j in 0..2 * n {
let temp = aug[[k, j]];
aug[[k, j]] = aug[[max_row, j]];
aug[[max_row, j]] = temp;
}
for i in (k + 1)..n {
let factor = aug[[i, k]] / aug[[k, k]];
for j in k..2 * n {
aug[[i, j]] -= factor * aug[[k, j]];
}
}
}
let mut result = Array2::zeros((n, n));
for j in 0..n {
for i in (0..n).rev() {
let mut sum = aug[[i, n + j]];
for k in (i + 1)..n {
sum -= aug[[i, k]] * result[[k, j]];
}
result[[i, j]] = sum / aug[[i, i]];
}
}
Some(result)
}
fn matrix_inverse(a: &Array2<f64>) -> Option<Array2<f64>> {
let n = a.nrows();
Self::solve_linear_system(a, &Array2::eye(n))
}
}
#[derive(Debug, Clone)]
pub struct Sp2n {
pub n: usize,
pub matrix: Array2<f64>,
}
impl Sp2n {
pub fn omega_matrix(n: usize) -> Array2<f64> {
let mut omega = Array2::zeros((2 * n, 2 * n));
for i in 0..n {
omega[[i, n + i]] = 1.0;
omega[[n + i, i]] = -1.0;
}
omega
}
pub fn is_hamiltonian(&self) -> bool {
let omega = Self::omega_matrix(self.n);
let test = &self.matrix.t().dot(&omega) + &omega.dot(&self.matrix);
test.iter().all(|&x| x.abs() < 1e-10)
}
}
impl LieAlgebra for Sp2n {
fn dim(&self) -> usize {
0 }
fn bracket(&self, other: &Self) -> Self {
let ab = self.matrix.dot(&other.matrix);
let ba = other.matrix.dot(&self.matrix);
Sp2n {
n: self.n,
matrix: ab - ba,
}
}
fn from_vector(v: &ArrayView1<f64>) -> Self {
let n = 2;
let expected_size = n * n + n * (n + 1) / 2 + n * (n + 1) / 2;
if v.len() != expected_size {
let len = v.len() as f64;
let n_float = (-1.0 + (1.0 + 8.0 * len).sqrt()) / 4.0;
let n = n_float.round() as usize;
if n * (2 * n + 1) != v.len() {
let n = 2;
let mut matrix = Array2::zeros((2 * n, 2 * n));
let available = v.len().min(n * n);
for i in 0..n {
for j in 0..n {
let idx = i * n + j;
if idx < available {
matrix[[i, j]] = v[idx];
}
}
}
return Sp2n { n, matrix };
}
}
let n = 2; let mut matrix = Array2::zeros((2 * n, 2 * n));
let mut offset = 0;
for i in 0..n {
for j in 0..n {
matrix[[i, j]] = v[offset];
offset += 1;
}
}
for i in 0..n {
for j in i..n {
let val = v[offset];
matrix[[i, n + j]] = val;
if i != j {
matrix[[j, n + i]] = val; }
offset += 1;
}
}
for i in 0..n {
for j in i..n {
let val = v[offset];
matrix[[n + i, j]] = val;
if i != j {
matrix[[n + j, i]] = val; }
offset += 1;
}
}
for i in 0..n {
for j in 0..n {
matrix[[n + i, n + j]] = -matrix[[j, i]];
}
}
Sp2n { n, matrix }
}
fn to_vector(&self) -> Array1<f64> {
let n = self.n;
let mut v = Vec::new();
for i in 0..n {
for j in 0..n {
v.push(self.matrix[[i, j]]);
}
}
for i in 0..n {
for j in i..n {
v.push(self.matrix[[i, n + j]]);
}
}
for i in 0..n {
for j in i..n {
v.push(self.matrix[[n + i, j]]);
}
}
Array1::from_vec(v)
}
fn norm(&self) -> f64 {
self.matrix.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
}
#[derive(Debug, Clone)]
pub struct HeisenbergAlgebra {
pub p: f64,
pub q: f64,
pub z: f64,
}
impl LieAlgebra for HeisenbergAlgebra {
fn dim(&self) -> usize {
3
}
fn bracket(&self, other: &Self) -> Self {
HeisenbergAlgebra {
p: 0.0,
q: 0.0,
z: self.p * other.q - self.q * other.p,
}
}
fn from_vector(v: &ArrayView1<f64>) -> Self {
HeisenbergAlgebra {
p: v[0],
q: v[1],
z: v[2],
}
}
fn to_vector(&self) -> Array1<f64> {
Array1::from_vec(vec![self.p, self.q, self.z])
}
fn norm(&self) -> f64 {
(self.p * self.p + self.q * self.q + self.z * self.z).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct HeisenbergGroup {
pub p: f64,
pub q: f64,
pub z: f64,
}
impl ExponentialMap for HeisenbergGroup {
type Algebra = HeisenbergAlgebra;
fn exp(algebra: &Self::Algebra) -> Self {
HeisenbergGroup {
p: algebra.p,
q: algebra.q,
z: algebra.z + algebra.p * algebra.q / 2.0,
}
}
fn log(&self) -> Self::Algebra {
HeisenbergAlgebra {
p: self.p,
q: self.q,
z: self.z - self.p * self.q / 2.0,
}
}
fn multiply(&self, other: &Self) -> Self {
HeisenbergGroup {
p: self.p + other.p,
q: self.q + other.q,
z: self.z + other.z + self.p * other.q,
}
}
fn inverse(&self) -> Self {
HeisenbergGroup {
p: -self.p,
q: -self.q,
z: -self.z,
}
}
fn identity(&self) -> Self {
HeisenbergGroup {
p: 0.0,
q: 0.0,
z: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct GLn {
pub n: usize,
pub matrix: Array2<f64>,
}
impl ExponentialMap for GLn {
type Algebra = Gln;
fn exp(algebra: &Self::Algebra) -> Self {
let n = algebra.n;
let exp_matrix = matrix_exponential(&algebra.matrix);
GLn {
n,
matrix: exp_matrix,
}
}
fn log(&self) -> Self::Algebra {
let log_matrix = matrix_logarithm(&self.matrix);
Gln {
n: self.n,
matrix: log_matrix,
}
}
fn multiply(&self, other: &Self) -> Self {
GLn {
n: self.n,
matrix: self.matrix.dot(&other.matrix),
}
}
fn inverse(&self) -> Self {
GLn {
n: self.n,
matrix: SLn::matrix_inverse(&self.matrix).unwrap_or(Array2::eye(self.n)),
}
}
fn identity(&self) -> Self {
let n = 2; GLn {
n,
matrix: Array2::eye(n),
}
}
}
#[derive(Debug, Clone)]
pub struct Gln {
pub n: usize,
pub matrix: Array2<f64>,
}
impl LieAlgebra for Gln {
fn dim(&self) -> usize {
0 }
fn bracket(&self, other: &Self) -> Self {
let ab = self.matrix.dot(&other.matrix);
let ba = other.matrix.dot(&self.matrix);
Gln {
n: self.n,
matrix: ab - ba,
}
}
fn from_vector(v: &ArrayView1<f64>) -> Self {
let n = (v.len() as f64).sqrt() as usize;
let matrix = Array2::from_shape_vec((n, n), v.to_vec()).expect("Operation failed");
Gln { n, matrix }
}
fn to_vector(&self) -> Array1<f64> {
Array1::from_vec(self.matrix.as_slice().expect("Operation failed").to_vec())
}
fn norm(&self) -> f64 {
self.matrix.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
}
#[allow(dead_code)]
fn matrix_exponential(a: &Array2<f64>) -> Array2<f64> {
let n = a.nrows();
let mut result = Array2::eye(n);
let mut term = Array2::eye(n);
for k in 1..20 {
term = term.dot(a) / (k as f64);
result += &term;
if term.iter().map(|&x| x.abs()).sum::<f64>() < 1e-12 {
break;
}
}
result
}
#[allow(dead_code)]
fn matrix_logarithm(a: &Array2<f64>) -> Array2<f64> {
let n = a.nrows();
let i = Array2::<f64>::eye(n);
let x = a - &i;
let mut result = Array2::zeros((n, n));
let mut term = x.clone();
for k in 1..20 {
if k % 2 == 1 {
result = result + &term / (k as f64);
} else {
result = result - &term / (k as f64);
}
term = term.dot(&x);
if term.iter().map(|&x| x.abs()).sum::<f64>() < 1e-12 {
break;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_so3_exp_log() {
let omega = Array1::from_vec(vec![0.1, 0.2, 0.3]);
let algebra = So3 { omega };
let group = SO3::exp(&algebra);
let algebra_recovered = group.log();
for i in 0..3 {
assert_relative_eq!(
algebra.omega[i],
algebra_recovered.omega[i],
epsilon = 1e-10
);
}
}
#[test]
fn test_rigid_body_energy_conservation() {
let dt = 0.01;
let integrator = SO3Integrator::new(dt, LieGroupMethod::RKMK4);
let orientation = SO3 {
matrix: Array2::eye(3),
};
let angular_velocity = Array1::from_vec(vec![0.1, 0.5, 0.3]);
let inertia =
Array2::from_shape_vec((3, 3), vec![2.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 4.0])
.expect("Operation failed");
let external_torque = Array1::zeros(3);
let initial_energy = 0.5 * angular_velocity.dot(&inertia.dot(&angular_velocity));
let states = integrator
.integrate_rigid_body(
&orientation,
&angular_velocity,
&inertia,
&external_torque,
100,
)
.expect("Operation failed");
let (_, final_omega) = &states.last().expect("Operation failed");
let final_energy = 0.5 * final_omega.dot(&inertia.dot(final_omega));
assert_relative_eq!(initial_energy, final_energy, epsilon = 1e-4);
}
}