|
| 1 | +#include "fcarouge/kalman.hpp" |
| 2 | + |
| 3 | +#include <cassert> |
| 4 | + |
| 5 | +namespace fcarouge::sample |
| 6 | +{ |
| 7 | +namespace |
| 8 | +{ |
| 9 | +//! @test Estimating the Temperature of the Liquid in a Tank |
| 10 | +//! |
| 11 | +//! @copyright This example is transcribed from KalmanFilter.NET copyright Alex |
| 12 | +//! Becker. |
| 13 | +//! |
| 14 | +//! @see https://www.kalmanfilter.net/kalman1d.html#ex6 |
| 15 | +//! |
| 16 | +//! @details We would like to estimate the temperature of the liquid in a tank. |
| 17 | +//! We assume that at steady state the liquid temperature is constant. However, |
| 18 | +//! some fluctuations in the true liquid temperature are possible. We can |
| 19 | +//! describe the system dynamics by the following equation: xn = T + wn where: T |
| 20 | +//! is the constant temperature wn is a random process noise with variance q. |
| 21 | +//! Let us assume a true temperature of 50 degrees Celsius. The measurements are |
| 22 | +//! taken every 5 seconds. The true liquid temperature at the measurement points |
| 23 | +//! is: 49.979°C, 50.025°C, 50°C, 50.003°C, 49.994°C, 50.002°C, 49.999°C, |
| 24 | +//! 50.006°C, 49.998°C, and 49.991°C. The set of measurements is: 49.95°C, |
| 25 | +//! 49.967°C, 50.1°C, 50.106°C, 49.992°C, 49.819°C, 49.933°C, 50.007°C, |
| 26 | +//! 50.023°C, and 49.99°C. |
| 27 | +//! |
| 28 | +//! @example liquid_temperature.cpp |
| 29 | +[[maybe_unused]] constexpr auto liquid_temperature{ []() { |
| 30 | + // One-dimensional filter, constant system dynamic model. |
| 31 | + using kalman = kalman<float>; |
| 32 | + kalman k; |
| 33 | + |
| 34 | + // Initialization |
| 35 | + // Before the first iteration, we must initialize the Kalman filter and |
| 36 | + // predict the next state (which is the first state). We don't know what the |
| 37 | + // temperature of the liquid is, and our guess is 10°C. |
| 38 | + k.state_x = kalman::state{ 10 }; |
| 39 | + |
| 40 | + // Our guess is very imprecise, so we set our initialization estimate error σ |
| 41 | + // to 100. The estimate uncertainty of the initialization is the error |
| 42 | + // variance σ^2: p0,0 = 100^2 = 10,000. This variance is very high. If we |
| 43 | + // initialize with a more meaningful value, we will get faster Kalman filter |
| 44 | + // convergence. |
| 45 | + k.estimate_uncertainty_p = kalman::estimate_uncertainty{ 100 * 100 }; |
| 46 | + |
| 47 | + // Prediction |
| 48 | + // Now, we shall predict the next state based on the initialization values. We |
| 49 | + // think that we have an accurate model, thus we set the process noise |
| 50 | + // variance q to 0.0001. |
| 51 | + k.noise_process_q = []() { |
| 52 | + return kalman::process_noise_uncertainty{ 0.0001 }; |
| 53 | + }; |
| 54 | + |
| 55 | + k.predict(); |
| 56 | + |
| 57 | + assert(10 == k.state_x && |
| 58 | + "Since our model has constant dynamics, the predicted estimate is " |
| 59 | + "equal to the current estimate: x^1,0 = 10°C."); |
| 60 | + assert(10000.0001 - 0.001 < k.estimate_uncertainty_p && |
| 61 | + k.estimate_uncertainty_p < 10000.0001 + 0.001 && |
| 62 | + "The extrapolated estimate uncertainty (variance): p1,0 = p0,0 + q = " |
| 63 | + "10000 + 0.0001 = 10000.0001."); |
| 64 | + |
| 65 | + // Measure and Update |
| 66 | + // The measurement value: z1 = 49.95°C. Since the measurement error is σ = |
| 67 | + // 0.1, the variance σ^2 would be 0.01, thus the measurement uncertainty is: |
| 68 | + // r1 = 0.01. The measurement error (standard deviation) is 0.1 degrees |
| 69 | + // Celsius. |
| 70 | + k.noise_observation_r = []() { |
| 71 | + return kalman::observation_noise_uncertainty{ 0.1 * 0.1 }; |
| 72 | + }; |
| 73 | + |
| 74 | + k.update(49.95); |
| 75 | + |
| 76 | + // And so on. |
| 77 | + k.predict(); |
| 78 | + k.update(49.967); |
| 79 | + k.predict(); |
| 80 | + k.update(50.1); |
| 81 | + k.predict(); |
| 82 | + k.update(50.106); |
| 83 | + k.predict(); |
| 84 | + k.update(49.992); |
| 85 | + k.predict(); |
| 86 | + k.update(49.819); |
| 87 | + k.predict(); |
| 88 | + k.update(49.933); |
| 89 | + k.predict(); |
| 90 | + k.update(50.007); |
| 91 | + k.predict(); |
| 92 | + k.update(50.023); |
| 93 | + k.predict(); |
| 94 | + k.update(49.99); |
| 95 | + k.predict(); |
| 96 | + |
| 97 | + // The estimate uncertainty quickly goes down, after 10 measurements: |
| 98 | + assert(49.988 - 0.0001 < k.state_x && k.state_x < 49.988 + 0.0001 && |
| 99 | + "The filter estimates the liquid temperature at 49.988°C."); |
| 100 | + assert(0.0013 - 0.0001 < k.estimate_uncertainty_p && |
| 101 | + k.estimate_uncertainty_p < 0.0013 + 0.0001 && |
| 102 | + "The estimate uncertainty is 0.0013, i.e. the estimate error standard " |
| 103 | + "deviation is: 0.036°C."); |
| 104 | + // So we can say that the liquid temperature estimate is: 49.988 ± 0.036°C. |
| 105 | + // In this example we've measured a liquid temperature using the |
| 106 | + // one-dimensional Kalman filter. Although the system dynamics include a |
| 107 | + // random process noise, the Kalman filter can provide good estimation. |
| 108 | + |
| 109 | + return 0; |
| 110 | +}() }; |
| 111 | + |
| 112 | +} // namespace |
| 113 | +} // namespace fcarouge::sample |
0 commit comments