kalman_fusion/
lib.rs

1
2use num_traits::float::Float;
3
4/// Holds state for a simple Kalman filter in a single variable.
5/// This can be used to fuse inputs from multiple identical "sensors".
6#[derive(Debug, Clone, Copy)]
7pub struct KalmanState<T> {
8  pub estimate: T,/// Estimated value of the variable
9  pub uncertainty: T,/// Calculated total uncertainty in the estimate
10  measurement_variance: T,  // Uncertainty in the measurement itself
11  process_variance: T,      // Error introduced by uncertainty in the process (model)
12}
13
14impl<T> KalmanState<T>
15  where T: Float
16{
17  pub fn new_float(
18    estimate: T,
19    uncertainty: T,
20    measurement_variance: T,
21    process_variance: T) -> KalmanState<T>
22  {
23    KalmanState {
24      estimate,
25      uncertainty: uncertainty.abs(),
26      measurement_variance: measurement_variance.abs(),
27      process_variance: process_variance.abs(),
28    }
29  }
30}
31
32
33
34/// Kalman update function (fold function) for Float types
35pub fn kalman_update_float<T>(state: &KalmanState<T>, observation: T) -> KalmanState<T>
36  where
37    T: Float,
38{
39  // Kalman gain
40  let kalman_gain = state.uncertainty / (state.uncertainty + state.measurement_variance);
41
42  // Update estimate
43  let new_estimate = state.estimate + kalman_gain * (observation - state.estimate);
44
45  // Update uncertainty
46  let mut new_uncertainty = (T::one() - kalman_gain) * state.uncertainty;
47  // adjust for process variance (normally done in a "predict" step
48  // new_uncertainty = new_uncertainty + state.process_variance;
49  new_uncertainty = new_uncertainty + state.process_variance*(state.estimate - new_estimate).abs();
50
51  KalmanState::new_float(new_estimate, new_uncertainty,
52                         state.measurement_variance,state.process_variance)
53
54}
55
56use fixed::traits::{ Fixed };
57
58impl<T> KalmanState<T>
59  where T: Fixed
60{
61  pub fn new_fixed (
62    estimate: T,
63    uncertainty: T,
64    measurement_variance: T,
65    process_variance: T) -> KalmanState<T>
66  {
67    if T::IS_SIGNED {
68      KalmanState {
69        estimate,
70        uncertainty:
71          if uncertainty < 0 { T::ZERO - uncertainty } else { uncertainty },
72
73        measurement_variance:
74         if measurement_variance < 0 { T::ZERO - measurement_variance } else { measurement_variance },
75
76        process_variance:
77          if process_variance < 0 { T::ZERO - process_variance } else { process_variance },
78      }
79    }
80    else {
81      KalmanState {
82        estimate,
83        uncertainty,
84        measurement_variance,
85        process_variance,
86      }
87    }
88  }
89}
90
91
92
93/// Kalman update function (fold function) for Fixed types
94pub fn kalman_update_fixed<T>(state: &KalmanState<T>, observation: T) -> KalmanState<T>
95  where
96    T: Fixed ,
97{
98  // Kalman gain
99  let kalman_gain = state.uncertainty / (state.uncertainty + state.measurement_variance);
100
101  // Update estimate
102  let new_estimate = state.estimate + kalman_gain * (observation - state.estimate);
103
104  // Update uncertainty
105  let mut new_uncertainty = (T::TRY_ONE.unwrap() - kalman_gain) * state.uncertainty;
106  // adjust for process variance (normally done in a "predict" step
107  new_uncertainty = new_uncertainty + state.process_variance;
108  let est_diff =
109    if state.estimate > new_estimate { state.estimate - new_estimate }
110    else { new_estimate - state.estimate };
111  new_uncertainty = new_uncertainty + state.process_variance*est_diff;
112
113  KalmanState::new_fixed(new_estimate, new_uncertainty,
114                                state.measurement_variance, state.process_variance)
115}
116
117#[cfg(test)]
118mod tests {
119  use super::*;
120
121  // Helper function for floating-point comparison
122  fn assert_near_eq(a: f64, b: f64, tol: f64) {
123    assert!(
124      (a - b).abs() < tol,
125      "assertion failed: `(left !== right)`\n left: `{:?}`,\n right: `{:?}`",
126      a,
127      b
128    );
129  }
130
131  fn assert_near_eq_fixed<T: Fixed>(a: T, b: T, tol: T) {
132    let diff = if a > b { a - b } else { b - a };
133    assert!(
134      diff < tol,
135      "assertion failed: `(left !== right)`\n left: `{:?}`,\n right: `{:?}`",
136      a,
137      b
138    );
139  }
140
141  #[test]
142  fn test_kalman_filter_f64() {
143    let mut kstate = KalmanState {
144      estimate: 0.5f64,
145      uncertainty: 0.1,
146      measurement_variance: 1E-4,
147      process_variance: 1.0,
148    };
149
150    for _i in 0..10 {
151      kstate = kalman_update_float(&kstate, 1.0);
152    }
153    println!("{}", kstate.estimate);
154    assert_near_eq(kstate.estimate, 1.0, 1E-4);
155  }
156
157  #[test]
158  fn test_with_monotonic_increasing_f64() {
159    let mut kstate = KalmanState::new_float(
160      0.0f64,
161      1.0,
162      1E-6,
163      1E-3
164    );
165
166    const MAX_ITERATIONS: usize = 1000;
167    for i in 1..=MAX_ITERATIONS {
168      kstate = kalman_update_float(&kstate, i as f64);
169    }
170
171    // Check the final estimate and error_covariance
172    println!("est: {} uncert: {}", kstate.estimate, kstate.uncertainty);
173    assert_near_eq(kstate.estimate, MAX_ITERATIONS as f64, 1E-3);
174    assert_near_eq(kstate.uncertainty, 0.001, 1E-4);
175  }
176
177  use fixed::types::{I16F16, I8F24, U32F32};
178
179  #[test]
180  fn test_kalman_filter_i8f24() {
181    let mut kstate = KalmanState {
182      estimate: I8F24::from_num(0.5),
183      uncertainty: I8F24::from_num(0.1),
184      measurement_variance: I8F24::from_num(1E-4),
185      process_variance: I8F24::from_num(1.0),
186    };
187
188    for _i in 0..10 {
189      kstate = kalman_update_fixed(&kstate, I8F24::from_num(1.0));
190    }
191    println!("{}", kstate.estimate);
192    assert_near_eq(kstate.estimate.into(), 1.0, 1E-4);
193  }
194
195  #[test]
196  fn test_with_monotonic_increasing_i8f24() {
197    type TestType = I8F24;
198    let mut kstate = KalmanState::new_fixed(
199      TestType::from_num(0),
200      TestType::from_num(1),
201      TestType::from_num(1E-3),
202      TestType::from_num(1E-3),
203    );
204
205    let max_iterations: usize = TestType::MAX.to_num::<u32>() as usize;
206    for i in 1..=max_iterations {
207      kstate = kalman_update_fixed(&kstate, TestType::from_num(i));
208    }
209
210    // Check the final estimate and error_covariance
211    println!("est: {} uncert: {}", kstate.estimate, kstate.uncertainty);
212    assert_near_eq_fixed(
213      kstate.estimate,
214      TestType::from_num(max_iterations),
215      TestType::from_num( 4E-1),
216    );
217    assert_near_eq_fixed(
218      kstate.uncertainty,
219      TestType::from_num(0.002),
220      TestType::from_num(1E-3),
221    );
222  }
223
224  #[test]
225  fn test_with_monotonic_increasing_i16f16() {
226    type TestType = I16F16;
227    let mut kstate = KalmanState::new_fixed(
228      TestType::from_num(0.0),
229      TestType::from_num(1.0),
230      TestType::from_num(1E-3),
231      TestType::from_num(1E-3),
232    );
233
234    let max_iterations: usize = TestType::MAX.to_num::<u32>() as usize;
235    for i in 1..=max_iterations {
236      kstate = kalman_update_fixed(&kstate, TestType::from_num(i));
237    }
238
239    // Check the final estimate and error_covariance
240    println!("est: {} uncert: {}", kstate.estimate, kstate.uncertainty);
241    assert_near_eq_fixed(
242      kstate.estimate,
243      TestType::from_num(max_iterations),
244      TestType::from_num(0.75),
245    );
246    assert_near_eq_fixed(
247      kstate.uncertainty,
248      TestType::from_num(0.003),
249      TestType::from_num(1E-3),
250    );
251  }
252
253  #[test]
254  fn test_with_monotonic_increasing_u32f32() {
255    type TestType = U32F32;
256    let step_size: usize = 1000;
257
258    let mut kstate = KalmanState::new_fixed (
259      TestType::from_num(0),
260      TestType::from_num(1),
261      TestType::from_num(1E-3),
262      TestType::from_num(1E-3),
263    );
264
265    let max_iterations: usize = TestType::MAX.to_num::<u32>() as usize;
266    for i in (1..=max_iterations).step_by(step_size) {
267      kstate = kalman_update_fixed(&kstate, TestType::from_num(i));
268    }
269
270    // Check the final estimate and error_covariance
271    println!("est: {} uncert: {}", kstate.estimate, kstate.uncertainty);
272    assert_near_eq_fixed(
273      kstate.estimate,
274      TestType::from_num(max_iterations),
275      TestType::from_num(step_size),
276    );
277    assert_near_eq_fixed(
278      kstate.uncertainty,
279      TestType::from_num(1),
280      TestType::from_num(1E-2),
281    );
282  }
283}