vqf_rs/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(not(feature = "f32"), allow(clippy::unnecessary_cast))]
3//! An implementation of the VQF IMU orientation estimation filter in pure Rust.
4//!
5//! This is, currently, a pretty direct port of the C++ implemenataion in <https://github.com/dlaidig/vqf>;
6//! further efforts to make the code more idiomatic may be helpful.
7//!
8//! The main entry point for this crate is [`VQF`]; look there to get started.
9//!
10//! This crate needs a library for floating-point arithmetic; currently `std`, `libm`, and `micromath` is supported.
11//! Notably, `micromath` implies `f32` as `micromath` does not provide `f64` arithmetic.
12
13#[cfg(feature = "f32")]
14/// Typedef for the floating-point data type used for most operations.
15///
16/// By default, all floating-point calculations are performed using `f64`. Enable the `f32` crate feature to
17/// change this type to `f32`. Note that the Butterworth filter implementation will always use `f64` as
18/// using `f32` can cause numeric issues.
19pub type Float = f32;
20#[cfg(not(feature = "f32"))]
21/// Typedef for the floating-point data type used for most operations.
22///
23/// By default, all floating-point calculations are performed using `f64`. Enable the `f32` crate feature to
24/// change this type to `f32`. Note that the Butterworth filter implementation will always use `f64` as
25/// using `f32` can cause numeric issues.
26pub type Float = f64;
27
28#[cfg(any(feature = "std", feature = "micromath"))]
29mod math {
30    use crate::Float;
31    #[cfg(feature = "micromath")]
32    use micromath::F32Ext;
33
34    pub fn sqrt(value: Float) -> Float {
35        value.sqrt()
36    }
37
38    pub fn fabs(value: Float) -> Float {
39        value.abs()
40    }
41
42    pub fn exp(value: Float) -> Float {
43        value.exp()
44    }
45
46    pub fn sin(value: Float) -> Float {
47        value.sin()
48    }
49
50    pub fn cos(value: Float) -> Float {
51        value.cos()
52    }
53
54    #[cfg(feature = "std")]
55    pub fn tan_f64(value: f64) -> f64 {
56        value.tan()
57    }
58
59    #[cfg(feature = "micromath")]
60    pub fn tan_f64(value: f64) -> f64 {
61        (value as f32).tan() as f64
62    }
63
64    pub fn asin(value: Float) -> Float {
65        value.asin()
66    }
67
68    pub fn acos(value: Float) -> Float {
69        value.acos()
70    }
71
72    pub fn atan2(value: Float, other: Float) -> Float {
73        value.atan2(other)
74    }
75}
76
77#[cfg(feature = "libm")]
78mod math {
79    use crate::Float;
80
81    pub fn sqrt(value: Float) -> Float {
82        libm::Libm::<Float>::sqrt(value)
83    }
84
85    pub fn fabs(value: Float) -> Float {
86        libm::Libm::<Float>::fabs(value)
87    }
88
89    pub fn exp(value: Float) -> Float {
90        libm::Libm::<Float>::exp(value)
91    }
92
93    pub fn sin(value: Float) -> Float {
94        libm::Libm::<Float>::sin(value)
95    }
96
97    pub fn cos(value: Float) -> Float {
98        libm::Libm::<Float>::cos(value)
99    }
100
101    pub fn tan_f64(value: f64) -> f64 {
102        libm::tan(value)
103    }
104
105    pub fn asin(value: Float) -> Float {
106        libm::Libm::<Float>::asin(value)
107    }
108
109    pub fn acos(value: Float) -> Float {
110        libm::Libm::<Float>::acos(value)
111    }
112
113    pub fn atan2(value: Float, other: Float) -> Float {
114        libm::Libm::<Float>::atan2(value, other)
115    }
116}
117
118#[cfg(feature = "f32")]
119use core::f32::consts as fc;
120#[cfg(not(feature = "f32"))]
121use core::f64::consts as fc;
122use core::{
123    f64::consts as f64c,
124    ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},
125};
126
127fn flatten<const N: usize, const M: usize, const R: usize>(value: &mut [[Float; N]; M]) -> &mut [Float; R] {
128    assert_eq!(N * M, R);
129    // SAFETY: `[[T; N]; M]` is layout-identical to `[T; N * M]`
130    unsafe { core::mem::transmute(value) }
131}
132
133/// A quaternion.
134///
135/// As this type was made solely for internal purposes, external usage seems ill-advised.
136#[derive(Clone, Copy, Default)]
137pub struct Quaternion(pub Float, pub Float, pub Float, pub Float);
138
139impl Mul for Quaternion {
140    type Output = Quaternion;
141
142    fn mul(self, rhs: Self) -> Self::Output {
143        let w = self.0 * rhs.0 - self.1 * rhs.1 - self.2 * rhs.2 - self.3 * rhs.3;
144        let x = self.0 * rhs.1 + self.1 * rhs.0 + self.2 * rhs.3 - self.3 * rhs.2;
145        let y = self.0 * rhs.2 - self.1 * rhs.3 + self.2 * rhs.0 + self.3 * rhs.1;
146        let z = self.0 * rhs.3 + self.1 * rhs.2 - self.2 * rhs.1 + self.3 * rhs.0;
147        Self(w, x, y, z)
148    }
149}
150
151impl MulAssign for Quaternion {
152    fn mul_assign(&mut self, rhs: Self) {
153        *self = *self * rhs;
154    }
155}
156
157impl From<[Float; 4]> for Quaternion {
158    fn from(value: [Float; 4]) -> Self {
159        Self(value[0], value[1], value[2], value[3])
160    }
161}
162
163impl Quaternion {
164    pub fn norm(&self) -> Float {
165        math::sqrt(square(self.0) + square(self.1) + square(self.2) + square(self.3))
166    }
167
168    pub fn normalize(&mut self) {
169        let n = self.norm();
170        if n < Float::EPSILON {
171            return;
172        }
173        self.0 /= n;
174        self.1 /= n;
175        self.2 /= n;
176        self.3 /= n;
177    }
178
179    pub fn rotate(&self, v: [Float; 3]) -> [Float; 3] {
180        let x = (1.0 - 2.0 * self.2 * self.2 - 2.0 * self.3 * self.3) * v[0]
181            + 2.0 * v[1] * (self.2 * self.1 - self.0 * self.3)
182            + 2.0 * v[2] * (self.0 * self.2 + self.3 * self.1);
183        let y = 2.0 * v[0] * (self.0 * self.3 + self.2 * self.1)
184            + v[1] * (1.0 - 2.0 * self.1 * self.1 - 2.0 * self.3 * self.3)
185            + 2.0 * v[2] * (self.2 * self.3 - self.1 * self.0);
186        let z = 2.0 * v[0] * (self.3 * self.1 - self.0 * self.2)
187            + 2.0 * v[1] * (self.0 * self.1 + self.3 * self.2)
188            + v[2] * (1.0 - 2.0 * self.1 * self.1 - 2.0 * self.2 * self.2);
189        [x, y, z]
190    }
191
192    pub fn apply_delta(&self, delta: Float) -> Quaternion {
193        let c = math::cos(delta / 2.0);
194        let s = math::sin(delta / 2.0);
195        let w = c * self.0 - s * self.3;
196        let x = c * self.1 - s * self.2;
197        let y = c * self.2 + s * self.1;
198        let z = c * self.3 + s * self.0;
199        Self(w, x, y, z)
200    }
201}
202
203/// A fixed-size matrix.
204///
205/// As this type was made solely for internal purposes, external usage seems ill-advised.
206#[derive(Clone, Copy)]
207pub struct Matrix<const W: usize, const H: usize>(pub [[Float; W]; H]);
208
209impl<const W: usize, const H: usize> Default for Matrix<W, H> {
210    fn default() -> Self {
211        Self([[0.0; W]; H])
212    }
213}
214
215impl<const W: usize, const H: usize> From<[[Float; W]; H]> for Matrix<W, H> {
216    fn from(value: [[Float; W]; H]) -> Self {
217        Self(value)
218    }
219}
220
221impl<const M: usize, const N: usize, const P: usize> Mul<Matrix<P, N>> for Matrix<N, M> {
222    type Output = Matrix<P, M>;
223
224    fn mul(self, rhs: Matrix<P, N>) -> Self::Output {
225        let mut out: Matrix<P, M> = Default::default();
226        for i in 0..M {
227            for j in 0..P {
228                let mut val = 0.0;
229                for k in 0..N {
230                    val += self.0[i][k] * rhs.0[k][j];
231                }
232                out.0[i][j] = val;
233            }
234        }
235        out
236    }
237}
238
239impl<const X: usize> MulAssign for Matrix<X, X> {
240    fn mul_assign(&mut self, rhs: Self) {
241        *self = *self * rhs;
242    }
243}
244
245impl<const W: usize, const H: usize> Add for Matrix<W, H> {
246    type Output = Self;
247
248    fn add(self, rhs: Self) -> Self::Output {
249        let mut out: Self = Default::default();
250        for i in 0..W {
251            for j in 0..H {
252                out.0[j][i] = self.0[j][i] + rhs.0[j][i];
253            }
254        }
255        out
256    }
257}
258
259impl<const W: usize, const H: usize> AddAssign for Matrix<W, H> {
260    fn add_assign(&mut self, rhs: Self) {
261        *self = *self + rhs;
262    }
263}
264
265impl<const W: usize, const H: usize> Sub for Matrix<W, H> {
266    type Output = Self;
267
268    fn sub(self, rhs: Self) -> Self::Output {
269        let mut out: Self = Default::default();
270        for i in 0..W {
271            for j in 0..H {
272                out.0[j][i] = self.0[j][i] - rhs.0[j][i];
273            }
274        }
275        out
276    }
277}
278
279impl<const W: usize, const H: usize> SubAssign for Matrix<W, H> {
280    fn sub_assign(&mut self, rhs: Self) {
281        *self = *self - rhs;
282    }
283}
284
285impl<const W: usize, const H: usize> Matrix<W, H> {
286    pub fn transpose(self) -> Matrix<H, W> {
287        let mut out: Matrix<H, W> = Default::default();
288        for i in 0..W {
289            for j in 0..H {
290                out.0[i][j] = self.0[j][i];
291            }
292        }
293        out
294    }
295}
296
297impl Matrix<3, 3> {
298    pub fn invert(self) -> Option<Self> {
299        // in = [a b c; d e f; g h i]
300        let a =
301            self.0[1][1] as f64 * self.0[2][2] as f64 - self.0[1][2] as f64 * self.0[2][1] as f64; // (e*i - f*h)
302        let d =
303            self.0[0][2] as f64 * self.0[2][1] as f64 - self.0[0][1] as f64 * self.0[2][2] as f64; // -(b*i - c*h)
304        let g =
305            self.0[0][1] as f64 * self.0[1][2] as f64 - self.0[0][2] as f64 * self.0[1][1] as f64; // (b*f - c*e)
306        let b =
307            self.0[1][2] as f64 * self.0[2][0] as f64 - self.0[1][0] as f64 * self.0[2][2] as f64; // -(d*i - f*g)
308        let e =
309            self.0[0][0] as f64 * self.0[2][2] as f64 - self.0[0][2] as f64 * self.0[2][0] as f64; // (a*i - c*g)
310        let h =
311            self.0[0][2] as f64 * self.0[1][0] as f64 - self.0[0][0] as f64 * self.0[1][2] as f64; // -(a*f - c*d)
312        let c =
313            self.0[1][0] as f64 * self.0[2][1] as f64 - self.0[1][1] as f64 * self.0[2][0] as f64; // (d*h - e*g)
314        let f =
315            self.0[0][1] as f64 * self.0[2][0] as f64 - self.0[0][0] as f64 * self.0[2][1] as f64; // -(a*h - b*g)
316        let i =
317            self.0[0][0] as f64 * self.0[1][1] as f64 - self.0[0][1] as f64 * self.0[1][0] as f64; // (a*e - b*d)
318
319        let det = self.0[0][0] as f64 * a + self.0[0][1] as f64 * b + self.0[0][2] as f64 * c; // a*A + b*B + c*C;
320
321        if (-f64::EPSILON..=f64::EPSILON).contains(&det) {
322            return None;
323        }
324
325        // out = [A D G; B E H; C F I]/det
326        Some(
327            [
328                [(a / det) as Float, (d / det) as Float, (g / det) as Float],
329                [(b / det) as Float, (e / det) as Float, (h / det) as Float],
330                [(c / det) as Float, (f / det) as Float, (i / det) as Float],
331            ]
332            .into(),
333        )
334    }
335}
336
337/// Struct containing all tuning parameters used by the VQF class.
338///
339/// The parameters influence the behavior of the algorithm and are independent of the sampling rate of the IMU data. The
340/// constructor sets all parameters to the default values.
341///
342/// The parameters [`motion_bias_est_enabled`](Self::motion_bias_est_enabled), [`rest_bias_est_enabled`](Self::rest_bias_est_enabled), and [`mag_dist_rejection_enabled`](Self::mag_dist_rejection_enabled) can be used to enable/disable
343/// the main features of the VQF algorithm. The time constants [`tau_acc`](Self::tau_acc) and [`tau_mag`](Self::tau_mag) can be tuned to change the trust on
344/// the accelerometer and magnetometer measurements, respectively. The remaining parameters influence bias estimation
345/// and magnetometer rejection.
346#[cfg_attr(doc, doc = include_str!("../katex.html"))]
347#[derive(Clone, Copy)]
348pub struct Params {
349    /// Time constant $\tau_\mathrm{acc}$ for accelerometer low-pass filtering in seconds.
350    ///
351    /// Small values for $\tau_\mathrm{acc}$ imply trust on the accelerometer measurements and while large values of
352    /// $\tau_\mathrm{acc}$ imply trust on the gyroscope measurements.
353    ///
354    /// The time constant $\tau_\mathrm{acc}$ corresponds to the cutoff frequency $f_\mathrm{c}$ of the
355    /// second-order Butterworth low-pass filter as follows: $f_\mathrm{c} = \frac{\sqrt{2}}{2\pi\tau_\mathrm{acc}}$.
356    ///
357    /// Default value: 3.0 s
358    pub tau_acc: Float,
359
360    /// Time constant $\tau_\mathrm{mag}$ for magnetometer update in seconds.
361    ///
362    /// Small values for $\tau_\mathrm{mag}$ imply trust on the magnetometer measurements and while large values of
363    /// $\tau_\mathrm{mag}$ imply trust on the gyroscope measurements.
364    ///
365    /// The time constant $\tau_\mathrm{mag}$ corresponds to the cutoff frequency $f_\mathrm{c}$ of the
366    /// first-order low-pass filter for the heading correction as follows:
367    /// $f_\mathrm{c} = \frac{1}{2\pi\tau_\mathrm{mag}}$.
368    ///
369    /// Default value: 9.0 s
370    pub tau_mag: Float,
371
372    #[cfg(feature = "motion-bias-estimation")]
373    /// Enables gyroscope bias estimation during motion phases.
374    ///
375    /// If set to true (default), gyroscope bias is estimated based on the inclination correction only, i.e. without
376    /// using magnetometer measurements.
377    pub motion_bias_est_enabled: bool,
378
379    /// Enables rest detection and gyroscope bias estimation during rest phases.
380    ///
381    /// If set to true (default), phases in which the IMU is at rest are detected. During rest, the gyroscope bias
382    /// is estimated from the low-pass filtered gyroscope readings.
383    pub rest_bias_est_enabled: bool,
384
385    /// Enables magnetic disturbance detection and magnetic disturbance rejection.
386    ///
387    /// If set to true (default), the magnetic field is analyzed. For short disturbed phases, the magnetometer-based
388    /// correction is disabled totally. If the magnetic field is always regarded as disturbed or if the duration of
389    /// the disturbances exceeds [`mag_max_rejection_time`](Self::mag_max_rejection_time), magnetometer-based updates are performed, but with an increased
390    /// time constant.
391    pub mag_dist_rejection_enabled: bool,
392
393    /// Standard deviation of the initial bias estimation uncertainty (in degrees per second).
394    ///
395    /// Default value: 0.5 °/s
396    pub bias_sigma_init: Float,
397
398    /// Time in which the bias estimation uncertainty increases from 0 °/s to 0.1 °/s (in seconds).
399    ///
400    /// This value determines the system noise assumed by the Kalman filter.
401    ///
402    /// Default value: 100.0 s
403    pub bias_forgetting_time: Float,
404
405    /// Maximum expected gyroscope bias (in degrees per second).
406    ///
407    /// This value is used to clip the bias estimate and the measurement error in the bias estimation update step. It is
408    /// further used by the rest detection algorithm in order to not regard measurements with a large but constant
409    /// angular rate as rest.
410    ///
411    /// Default value: 2.0 °/s
412    pub bias_clip: Float,
413
414    #[cfg(feature = "motion-bias-estimation")]
415    /// Standard deviation of the converged bias estimation uncertainty during motion (in degrees per second).
416    ///
417    /// This value determines the trust on motion bias estimation updates. A small value leads to fast convergence.
418    ///
419    /// Default value: 0.1 °/s
420    pub bias_sigma_motion: Float,
421
422    #[cfg(feature = "motion-bias-estimation")]
423    /// Forgetting factor for unobservable bias in vertical direction during motion.
424    ///
425    /// As magnetometer measurements are deliberately not used during motion bias estimation, gyroscope bias is not
426    /// observable in vertical direction. This value is the relative weight of an artificial zero measurement that
427    /// ensures that the bias estimate in the unobservable direction will eventually decay to zero.
428    ///
429    /// Default value: 0.0001
430    pub bias_vertical_forgetting_factor: Float,
431
432    /// Standard deviation of the converged bias estimation uncertainty during rest (in degrees per second).
433    ///
434    /// This value determines the trust on rest bias estimation updates. A small value leads to fast convergence.
435    ///
436    /// Default value: 0.03 °
437    pub bias_sigma_rest: Float,
438
439    /// Time threshold for rest detection (in seconds).
440    ///
441    /// Rest is detected when the measurements have been close to the low-pass filtered reference for the given time.
442    ///
443    /// Default value: 1.5 s
444    pub rest_min_t: Float,
445
446    /// Time constant for the low-pass filter used in rest detection (in seconds).
447    ///
448    /// This time constant characterizes a second-order Butterworth low-pass filter used to obtain the reference for
449    /// rest detection.
450    ///
451    /// Default value: 0.5 s
452    pub rest_filter_tau: Float,
453
454    /// Angular velocity threshold for rest detection (in °/s).
455    ///
456    /// For rest to be detected, the norm of the deviation between measurement and reference must be below the given
457    /// threshold. (Furthermore, the absolute value of each component must be below [`bias_clip`](Self::bias_clip)).
458    ///
459    /// Default value: 2.0 °/s/
460    pub rest_th_gyr: Float,
461
462    /// Acceleration threshold for rest detection (in m/s²).
463    ///
464    /// For rest to be detected, the norm of the deviation between measurement and reference must be below the given
465    /// threshold.
466    ///
467    /// Default value: 0.5 m/s²
468    pub rest_th_acc: Float,
469
470    /// Time constant for current norm/dip value in magnetic disturbance detection (in seconds).
471    ///
472    /// This (very fast) low-pass filter is intended to provide additional robustness when the magnetometer measurements
473    /// are noisy or not sampled perfectly in sync with the gyroscope measurements. Set to -1 to disable the low-pass
474    /// filter and directly use the magnetometer measurements.
475    ///
476    /// Default value: 0.05 s
477    pub mag_current_tau: Float,
478
479    /// Time constant for the adjustment of the magnetic field reference (in seconds).
480    ///
481    /// This adjustment allows the reference estimate to converge to the observed undisturbed field.
482    ///
483    /// Default value: 20.0 s
484    pub mag_ref_tau: Float,
485
486    /// Relative threshold for the magnetic field strength for magnetic disturbance detection.
487    ///
488    /// This value is relative to the reference norm.
489    ///
490    /// Default value: 0.1 (10%)
491    pub mag_norm_th: Float,
492
493    /// Threshold for the magnetic field dip angle for magnetic disturbance detection (in degrees).
494    ///
495    /// Default vaule: 10 °
496    pub mag_dip_th: Float,
497
498    /// Duration after which to accept a different homogeneous magnetic field (in seconds).
499    ///
500    /// A different magnetic field reference is accepted as the new field when the measurements are within the thresholds
501    /// [`mag_norm_th`](Self::mag_norm_th) and [`mag_dip_th`](Self::mag_dip_th) for the given time. Additionally, only phases with sufficient movement, specified by
502    /// [`mag_new_min_gyr`](Self::mag_new_min_gyr), count.
503    ///
504    /// Default value: 20.0
505    pub mag_new_time: Float,
506
507    /// Duration after which to accept a homogeneous magnetic field for the first time (in seconds).
508    ///
509    /// This value is used instead of [`mag_new_time`](Self::mag_new_time) when there is no current estimate in order to allow for the initial
510    /// magnetic field reference to be obtained faster.
511    ///
512    /// Default value: 5.0
513    pub mag_new_first_time: Float,
514
515    /// Minimum angular velocity needed in order to count time for new magnetic field acceptance (in °/s).
516    ///
517    /// Durations for which the angular velocity norm is below this threshold do not count towards reaching [`mag_new_time`](Self::mag_new_time).
518    ///
519    /// Default value: 20.0 °/s
520    pub mag_new_min_gyr: Float,
521
522    /// Minimum duration within thresholds after which to regard the field as undisturbed again (in seconds).
523    ///
524    /// Default value: 0.5 s
525    pub mag_min_undisturbed_time: Float,
526
527    /// Maximum duration of full magnetic disturbance rejection (in seconds).
528    ///
529    /// For magnetic disturbances up to this duration, heading correction is fully disabled and heading changes are
530    /// tracked by gyroscope only. After this duration (or for many small disturbed phases without sufficient time in the
531    /// undisturbed field in between), the heading correction is performed with an increased time constant (see
532    /// [`mag_rejection_factor`](Self::mag_rejection_factor)).
533    ///
534    /// Default value: 60.0 s
535    pub mag_max_rejection_time: Float,
536
537    /// Factor by which to slow the heading correction during long disturbed phases.
538    ///
539    /// After [`mag_max_rejection_time`](Self::mag_max_rejection_time) of full magnetic disturbance rejection, heading correction is performed with an
540    /// increased time constant. This parameter (approximately) specifies the factor of the increase.
541    ///
542    /// Furthermore, after spending [`mag_max_rejection_time`](Self::mag_max_rejection_time)/[`mag_rejection_factor`](Self::mag_rejection_factor) seconds in an undisturbed magnetic field,
543    /// the time is reset and full magnetic disturbance rejection will be performed for up to [`mag_max_rejection_time`](Self::mag_max_rejection_time) again.
544    ///
545    /// Default value: 2.0
546    pub mag_rejection_factor: Float,
547}
548
549impl Default for Params {
550    fn default() -> Self {
551        Self {
552            tau_acc: 3.0,
553            tau_mag: 9.0,
554            #[cfg(feature = "motion-bias-estimation")]
555            motion_bias_est_enabled: true,
556            rest_bias_est_enabled: true,
557            mag_dist_rejection_enabled: true,
558            bias_sigma_init: 0.5,
559            bias_forgetting_time: 100.0,
560            bias_clip: 2.0,
561            #[cfg(feature = "motion-bias-estimation")]
562            bias_sigma_motion: 0.1,
563            #[cfg(feature = "motion-bias-estimation")]
564            bias_vertical_forgetting_factor: 0.0001,
565            bias_sigma_rest: 0.03,
566            rest_min_t: 1.5,
567            rest_filter_tau: 0.5,
568            rest_th_gyr: 2.0,
569            rest_th_acc: 0.5,
570            mag_current_tau: 0.05,
571            mag_ref_tau: 20.0,
572            mag_norm_th: 0.1,
573            mag_dip_th: 10.0,
574            mag_new_time: 20.0,
575            mag_new_first_time: 5.0,
576            mag_new_min_gyr: 20.0,
577            mag_min_undisturbed_time: 0.5,
578            mag_max_rejection_time: 60.0,
579            mag_rejection_factor: 2.0,
580        }
581    }
582}
583
584/// Struct containing the filter state of the VQF class.
585///
586/// The relevant parts of the state can be accessed via functions of the VQF class, e.g. [`VQF::quat_6d()`],
587/// [`VQF::quat_9d()`], [`VQF::bias_estimate()`], [`VQF::set_bias_estimate()`], [`VQF::rest_detected()`] and
588/// [`VQF::mag_dist_detected()`]. To reset the state to the initial values, use [`VQF::reset_state()`].
589///
590/// Direct access to the full state is typically not needed but can be useful in some cases, e.g. for debugging. For this
591/// purpose, the state can be accessed by [`VQF::state()`] and set by [`VQF::state_mut()`].
592#[cfg_attr(doc, doc = include_str!("../katex.html"))]
593#[derive(Clone, Copy, Default)]
594pub struct State {
595    /// Angular velocity strapdown integration quaternion $^{\mathcal{S}\_i}_{\mathcal{I}\_i}\mathbf{q}$.
596    pub gyr_quat: Quaternion,
597
598    /// Inclination correction quaternion $^{\mathcal{I}\_i}\_{\mathcal{E}_i}\mathbf{q}$.
599    pub acc_quat: Quaternion,
600
601    /// Heading difference $\delta$ between $\mathcal{E}_i$ and $\mathcal{E}$.
602    ///
603    /// $^{\mathcal{E}\_i}\_{\mathcal{E}}\mathbf{q} = \begin{bmatrix}\cos\frac{\delta}{2} & 0 & 0 &
604    /// \sin\frac{\delta}{2}\end{bmatrix}^T$.
605    pub delta: Float,
606
607    /// True if it has been detected that the IMU is currently at rest.
608    ///
609    /// Used to switch between rest and motion gyroscope bias estimation.
610    pub rest_detected: bool,
611
612    /// True if magnetic disturbances have been detected.
613    pub mag_dist_detected: bool,
614
615    /// Last low-pass filtered acceleration in the $\mathcal{I}_i$ frame.
616    pub last_acc_lp: [Float; 3],
617
618    /// Internal low-pass filter state for [`last_acc_lp`](Self::last_acc_lp).
619    pub acc_lp_state: [[f64; 2]; 3],
620
621    /// Last inclination correction angular rate.
622    ///
623    /// Change to inclination correction quaternion $^{\mathcal{I}\_i}\_{\mathcal{E}_i}\mathbf{q}$ performed in the
624    /// last accelerometer update, expressed as an angular rate (in rad/s).
625    pub last_acc_corr_angular_rate: Float,
626
627    /// Gain used for heading correction to ensure fast initial convergence.
628    ///
629    /// This value is used as the gain for heading correction in the beginning if it is larger than the normal filter
630    /// gain. It is initialized to 1 and then updated to 0.5, 0.33, 0.25, ... After [`Params::tau_mag`] seconds, it is
631    /// set to zero.
632    pub k_mag_init: Float,
633
634    /// Last heading disagreement angle.
635    ///
636    /// Disagreement between the heading $\hat\delta$ estimated from the last magnetometer sample and the state
637    /// $\delta$ (in rad).
638    pub last_mag_dis_angle: Float,
639
640    /// Last heading correction angular rate.
641    ///
642    /// Change to heading $\delta$ performed in the last magnetometer update,
643    /// expressed as an angular rate (in rad/s).
644    pub last_mag_corr_angular_rate: Float,
645
646    /// Current gyroscope bias estimate (in rad/s).
647    pub bias: [Float; 3],
648    #[cfg(feature = "motion-bias-estimation")]
649    /// Covariance matrix of the gyroscope bias estimate.
650    ///
651    /// The 3x3 matrix is stored in row-major order. Note that for numeric reasons the internal unit used is 0.01 °/s,
652    /// i.e. to get the standard deviation in degrees per second use $\sigma = \frac{\sqrt{p_{ii}}}{100}$.
653    pub bias_p: Matrix<3, 3>,
654
655    #[cfg(not(feature = "motion-bias-estimation"))]
656    // If only rest gyr bias estimation is enabled, P and K of the KF are always diagonal
657    // and matrix inversion is not needed. If motion bias estimation is disabled at compile
658    // time, storing the full P matrix is not necessary.
659    pub bias_p: Float,
660
661    #[cfg(feature = "motion-bias-estimation")]
662    /// Internal state of the Butterworth low-pass filter for the rotation matrix coefficients used in motion
663    /// bias estimation.
664    pub motion_bias_est_rlp_state: [[f64; 2]; 9],
665    #[cfg(feature = "motion-bias-estimation")]
666    /// Internal low-pass filter state for the rotated bias estimate used in motion bias estimation.
667    pub motion_bias_est_bias_lp_state: [[f64; 2]; 2],
668
669    /// Last (squared) deviations from the reference of the last sample used in rest detection.
670    ///
671    /// Looking at those values can be useful to understand how rest detection is working and which thresholds are
672    /// suitable. The array contains the last values for gyroscope and accelerometer in the respective
673    /// units. Note that the values are squared.
674    ///
675    /// The method [`VQF::relative_rest_deviations()`] provides an easier way to obtain and interpret those values.
676    pub rest_last_squared_deviations: [Float; 2],
677
678    /// The current duration for which all sensor readings are within the rest detection thresholds.
679    ///
680    /// Rest is detected if this value is larger or equal to [`Params::rest_min_t`].
681    pub rest_t: Float,
682
683    /// Last low-pass filtered gyroscope measurement used as the reference for rest detection.
684    ///
685    /// Note that this value is also used for gyroscope bias estimation when rest is detected.
686    pub rest_last_gyr_lp: [Float; 3],
687
688    /// Internal low-pass filter state for [`rest_last_gyr_lp`](Self::rest_last_gyr_lp).
689    pub rest_gyr_lp_state: [[f64; 2]; 3],
690
691    /// Last low-pass filtered accelerometer measurement used as the reference for rest detection.
692    pub rest_last_acc_lp: [Float; 3],
693
694    /// Internal low-pass filter state for [`rest_last_acc_lp`](Self::rest_last_acc_lp).
695    pub rest_acc_lp_state: [[f64; 2]; 3],
696
697    /// Norm of the currently accepted magnetic field reference.
698    ///
699    /// A value of -1 indicates that no homogeneous field is found yet.
700    pub mag_ref_norm: Float,
701
702    /// Dip angle of the currently accepted magnetic field reference.
703    pub mag_ref_dip: Float,
704
705    /// The current duration for which the current norm and dip are close to the reference.
706    ///
707    /// The magnetic field is regarded as undisturbed when this value reaches [`Params::mag_min_undisturbed_time`].
708    pub mag_undisturbed_t: Float,
709
710    /// The current duration for which the magnetic field was rejected.
711    ///
712    /// If the magnetic field is disturbed and this value is smaller than [`Params::mag_max_rejection_time`], heading
713    /// correction updates are fully disabled.
714    pub mag_reject_t: Float,
715
716    /// Norm of the alternative magnetic field reference currently being evaluated.
717    pub mag_candidate_norm: Float,
718
719    /// Dip angle of the alternative magnetic field reference currently being evaluated.
720    pub mag_candidate_dip: Float,
721
722    /// The current duration for which the norm and dip are close to the candidate.
723    ///
724    /// If this value exceeds [`Params::mag_new_time`] (or [`Params::mag_new_first_time`] if [`mag_ref_norm`](Self::mag_ref_norm) < 0), the current
725    /// candidate is accepted as the new reference.
726    pub mag_candidate_t: Float,
727
728    /// Norm and dip angle of the current magnetometer measurements.
729    ///
730    /// Slightly low-pass filtered, see [`Params::mag_current_tau`].
731    pub mag_norm_dip: [Float; 2],
732
733    /// Internal low-pass filter state for the current norm and dip angle.
734    pub mag_norm_dip_lp_state: [[f64; 2]; 2],
735}
736
737/// Struct containing coefficients used by the VQF class.
738///
739/// Coefficients are values that depend on the parameters and the sampling times, but do not change during update steps.
740/// They are calculated in [`VQF::new()`].
741#[cfg_attr(doc, doc = include_str!("../katex.html"))]
742#[derive(Clone, Copy, Default)]
743pub struct Coefficients {
744    /// Sampling time of the gyroscope measurements (in seconds).
745    pub gyr_ts: Float,
746
747    /// Sampling time of the accelerometer measurements (in seconds).
748    pub acc_ts: Float,
749
750    /// Sampling time of the magnetometer measurements (in seconds).
751    pub mag_ts: Float,
752
753    /// Numerator coefficients of the acceleration low-pass filter.
754    ///
755    /// The array contains $\begin{bmatrix}b_0 & b_1 & b_2\end{bmatrix}$.
756    pub acc_lp_b: [f64; 3],
757
758    /// Denominator coefficients of the acceleration low-pass filter.
759    ///
760    /// The array contains $\begin{bmatrix}a_1 & a_2\end{bmatrix}$ and $a_0=1$.
761    pub acc_lp_a: [f64; 2],
762
763    /// Gain of the first-order filter used for heading correction.
764    pub k_mag: Float,
765
766    /// Variance of the initial gyroscope bias estimate.
767    pub bias_p0: Float,
768
769    /// System noise variance used in gyroscope bias estimation.
770    pub bias_v: Float,
771
772    #[cfg(feature = "motion-bias-estimation")]
773    /// Measurement noise variance for the motion gyroscope bias estimation update.
774    pub bias_motion_w: Float,
775
776    #[cfg(feature = "motion-bias-estimation")]
777    /// Measurement noise variance for the motion gyroscope bias estimation update in vertical direction.
778    pub bias_vertical_w: Float,
779
780    /// Measurement noise variance for the rest gyroscope bias estimation update.
781    pub bias_rest_w: Float,
782
783    /// Numerator coefficients of the gyroscope measurement low-pass filter for rest detection.
784    pub rest_gyr_lp_b: [f64; 3],
785
786    /// Denominator coefficients of the gyroscope measurement low-pass filter for rest detection.
787    pub rest_gyr_lp_a: [f64; 2],
788
789    /// Numerator coefficients of the accelerometer measurement low-pass filter for rest detection.
790    pub rest_acc_lp_b: [f64; 3],
791
792    /// Denominator coefficients of the accelerometer measurement low-pass filter for rest detection.
793    pub rest_acc_lp_a: [f64; 2],
794
795    /// Gain of the first-order filter used for to update the magnetic field reference and candidate.
796    pub k_mag_ref: Float,
797
798    /// Numerator coefficients of the low-pass filter for the current magnetic norm and dip.
799    pub mag_norm_dip_lp_b: [f64; 3],
800
801    /// Denominator coefficients of the low-pass filter for the current magnetic norm and dip.
802    pub mag_norm_dip_lp_a: [f64; 2],
803}
804
805/// A Versatile Quaternion-based Filter for IMU Orientation Estimation.
806///
807/// This class implements the orientation estimation filter described in the following publication:
808/// > D. Laidig and T. Seel. "VQF: Highly Accurate IMU Orientation Estimation with Bias Estimation and Magnetic
809/// > Disturbance Rejection." Information Fusion 2023, 91, 187–204.
810/// > [doi:10.1016/j.inffus.2022.10.014](https://doi.org/10.1016/j.inffus.2022.10.014).
811/// > [Accepted manuscript available at [arXiv:2203.17024](https://arxiv.org/abs/2203.17024).]
812///
813/// The filter can perform simultaneous 6D (magnetometer-free) and 9D (gyr+acc+mag) sensor fusion and can also be used
814/// without magnetometer data. It performs rest detection, gyroscope bias estimation during rest and motion, and magnetic
815/// disturbance detection and rejection. Different sampling rates for gyroscopes, accelerometers, and magnetometers are
816/// supported as well. While in most cases, the defaults will be reasonable, the algorithm can be influenced via a
817/// number of tuning parameters.
818///
819/// To use this implementation,
820/// 1. create a instance of the class and provide the sampling time and, optionally, parameters
821/// 2. for every sample, call one of the update functions to feed the algorithm with IMU data
822/// 3. access the estimation results with [`quat_6d()`](Self::quat_6d()), [`quat_9d()`](Self::quat_9d()) and
823/// the other getter methods.
824///
825/// This class is a port of the official, original C++ implementation of the algorithm. For usage in C++/Python/MATLAB,
826/// the original implementations should be used.
827pub struct VQF {
828    params: Params,
829    state: State,
830    coeffs: Coefficients,
831}
832
833#[inline(always)]
834fn square(t: Float) -> Float {
835    t * t
836}
837
838#[cfg_attr(doc, doc = include_str!("../katex.html"))]
839impl VQF {
840    /// Creates a new VQF instance.
841    ///
842    /// In the most common case (using the default parameters and all data being sampled with the same frequency,
843    /// create the class like this:
844    /// ```rust
845    /// # use vqf_rs::VQF;
846    /// let vqf = VQF::new(0.01, None, None, None); // 0.01 s sampling time, i.e. 100 Hz
847    /// ```
848    ///
849    /// # Panics
850    ///
851    /// Panics if `gyr_ts`, `acc_ts`, or `mag_ts` is negative.
852    pub fn new(
853        gyr_ts: Float,
854        acc_ts: Option<Float>,
855        mag_ts: Option<Float>,
856        params: Option<Params>,
857    ) -> Self {
858        let mut ret = Self {
859            params: params.unwrap_or_default(),
860            state: Default::default(),
861            coeffs: Default::default(),
862        };
863        ret.coeffs.gyr_ts = gyr_ts;
864        ret.coeffs.acc_ts = acc_ts.unwrap_or(gyr_ts);
865        ret.coeffs.mag_ts = mag_ts.unwrap_or(gyr_ts);
866        ret.setup();
867        ret
868    }
869
870    /// Performs gyroscope update step, using the measurement in rad/s.
871    ///
872    /// It is only necessary to call this function directly if gyroscope, accelerometers and magnetometers have
873    /// different sampling rates. Otherwise, simply use [`update()`](Self::update()).
874    pub fn update_gyr(&mut self, gyr: [Float; 3]) {
875        self.update_gyr_with_ts(gyr, self.coeffs.gyr_ts);
876    }
877
878    /// Performs gyroscope update step, using a specified timestep for integration.
879    pub fn update_gyr_with_ts(&mut self, gyr: [Float; 3], ts: Float) {
880        // rest detection
881        if self.params.rest_bias_est_enabled || self.params.mag_dist_rejection_enabled {
882            Self::filter_vec(
883                gyr,
884                self.params.rest_filter_tau,
885                self.coeffs.gyr_ts,
886                self.coeffs.rest_gyr_lp_b,
887                self.coeffs.rest_gyr_lp_a,
888                &mut self.state.rest_gyr_lp_state,
889                &mut self.state.rest_last_gyr_lp,
890            );
891
892            self.state.rest_last_squared_deviations[0] =
893                square(gyr[0] - self.state.rest_last_gyr_lp[0])
894                    + square(gyr[1] - self.state.rest_last_gyr_lp[1])
895                    + square(gyr[2] - self.state.rest_last_gyr_lp[2]);
896
897            let bias_clip = self.params.bias_clip * (fc::PI / 180.0);
898            if self.state.rest_last_squared_deviations[0]
899                >= square(self.params.rest_th_gyr * (fc::PI / 180.0))
900                || math::fabs(self.state.rest_last_gyr_lp[0]) > bias_clip
901                || math::fabs(self.state.rest_last_gyr_lp[1]) > bias_clip
902                || math::fabs(self.state.rest_last_gyr_lp[2]) > bias_clip
903            {
904                self.state.rest_t = 0.0;
905                self.state.rest_detected = false;
906            }
907        }
908
909        // remove estimated gyro bias
910        let gyr_no_bias = [
911            gyr[0] - self.state.bias[0],
912            gyr[1] - self.state.bias[1],
913            gyr[2] - self.state.bias[2],
914        ];
915
916        // gyroscope prediction step
917        let gyr_norm = Self::norm(&gyr_no_bias);
918        let angle = gyr_norm * ts;
919        if gyr_norm > Float::EPSILON {
920            let c = math::cos(angle / 2.0);
921            let s = math::sin(angle / 2.0) / gyr_norm;
922            let gyr_step_quat = [
923                c,
924                s * gyr_no_bias[0],
925                s * gyr_no_bias[1],
926                s * gyr_no_bias[2],
927            ];
928            self.state.gyr_quat *= gyr_step_quat.into();
929            self.state.gyr_quat.normalize();
930        }
931    }
932
933    /// Performs accelerometer update step, using the measurement in m/s².
934    ///
935    /// It is only necessary to call this function directly if gyroscope, accelerometers and magnetometers have
936    /// different sampling rates. Otherwise, simply use [`update()`](Self::update()).
937    ///
938    /// Should be called after [`update_gyr()`](Self::update_gyr()) and before [`update_mag()`](Self::update_mag()).
939    pub fn update_acc(&mut self, acc: [Float; 3]) {
940        // ignore [0 0 0] samples
941        if acc == [0.0; 3] {
942            return;
943        }
944
945        // rest detection
946        if self.params.rest_bias_est_enabled {
947            Self::filter_vec(
948                acc,
949                self.params.rest_filter_tau,
950                self.coeffs.acc_ts,
951                self.coeffs.rest_acc_lp_b,
952                self.coeffs.rest_acc_lp_a,
953                &mut self.state.rest_acc_lp_state,
954                &mut self.state.rest_last_acc_lp,
955            );
956
957            self.state.rest_last_squared_deviations[1] =
958                square(acc[0] - self.state.rest_last_acc_lp[0])
959                    + square(acc[1] - self.state.rest_last_acc_lp[1])
960                    + square(acc[2] - self.state.rest_last_acc_lp[2]);
961
962            if self.state.rest_last_squared_deviations[1] >= square(self.params.rest_th_acc) {
963                self.state.rest_t = 0.0;
964                self.state.rest_detected = false;
965            } else {
966                self.state.rest_t += self.coeffs.acc_ts;
967                if self.state.rest_t >= self.params.rest_min_t {
968                    self.state.rest_detected = true;
969                }
970            }
971        }
972
973        // filter acc in inertial frame
974        let acc_earth = self.state.gyr_quat.rotate(acc);
975        Self::filter_vec(
976            acc_earth,
977            self.params.tau_acc,
978            self.coeffs.acc_ts,
979            self.coeffs.acc_lp_b,
980            self.coeffs.acc_lp_a,
981            &mut self.state.acc_lp_state,
982            &mut self.state.last_acc_lp,
983        );
984
985        // transform to 6D earth frame and normalize
986        let mut acc_earth = self.state.acc_quat.rotate(self.state.last_acc_lp);
987        Self::normalize(&mut acc_earth);
988
989        // inclination correction
990        let q_w = math::sqrt((acc_earth[2] + 1.0) / 2.0);
991        let acc_corr_quat: Quaternion = if q_w > 1e-6 {
992            [
993                q_w,
994                0.5 * acc_earth[1] / q_w,
995                -0.5 * acc_earth[0] / q_w,
996                0.0,
997            ]
998            .into()
999        } else {
1000            // to avoid numeric issues when acc is close to [0 0 -1], i.e. the correction step is close (<= 0.00011°) to 180°:
1001            [0.0, 1.0, 0.0, 1.0].into()
1002        };
1003        self.state.acc_quat = acc_corr_quat * self.state.acc_quat;
1004        self.state.acc_quat.normalize();
1005
1006        // calculate correction angular rate to facilitate debugging
1007        self.state.last_acc_corr_angular_rate =
1008            math::acos(acc_earth[2]) / self.coeffs.acc_ts;
1009
1010        // bias estimation
1011        #[cfg(feature = "motion-bias-estimation")]
1012        {
1013            if self.params.motion_bias_est_enabled || self.params.rest_bias_est_enabled {
1014                let bias_clip = self.params.bias_clip * (fc::PI / 180.0);
1015
1016                // get rotation matrix corresponding to accGyrQuat
1017                let acc_gyr_quat = self.quat_6d();
1018                let mut r: Matrix<3, 3> = [
1019                    [
1020                        1.0 - 2.0 * square(acc_gyr_quat.2) - 2.0 * square(acc_gyr_quat.3), // r11
1021                        2.0 * (acc_gyr_quat.2 * acc_gyr_quat.1 - acc_gyr_quat.0 * acc_gyr_quat.3), // r12
1022                        2.0 * (acc_gyr_quat.0 * acc_gyr_quat.2 + acc_gyr_quat.3 * acc_gyr_quat.1), // r13
1023                    ],
1024                    [
1025                        2.0 * (acc_gyr_quat.0 * acc_gyr_quat.3 + acc_gyr_quat.2 * acc_gyr_quat.1), // r21
1026                        1.0 - 2.0 * square(acc_gyr_quat.1) - 2.0 * square(acc_gyr_quat.3), // r22
1027                        2.0 * (acc_gyr_quat.2 * acc_gyr_quat.3 - acc_gyr_quat.1 * acc_gyr_quat.0), // r23
1028                    ],
1029                    [
1030                        2.0 * (acc_gyr_quat.3 * acc_gyr_quat.1 - acc_gyr_quat.0 * acc_gyr_quat.2), // r31
1031                        2.0 * (acc_gyr_quat.0 * acc_gyr_quat.1 + acc_gyr_quat.3 * acc_gyr_quat.2), // r32
1032                        1.0 - 2.0 * square(acc_gyr_quat.1) - 2.0 * square(acc_gyr_quat.2), // r33
1033                    ],
1034                ]
1035                .into();
1036
1037                // calculate R*b_hat (only the x and y component, as z is not needed)
1038                let mut bias_lp = [
1039                    r.0[0][0] * self.state.bias[0]
1040                        + r.0[0][1] * self.state.bias[1]
1041                        + r.0[0][2] * self.state.bias[2],
1042                    r.0[1][0] * self.state.bias[0]
1043                        + r.0[1][1] * self.state.bias[1]
1044                        + r.0[1][2] * self.state.bias[2],
1045                ];
1046
1047                // low-pass filter R and R*b_hat
1048                let r_arr: &mut [Float; 9] = flatten(&mut r.0);
1049                Self::filter_vec(
1050                    *r_arr,
1051                    self.params.tau_acc,
1052                    self.coeffs.acc_ts,
1053                    self.coeffs.acc_lp_b,
1054                    self.coeffs.acc_lp_a,
1055                    &mut self.state.motion_bias_est_rlp_state,
1056                    r_arr,
1057                );
1058                Self::filter_vec(
1059                    bias_lp,
1060                    self.params.tau_acc,
1061                    self.coeffs.acc_ts,
1062                    self.coeffs.acc_lp_b,
1063                    self.coeffs.acc_lp_a,
1064                    &mut self.state.motion_bias_est_bias_lp_state,
1065                    &mut bias_lp,
1066                );
1067
1068                // set measurement error and covariance for the respective Kalman filter update
1069                let (mut e, w) = if self.state.rest_detected && self.params.rest_bias_est_enabled {
1070                    let e = [
1071                        self.state.rest_last_gyr_lp[0] - self.state.bias[0],
1072                        self.state.rest_last_gyr_lp[1] - self.state.bias[1],
1073                        self.state.rest_last_gyr_lp[2] - self.state.bias[2],
1074                    ];
1075                    r = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]].into();
1076                    let w = [self.coeffs.bias_rest_w; 3];
1077                    (e, w)
1078                } else if self.params.motion_bias_est_enabled {
1079                    let e = [
1080                        -acc_earth[1] / self.coeffs.acc_ts + bias_lp[0]
1081                            - r.0[0][0] * self.state.bias[0]
1082                            - r.0[0][1] * self.state.bias[1]
1083                            - r.0[0][2] * self.state.bias[2],
1084                        acc_earth[0] / self.coeffs.acc_ts + bias_lp[1]
1085                            - r.0[1][0] * self.state.bias[0]
1086                            - r.0[1][1] * self.state.bias[1]
1087                            - r.0[1][2] * self.state.bias[2],
1088                        -r.0[2][0] * self.state.bias[0]
1089                            - r.0[2][1] * self.state.bias[1]
1090                            - r.0[2][2] * self.state.bias[2],
1091                    ];
1092                    let w = [
1093                        self.coeffs.bias_motion_w,
1094                        self.coeffs.bias_motion_w,
1095                        self.coeffs.bias_vertical_w,
1096                    ];
1097                    (e, w)
1098                } else {
1099                    ([0.0; 3], [-1.0; 3])
1100                };
1101
1102                // Kalman filter update
1103                // step 1: P = P + V (also increase covariance if there is no measurement update!)
1104                if self.state.bias_p.0[0][0] < self.coeffs.bias_p0 {
1105                    self.state.bias_p.0[0][0] += self.coeffs.bias_v;
1106                }
1107                if self.state.bias_p.0[1][1] < self.coeffs.bias_p0 {
1108                    self.state.bias_p.0[1][1] += self.coeffs.bias_v;
1109                }
1110                if self.state.bias_p.0[2][2] < self.coeffs.bias_p0 {
1111                    self.state.bias_p.0[2][2] += self.coeffs.bias_v;
1112                }
1113
1114                if w[0] >= 0.0 {
1115                    // clip disagreement to -2..2 °/s
1116                    // (this also effectively limits the harm done by the first inclination correction step)
1117                    Self::clip(&mut e, -bias_clip, bias_clip);
1118
1119                    // step 2: K = P R^T inv(W + R P R^T)
1120                    let mut k = self.state.bias_p * r.transpose(); // K = P R^T
1121                    k = r * k; // K = R P R^T
1122                    k.0[0][0] += w[0];
1123                    k.0[1][1] += w[1];
1124                    k.0[2][2] += w[2]; // K = W + R P R^T
1125                    k = k.invert().unwrap(); // K = inv(W + R P R^T)
1126                    k = r.transpose() * k; // K = R^T inv(W + R P R^T)
1127                    k = self.state.bias_p * k; // K = P R^T inv(W + R P R^T)
1128
1129                    // step 3: bias = bias + K (y - R bias) = bias + K e
1130                    self.state.bias[0] += k.0[0][0] * e[0] + k.0[0][1] * e[1] + k.0[0][2] * e[2];
1131                    self.state.bias[1] += k.0[1][0] * e[0] + k.0[1][1] * e[1] + k.0[1][2] * e[2];
1132                    self.state.bias[2] += k.0[2][0] * e[0] + k.0[2][1] * e[1] + k.0[2][2] * e[2];
1133
1134                    // step 4: P = P - K R P
1135                    k *= r; // K = K R
1136                    k *= self.state.bias_p; // K = K R P
1137                    self.state.bias_p -= k;
1138
1139                    // clip bias estimate to -2..2 °/s
1140                    Self::clip(&mut self.state.bias, -bias_clip, bias_clip);
1141                }
1142            }
1143        }
1144        #[cfg(not(feature = "motion-bias-estimation"))]
1145        {
1146            // simplified implementation of bias estimation for the special case in which only rest bias estimation is enabled
1147            if self.params.rest_bias_est_enabled {
1148                let bias_clip = self.params.bias_clip * (fc::PI / 180.0);
1149                if self.state.bias_p < self.coeffs.bias_p0 {
1150                    self.state.bias_p += self.coeffs.bias_v;
1151                }
1152                if self.state.rest_detected {
1153                    let mut e = [
1154                        self.state.rest_last_gyr_lp[0] - self.state.bias[0],
1155                        self.state.rest_last_gyr_lp[1] - self.state.bias[1],
1156                        self.state.rest_last_gyr_lp[2] - self.state.bias[2],
1157                    ];
1158                    Self::clip(&mut e, -bias_clip, bias_clip);
1159
1160                    // Kalman filter update, simplified scalar version for rest update
1161                    // (this version only uses the first entry of P as P is diagonal and all diagonal elements are the same)
1162                    // step 1: P = P + V (done above!)
1163                    // step 2: K = P R^T inv(W + R P R^T)
1164                    let k = self.state.bias_p / (self.coeffs.bias_rest_w + self.state.bias_p);
1165                    // step 3: bias = bias + K (y - R bias) = bias + K e
1166                    self.state.bias[0] += k * e[0];
1167                    self.state.bias[1] += k * e[1];
1168                    self.state.bias[2] += k * e[2];
1169                    // step 4: P = P - K R P
1170                    self.state.bias_p -= k * self.state.bias_p;
1171                    Self::clip(&mut self.state.bias, -bias_clip, bias_clip);
1172                }
1173            }
1174        }
1175    }
1176
1177    /// Performs magnetometer update step.
1178    ///
1179    /// It is only necessary to call this function directly if gyroscope, accelerometers and magnetometers have
1180    /// different sampling rates. Otherwise, simply use [`update()`](Self::update()).
1181    ///
1182    /// Should be called after [`update_acc()`](Self::update_acc()).
1183    pub fn update_mag(&mut self, mag: [Float; 3]) {
1184        // ignore [0 0 0] samples
1185        if mag == [0.0; 3] {
1186            return;
1187        }
1188
1189        // bring magnetometer measurement into 6D earth frame
1190        let acc_gyr_quat = self.quat_6d();
1191        let mag_earth = acc_gyr_quat.rotate(mag);
1192
1193        if self.params.mag_dist_rejection_enabled {
1194            self.state.mag_norm_dip[0] = Self::norm(&mag_earth);
1195            self.state.mag_norm_dip[1] =
1196                -math::asin(mag_earth[2] / self.state.mag_norm_dip[0]);
1197
1198            if self.params.mag_current_tau > 0.0 {
1199                Self::filter_vec(
1200                    self.state.mag_norm_dip,
1201                    self.params.mag_current_tau,
1202                    self.coeffs.mag_ts,
1203                    self.coeffs.mag_norm_dip_lp_b,
1204                    self.coeffs.mag_norm_dip_lp_a,
1205                    &mut self.state.mag_norm_dip_lp_state,
1206                    &mut self.state.mag_norm_dip,
1207                );
1208            }
1209
1210            // magnetic disturbance detection
1211            if math::fabs(self.state.mag_norm_dip[0] - self.state.mag_ref_norm)
1212                < self.params.mag_norm_th * self.state.mag_ref_norm
1213                && math::fabs(self.state.mag_norm_dip[1] - self.state.mag_ref_dip)
1214                    < self.params.mag_dip_th * (fc::PI / 180.0)
1215            {
1216                self.state.mag_undisturbed_t += self.coeffs.mag_ts;
1217                if self.state.mag_undisturbed_t >= self.params.mag_min_undisturbed_time {
1218                    self.state.mag_dist_detected = false;
1219                    self.state.mag_ref_norm += self.coeffs.k_mag_ref
1220                        * (self.state.mag_norm_dip[0] - self.state.mag_ref_norm);
1221                    self.state.mag_ref_dip += self.coeffs.k_mag_ref
1222                        * (self.state.mag_norm_dip[1] - self.state.mag_ref_dip);
1223                }
1224            } else {
1225                self.state.mag_undisturbed_t = 0.0;
1226                self.state.mag_dist_detected = true;
1227            }
1228
1229            // new magnetic field acceptance
1230            if math::fabs(self.state.mag_norm_dip[0] - self.state.mag_candidate_norm)
1231                < self.params.mag_norm_th * self.state.mag_candidate_norm
1232                && math::fabs(self.state.mag_norm_dip[1] - self.state.mag_candidate_dip)
1233                    < self.params.mag_dip_th * (fc::PI / 180.0)
1234            {
1235                if Self::norm(&self.state.rest_last_gyr_lp)
1236                    >= self.params.mag_new_min_gyr * fc::PI / 180.0
1237                {
1238                    self.state.mag_candidate_t += self.coeffs.mag_ts;
1239                }
1240                self.state.mag_candidate_norm += self.coeffs.k_mag_ref
1241                    * (self.state.mag_norm_dip[0] - self.state.mag_candidate_norm);
1242                self.state.mag_candidate_dip += self.coeffs.k_mag_ref
1243                    * (self.state.mag_norm_dip[1] - self.state.mag_candidate_dip);
1244
1245                if self.state.mag_dist_detected
1246                    && (self.state.mag_candidate_t >= self.params.mag_new_time
1247                        || (self.state.mag_ref_norm == 0.0
1248                            && self.state.mag_candidate_t >= self.params.mag_new_first_time))
1249                {
1250                    self.state.mag_ref_norm = self.state.mag_candidate_norm;
1251                    self.state.mag_ref_dip = self.state.mag_candidate_dip;
1252                    self.state.mag_dist_detected = false;
1253                    self.state.mag_undisturbed_t = self.params.mag_min_undisturbed_time;
1254                }
1255            } else {
1256                self.state.mag_candidate_t = 0.0;
1257                self.state.mag_candidate_norm = self.state.mag_norm_dip[0];
1258                self.state.mag_candidate_dip = self.state.mag_norm_dip[1];
1259            }
1260        }
1261
1262        // calculate disagreement angle based on current magnetometer measurement
1263        self.state.last_mag_dis_angle =
1264            math::atan2(mag_earth[0], mag_earth[1]) - self.state.delta;
1265
1266        // make sure the disagreement angle is in the range [-pi, pi]
1267        if self.state.last_mag_dis_angle > fc::PI {
1268            self.state.last_mag_dis_angle -= 2.0 * fc::PI;
1269        } else if self.state.last_mag_dis_angle < (-fc::PI) {
1270            self.state.last_mag_dis_angle += 2.0 * fc::PI;
1271        }
1272
1273        let mut k = self.coeffs.k_mag;
1274
1275        if self.params.mag_dist_rejection_enabled {
1276            // magnetic disturbance rejection
1277            if self.state.mag_dist_detected {
1278                if self.state.mag_reject_t <= self.params.mag_max_rejection_time {
1279                    self.state.mag_reject_t += self.coeffs.mag_ts;
1280                    k = 0.0;
1281                } else {
1282                    k /= self.params.mag_rejection_factor;
1283                }
1284            } else {
1285                self.state.mag_reject_t = (0.0 as Float).max(
1286                    self.state.mag_reject_t - self.params.mag_rejection_factor * self.coeffs.mag_ts,
1287                );
1288            }
1289        }
1290
1291        // ensure fast initial convergence
1292        if self.state.k_mag_init != 0.0 {
1293            // make sure that the gain k is at least 1/N, N=1,2,3,... in the first few samples
1294            if k < self.state.k_mag_init {
1295                k = self.state.k_mag_init;
1296            }
1297
1298            // iterative expression to calculate 1/N
1299            self.state.k_mag_init = self.state.k_mag_init / (self.state.k_mag_init + 1.0);
1300
1301            // disable if t > tauMag
1302            if self.state.k_mag_init * self.params.tau_mag < self.coeffs.mag_ts {
1303                self.state.k_mag_init = 0.0;
1304            }
1305        }
1306
1307        // first-order filter step
1308        self.state.delta += k * self.state.last_mag_dis_angle;
1309        // calculate correction angular rate to facilitate debugging
1310        self.state.last_mag_corr_angular_rate =
1311            k * self.state.last_mag_dis_angle / self.coeffs.mag_ts;
1312
1313        // make sure delta is in the range [-pi, pi]
1314        if self.state.delta > fc::PI {
1315            self.state.delta -= 2.0 * fc::PI;
1316        } else if self.state.delta < -fc::PI {
1317            self.state.delta += 2.0 * fc::PI;
1318        }
1319    }
1320
1321    /// Performs filter update step for one sample.
1322    pub fn update(&mut self, gyr: [Float; 3], acc: [Float; 3], mag: Option<[Float; 3]>) {
1323        self.update_gyr(gyr);
1324        self.update_acc(acc);
1325        if let Some(mag) = mag {
1326            self.update_mag(mag);
1327        }
1328    }
1329
1330    /// Returns the angular velocity strapdown integration quaternion
1331    /// $^{\mathcal{S}\_i}\_{\mathcal{I}_i}\mathbf{q}$.
1332    pub fn quat_3d(&self) -> Quaternion {
1333        self.state.gyr_quat
1334    }
1335
1336    /// Returns the 6D (magnetometer-free) orientation quaternion
1337    /// $^{\mathcal{S}\_i}\_{\mathcal{E}_i}\mathbf{q}$.
1338    pub fn quat_6d(&self) -> Quaternion {
1339        self.state.acc_quat * self.state.gyr_quat
1340    }
1341
1342    /// Returns the 9D (with magnetometers) orientation quaternion
1343    /// $^{\mathcal{S}\_i}\_{\mathcal{E}}\mathbf{q}$.
1344    pub fn quat_9d(&self) -> Quaternion {
1345        (self.state.acc_quat * self.state.gyr_quat).apply_delta(self.state.delta)
1346    }
1347
1348    /// Returns the heading difference $\delta$ between $\mathcal{E}_i$ and $\mathcal{E}$ in radians.
1349    ///
1350    /// $^{\mathcal{E}\_i}\_{\mathcal{E}}\mathbf{q} = \begin{bmatrix}\cos\frac{\delta}{2} & 0 & 0 &
1351    /// \sin\frac{\delta}{2}\end{bmatrix}^T$.
1352    pub fn delta(&self) -> Float {
1353        self.state.delta
1354    }
1355
1356    #[cfg(feature = "motion-bias-estimation")]
1357    /// Returns the current gyroscope bias estimate and the uncertainty in rad/s.
1358    ///
1359    /// The returned standard deviation sigma represents the estimation uncertainty in the worst direction and is based
1360    /// on an upper bound of the largest eigenvalue of the covariance matrix.
1361    pub fn bias_estimate(&self) -> ([Float; 3], Float) {
1362        // use largest absolute row sum as upper bound estimate for largest eigenvalue (Gershgorin circle theorem)
1363        // and clip output to biasSigmaInit
1364        let sum1 = math::fabs(self.state.bias_p.0[0][0])
1365            + math::fabs(self.state.bias_p.0[0][1])
1366            + math::fabs(self.state.bias_p.0[0][2]);
1367        let sum2 = math::fabs(self.state.bias_p.0[1][0])
1368            + math::fabs(self.state.bias_p.0[1][1])
1369            + math::fabs(self.state.bias_p.0[1][2]);
1370        let sum3 = math::fabs(self.state.bias_p.0[2][0])
1371            + math::fabs(self.state.bias_p.0[2][1])
1372            + math::fabs(self.state.bias_p.0[2][2]);
1373        let p = sum1.max(sum2).max(sum3).min(self.coeffs.bias_p0);
1374        (
1375            self.state.bias,
1376            math::sqrt(p) * (fc::PI / 100.0 / 180.0),
1377        )
1378    }
1379
1380    #[cfg(not(feature = "motion-bias-estimation"))]
1381    /// Returns the current gyroscope bias estimate and the uncertainty in rad/s.
1382    ///
1383    /// The returned standard deviation sigma represents the estimation uncertainty in the worst direction and is based
1384    /// on an upper bound of the largest eigenvalue of the covariance matrix.
1385    pub fn bias_estimate(&self) -> ([Float; 3], Float) {
1386        (
1387            self.state.bias,
1388            math::sqrt(self.state.bias_p) * (fc::PI / 100.0 / 180.0),
1389        )
1390    }
1391
1392    /// Sets the current gyroscope bias estimate and the uncertainty in rad/s.
1393    ///
1394    /// If a value for the uncertainty sigma is given in `sigma`, the covariance matrix is set to a corresponding scaled
1395    /// identity matrix.
1396    pub fn set_bias_estimate(&mut self, bias: [Float; 3], sigma: Option<Float>) {
1397        self.state.bias = bias;
1398        if let Some(sigma) = sigma {
1399            let p = square(sigma * (180.0 * 100.0 / fc::PI));
1400            #[cfg(feature = "motion-bias-estimation")]
1401            {
1402                self.state.bias_p = [[p, 0.0, 0.0], [0.0, p, 0.0], [0.0, 0.0, p]].into();
1403            }
1404            #[cfg(not(feature = "motion-bias-estimation"))]
1405            {
1406                self.state.bias_p = p;
1407            }
1408        }
1409    }
1410
1411    /// Returns true if rest was detected.
1412    pub fn rest_detected(&self) -> bool {
1413        self.state.rest_detected
1414    }
1415
1416    /// Returns true if a disturbed magnetic field was detected.
1417    pub fn mag_dist_detected(&self) -> bool {
1418        self.state.mag_dist_detected
1419    }
1420
1421    /// Returns the relative deviations used in rest detection.
1422    ///
1423    /// Looking at those values can be useful to understand how rest detection is working and which thresholds are
1424    /// suitable. The output array is filled with the last values for gyroscope and accelerometer,
1425    /// relative to the threshold. In order for rest to be detected, both values must stay below 1.
1426    pub fn relative_rest_deviations(&self) -> [Float; 2] {
1427        [
1428            math::sqrt(self.state.rest_last_squared_deviations[0])
1429                / (self.params.rest_th_gyr * (fc::PI / 180.0)),
1430            math::sqrt(self.state.rest_last_squared_deviations[1])
1431                / self.params.rest_th_acc,
1432        ]
1433    }
1434
1435    /// Returns the norm of the currently accepted magnetic field reference.
1436    pub fn mag_ref_norm(&self) -> Float {
1437        self.state.mag_ref_norm
1438    }
1439
1440    /// Returns the dip angle of the currently accepted magnetic field reference.
1441    pub fn mag_ref_dip(&self) -> Float {
1442        self.state.mag_ref_dip
1443    }
1444
1445    /// Overwrites the current magnetic field reference.
1446    pub fn set_mag_ref(&mut self, norm: Float, dip: Float) {
1447        self.state.mag_ref_norm = norm;
1448        self.state.mag_ref_dip = dip;
1449    }
1450
1451    /// Sets the time constant $\tau_\mathrm{acc}$ in seconds for accelerometer low-pass filtering.
1452    ///
1453    /// For more details, see [`Params::tau_acc`].
1454    pub fn set_tau_acc(&mut self, tau_acc: Float) {
1455        if self.params.tau_acc == tau_acc {
1456            return;
1457        }
1458        self.params.tau_acc = tau_acc;
1459        let mut new_b = [0.0; 3];
1460        let mut new_a = [0.0; 2];
1461
1462        Self::filter_coeffs(
1463            self.params.tau_acc,
1464            self.coeffs.acc_ts,
1465            &mut new_b,
1466            &mut new_a,
1467        );
1468        Self::filter_adapt_state_for_coeff_change(
1469            self.state.last_acc_lp,
1470            self.coeffs.acc_lp_b,
1471            self.coeffs.acc_lp_a,
1472            new_b,
1473            new_a,
1474            &mut self.state.acc_lp_state,
1475        );
1476
1477        #[cfg(feature = "motion-bias-estimation")]
1478        {
1479            // For R and biasLP, the last value is not saved in the state.
1480            // Since b0 is small (at reasonable settings), the last output is close to state[0].
1481            let mut r: [Float; 9] = [0.0; 9];
1482            for (i, val) in r.iter_mut().enumerate() {
1483                *val = self.state.motion_bias_est_rlp_state[i][0] as Float;
1484            }
1485            Self::filter_adapt_state_for_coeff_change(
1486                r,
1487                self.coeffs.acc_lp_b,
1488                self.coeffs.acc_lp_a,
1489                new_b,
1490                new_a,
1491                &mut self.state.motion_bias_est_rlp_state,
1492            );
1493            let mut bias_lp: [Float; 2] = [0.0; 2];
1494            for (i, val) in bias_lp.iter_mut().enumerate() {
1495                *val = self.state.motion_bias_est_bias_lp_state[i][0] as Float;
1496            }
1497            Self::filter_adapt_state_for_coeff_change(
1498                bias_lp,
1499                self.coeffs.acc_lp_b,
1500                self.coeffs.acc_lp_a,
1501                new_b,
1502                new_a,
1503                &mut self.state.motion_bias_est_bias_lp_state,
1504            );
1505        }
1506
1507        self.coeffs.acc_lp_b = new_b;
1508        self.coeffs.acc_lp_a = new_a;
1509    }
1510
1511    /// Sets the time constant $\tau_\mathrm{mag}$ in seconds for the magnetometer update.
1512    ///
1513    /// For more details, see [`Params::tau_mag`].
1514    pub fn set_tau_mag(&mut self, tau_mag: Float) {
1515        self.params.tau_mag = tau_mag;
1516        self.coeffs.k_mag = Self::gain_from_tau(self.params.tau_mag, self.coeffs.mag_ts)
1517    }
1518
1519    #[cfg(feature = "motion-bias-estimation")]
1520    /// Enables/disables gyroscope bias estimation during motion.
1521    pub fn set_motion_bias_est_enabled(&mut self, enabled: bool) {
1522        if self.params.motion_bias_est_enabled == enabled {
1523            return;
1524        }
1525        self.params.motion_bias_est_enabled = enabled;
1526        self.state.motion_bias_est_rlp_state = [[f64::NAN; 2]; 9];
1527        self.state.motion_bias_est_bias_lp_state = [[f64::NAN; 2]; 2];
1528    }
1529
1530    /// Enables/disables rest detection and bias estimation during rest.
1531    pub fn set_rest_bias_est_enabled(&mut self, enabled: bool) {
1532        if self.params.rest_bias_est_enabled == enabled {
1533            return;
1534        }
1535        self.params.rest_bias_est_enabled = enabled;
1536        self.state.rest_detected = false;
1537        self.state.rest_last_squared_deviations = [0.0; 2];
1538        self.state.rest_t = 0.0;
1539        self.state.rest_last_gyr_lp = [0.0; 3];
1540        self.state.rest_gyr_lp_state = [[f64::NAN; 2]; 3];
1541        self.state.rest_last_acc_lp = [0.0; 3];
1542        self.state.rest_acc_lp_state = [[f64::NAN; 2]; 3];
1543    }
1544
1545    /// Enables/disables magnetic disturbance detection and rejection.
1546    pub fn set_mag_dist_rejection_enabled(&mut self, enabled: bool) {
1547        if self.params.mag_dist_rejection_enabled == enabled {
1548            return;
1549        }
1550        self.params.mag_dist_rejection_enabled = enabled;
1551        self.state.mag_dist_detected = true;
1552        self.state.mag_ref_norm = 0.0;
1553        self.state.mag_ref_dip = 0.0;
1554        self.state.mag_undisturbed_t = 0.0;
1555        self.state.mag_reject_t = self.params.mag_max_rejection_time;
1556        self.state.mag_candidate_norm = -1.0;
1557        self.state.mag_candidate_dip = 0.0;
1558        self.state.mag_candidate_t = 0.0;
1559        self.state.mag_norm_dip_lp_state = [[f64::NAN; 2]; 2];
1560    }
1561
1562    /// Sets the current thresholds for rest detection.
1563    ///
1564    /// For details about the parameters, see [`Params::rest_th_gyr`] and [`Params::rest_th_acc`].
1565    pub fn set_rest_detection_thresholds(&mut self, th_gyr: Float, th_acc: Float) {
1566        self.params.rest_th_gyr = th_gyr;
1567        self.params.rest_th_acc = th_acc;
1568    }
1569
1570    /// Returns the current parameters.
1571    pub fn params(&self) -> &Params {
1572        &self.params
1573    }
1574
1575    /// Returns the coefficients used by the algorithm.
1576    pub fn coeffs(&self) -> &Coefficients {
1577        &self.coeffs
1578    }
1579
1580    /// Returns the current state.
1581    pub fn state(&self) -> &State {
1582        &self.state
1583    }
1584
1585    /// Gets the current state for modification.
1586    ///
1587    /// This method allows to set a completely arbitrary filter state and is intended for debugging purposes.
1588    pub fn state_mut(&mut self) -> &mut State {
1589        &mut self.state
1590    }
1591
1592    /// Resets the state to the default values at initialization.
1593    ///
1594    /// Resetting the state is equivalent to creating a new instance of this struct.
1595    pub fn reset_state(&mut self) {
1596        self.state.gyr_quat = [1.0, 0.0, 0.0, 0.0].into();
1597        self.state.acc_quat = [1.0, 0.0, 0.0, 0.0].into();
1598        self.state.delta = 0.0;
1599
1600        self.state.rest_detected = false;
1601        self.state.mag_dist_detected = true;
1602
1603        self.state.last_acc_lp = [0.0; 3];
1604        self.state.acc_lp_state = [[f64::NAN; 2]; 3];
1605        self.state.last_acc_corr_angular_rate = 0.0;
1606
1607        self.state.k_mag_init = 1.0;
1608        self.state.last_mag_dis_angle = 0.0;
1609        self.state.last_mag_corr_angular_rate = 0.0;
1610
1611        self.state.bias = [0.0; 3];
1612
1613        #[cfg(feature = "motion-bias-estimation")]
1614        {
1615            self.state.bias_p = [
1616                [self.coeffs.bias_p0, 0.0, 0.0],
1617                [0.0, self.coeffs.bias_p0, 0.0],
1618                [0.0, 0.0, self.coeffs.bias_p0],
1619            ]
1620            .into();
1621        }
1622
1623        #[cfg(not(feature = "motion-bias-estimation"))]
1624        {
1625            self.state.bias_p = self.coeffs.bias_p0;
1626        }
1627
1628        #[cfg(feature = "motion-bias-estimation")]
1629        {
1630            self.state.motion_bias_est_rlp_state = [[f64::NAN; 2]; 9];
1631            self.state.motion_bias_est_bias_lp_state = [[f64::NAN; 2]; 2];
1632        }
1633
1634        self.state.rest_last_squared_deviations = [0.0; 2];
1635        self.state.rest_t = 0.0;
1636        self.state.rest_last_gyr_lp = [0.0; 3];
1637        self.state.rest_gyr_lp_state = [[f64::NAN; 2]; 3];
1638        self.state.rest_last_acc_lp = [0.0; 3];
1639        self.state.rest_acc_lp_state = [[f64::NAN; 2]; 3];
1640
1641        self.state.mag_ref_norm = 0.0;
1642        self.state.mag_ref_dip = 0.0;
1643        self.state.mag_undisturbed_t = 0.0;
1644        self.state.mag_reject_t = self.params.mag_max_rejection_time;
1645        self.state.mag_candidate_norm = -1.0;
1646        self.state.mag_candidate_dip = 0.0;
1647        self.state.mag_candidate_t = 0.0;
1648        self.state.mag_norm_dip = [0.0; 2];
1649        self.state.mag_norm_dip_lp_state = [[f64::NAN; 2]; 2];
1650    }
1651
1652    fn norm<const N: usize>(vec: &[Float; N]) -> Float {
1653        let mut s = 0.0;
1654        for i in vec {
1655            s += i * i;
1656        }
1657        math::sqrt(s)
1658    }
1659
1660    fn normalize<const N: usize>(vec: &mut [Float; N]) {
1661        let n = Self::norm(vec);
1662        if n < Float::EPSILON {
1663            return;
1664        }
1665        for i in vec.iter_mut() {
1666            *i /= n;
1667        }
1668    }
1669
1670    fn clip<const N: usize>(vec: &mut [Float; N], min: Float, max: Float) {
1671        for i in vec.iter_mut() {
1672            if *i < min {
1673                *i = min;
1674            } else if *i > max {
1675                *i = max;
1676            }
1677        }
1678    }
1679
1680    fn gain_from_tau(tau: Float, ts: Float) -> Float {
1681        assert!(ts > 0.0);
1682        if tau < 0.0 {
1683            0.0 // k=0 for negative tau (disable update)
1684        } else if tau == 0.0 {
1685            1.0 // k=1 for tau=0
1686        } else {
1687            1.0 - math::exp(-ts / tau) // fc = 1/(2*pi*tau)
1688        }
1689    }
1690
1691    fn filter_coeffs(tau: Float, ts: Float, out_b: &mut [f64; 3], out_a: &mut [f64; 2]) {
1692        assert!(tau > 0.0);
1693        assert!(ts > 0.0);
1694        // second order Butterworth filter based on https://stackoverflow.com/a/52764064
1695        let fc = (f64c::SQRT_2 / (2.0 * f64c::PI)) / (tau as f64); // time constant of dampened, non-oscillating part of step response
1696        let c = math::tan_f64(f64c::PI * fc * ts as f64);
1697        let d = c * c + f64c::SQRT_2 * c + 1.0;
1698        let b0 = c * c / d;
1699        out_b[0] = b0;
1700        out_b[1] = 2.0 * b0;
1701        out_b[2] = b0;
1702        // a0 = 1.0
1703        out_a[0] = 2.0 * (c * c - 1.0) / d; // a1
1704        out_a[1] = (1.0 - f64c::SQRT_2 * c + c * c) / d; // a2
1705    }
1706
1707    fn filter_initial_state(x0: Float, b: [f64; 3], a: [f64; 2], out: &mut [f64; 2]) {
1708        // initial state for steady state (equivalent to scipy.signal.lfilter_zi, obtained by setting y=x=x0 in the filter
1709        // update equation)
1710        out[0] = (x0 as f64) * (1.0 - b[0]);
1711        out[1] = (x0 as f64) * (b[2] - a[1]);
1712    }
1713
1714    fn filter_adapt_state_for_coeff_change<const N: usize>(
1715        last_y: [Float; N],
1716        b_old: [f64; 3],
1717        a_old: [f64; 2],
1718        b_new: [f64; 3],
1719        a_new: [f64; 2],
1720        state: &mut [[f64; 2]; N],
1721    ) {
1722        if state[0][0].is_nan() {
1723            return;
1724        }
1725        for (i, row) in state.iter_mut().enumerate() {
1726            row[0] += (b_old[0] - b_new[0]) * last_y[i] as f64;
1727            row[1] += (b_old[1] - b_new[1] - a_old[0] + a_new[0]) * last_y[i] as f64;
1728        }
1729    }
1730
1731    fn filter_step(x: Float, b: [f64; 3], a: [f64; 2], state: &mut [f64; 2]) -> Float {
1732        // difference equations based on scipy.signal.lfilter documentation
1733        // assumes that a0 == 1.0
1734        let y = b[0] * (x as f64) + state[0];
1735        state[0] = b[1] * (x as f64) - a[0] * y + state[1];
1736        state[1] = b[2] * (x as f64) - a[1] * y;
1737        y as Float
1738    }
1739
1740    fn filter_vec<const N: usize>(
1741        x: [Float; N],
1742        tau: Float,
1743        ts: Float,
1744        b: [f64; 3],
1745        a: [f64; 2],
1746        state: &mut [[f64; 2]; N],
1747        out: &mut [Float; N],
1748    ) {
1749        assert!(N >= 2);
1750
1751        // to avoid depending on a single sample, average the first samples (for duration tau)
1752        // and then use this average to calculate the filter initial state
1753        if state[0][0].is_nan() {
1754            // initialization phase
1755            if state[1][0].is_nan() {
1756                // first sample
1757                state[1][0] = 0.0; // state[1][0] is used to store the sample count
1758                for row in state.iter_mut() {
1759                    row[1] = 0.0; // state[i][1] is used to store the sum
1760                }
1761            }
1762            state[1][0] += 1.0;
1763            for i in 0..N {
1764                state[i][1] += x[i] as f64;
1765                out[i] = (state[i][1] / state[1][0]) as Float;
1766            }
1767            if (state[1][0] as Float) * ts >= tau {
1768                for i in 0..N {
1769                    Self::filter_initial_state(out[i], b, a, &mut state[i]);
1770                }
1771            }
1772            return;
1773        }
1774
1775        for i in 0..N {
1776            out[i] = Self::filter_step(x[i], b, a, &mut state[i]);
1777        }
1778    }
1779
1780    fn setup(&mut self) {
1781        assert!(self.coeffs.gyr_ts > 0.0);
1782        assert!(self.coeffs.acc_ts > 0.0);
1783        assert!(self.coeffs.mag_ts > 0.0);
1784
1785        Self::filter_coeffs(
1786            self.params.tau_acc,
1787            self.coeffs.acc_ts,
1788            &mut self.coeffs.acc_lp_b,
1789            &mut self.coeffs.acc_lp_a,
1790        );
1791
1792        self.coeffs.k_mag = Self::gain_from_tau(self.params.tau_mag, self.coeffs.mag_ts);
1793
1794        self.coeffs.bias_p0 = square(self.params.bias_sigma_init * 100.0);
1795        // the system noise increases the variance from 0 to (0.1 °/s)^2 in biasForgettingTime seconds
1796        self.coeffs.bias_v =
1797            square(0.1 * 100.0) * self.coeffs.acc_ts / self.params.bias_forgetting_time;
1798
1799        #[cfg(feature = "motion-bias-estimation")]
1800        {
1801            let p_motion = square(self.params.bias_sigma_motion * 100.0);
1802            self.coeffs.bias_motion_w = square(p_motion) / self.coeffs.bias_v + p_motion;
1803            self.coeffs.bias_vertical_w =
1804                self.coeffs.bias_motion_w / self.params.bias_vertical_forgetting_factor.max(1e-10);
1805        }
1806
1807        let p_rest = square(self.params.bias_sigma_rest * 100.0);
1808        self.coeffs.bias_rest_w = square(p_rest) / self.coeffs.bias_v + p_rest;
1809
1810        Self::filter_coeffs(
1811            self.params.rest_filter_tau,
1812            self.coeffs.gyr_ts,
1813            &mut self.coeffs.rest_gyr_lp_b,
1814            &mut self.coeffs.rest_gyr_lp_a,
1815        );
1816        Self::filter_coeffs(
1817            self.params.rest_filter_tau,
1818            self.coeffs.acc_ts,
1819            &mut self.coeffs.rest_acc_lp_b,
1820            &mut self.coeffs.rest_acc_lp_a,
1821        );
1822
1823        self.coeffs.k_mag_ref = Self::gain_from_tau(self.params.mag_ref_tau, self.coeffs.mag_ts);
1824        if self.params.mag_current_tau > 0.0 {
1825            Self::filter_coeffs(
1826                self.params.mag_current_tau,
1827                self.coeffs.mag_ts,
1828                &mut self.coeffs.mag_norm_dip_lp_b,
1829                &mut self.coeffs.mag_norm_dip_lp_a,
1830            );
1831        } else {
1832            self.coeffs.mag_norm_dip_lp_b = [f64::NAN; 3];
1833            self.coeffs.mag_norm_dip_lp_a = [f64::NAN; 2];
1834        }
1835
1836        self.reset_state();
1837    }
1838}
1839
1840#[cfg(test)]
1841mod tests {
1842    use crate::{Params, Quaternion, VQF};
1843
1844    #[test]
1845    fn basic_parity() {
1846        for mode in 0..=5 {
1847            let params = match mode {
1848                0 => Default::default(),
1849                1 => Params {
1850                    mag_dist_rejection_enabled: false,
1851                    ..Default::default()
1852                },
1853                2 => Params {
1854                    rest_bias_est_enabled: false,
1855                    ..Default::default()
1856                },
1857                3 => Params {
1858                    motion_bias_est_enabled: false,
1859                    ..Default::default()
1860                },
1861                4 => Params {
1862                    rest_bias_est_enabled: false,
1863                    motion_bias_est_enabled: false,
1864                    ..Default::default()
1865                },
1866                5 => Params {
1867                    mag_dist_rejection_enabled: false,
1868                    rest_bias_est_enabled: false,
1869                    motion_bias_est_enabled: false,
1870                    ..Default::default()
1871                },
1872                _ => panic!(),
1873            };
1874            let expected: Quaternion = match mode {
1875                0 => [0.499988, 0.499988, 0.500012, 0.500012].into(),
1876                1 => [0.5, 0.5, 0.5, 0.5].into(),
1877                2 => [0.451372, 0.453052, 0.543672, 0.543533].into(),
1878                3 => [0.499988, 0.499988, 0.500012, 0.500012].into(),
1879                4 => [0.424513, 0.454375, 0.555264, 0.55228].into(),
1880                5 => [0.44869, 0.478654, 0.534476, 0.532825].into(),
1881                _ => panic!(),
1882            };
1883            let mut vqf = VQF::new(0.01, None, None, Some(params));
1884
1885            let gyr = [0.01; 3];
1886            let acc = [0.0, 9.8, 0.0];
1887            let mag = [0.5, 0.8, 0.0];
1888
1889            for _ in 0..10000 {
1890                vqf.update(gyr, acc, Some(mag))
1891            }
1892
1893            let quat = vqf.quat_9d();
1894            assert!((quat.0 - expected.0).abs() < 1e-6);
1895            assert!((quat.1 - expected.1).abs() < 1e-6);
1896            assert!((quat.2 - expected.2).abs() < 1e-6);
1897            assert!((quat.3 - expected.3).abs() < 1e-6);
1898        }
1899    }
1900
1901    #[allow(clippy::approx_constant)]
1902    #[allow(non_snake_case)]
1903    mod wigwagwent_tests {
1904        use crate::{Params, VQF};
1905
1906    
1907        #[test]
1908        fn single_same_3D_quat() {
1909            let mut vqf = VQF::new(0.01, None, None, None);
1910    
1911            let gyr = [0.021, 0.021, 0.021];
1912            vqf.update_gyr(gyr);
1913    
1914            let quat = vqf.quat_3d();
1915            assert!((quat.0 - 1.0).abs() < 1e-6);
1916            assert!((quat.1 - 0.000105).abs() < 1e-6);
1917            assert!((quat.2 - 0.000105).abs() < 1e-6);
1918            assert!((quat.3 - 0.000105).abs() < 1e-6);
1919        }
1920    
1921        #[test]
1922        fn single_x_3D_quat() {
1923            let mut vqf = VQF::new(0.01, None, None, None);
1924    
1925            let gyr = [0.25, 0.0, 0.0];
1926            vqf.update_gyr(gyr);
1927    
1928            let quat = vqf.quat_3d();
1929            assert!((quat.0 - 0.9999999).abs() < 1e-6);
1930            assert!((quat.1 - 0.00125).abs() < 1e-6);
1931            assert!((quat.2 - 0.0).abs() < 1e-6);
1932            assert!((quat.3 - 0.0).abs() < 1e-6);
1933        }
1934    
1935        #[test]
1936        fn single_y_3D_quat() {
1937            let mut vqf = VQF::new(0.01, None, None, None);
1938    
1939            let gyr = [0.0, 0.25, 0.0];
1940            vqf.update_gyr(gyr);
1941    
1942            let quat = vqf.quat_3d();
1943            assert!((quat.0 - 0.9999999).abs() < 1e-6);
1944            assert!((quat.1 - 0.0).abs() < 1e-6);
1945            assert!((quat.2 - 0.00125).abs() < 1e-6);
1946            assert!((quat.3 - 0.0).abs() < 1e-6);
1947        }
1948    
1949        #[test]
1950        fn single_z_3D_quat() {
1951            let mut vqf = VQF::new(0.01, None, None, None);
1952    
1953            let gyr = [0.0, 0.0, 0.25];
1954            vqf.update_gyr(gyr);
1955    
1956            let quat = vqf.quat_3d();
1957            assert!((quat.0 - 0.9999999).abs() < 1e-6);
1958            assert!((quat.1 - 0.0).abs() < 1e-6);
1959            assert!((quat.2 - 0.0).abs() < 1e-6);
1960            assert!((quat.3 - 0.00125).abs() < 1e-6);
1961        }
1962    
1963        #[test]
1964        fn single_different_3D_quat() {
1965            let mut vqf = VQF::new(0.01, None, None, None);
1966    
1967            let gyr = [0.054, 0.012, -0.9];
1968            vqf.update_gyr(gyr);
1969    
1970            let quat = vqf.quat_3d();
1971            assert!((quat.0 - 0.99999).abs() < 1e-6);
1972            assert!((quat.1 - 0.000269999).abs() < 1e-6);
1973            assert!((quat.2 - 5.99998e-5).abs() < 1e-6);
1974            assert!((quat.3 - -0.00449998).abs() < 1e-6);
1975        }
1976    
1977        #[test]
1978        fn many_same_3D_quat() {
1979            let mut vqf = VQF::new(0.01, None, None, None);
1980    
1981            let gyr = [0.021, 0.021, 0.021];
1982            for _ in 0..10_000 {
1983                vqf.update_gyr(gyr);
1984            }
1985    
1986            let quat = vqf.quat_3d();
1987            assert!((quat.0 - -0.245327).abs() < 1e-6); //slightly different results
1988            assert!((quat.1 - 0.559707).abs() < 1e-6);
1989            assert!((quat.2 - 0.559707).abs() < 1e-6);
1990            assert!((quat.3 - 0.559707).abs() < 1e-6);
1991        }
1992    
1993        #[test]
1994        fn many_different_3D_quat() {
1995            let mut vqf = VQF::new(0.01, None, None, None);
1996    
1997            let gyr = [0.054, 0.012, -0.09];
1998            for _ in 0..10_000 {
1999                vqf.update_gyr(gyr);
2000            }
2001    
2002            let quat = vqf.quat_3d();
2003            assert!((quat.0 - 0.539342).abs() < 1e-6); //slightly different results
2004            assert!((quat.1 - -0.430446).abs() < 1e-6);
2005            assert!((quat.2 - -0.0956546).abs() < 1e-6);
2006            assert!((quat.3 - 0.71741).abs() < 1e-6);
2007        }
2008    
2009        #[test]
2010        fn single_same_6D_quat() {
2011            let mut vqf = VQF::new(0.01, None, None, None);
2012    
2013            let gyr = [0.021, 0.021, 0.021];
2014            let acc = [5.663806, 5.663806, 5.663806];
2015            vqf.update_gyr(gyr);
2016            vqf.update_acc(acc);
2017    
2018            let quat = vqf.quat_6d();
2019            assert!((quat.0 - 0.888074).abs() < 1e-6);
2020            assert!((quat.1 - 0.325117).abs() < 1e-6);
2021            assert!((quat.2 - -0.324998).abs() < 1e-6);
2022            assert!((quat.3 - 0.00016151).abs() < 1e-6);
2023        }
2024    
2025        #[test]
2026        fn single_x_6D_quat() {
2027            let mut vqf = VQF::new(0.01, None, None, None);
2028    
2029            let gyr = [0.021, 0.021, 0.021];
2030            let acc = [9.81, 0.0, 0.0];
2031            vqf.update(gyr, acc, None);
2032    
2033            let quat = vqf.quat_6d();
2034            assert!((quat.0 - 0.707107).abs() < 1e-6);
2035            assert!((quat.1 - 0.000148508).abs() < 1e-6);
2036            assert!((quat.2 - -0.707107).abs() < 1e-6);
2037            assert!((quat.3 - 0.000148508).abs() < 1e-6);
2038        }
2039    
2040        #[test]
2041        fn single_y_6D_quat() {
2042            let mut vqf = VQF::new(0.01, None, None, None);
2043    
2044            let gyr = [0.021, 0.021, 0.021];
2045            let acc = [0.0, 9.81, 0.0];
2046            vqf.update(gyr, acc, None);
2047    
2048            let quat = vqf.quat_6d();
2049            assert!((quat.0 - 0.707107).abs() < 1e-6);
2050            assert!((quat.1 - 0.707107).abs() < 1e-6);
2051            assert!((quat.2 - 0.000148477).abs() < 1e-6);
2052            assert!((quat.3 - 0.000148477).abs() < 1e-6);
2053        }
2054    
2055        #[test]
2056        fn single_z_6D_quat() {
2057            let mut vqf = VQF::new(0.01, None, None, None);
2058    
2059            let gyr = [0.021, 0.021, 0.021];
2060            let acc = [0.0, 0.0, 9.81];
2061            vqf.update(gyr, acc, None);
2062    
2063            let quat = vqf.quat_6d();
2064            assert!((quat.0 - 1.0).abs() < 1e-6);
2065            assert!((quat.1 - -1.72732e-20).abs() < 1e-6);
2066            assert!((quat.2 - -4.06576e-20).abs() < 1e-6);
2067            assert!((quat.3 - 0.000105).abs() < 1e-6);
2068        }
2069    
2070        #[test]
2071        fn single_different_6D_quat() {
2072            let mut vqf = VQF::new(0.01, None, None, None);
2073    
2074            let gyr = [0.021, 0.021, 0.021];
2075            let acc = [4.5, 6.7, 3.2];
2076            vqf.update(gyr, acc, None);
2077    
2078            let quat = vqf.quat_6d();
2079            assert!((quat.0 - 0.827216).abs() < 1e-6);
2080            assert!((quat.1 - 0.466506).abs() < 1e-6);
2081            assert!((quat.2 - -0.313187).abs() < 1e-6);
2082            assert!((quat.3 - 0.000168725).abs() < 1e-6);
2083        }
2084    
2085        #[test]
2086        fn many_same_6D_quat() {
2087            let mut vqf = VQF::new(0.01, None, None, None);
2088    
2089            let gyr = [0.021, 0.021, 0.021];
2090            let acc = [5.663806, 5.663806, 5.663806];
2091    
2092            for _ in 0..10_000 {
2093                vqf.update(gyr, acc, None);
2094            }
2095    
2096            let quat = vqf.quat_6d();
2097            assert!((quat.0 - 0.887649).abs() < 1e-6); //Look into why there is so
2098            assert!((quat.1 - 0.334951).abs() < 1e-6); // much difference between them
2099            assert!((quat.2 - -0.314853).abs() < 1e-6); // we use f32 math, they use mostly double math
2100            assert!((quat.3 - 0.0274545).abs() < 1e-6);
2101        }
2102    
2103        #[test]
2104        fn many_different_6D_quat() {
2105            let mut vqf = VQF::new(0.01, None, None, None);
2106    
2107            let gyr = [0.021, 0.021, 0.021];
2108            let acc = [4.5, 6.7, 3.2];
2109    
2110            for _ in 0..10_000 {
2111                vqf.update(gyr, acc, None);
2112            }
2113    
2114            let quat = vqf.quat_6d();
2115            assert!((quat.0 - 0.826852).abs() < 1e-6); //Look into why there is so
2116            assert!((quat.1 - 0.475521).abs() < 1e-6); // much difference between them
2117            assert!((quat.2 - -0.299322).abs() < 1e-6); // we use f32 math, they use mostly double math
2118            assert!((quat.3 - 0.0245133).abs() < 1e-6);
2119        }
2120    
2121        #[test]
2122        fn single_same_9D_quat() {
2123            let mut vqf = VQF::new(0.01, None, None, None);
2124    
2125            let gyr = [0.021, 0.021, 0.021];
2126            let acc = [5.663806, 5.663806, 5.663806];
2127            let mag = [10.0, 10.0, 10.0];
2128            vqf.update_gyr(gyr);
2129            vqf.update_acc(acc);
2130            vqf.update_mag(mag);
2131    
2132            let quat = vqf.quat_9d();
2133            assert!((quat.0 - 0.86428).abs() < 1e-6);
2134            assert!((quat.1 - 0.391089).abs() < 1e-6);
2135            assert!((quat.2 - -0.241608).abs() < 1e-6);
2136            assert!((quat.3 - 0.204195).abs() < 1e-6);
2137        }
2138    
2139        #[test]
2140        fn single_x_9D_quat() {
2141            let mut vqf = VQF::new(0.01, None, None, None);
2142    
2143            let gyr = [0.021, 0.021, 0.021];
2144            let acc = [5.663806, 5.663806, 5.663806];
2145            let mag = [10.0, 0.0, 0.0];
2146            vqf.update(gyr, acc, Some(mag));
2147    
2148            let quat = vqf.quat_9d();
2149            assert!((quat.0 - 0.540625).abs() < 1e-6);
2150            assert!((quat.1 - 0.455768).abs() < 1e-6);
2151            assert!((quat.2 - 0.060003).abs() < 1e-6);
2152            assert!((quat.3 - 0.704556).abs() < 1e-6);
2153        }
2154    
2155        #[test]
2156        fn single_y_9D_quat() {
2157            let mut vqf = VQF::new(0.01, None, None, None);
2158    
2159            let gyr = [0.021, 0.021, 0.021];
2160            let acc = [5.663806, 5.663806, 5.663806];
2161            let mag = [0.0, 10.0, 0.0];
2162            vqf.update(gyr, acc, Some(mag));
2163    
2164            let quat = vqf.quat_9d();
2165            assert!((quat.0 - 0.880476).abs() < 1e-6);
2166            assert!((quat.1 - 0.279848).abs() < 1e-6);
2167            assert!((quat.2 - -0.364705).abs() < 1e-6);
2168            assert!((quat.3 - -0.115917).abs() < 1e-6);
2169        }
2170    
2171        #[test]
2172        fn single_z_9D_quat() {
2173            let mut vqf = VQF::new(0.01, None, None, None);
2174    
2175            let gyr = [0.021, 0.021, 0.021];
2176            let acc = [5.663806, 5.663806, 5.663806];
2177            let mag = [0.0, 0.0, 10.0];
2178            vqf.update(gyr, acc, Some(mag));
2179    
2180            let quat = vqf.quat_9d();
2181            assert!((quat.0 - 0.339851).abs() < 1e-6);
2182            assert!((quat.1 - -0.17592).abs() < 1e-6);
2183            assert!((quat.2 - -0.424708).abs() < 1e-6);
2184            assert!((quat.3 - -0.820473).abs() < 1e-6);
2185        }
2186    
2187        #[test]
2188        fn single_different_9D_quat() {
2189            let mut vqf = VQF::new(0.01, None, None, None);
2190    
2191            let gyr = [0.021, 0.021, 0.021];
2192            let acc = [5.663806, 5.663806, 5.663806];
2193            let mag = [3.54, 6.32, -2.34];
2194            vqf.update(gyr, acc, Some(mag));
2195    
2196            let quat = vqf.quat_9d();
2197            assert!((quat.0 - 0.864117).abs() < 1e-6);
2198            assert!((quat.1 - 0.391281).abs() < 1e-6);
2199            assert!((quat.2 - -0.241297).abs() < 1e-6);
2200            assert!((quat.3 - 0.204882).abs() < 1e-6);
2201        }
2202    
2203        #[test]
2204        fn many_same_9D_quat() {
2205            let mut vqf = VQF::new(0.01, None, None, None);
2206    
2207            let gyr = [0.021, 0.021, 0.021];
2208            let acc = [5.663806, 5.663806, 5.663806];
2209            let mag = [10.0, 10.0, 10.0];
2210    
2211            for _ in 0..10_000 {
2212                vqf.update(gyr, acc, Some(mag));
2213            }
2214    
2215            let quat = vqf.quat_9d();
2216            assert!((quat.0 - 0.338005).abs() < 1e-6); //Look into why there is so
2217            assert!((quat.1 - -0.176875).abs() < 1e-6); // much difference between them
2218            assert!((quat.2 - -0.424311).abs() < 1e-6); // we use f32 math, they use mostly double math
2219            assert!((quat.3 - -0.821236).abs() < 1e-6);
2220        }
2221    
2222        #[test]
2223        fn many_different_9D_quat() {
2224            let mut vqf = VQF::new(0.01, None, None, None);
2225    
2226            let gyr = [0.021, 0.021, 0.021];
2227            let acc = [5.663806, 5.663806, 5.663806];
2228            let mag = [3.54, 6.32, -2.34];
2229    
2230            for _ in 0..10_000 {
2231                vqf.update(gyr, acc, Some(mag));
2232            }
2233    
2234            let quat = vqf.quat_9d();
2235            assert!((quat.0 - 0.864111).abs() < 1e-6); //Look into why there is so
2236            assert!((quat.1 - 0.391288).abs() < 1e-6); // much difference between them
2237            assert!((quat.2 - -0.241286).abs() < 1e-6); // we use f32 math, they use mostly double math
2238            assert!((quat.3 - 0.204906).abs() < 1e-6);
2239        }
2240    
2241        #[test]
2242        fn run_vqf_cpp_example() {
2243            let mut vqf = VQF::new(0.01, None, None, None);
2244    
2245            let gyr = [0.01745329; 3];
2246            let acc = [5.663806; 3];
2247    
2248            for _ in 0..6000 {
2249                vqf.update(gyr, acc, None);
2250            }
2251    
2252            let quat = vqf.quat_6d();
2253            assert!((quat.0 - 0.887781).abs() < 1e-6);
2254            assert!((quat.1 - 0.333302).abs() < 1e-6);
2255            assert!((quat.2 - -0.316598).abs() < 1e-6);
2256            assert!((quat.3 - 0.0228175).abs() < 1e-6);
2257        }
2258    
2259        #[test]
2260        fn run_vqf_cpp_example_basic() {
2261            let mut vqf = VQF::new(0.01, None, None, Some(Params {
2262                rest_bias_est_enabled: false,
2263                motion_bias_est_enabled: false,
2264                mag_dist_rejection_enabled: false,
2265                ..Default::default()
2266            }));
2267    
2268            let gyr = [0.01745329; 3];
2269            let acc = [5.663806; 3];
2270    
2271            for _ in 0..6000 {
2272                vqf.update(gyr, acc, None);
2273            }
2274    
2275            let quat = vqf.quat_6d();
2276            assert!((quat.0 - 0.547223).abs() < 1e-6);
2277            assert!((quat.1 - 0.456312).abs() < 1e-6);
2278            assert!((quat.2 - 0.055717).abs() < 1e-6);
2279            assert!((quat.3 - 0.699444).abs() < 1e-6);
2280        }
2281    }
2282}