use core::fmt;
use super::{AccelUnbiased, Gravity, GyroUnbiased, ImuState};
use crate::{
dtype,
linalg::{Matrix, Matrix3, Numeric, SupersetOf, Vector3},
variables::{ImuBias, MatrixLieGroup, SO3, Variable},
};
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct ImuDelta<T: Numeric = dtype> {
pub(crate) dt: T,
pub(crate) xi_theta: Vector3<T>,
pub(crate) xi_vel: Vector3<T>,
pub(crate) xi_pos: Vector3<T>,
bias_init: ImuBias<T>,
h_bias_accel: Matrix<9, 3, T>,
h_bias_gyro: Matrix<9, 3, T>,
gravity: Gravity<T>,
}
impl<T: Numeric> ImuDelta<T> {
pub(crate) fn new(gravity: Gravity<T>, bias_init: ImuBias<T>) -> Self {
Self {
dt: T::from(0.0),
xi_theta: Vector3::zeros(),
xi_vel: Vector3::zeros(),
xi_pos: Vector3::zeros(),
bias_init,
h_bias_accel: Matrix::zeros(),
h_bias_gyro: Matrix::zeros(),
gravity,
}
}
pub fn bias_init(&self) -> &ImuBias<T> {
&self.bias_init
}
pub(crate) fn first_order_update(&self, bias_diff: &ImuBias<T>, idx: usize) -> Vector3<T> {
self.h_bias_accel.fixed_rows(idx) * bias_diff.accel()
+ self.h_bias_gyro.fixed_rows(idx) * bias_diff.gyro()
}
pub(crate) fn predict(&self, x1: &ImuState<T>) -> ImuState<T> {
let ImuState { r, v, p, bias } = x1;
let bias_diff = bias - &self.bias_init;
let xi_theta = self.xi_theta + self.first_order_update(&bias_diff, 0);
let xi_v = self.xi_vel + self.first_order_update(&bias_diff, 3);
let xi_p = self.xi_pos + self.first_order_update(&bias_diff, 6);
let r2_meas = r.compose(&SO3::exp(xi_theta.as_view()));
let v2_meas = v + self.gravity.0 * self.dt + r.apply(xi_v.as_view());
let p2_meas = p
+ v * self.dt
+ self.gravity.0 * self.dt * self.dt * T::from(0.5)
+ r.apply(xi_p.as_view());
let b2_meas = bias.clone();
ImuState {
r: r2_meas,
v: v2_meas,
p: p2_meas,
bias: b2_meas,
}
}
#[allow(non_snake_case)]
pub(crate) fn integrate(
&mut self,
gyro: &GyroUnbiased<T>,
accel: &AccelUnbiased<T>,
dt: T,
) -> Matrix<15, 15, T> {
let A = self.A(gyro, accel, dt);
self.dt += dt;
let accel_world = SO3::exp(self.xi_theta.as_view()).apply(accel.0.as_view());
let H = SO3::dexp_right(self.xi_theta.as_view());
let Hinv = H.try_inverse().expect("Failed to invert H(theta)");
self.xi_pos += self.xi_vel * dt + accel_world * (dt * dt * 0.5);
self.xi_vel += accel_world * dt;
self.xi_theta += Hinv * gyro.0 * dt;
self.propagate_H(&A);
A
}
#[allow(non_snake_case)]
fn propagate_H(&mut self, A: &Matrix<15, 15, T>) {
let Amini = A.fixed_view::<9, 9>(0, 0);
let Bgyro = A.fixed_view::<9, 3>(0, 9);
let Baccel = A.fixed_view::<9, 3>(0, 12);
self.h_bias_gyro = Amini * self.h_bias_gyro + Bgyro;
self.h_bias_accel = Amini * self.h_bias_accel + Baccel;
}
#[allow(non_snake_case)]
fn A(&self, gyro: &GyroUnbiased<T>, accel: &AccelUnbiased<T>, dt: T) -> Matrix<15, 15, T> {
let H = SO3::dexp_right(self.xi_theta.as_view());
let Hinv = H.try_inverse().expect("Failed to invert H(theta)");
let R: nalgebra::Matrix<
T,
nalgebra::Const<3>,
nalgebra::Const<3>,
nalgebra::ArrayStorage<T, 3, 3>,
> = SO3::exp(self.xi_theta.as_view()).to_matrix();
let mut A = Matrix::<15, 15, T>::identity();
let M = Matrix3::<T>::identity() - SO3::hat(gyro.0.as_view()) * dt / T::from(2.0);
A.fixed_view_mut::<3, 3>(0, 0).copy_from(&M);
let mut M = -R * SO3::hat(accel.0.as_view()) * H * dt;
A.fixed_view_mut::<3, 3>(3, 0).copy_from(&M);
M *= dt / 2.0;
A.fixed_view_mut::<3, 3>(6, 0).copy_from(&M);
let M = Matrix3::<T>::identity() * dt;
A.fixed_view_mut::<3, 3>(6, 3).copy_from(&M);
let M = -Hinv * dt;
A.fixed_view_mut::<3, 3>(0, 9).copy_from(&M);
let mut M = -R * dt;
A.fixed_view_mut::<3, 3>(3, 12).copy_from(&M);
M *= dt / 2.0;
A.fixed_view_mut::<3, 3>(6, 12).copy_from(&M);
A
}
pub fn cast<TT: Numeric + SupersetOf<T>>(&self) -> ImuDelta<TT> {
ImuDelta {
dt: TT::from_subset(&self.dt),
xi_theta: self.xi_theta.cast(),
xi_vel: self.xi_vel.cast(),
xi_pos: self.xi_pos.cast(),
bias_init: self.bias_init.cast(),
h_bias_accel: self.h_bias_accel.cast(),
h_bias_gyro: self.h_bias_gyro.cast(),
gravity: Gravity(self.gravity.0.cast()),
}
}
}
impl<T: Numeric> fmt::Display for ImuDelta<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"ImuDelta(dt: {}, theta: {}, v: {}, p: {})",
self.dt, self.xi_theta, self.xi_vel, self.xi_pos
)
}
}
#[cfg(test)]
mod test {
use matrixcompare::{assert_matrix_eq, assert_scalar_eq};
use nalgebra::Const;
use super::*;
use crate::{
linalg::{Diff, DualVector, ForwardProp, Vector, VectorX},
residuals::{Accel, Gyro},
variables::VectorVar,
};
#[cfg(not(feature = "f32"))]
const TOL: f64 = 1e-5;
#[cfg(feature = "f32")]
const TOL: f32 = 1e-3;
fn integrate<T: Numeric>(
gyro: &Gyro<T>,
accel: &Accel<T>,
bias: &ImuBias<T>,
n: i32,
t: dtype,
) -> ImuDelta<T> {
let mut delta = ImuDelta::new(Gravity::up(), bias.clone());
let accel = accel.remove_bias(bias);
let gyro = gyro.remove_bias(bias);
let dt = T::from(t / n as dtype);
for _ in 0..n {
delta.integrate(&gyro, &accel, dt);
}
delta
}
#[test]
fn integrate_accel() {
let a = 5.0;
let t = 3.0;
let n = 100;
let accel = Accel(Vector3::new(a, 0.0, 0.0));
let gyro = Gyro(Vector3::zeros());
let delta = integrate(&gyro, &accel, &ImuBias::identity(), n, t);
println!("Delta: {delta}");
assert_scalar_eq!(delta.dt, t, comp = abs, tol = TOL);
assert_matrix_eq!(delta.xi_vel, accel.0 * t, comp = abs, tol = TOL);
assert_matrix_eq!(delta.xi_pos, accel.0 * t * t / 2.0, comp = abs, tol = TOL);
}
#[test]
fn integrate_gyro() {
let a = 5.0;
let t = 3.0;
let n = 100;
let accel = Accel(Vector3::zeros());
let gyro = Gyro(Vector3::new(0.0, 0.0, a));
let delta = integrate(&gyro, &accel, &ImuBias::identity(), n, t);
println!("Delta: {delta}");
assert_scalar_eq!(delta.dt, t, comp = abs, tol = TOL);
assert_matrix_eq!(delta.xi_theta, gyro.0 * t, comp = abs, tol = TOL);
}
#[test]
fn make_a() {
let dt = 0.1;
let v = Vector::<15>::from_fn(|i, _| i as dtype / 10.0);
let gyro = Gyro::new(3.0, 2.0, 1.0);
let accel: Accel = Accel::new(1.0, 2.0, 3.0);
fn delta_from_vec<T: Numeric>(v: Vector<15, T>) -> ImuDelta<T> {
let xi_theta = v.fixed_rows::<3>(0).into_owned();
let xi_vel = v.fixed_rows::<3>(3).into_owned();
let xi_pos = v.fixed_rows::<3>(6).into_owned();
let bias_init = ImuBias::new(
Gyro(v.fixed_rows::<3>(9).into_owned()),
Accel(v.fixed_rows::<3>(12).into_owned()),
);
ImuDelta {
dt: T::from(0.0),
xi_theta,
xi_vel,
xi_pos,
bias_init,
h_bias_accel: Matrix::zeros(),
h_bias_gyro: Matrix::zeros(),
gravity: Gravity::up(),
}
}
let f = |v: VectorVar<15, DualVector<Const<15>>>| {
let gyro = Gyro(gyro.0.map(|g| g.into()));
let accel = Accel(accel.0.map(|a| a.into()));
let mut delta = delta_from_vec(v.0);
let gyro = gyro.remove_bias(&delta.bias_init);
let accel = accel.remove_bias(&delta.bias_init);
delta.integrate(&gyro, &accel, DualVector::<Const<15>>::from(dt));
let mut out = VectorX::zeros(15);
out.fixed_rows_mut::<3>(0).copy_from(&delta.xi_theta);
out.fixed_rows_mut::<3>(3).copy_from(&delta.xi_vel);
out.fixed_rows_mut::<3>(6).copy_from(&delta.xi_pos);
out.fixed_rows_mut::<3>(9).copy_from(delta.bias_init.gyro());
out.fixed_rows_mut::<3>(12)
.copy_from(delta.bias_init.accel());
out
};
let vv: VectorVar<15> = VectorVar::from(v);
let a_exp = ForwardProp::<Const<15>>::jacobian_1(f, &vv).diff;
let delta = delta_from_vec(v);
let gyro = gyro.remove_bias(&delta.bias_init);
let accel = accel.remove_bias(&delta.bias_init);
let a_got = delta.A(&gyro, &accel, dt);
println!("A_exp: {a_exp:.4}");
println!("A_got: {a_got:.4}");
assert_matrix_eq!(
a_exp.fixed_view::<12, 3>(3, 0),
a_got.fixed_view::<12, 3>(3, 0),
comp = abs,
tol = 1e-3
);
assert_matrix_eq!(
a_exp.fixed_view::<15, 12>(0, 3),
a_got.fixed_view::<15, 12>(0, 3),
comp = abs,
tol = TOL
);
}
#[test]
#[allow(non_snake_case)]
fn propagate_h() {
let t = 1.0;
let n = 2;
let accel = Accel::new(1.0, 2.0, 3.0);
let gyro = Gyro::new(0.1, 0.2, 0.3);
let bias = ImuBias::new(Gyro::new(1.0, 2.0, 3.0), Accel::new(0.1, 0.2, 0.3));
let delta = integrate(&gyro, &accel, &bias, n, t);
let H_accel_got = delta.h_bias_accel;
let H_gyro_got = delta.h_bias_gyro;
let integrate_diff = |bias: ImuBias<DualVector<Const<6>>>| {
let accel = Accel(accel.0.map(|a| a.into()));
let gyro = Gyro(gyro.0.map(|g| g.into()));
let delta = integrate(&gyro, &accel, &bias, n, t);
let mut preint = VectorX::zeros(9);
preint.fixed_rows_mut::<3>(0).copy_from(&delta.xi_theta);
preint.fixed_rows_mut::<3>(3).copy_from(&delta.xi_vel);
preint.fixed_rows_mut::<3>(6).copy_from(&delta.xi_pos);
preint
};
let H_exp = ForwardProp::<Const<6>>::jacobian_1(integrate_diff, &bias).diff;
let H_gyro_exp = H_exp.fixed_view::<9, 3>(0, 0);
let H_accel_exp = H_exp.fixed_view::<9, 3>(0, 3);
println!("H_accel_got: {H_accel_got:.4}");
println!("H_accel_exp: {H_accel_exp:.4}");
assert_matrix_eq!(H_accel_got, H_accel_exp, comp = abs, tol = TOL);
println!("H_gyro_got: {H_gyro_got:.4}");
println!("H_gyro_exp: {H_gyro_exp:.4}");
assert_matrix_eq!(
H_gyro_got.fixed_view::<6, 3>(3, 0),
H_gyro_exp.fixed_view::<6, 3>(3, 0),
comp = abs,
tol = TOL
);
}
}