1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module my.signal_theory.kalman;
7 
8 import core.time : Duration, dur;
9 import std.math : abs;
10 import std.range : isOutputRange;
11 
12 @safe:
13 
14 /// Kalman filter for a unidimensional models.
15 struct KalmanFilter {
16     double currentEstimate = 0;
17     double estimateError = 0;
18     double kalmanGain = 0;
19     double lastEstimate = 0;
20     double measurementError = 0;
21     double q = 0;
22 
23     /**
24      * Params:
25      *  measurementError = measurement uncertainty. How much the measurements
26      *      is expected to vary.
27      *  estimationError = estimation uncertainty. Adjusted over time by the
28      *      Kalman Filter but can be initialized to mea_e.
29      *  q = process variance. usually a small number [0.001, 1]. How fast the
30      *      measurement moves. Recommended is 0.001, tune as needed.
31      */
32     this(double measurementError, double estimateError, double q) {
33         this.measurementError = measurementError;
34         this.estimateError = estimateError;
35         this.q = q;
36     }
37 
38     void updateEstimate(double mea) {
39         kalmanGain = estimateError / (estimateError + measurementError);
40         currentEstimate = lastEstimate + kalmanGain * (mea - lastEstimate);
41         estimateError = (1.0 - kalmanGain) * estimateError + abs(lastEstimate - currentEstimate) * q;
42         lastEstimate = currentEstimate;
43     }
44 
45     string toString() @safe const {
46         import std.array : appender;
47 
48         auto buf = appender!string;
49         toString(buf);
50         return buf.data;
51     }
52 
53     void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) {
54         import std.format : formattedWrite;
55 
56         formattedWrite(w,
57                 "KalmanFilter(measurementError:%s estimateError:%s q:%s currentEstimate:%s lastEstimate:%s gain:%s",
58                 measurementError, estimateError, q, currentEstimate, lastEstimate, kalmanGain);
59     }
60 }
61 
62 @("shall instantiate and run a kalman filter")
63 unittest {
64     import std.stdio : writefln, writeln;
65     import my.signal_theory.simulate;
66 
67     Simulator sim;
68 
69     const period = sim.period;
70     const double ticks = cast(double) 1.dur!"seconds"
71         .total!"nsecs" / cast(double) period.total!"nsecs";
72     const clamp = period.total!"nsecs" / 2;
73 
74     auto kf = KalmanFilter(2, 2, 0.01);
75 
76     while (sim.currTime < 1000.dur!"msecs") {
77         sim.tick!"nsecs"(a => kf.updateEstimate(a.total!"nsecs"), () => -kf.currentEstimate);
78         if (sim.updated) {
79             const diff = sim.targetTime - sim.wakeupTime;
80             //writefln!"time[%s] pv[%s] diff[%s]"(sim.currTime, sim.pv, diff);
81             //writeln(kf);
82         }
83     }
84 
85     assert(abs(sim.pv.total!"msecs") < 100);
86     assert(abs(kf.currentEstimate) < 18000.0);
87 }