1use super::{invalid_input, validate_nonnegative, validate_positive, InertialError};
4
5pub const RANDOM_WALK_BIAS_TAU_S: f64 = f64::INFINITY;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ImuGrade {
11 Mems,
13 Tactical,
15 Navigation,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct ImuSpec {
26 pub accel_vrw_mps_sqrt_s: f64,
28 pub gyro_arw_rad_sqrt_s: f64,
30 pub accel_bias_instab_mps2: f64,
32 pub gyro_bias_instab_rps: f64,
34 pub accel_bias_tau_s: f64,
36 pub gyro_bias_tau_s: f64,
38 pub accel_scale_instab_ppm: Option<f64>,
40 pub gyro_scale_instab_ppm: Option<f64>,
42}
43
44impl ImuSpec {
45 #[allow(clippy::too_many_arguments)]
47 pub const fn datasheet(
48 accel_vrw_mps_sqrt_s: f64,
49 gyro_arw_rad_sqrt_s: f64,
50 accel_bias_instab_mps2: f64,
51 gyro_bias_instab_rps: f64,
52 accel_bias_tau_s: f64,
53 gyro_bias_tau_s: f64,
54 accel_scale_instab_ppm: Option<f64>,
55 gyro_scale_instab_ppm: Option<f64>,
56 ) -> Self {
57 Self {
58 accel_vrw_mps_sqrt_s,
59 gyro_arw_rad_sqrt_s,
60 accel_bias_instab_mps2,
61 gyro_bias_instab_rps,
62 accel_bias_tau_s,
63 gyro_bias_tau_s,
64 accel_scale_instab_ppm,
65 gyro_scale_instab_ppm,
66 }
67 }
68
69 pub const fn preset(grade: ImuGrade) -> Self {
71 match grade {
72 ImuGrade::Mems => Self::mems(),
73 ImuGrade::Tactical => Self::tactical(),
74 ImuGrade::Navigation => Self::navigation(),
75 }
76 }
77
78 pub const fn mems() -> Self {
80 Self::datasheet(
81 5.0e-2,
82 5.0e-3,
83 5.0e-2,
84 5.0e-4,
85 3_600.0,
86 3_600.0,
87 Some(500.0),
88 Some(500.0),
89 )
90 }
91
92 pub const fn tactical() -> Self {
94 Self::datasheet(
95 5.0e-3,
96 2.0e-4,
97 5.0e-3,
98 2.0e-5,
99 7_200.0,
100 7_200.0,
101 Some(50.0),
102 Some(50.0),
103 )
104 }
105
106 pub const fn navigation() -> Self {
108 Self::datasheet(
109 5.0e-4,
110 2.0e-5,
111 2.0e-4,
112 1.0e-6,
113 14_400.0,
114 14_400.0,
115 Some(5.0),
116 Some(5.0),
117 )
118 }
119
120 pub fn validate(&self) -> Result<(), InertialError> {
122 validate_nonnegative(self.accel_vrw_mps_sqrt_s, "accel_vrw_mps_sqrt_s")?;
123 validate_nonnegative(self.gyro_arw_rad_sqrt_s, "gyro_arw_rad_sqrt_s")?;
124 validate_nonnegative(self.accel_bias_instab_mps2, "accel_bias_instab_mps2")?;
125 validate_nonnegative(self.gyro_bias_instab_rps, "gyro_bias_instab_rps")?;
126 validate_bias_tau(self.accel_bias_tau_s, "accel_bias_tau_s")?;
127 validate_bias_tau(self.gyro_bias_tau_s, "gyro_bias_tau_s")?;
128 if let Some(scale) = self.accel_scale_instab_ppm {
129 validate_nonnegative(scale, "accel_scale_instab_ppm")?;
130 }
131 if let Some(scale) = self.gyro_scale_instab_ppm {
132 validate_nonnegative(scale, "gyro_scale_instab_ppm")?;
133 }
134 Ok(())
135 }
136
137 pub fn accel_bias_decay(&self, dt_s: f64) -> Result<f64, InertialError> {
139 gauss_markov_bias_decay(dt_s, self.accel_bias_tau_s)
140 }
141
142 pub fn gyro_bias_decay(&self, dt_s: f64) -> Result<f64, InertialError> {
144 gauss_markov_bias_decay(dt_s, self.gyro_bias_tau_s)
145 }
146
147 pub fn accel_bias_variance_increment(&self, dt_s: f64) -> Result<f64, InertialError> {
149 gauss_markov_bias_variance_increment(
150 self.accel_bias_instab_mps2,
151 dt_s,
152 self.accel_bias_tau_s,
153 )
154 }
155
156 pub fn gyro_bias_variance_increment(&self, dt_s: f64) -> Result<f64, InertialError> {
158 gauss_markov_bias_variance_increment(self.gyro_bias_instab_rps, dt_s, self.gyro_bias_tau_s)
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum ConingCorrection {
165 Off,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct MechanizationConfig {
172 pub coning_correction: ConingCorrection,
174}
175
176impl Default for MechanizationConfig {
177 fn default() -> Self {
178 Self {
179 coning_correction: ConingCorrection::Off,
180 }
181 }
182}
183
184pub fn gauss_markov_bias_decay(dt_s: f64, tau_s: f64) -> Result<f64, InertialError> {
188 validate_nonnegative(dt_s, "dt_s")?;
189 validate_bias_tau(tau_s, "tau_s")?;
190 if tau_s.is_infinite() {
191 Ok(1.0)
192 } else {
193 Ok((-dt_s / tau_s).exp())
194 }
195}
196
197pub fn gauss_markov_bias_variance_increment(
203 instability: f64,
204 dt_s: f64,
205 tau_s: f64,
206) -> Result<f64, InertialError> {
207 validate_nonnegative(instability, "instability")?;
208 validate_nonnegative(dt_s, "dt_s")?;
209 validate_bias_tau(tau_s, "tau_s")?;
210 let variance = instability * instability;
211 if tau_s.is_infinite() {
212 Ok(variance * dt_s)
213 } else {
214 Ok(variance * (1.0 - (-2.0 * dt_s / tau_s).exp()))
215 }
216}
217
218fn validate_bias_tau(value: f64, field: &'static str) -> Result<(), InertialError> {
219 if value.is_infinite() && value.is_sign_positive() {
220 Ok(())
221 } else if value.is_finite() {
222 validate_positive(value, field)
223 } else {
224 Err(invalid_input(field, "must be positive or infinite"))
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
236
237 #[test]
238 fn gauss_markov_decay_matches_closed_form() {
239 let decay = gauss_markov_bias_decay(12.5, 100.0).expect("decay");
240 assert_eq!(decay.to_bits(), (-0.125_f64).exp().to_bits());
241 assert_eq!(
242 gauss_markov_bias_decay(12.5, RANDOM_WALK_BIAS_TAU_S)
243 .expect("random walk")
244 .to_bits(),
245 1.0_f64.to_bits()
246 );
247 }
248
249 #[test]
250 fn gauss_markov_variance_matches_closed_form() {
251 let increment = gauss_markov_bias_variance_increment(0.02, 10.0, 200.0).expect("variance");
252 let expected = 0.02_f64 * 0.02 * (1.0 - (-0.1_f64).exp());
253 assert_eq!(increment.to_bits(), expected.to_bits());
254
255 let random_walk = gauss_markov_bias_variance_increment(0.02, 10.0, RANDOM_WALK_BIAS_TAU_S)
256 .expect("random-walk variance");
257 assert_eq!(random_walk.to_bits(), (0.02_f64 * 0.02 * 10.0).to_bits());
258 }
259
260 #[test]
261 fn presets_validate() {
262 ImuSpec::preset(ImuGrade::Mems).validate().expect("mems");
263 ImuSpec::preset(ImuGrade::Tactical)
264 .validate()
265 .expect("tactical");
266 ImuSpec::preset(ImuGrade::Navigation)
267 .validate()
268 .expect("navigation");
269 }
270}