use super::{invalid_input, validate_nonnegative, validate_positive, InertialError};
pub const RANDOM_WALK_BIAS_TAU_S: f64 = f64::INFINITY;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImuGrade {
Mems,
Tactical,
Navigation,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImuSpec {
pub accel_vrw_mps_sqrt_s: f64,
pub gyro_arw_rad_sqrt_s: f64,
pub accel_bias_instab_mps2: f64,
pub gyro_bias_instab_rps: f64,
pub accel_bias_tau_s: f64,
pub gyro_bias_tau_s: f64,
pub accel_scale_instab_ppm: Option<f64>,
pub gyro_scale_instab_ppm: Option<f64>,
}
impl ImuSpec {
#[allow(clippy::too_many_arguments)]
pub const fn datasheet(
accel_vrw_mps_sqrt_s: f64,
gyro_arw_rad_sqrt_s: f64,
accel_bias_instab_mps2: f64,
gyro_bias_instab_rps: f64,
accel_bias_tau_s: f64,
gyro_bias_tau_s: f64,
accel_scale_instab_ppm: Option<f64>,
gyro_scale_instab_ppm: Option<f64>,
) -> Self {
Self {
accel_vrw_mps_sqrt_s,
gyro_arw_rad_sqrt_s,
accel_bias_instab_mps2,
gyro_bias_instab_rps,
accel_bias_tau_s,
gyro_bias_tau_s,
accel_scale_instab_ppm,
gyro_scale_instab_ppm,
}
}
pub const fn preset(grade: ImuGrade) -> Self {
match grade {
ImuGrade::Mems => Self::mems(),
ImuGrade::Tactical => Self::tactical(),
ImuGrade::Navigation => Self::navigation(),
}
}
pub const fn mems() -> Self {
Self::datasheet(
5.0e-2,
5.0e-3,
5.0e-2,
5.0e-4,
3_600.0,
3_600.0,
Some(500.0),
Some(500.0),
)
}
pub const fn tactical() -> Self {
Self::datasheet(
5.0e-3,
2.0e-4,
5.0e-3,
2.0e-5,
7_200.0,
7_200.0,
Some(50.0),
Some(50.0),
)
}
pub const fn navigation() -> Self {
Self::datasheet(
5.0e-4,
2.0e-5,
2.0e-4,
1.0e-6,
14_400.0,
14_400.0,
Some(5.0),
Some(5.0),
)
}
pub fn validate(&self) -> Result<(), InertialError> {
validate_nonnegative(self.accel_vrw_mps_sqrt_s, "accel_vrw_mps_sqrt_s")?;
validate_nonnegative(self.gyro_arw_rad_sqrt_s, "gyro_arw_rad_sqrt_s")?;
validate_nonnegative(self.accel_bias_instab_mps2, "accel_bias_instab_mps2")?;
validate_nonnegative(self.gyro_bias_instab_rps, "gyro_bias_instab_rps")?;
validate_bias_tau(self.accel_bias_tau_s, "accel_bias_tau_s")?;
validate_bias_tau(self.gyro_bias_tau_s, "gyro_bias_tau_s")?;
if let Some(scale) = self.accel_scale_instab_ppm {
validate_nonnegative(scale, "accel_scale_instab_ppm")?;
}
if let Some(scale) = self.gyro_scale_instab_ppm {
validate_nonnegative(scale, "gyro_scale_instab_ppm")?;
}
Ok(())
}
pub fn accel_bias_decay(&self, dt_s: f64) -> Result<f64, InertialError> {
gauss_markov_bias_decay(dt_s, self.accel_bias_tau_s)
}
pub fn gyro_bias_decay(&self, dt_s: f64) -> Result<f64, InertialError> {
gauss_markov_bias_decay(dt_s, self.gyro_bias_tau_s)
}
pub fn accel_bias_variance_increment(&self, dt_s: f64) -> Result<f64, InertialError> {
gauss_markov_bias_variance_increment(
self.accel_bias_instab_mps2,
dt_s,
self.accel_bias_tau_s,
)
}
pub fn gyro_bias_variance_increment(&self, dt_s: f64) -> Result<f64, InertialError> {
gauss_markov_bias_variance_increment(self.gyro_bias_instab_rps, dt_s, self.gyro_bias_tau_s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConingCorrection {
Off,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MechanizationConfig {
pub coning_correction: ConingCorrection,
}
impl Default for MechanizationConfig {
fn default() -> Self {
Self {
coning_correction: ConingCorrection::Off,
}
}
}
pub fn gauss_markov_bias_decay(dt_s: f64, tau_s: f64) -> Result<f64, InertialError> {
validate_nonnegative(dt_s, "dt_s")?;
validate_bias_tau(tau_s, "tau_s")?;
if tau_s.is_infinite() {
Ok(1.0)
} else {
Ok((-dt_s / tau_s).exp())
}
}
pub fn gauss_markov_bias_variance_increment(
instability: f64,
dt_s: f64,
tau_s: f64,
) -> Result<f64, InertialError> {
validate_nonnegative(instability, "instability")?;
validate_nonnegative(dt_s, "dt_s")?;
validate_bias_tau(tau_s, "tau_s")?;
let variance = instability * instability;
if tau_s.is_infinite() {
Ok(variance * dt_s)
} else {
Ok(variance * (1.0 - (-2.0 * dt_s / tau_s).exp()))
}
}
fn validate_bias_tau(value: f64, field: &'static str) -> Result<(), InertialError> {
if value.is_infinite() && value.is_sign_positive() {
Ok(())
} else if value.is_finite() {
validate_positive(value, field)
} else {
Err(invalid_input(field, "must be positive or infinite"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gauss_markov_decay_matches_closed_form() {
let decay = gauss_markov_bias_decay(12.5, 100.0).expect("decay");
assert_eq!(decay.to_bits(), (-0.125_f64).exp().to_bits());
assert_eq!(
gauss_markov_bias_decay(12.5, RANDOM_WALK_BIAS_TAU_S)
.expect("random walk")
.to_bits(),
1.0_f64.to_bits()
);
}
#[test]
fn gauss_markov_variance_matches_closed_form() {
let increment = gauss_markov_bias_variance_increment(0.02, 10.0, 200.0).expect("variance");
let expected = 0.02_f64 * 0.02 * (1.0 - (-0.1_f64).exp());
assert_eq!(increment.to_bits(), expected.to_bits());
let random_walk = gauss_markov_bias_variance_increment(0.02, 10.0, RANDOM_WALK_BIAS_TAU_S)
.expect("random-walk variance");
assert_eq!(random_walk.to_bits(), (0.02_f64 * 0.02 * 10.0).to_bits());
}
#[test]
fn presets_validate() {
ImuSpec::preset(ImuGrade::Mems).validate().expect("mems");
ImuSpec::preset(ImuGrade::Tactical)
.validate()
.expect("tactical");
ImuSpec::preset(ImuGrade::Navigation)
.validate()
.expect("navigation");
}
}