Repository navigation
Expand file tree
/
Copy pathbayesianfiltering.hpp
More file actions
1454 lines (1290 loc) · 64.2 KB
/
Copy pathbayesianfiltering.hpp
File metadata and controls
1454 lines (1290 loc) · 64.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** @file */
/**********************************************************************************************
***********************************************************************************************
*** File: BayesianFiltering.hpp
*** Author: Geovany A. Borges
*** Contents: BayesianFiltering header file.
***********************************************************************************************
***********************************************************************************************
BayesianFiltering.hpp is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License (LGPL) as published by
the Free Software Foundation, either version 3 of the License, or any later version.
BayesianFiltering.hpp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with BayesianFiltering.hpp. If not, see <http://www.gnu.org/licenses/>.
Copyright 2020 Geovany Araujo Borges
**********************************************************************************************/
//******************************************************************
//** Classe BayesianFiltering
//******************************************************************
#include <deque>
#include <stdio.h>
#include <Eigen/Dense>
#include <Eigen/Eigen>
#ifndef BayesianFiltering_H
#define BayesianFiltering_H
/***********************************************************************************************
* Auxiliary Macros:
***********************************************************************************************/
/**
* @brief Print filename followed by line number at command line. Used commonly for debugging allowing to identify the code responsible for errors.
* @param[in] X The EIGEN::VectorXd variable.
*/
#define PRINT_LINE_DEBUG {printf("\n[%s] line %i",__FILE__,__LINE__); fflush(stdout);}
/**
* @brief Print pretty matrix X with name which may me different of "X" at command line (printf). Guaranteed print all lines before leaving (fflush).
* @param[in] X The EIGEN::MatrixXd variable.
* @param[in] name Name of the variable.
*/
inline void PrintEigenMatrixXd(Eigen::MatrixXd &X, const char* name)
{
printf("\nMatrix %s (%li x %li):",name,X.rows(),X.cols());
for (long int i=0; i < X.rows(); ++i){
printf("\n ");
for (long int j=0; j < X.cols(); ++j){
printf("%s(%li,%li)=%.10f; ",name,i,j,X(i,j));
}
}
fflush(stdout);
}
/**
* @brief Print pretty matrix X with the same variable name at command line (printf). Guaranteed print all lines before leaving (fflush).
* @param[in] X The EIGEN::MatrixXd variable.
*/
#define PRINT_EIGEN_MATRIX(X) PrintEigenMatrixXd(X,#X);
/**
* @brief Print pretty vector X with name which may me different of "X" at command line (printf). Guaranteed print all lines before leaving (fflush).
* @param[in] X The EIGEN::VectorXd variable.
* @param[in] name Name of the variable.
*/
inline void PrintEigenVectorXd(Eigen::VectorXd &X, const char* name)
{
printf("\nVector %s (%li x %li):",name,X.rows(),X.cols());
for (long int i=0; i < X.rows(); ++i){
printf("\n ");
for (long int j=0; j < X.cols(); ++j){
printf("%s(%li,%li)=%.10f; ",name,i,j,X(i,j));
}
}
fflush(stdout);
}
/**
* @brief Print pretty vector X with the same variable name at command line (printf). Guaranteed print all lines before leaving (fflush).
* @param[in] X The EIGEN::VectorXd variable.
*/
#define PRINT_EIGEN_VECTOR(X) PrintEigenVectorXd(X,#X);
namespace BayesianFiltering {
/***********************************************************************************************
* Support types and functions:
***********************************************************************************************/
/**
* @brief Encapsulated <bool,std::string> pair response of a function, with .first specifying status (success of failure) and .second the reason string in case of failure.
*/
typedef std::pair<bool, std::string> Result;
/**
* @brief Format <bool,std::string> pair response with defined status and reason string.
* @param[in] success bool indicating status
* @param[in] message string message
*/
inline Result ResultFormat(bool success, const std::string& message) {
return std::make_pair(success, message);
}
/**
* @brief Format <bool,std::string> pair response with defined status and reason string to be printf-like formated.
* @param[in] success bool indicating status
* @param[in] format formating string
* @param[in] args sequence of variable arguments for message formating
*/
template<typename ... Args>
Result ResultFormat(bool success, const std::string& format, Args ... args )
{
Result res; res.first = success;
int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
auto size = static_cast<size_t>( size_s );
std::unique_ptr<char[]> buf( new char[ size ] );
std::snprintf( buf.get(), size, format.c_str(), args ... );
res.second = std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
return res;
};
/**
* @brief Generate multivariate \p sample ~ N(\p 0, \p covariance_matrix).
* @param[out] sample will be filled with the outcome
* @param[in] covariance_matrix as above
* @return Result type
*/
Result MultivariateZeroMeanGaussianSampler(Eigen::VectorXd & sample, Eigen::MatrixXd & covariance_matrix)
{
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<double> dist(0.0, 1.0);
// Verify dimensions
if (covariance_matrix.rows() != covariance_matrix.cols()) return ResultFormat(false, "covariance_matrix should be square (%li x %li)",covariance_matrix.rows(),covariance_matrix.cols());
// Compute Cholesky Decomposition (LLT)
Eigen::LLT<Eigen::MatrixXd> llt(covariance_matrix);
Eigen::MatrixXd L = llt.matrixL();
// Generate independent standard normal vector Z
Eigen::VectorXd Z = Eigen::VectorXd::NullaryExpr(covariance_matrix.rows(), [&]() { return dist(gen); });
// Transform to Multivariate Normal
sample = L * Z;
return ResultFormat(true,"");
}
/**
* @brief Generate multivariate \p sample ~ N(\p mean_vector, \p covariance_matrix).
* @param[out] sample will be filled with the outcome
* @param[in] mean_vector as above
* @param[in] covariance_matrix as above
* @return Result type
*/
Result MultivariateGaussianSampler(Eigen::VectorXd & sample, Eigen::VectorXd & mean_vector, Eigen::MatrixXd & covariance_matrix)
{
// Verify dimensions
if (mean_vector.rows() != covariance_matrix.rows()) return ResultFormat(false, "mean_vector number of lines (%li) different of covariance_matrix number of lines (%li)",mean_vector.rows(),covariance_matrix.rows());
// Covariance Matrix will be checked at zero mean Gaussian sampler
// Generate sample from zero mean sample:
Result res = MultivariateZeroMeanGaussianSampler(sample, covariance_matrix);
if (!res.first) return res;
sample += mean_vector;
return res;
}
/**
* @brief Generate univariate \p sample ~ N(\p mean, \p variance).
* @param[in] mean as above
* @param[in] variance as above
* @return \p sample
*/
double UnivariateGaussianSampler(double mean, double variance)
{
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<double> dist(0.0, 1.0);
return mean + sqrt(variance) * dist(gen);
}
/**
* @brief Generate univariate \p sample ~ U(\p min_value, \p max_value).
* @param[in] min_value as above
* @param[in] max_value as above
* @return \p sample
*/
double UnivariateUniformSampler(double min_value, double max_value)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> dist(0.0, 1.0);
return dist(gen);
}
/***********************************************************************************************
* Linear model class:
***********************************************************************************************/
/**
* @brief Represents the parameters of a discrete time, generic linear state space model:
*
* x(k) = A*x(k-1) + B*u(k) + w(k) {Process equation}
*
* y(k) = C*x(k) + v(k) {Measurement equation}
* with \p x(k) being state variables vector, \p u(k) measurement vector and \p y(k) measurement vector. Also, \p w(k) ~ \p N(0,Q) and \p v(k) ~ \p N(0,R) are respectively process and measurement noises.
*
* This class contains only model parameters and evaluations of process and measurement equations.
* In case \p w(k) and/or \p v(k) are dependent of other noise sources, \p Q and \p R should consider the correct propagation from noise sources covariances.
*
* Initial state is a random variable following \p x(0) ~ \p N(X0,P0)
*
* Also dim(x) = \p Nx, dim(u) = \p Nu, dim(y) = \p Ny, dim(w) = \p Nw, dim(v) = \p Nv.
* These dimensions should be first stablished by calling \p SetDimmensions(), which make resizing of system parameters. After that, since parameters are public, they can be set at any time.
*
* Angular measurements in radians need special treatment when computing innovation vector in filters using \p atan2 function.
* In order to do this, \p Y_isangle is used to indicate which components of \p Y are angles, which should be set as \p true by the user when initializing the model.
*
* For the same reason, every change in state vector containing angles in some entries should me donne properly.
* Thus, \p X_isangle is used to indicate which components of \p x are angles, which should be set as \p true by the user when initializing the model.
*/
class StateSpaceLinearModel
{
public:
Eigen::MatrixXd A; //!< Process model transition matrix
Eigen::MatrixXd B; //!< Process model input matrix
Eigen::MatrixXd C; //!< Measurement model matrix
Eigen::MatrixXd Q; //!< Process model noise covariance matrix
Eigen::MatrixXd R; //!< Measurement model noise covariance matrix
Eigen::VectorXd X0; //!< Inicial state vector distribution mean vector parameter
Eigen::MatrixXd P0; //!< Inicial state vector distribution covariance matrix parameter
int Nx; //!< Number of state variables in X vector
int Nu; //!< Number of inputs in U vector
int Ny; //!< Number of measurements in Y vector
int Nw; //!< Number of process noise in W vector
int Nv; //!< Number of measurement noise in V vector
std::vector<bool> X_isangle; //!< Indicates which entries of X vector are angles in rad, for correct computation of correction equation.
std::vector<bool> Y_isangle; //!< Indicates which entries of Y vector are angles in rad, for correct computation of innovation vector
/**
* @brief Define dimensions of system variables and adjust size of model parameters accordingly.
* @param[in] _Nx Number of state variables in X vector
* @param[in] _Nu Number of inputs in U vector
* @param[in] _Ny Number of measurements in Y vector
* @param[in] _Nw Number of process noise in W vector
* @param[in] _Nv Number of measurement noise in V vector
*/
void SetDimensions(int _Nx, int _Nu, int _Ny, int _Nw, int _Nv)
{
Nx = _Nx; Nu =_Nu; Ny = _Ny; Nw = _Nw, Nv = _Nv;
A.resize(Nx, Nx);
B.resize(Nx, Nu);
C.resize(Ny, Nx);
Q.resize(Nx, Nx);
R.resize(Ny, Ny);
X0.resize(Nx);
P0.resize(Nx, Nx);
X_isangle.resize(Nx);
std::fill(X_isangle.begin(), X_isangle.end(), false);
Y_isangle.resize(Ny);
std::fill(Y_isangle.begin(), Y_isangle.end(), false);
}
/**
* @brief Compute process model to update state vector \p X (in place).
* @param[in,out] X state vector with \p x(k-1) to be updated to \p x(k)
* @param[in] U input vector
* @param[in] W current sample of process noise
*/
Result ProcessModel(Eigen::VectorXd & X, Eigen::VectorXd & U, Eigen::VectorXd & W)
{
Eigen::VectorXd Xpos;
Xpos = A * X + B * U + W; // By using an auxiliary variable, it avoids any risk of copy in place
X = Xpos;
return BayesianFiltering::ResultFormat(true,"");
}
/**
* @brief Compute system output \p Y from measurement model.
* @param[out] Y the computed output
* @param[in] X state vector with \p x(k)
* @param[in] V current sample of measurement noise
*/
Result MeasurementModel(Eigen::VectorXd & Y, Eigen::VectorXd & X, Eigen::VectorXd & V)
{
Y = C * X + V;
return BayesianFiltering::ResultFormat(true,"");
}
};
/***********************************************************************************************
* Nonlinear model base class:
***********************************************************************************************/
/**
* @brief Represents the parameters of a discrete time, generic nonlinear state space model:
*
* x(k) = f(x(k-1),u(k),w(k)) {Process equation}
*
* y(k) = h(x(k),v(k)) {Measurement equation}
* with \p x(k) being state variables vector, \p u(k) measurement vector and \p y(k) measurement vector. Also, \p w(k) ~ \p N(0,Q) and \p v(k) ~ \p N(0,R) are respectively process and measurement noises.
*
* This class contains only model parameters. It is a base virtual class, with computation of process and measurement equations as virtual member functions.
* Its definition should be done in a derived class at user side.
* Partial derivatives of process and measurement models should also be defined at a derived class. Some filters use such derivatives.
*
* Initial state is a random variable following \p x(0) ~ \p N(X0,P0)
*
* Also dim(x) = \p Nx, dim(u) = \p Nu, dim(y) = \p Ny, dim(w) = \p Nw, dim(v) = \p Nv.
* These dimensions should be first stablished by calling \p SetDimmensions(), which make resizing of system parameters. After that, since parameters are public, they can be set at any time.
*
* Angular measurements in radians need special treatment when computing innovation vector in filters using \p atan2 function.
* In order to do this, \p Y_isangle is used to indicate which components of \p Y are angles, which should be set as \p true by the user when initializing the model.
*
* For the same reason, every change in state vector containing angles in some entries should me donne properly. Also, some filters compute covariance matrix from samples, requiring special treatment.
* Thus, \p X_isangle is used to indicate which components of \p x are angles, which should be set as \p true by the user when initializing the model.
*/
class StateSpaceNonlinearModelBase
{
public:
Eigen::MatrixXd dfdX; //!< Partial derivative of f(X,U,W) with respect to X
Eigen::MatrixXd dfdW; //!< Partial derivative of f(X,U,W) with respect to W
Eigen::MatrixXd dhdX; //!< Partial derivative of h(X,V) with respect to X
Eigen::MatrixXd dhdV; //!< Partial derivative of h(X,V) with respect to V
Eigen::MatrixXd Q; //!< Process model noise covariance matrix
Eigen::MatrixXd R; //!< Measurement model noise covariance matrix
Eigen::VectorXd X0; //!< Inicial state vector distribution mean vector parameter
Eigen::MatrixXd P0; //!< Inicial state vector distribution covariance matrix parameter
int Nx; //!< Number of state variables in X vector
int Nu; //!< Number of inputs in U vector
int Ny; //!< Number of measurements in Y vector
int Nw; //!< Number of process noise in W vector
int Nv; //!< Number of measurement noise in V vector
std::vector<bool> X_isangle; //!< Indicates which entries of X vector are angles in rad, for correct computation of correction equation.
std::vector<bool> Y_isangle; //!< Indicates which entries of Y vector are angles in rad, for correct computation of innovation vector
// Always provide a virtual destructor if your class has virtual functions!
virtual ~StateSpaceNonlinearModelBase() = default;
/**
* @brief Define dimensions of system variables and adjust size of model parameters accordingly.
* @param[in] _Nx Number of state variables in X vector
* @param[in] _Nu Number of inputs in U vector
* @param[in] _Ny Number of measurements in Y vector
* @param[in] _Nw Number of process noise in W vector
* @param[in] _Nv Number of measurement noise in V vector
*/
void SetDimensions(int _Nx, int _Nu, int _Ny, int _Nw, int _Nv)
{
Nx = _Nx; Nu =_Nu; Ny = _Ny; Nw = _Nw, Nv = _Nv;
dfdX.resize(Nx, Nx);
dfdW.resize(Nx, Nw);
dhdX.resize(Ny, Nx);
dhdV.resize(Ny, Nv);
Q.resize(Nw, Nw);
R.resize(Nv, Nv);
X0.resize(Nx);
P0.resize(Nx, Nx);
X_isangle.resize(Nx);
std::fill(X_isangle.begin(), X_isangle.end(), false);
Y_isangle.resize(Ny);
std::fill(Y_isangle.begin(), Y_isangle.end(), false);
}
/**
* @brief Compute process model to update state vector \p X (in place). Virtual function to be defined in derived class.
* @param[in,out] X state vector with \p x(k-1) to be updated to \p x(k)
* @param[in] U input vector
* @param[in] t time instant scalar
* @param[in] W current sample of process noise
*/
virtual Result ProcessModel(Eigen::VectorXd & X, Eigen::VectorXd & U, double t, Eigen::VectorXd & W)=0; // Deve ser definido pelo usuario
/**
* @brief Compute system output \p Y from measurement model. Virtual function to be defined in derived class.
* @param[out] Y the computed output
* @param[in] X state vector with \p x(k)
* @param[in] t time instant scalar
* @param[in] V current sample of measurement noise
*/
virtual Result MeasurementModel(Eigen::VectorXd & Y, Eigen::VectorXd & X, double t, Eigen::VectorXd & V)=0; // Deve ser definido pelo usuario
/**
* @brief Update dfdX internal class member, the process model derivative with respect to \p x(k-1). Necessary only in filters using system matrix derivatives, where this function is called from inside the filter class. Virtual function to be defined in derived class.
* @param[in] X state vector containing \p x(k-1)
* @param[in] U input vector
* @param[in] t time instant scalar
* @param[in] W current sample of process noise
*/
virtual Result UpdatedfdX(Eigen::VectorXd & X, Eigen::VectorXd & U, double t, Eigen::VectorXd & W)=0; // Deve ser definido pelo usuario
/**
* @brief Update dfdW internal class member, the process model derivative with respect to \p w(k). Necessary only in filters using system matrix derivatives, where this function is called from inside the filter class. Virtual function to be defined in derived class.
* @param[in] X state vector containing \p x(k-1)
* @param[in] U input vector
* @param[in] t time instant scalar
* @param[in] W current sample of process noise
*/
virtual Result UpdatedfdW(Eigen::VectorXd & X, Eigen::VectorXd & U, double t, Eigen::VectorXd & W)=0; // Deve ser definido pelo usuario
/**
* @brief Update dhdX internal class member, the measurement model derivative with respect to \p x(k). Necessary only in filters using system matrix derivatives, where this function is called from inside the filter class. Virtual function to be defined in derived class.
* @param[in] X state vector containing \p x(k-1)
* @param[in] t time instant scalar
* @param[in] V current sample of measurement noise
*/
virtual Result UpdatedhdX(Eigen::VectorXd & X, double t, Eigen::VectorXd & V)=0; // Deve ser definido pelo usuario
/**
* @brief Update dhdV internal class member, the measurement model derivative with respect to \p v(k). Necessary only in filters using system matrix derivatives, where this function is called from inside the filter class. Virtual function to be defined in derived class.
* @param[in] X state vector containing \p x(k-1)
* @param[in] t time instant scalar
* @param[in] V current sample of measurement noise
*/
virtual Result UpdatedhdV(Eigen::VectorXd & X, double t, Eigen::VectorXd & V)=0; // Deve ser definido pelo usuario
};
/***********************************************************************************************
* Bayesian filter base class:
***********************************************************************************************/
/**
* @brief Base class of minimum variables of a Bayesian stochastic filter. All derived classes present filters for linear and nonlinear models.
* All filters follow the following loop
* 1. Reset
* 2. Prediction: compute x(k|k-1) using x(k-1) and input u(k)
* 3. Correction, in case of measurement available: compute x(k|k) using x(k|k-1) and measurement vector y(k);
* 4. In case of filter divergence, performe necessary actions and go back to step 1. Otherwise, go back to step 2.
*
* As it can be seen, as described above correction is optional, which allows filter continuing by prediction. Internal member variables present the current filter state.
*
* System model object should be not used in other purposes other than to be used as input to the filter.
* For the case of linear time varying models, it can have their parameters directly updated just before calling prediction/correction methods, as a way to deal with time varying systems.
* For the case of nonlinear time varying models, parameters updating is done in derived model class, where the methods are called from inside filter object.
* Thus, model updating is done in derived class, being not necessary to be done by the user.
*
*/
class BayesianFilter
{
public:
Eigen::VectorXd X; //!< State vector predictor \p x(k|k-1) or estimate \p x(k|k), depending on which stage the filter is.
Eigen::MatrixXd P; //!< State vector error covariance matrix predictor \p P(k|k-1) or \p P(k|k), depending on which stage the filter is.
Eigen::VectorXd Y_predicted; //!< Predicted measurement \p y(k|k-1).
Eigen::VectorXd V_predicted; //!< Predicted innovation \p y(k) - \p y(k|k-1).
Eigen::MatrixXd S_predicted; //!< Predicted innovation covariance matrix.
double d2_predicted; //!< Predicted innovation Mahalanobis distance, allowing to verify a filter os oprtimistic ou pessimistic, and divergence.
Eigen::VectorXd Y_corrected; //!< Corrected measurement \p y(k|k).
Eigen::VectorXd V_corrected; //!< Corrected innovation \p y(k) - \p y(k|k).
Eigen::MatrixXd S_corrected; //!< Corrected innovation covariance matrix.
double d2_corrected; //!< Corrected innovation Mahalanobis distance, allowing to verify a filter os oprtimistic ou pessimistic, and divergence.
BayesianFilter()
{
d2_predicted = 0;
d2_corrected = 0;
}
virtual ~BayesianFilter(){}
protected: // Only derived classes have access
/**
* @brief Reset filter, first procedure before prediction/correction.
* @param[in] X0 inicial state vector mean value of \p x(0) distribution
* @param[in] P0 inicial state error covariance matrix of \p x(0) distribution
* @param[in] Nx Number of state variables in vector \p X
* @param[in] Ny Number of measurements in vector \p Y
*/
void Reset(Eigen::VectorXd &X0, Eigen::MatrixXd &P0, int Nx, int Ny)
{
X.resize(Nx, 1);
P.resize(Nx, Nx);
Y_predicted.resize(Ny, 1);
V_predicted.resize(Ny, 1);
S_predicted.resize(Ny, Ny);
Y_corrected.resize(Ny, 1);
V_corrected.resize(Ny, 1);
S_corrected.resize(Ny, Ny);
d2_predicted = 0;
d2_corrected = 0;
X = X0;
P = P0;
}
};
/**
* @brief Base class of Bayesian filters for state space linear models. See derived classes.
*/
class LinearBayesianFilter : public BayesianFilter
{
public:
virtual ~LinearBayesianFilter() {std::cout << "\nLinearBayesianFilter Base destroyed\n";}
virtual Result Prediction(Eigen::VectorXd & U, StateSpaceLinearModel& linear_model)=0;
virtual Result Correction(Eigen::VectorXd & Y, StateSpaceLinearModel& linear_model)=0;
virtual void Reset(StateSpaceLinearModel& linear_model)=0;
protected:
Result CheckModel(StateSpaceLinearModel& linear_model)
{
if (linear_model.A.rows()!=X.rows()) return ResultFormat(false,"Number of rows of A should be the same number of rows of X");
if (linear_model.A.cols()!=X.rows()) return ResultFormat(false,"Number of cols of A should be the same number of rows of X");
if (linear_model.B.rows()!=X.rows()) return ResultFormat(false,"Number of rows of B should be the same number of rows of X");
return ResultFormat(true,"");
}
};
/**
* @brief Kalman filter, the best optimal unbiased estimator for stochastic linear state space models.
*
* This implementation is specific for discrete time, continuous state models.
*/
class KalmanFilter : public LinearBayesianFilter
{
public:
virtual ~KalmanFilter() {std::cout << "\nKalmanFilter derived class destroyed\n";}
/**
* @brief Reset filter, first procedure before prediction/correction.
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
void Reset(StateSpaceLinearModel& linear_model) override
{
BayesianFilter::Reset(linear_model.X0, linear_model.P0, linear_model.Nx, linear_model.Ny);
}
/**
* @brief Prediction procedure, which will update (in place) the internal member variables \p X and \p P based on input vector \p U and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] U input vector of the linear system process model
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
Result Prediction(Eigen::VectorXd & U, StateSpaceLinearModel& linear_model) override
{
Result res = CheckModel(linear_model); if(!res.first) return res;
// Predicao
X = linear_model.A * X + linear_model.B * U;
P = linear_model.A * P * linear_model.A.transpose() + linear_model.Q;
return ResultFormat(true,"");
}
/**
* @brief Correction procedure, which will update (in place) the internal member variables \p X and \p P based on measurement vector \p Y and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] Y measurement vector of the linear system process model
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
Result Correction(Eigen::VectorXd & Y, StateSpaceLinearModel& linear_model) override
{
Result res = CheckModel(linear_model); if(!res.first) return res;
// Correcao
Y_predicted = linear_model.C * X;
V_predicted = Y - Y_predicted;
for(int i=0; i < V_predicted.size(); ++i){
if(linear_model.Y_isangle[i]) V_predicted(i) = atan2(sin(V_predicted(i)),cos(V_predicted(i)));
}
S_predicted = linear_model.C * P * linear_model.C.transpose() + linear_model.R;
d2_predicted = (V_predicted.transpose() * S_predicted.inverse() * V_predicted)(0, 0);
Eigen::MatrixXd G = P * linear_model.C.transpose() * S_predicted.inverse();
Eigen::MatrixXd Gi = G * V_predicted;
for(int i=0; i < Gi.rows(); ++i){
if(linear_model.X_isangle[i]) Gi(i) = atan2(sin(Gi(i)),cos(Gi(i)));
}
X = X + Gi;
//P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C) * P;
// More numericaly stable P update:
P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C) * P *((Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C).transpose()) + G*linear_model.R*G.transpose();
Y_corrected = linear_model.C * X;
V_corrected = Y - Y_corrected;
for(int i=0; i < V_corrected.size(); ++i){
if(linear_model.Y_isangle[i]) V_corrected(i) = atan2(sin(V_corrected(i)),cos(V_corrected(i)));
}
S_corrected = linear_model.C * P * linear_model.C.transpose() + linear_model.R;
d2_corrected = (V_corrected.transpose() * S_corrected.inverse() * V_corrected)(0, 0);
return ResultFormat(true,"");
}
};
/**
* @brief Adaptive Kalman filter, with \p Q and \p R matrices changing when every measurement comes according two user supplied learning parameters.
*
* This implementation is specific for discrete time, continuous state models.
*/
class AdaptiveKalmanFilter : public LinearBayesianFilter
{
public:
double alpha; //!< User supplied learning parameter for updating \p R measurement noise covariance matrix. Must be in interval ]0,1], with 1 leading to fixed \p R matrix.
double beta; //!< User supplied learning parameter for updating \p Q process noise covariance matrix. Must be in interval ]0,1], with 1 leading to fixed \p Q matrix.
virtual ~AdaptiveKalmanFilter() {std::cout << "\nAdaptiveKalmanFilter derived class destroyed\n";}
/**
* @brief Reset filter with user supplied filter specific parameters. Must be called before the prediction/correction cycle.
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
* @param[in] _alpha user provided value for \p alpha member variable
* @param[in] _beta user provided value for \p beta member variable
*/
void Reset(StateSpaceLinearModel& linear_model, double _alpha, double _beta)
{
alpha = _alpha;
beta = _beta;
BayesianFilter::Reset(linear_model.X0, linear_model.P0, linear_model.Nx, linear_model.Ny);
}
/**
* @brief Reset filter with default filter specific parameters (\p alpha = 0.95 and \p beta = 0.99). Must be called before the prediction/correction cycle.
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
void Reset(StateSpaceLinearModel& linear_model) override
{
Reset(linear_model, 0.95, 0.99);
}
/**
* @brief Prediction procedure, which will update (in place) the internal member variables \p X and \p P based on input vector \p U and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] U input vector of the linear system process model
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
Result Prediction(Eigen::VectorXd & U, StateSpaceLinearModel& linear_model) override
{
Result res = CheckModel(linear_model); if(!res.first) return res;
// Predicao
X = linear_model.A * X + linear_model.B * U;
P = linear_model.A * P * linear_model.A.transpose() + linear_model.Q;
return ResultFormat(true,"");
}
/**
* @brief Correction procedure, which will update (in place) the internal member variables \p X and \p P based on measurement vector \p Y and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] Y measurement vector of the linear system process model
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
Result Correction(Eigen::VectorXd & Y, StateSpaceLinearModel& linear_model) override
{
Result res = CheckModel(linear_model); if(!res.first) return res;
// Innovation
Y_predicted = linear_model.C * X;
V_predicted = Y - Y_predicted;
for(int i=0; i < V_predicted.size(); ++i){
if(linear_model.Y_isangle[i]) V_predicted(i) = atan2(sin(V_predicted(i)),cos(V_predicted(i)));
}
S_predicted = linear_model.C * P * linear_model.C.transpose() + linear_model.R;
d2_predicted = (V_predicted.transpose() * S_predicted.inverse() * V_predicted)(0, 0);
Eigen::MatrixXd P_predicted = P;
// State update
Eigen::MatrixXd G = P * linear_model.C.transpose() * S_predicted.inverse();
Eigen::MatrixXd Gi = G * V_predicted;
for(int i=0; i < Gi.rows(); ++i){
if(linear_model.X_isangle[i]) Gi(i) = atan2(sin(Gi(i)),cos(Gi(i)));
}
X = X + Gi;
//P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C) * P;
// More numericaly stable P update:
P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C) * P *((Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C).transpose()) + G*linear_model.R*G.transpose();
// Innovation corrected
Y_corrected = linear_model.C * X;
V_corrected = Y - Y_corrected;
for(int i=0; i < V_corrected.size(); ++i){
if(linear_model.Y_isangle[i]) V_corrected(i) = atan2(sin(V_corrected(i)),cos(V_corrected(i)));
}
S_corrected = linear_model.C * P * linear_model.C.transpose() + linear_model.R;
d2_corrected = (V_corrected.transpose() * S_corrected.inverse() * V_corrected)(0, 0);
// R and Q updates
linear_model.R = alpha * linear_model.R + (1.0-alpha)*(V_predicted * V_predicted.transpose() + linear_model.C * P_predicted * linear_model.C.transpose());
linear_model.Q = beta * linear_model.Q + (1.0-beta)*(G * V_predicted * V_predicted.transpose() * G.transpose());
return ResultFormat(true,"");
}
};
/**
* @brief Robust Self Adaptive Kalman filter, with \p Q and \p R matrices changing when every measurement comes using covariance matching strategy.
*
* This implementation is specific for discrete time, continuous state models.
*
* From: Chen Y-W, Tu K-M. Robust self-adaptive Kalman filter with application in target tracking. Measurement and Control. 2022;55(9-10):935-944. doi:10.1177/00202940221083548
*/
class RobustSelfAdaptiveKalmanFilter : public LinearBayesianFilter
{
Eigen::MatrixXd P_corrected; //!< Previous correction \P matrix, used internly in the algorithm.
public:
virtual ~RobustSelfAdaptiveKalmanFilter() {std::cout << "\nRobustSelfAdaptiveKalmanFilter derived class destroyed\n";}
/**
* @brief Reset filter, first procedure before prediction/correction.
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
void Reset(StateSpaceLinearModel& linear_model) override
{
BayesianFilter::Reset(linear_model.X0, linear_model.P0, linear_model.Nx, linear_model.Ny);
P_corrected = linear_model.P0;
}
/**
* @brief Prediction procedure, which will update (in place) the internal member variables \p X and \p P based on input vector \p U and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] U input vector of the linear system process model
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
Result Prediction(Eigen::VectorXd & U, StateSpaceLinearModel& linear_model) override
{
Result res = CheckModel(linear_model); if(!res.first) return res;
// Predicao
X = linear_model.A * X + linear_model.B * U;
P = linear_model.A * P * linear_model.A.transpose() + linear_model.Q;
return ResultFormat(true,"");
}
/**
* @brief Correction procedure, which will update (in place) the internal member variables \p X and \p P based on measurement vector \p Y and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] Y measurement vector of the linear system process model
* @param[in] linear_model \p StateSpaceLinearModel object for which the filter will run
*/
Result Correction(Eigen::VectorXd & Y, StateSpaceLinearModel& linear_model) override
{
Result res = CheckModel(linear_model); if(!res.first) return res;
// Ohmega calculation
double sigma = linear_model.R(0,0);
double epsilon = abs(V_corrected(0,0));
double ohmega = 0.0;
if (sigma > 0.0){
ohmega = (epsilon <= sigma) ? 1.0 : sigma/epsilon;
}
// R update
linear_model.R = ohmega * linear_model.R + (1-ohmega)*(V_corrected * V_corrected.transpose() + linear_model.C * P_corrected * linear_model.C.transpose());
// Innovation
Y_predicted = linear_model.C * X;
V_predicted = Y - Y_predicted;
for(int i=0; i < V_predicted.size(); ++i){
if(linear_model.Y_isangle[i]) V_predicted(i) = atan2(sin(V_predicted(i)),cos(V_predicted(i)));
}
S_predicted = linear_model.C * P * linear_model.C.transpose() + linear_model.R;
d2_predicted = (V_predicted.transpose() * S_predicted.inverse() * V_predicted)(0, 0);
// State update
Eigen::MatrixXd G = P * linear_model.C.transpose() * S_predicted.inverse();
Eigen::MatrixXd Gi = G * V_predicted;
for(int i=0; i < Gi.rows(); ++i){
if(linear_model.X_isangle[i]) Gi(i) = atan2(sin(Gi(i)),cos(Gi(i)));
}
X = X + Gi;
//P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C) * P;
// More numericaly stable P update:
P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C) * P *((Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * linear_model.C).transpose()) + G*linear_model.R*G.transpose();
// Innovation corrected
Y_corrected = linear_model.C * X;
V_corrected = Y - Y_corrected;
for(int i=0; i < V_corrected.size(); ++i){
if(linear_model.Y_isangle[i]) V_corrected(i) = atan2(sin(V_corrected(i)),cos(V_corrected(i)));
}
S_corrected = linear_model.C * P * linear_model.C.transpose() + linear_model.R;
d2_corrected = (V_corrected.transpose() * S_corrected.inverse() * V_corrected)(0, 0);
// Q update
linear_model.Q = ohmega * linear_model.Q + (1-ohmega)*(G * V_corrected * V_corrected.transpose() * G.transpose());
// P corrected, to be used in next interaction
P_corrected = P;
return ResultFormat(true,"");
}
};
/**
* @brief Base class of Bayesian filters for state space nonlinear models. See derived classes.
*/
class NonlinearBayesianFilter : public BayesianFilter
{
public:
virtual ~NonlinearBayesianFilter() {std::cout << "\nNonlinearBayesianFilter Base destroyed\n";}
virtual Result Prediction(Eigen::VectorXd & U, double t, StateSpaceNonlinearModelBase& nonlinear_model)=0;
virtual Result Correction(Eigen::VectorXd & Y, double t, StateSpaceNonlinearModelBase& nonlinear_model)=0;
virtual void Reset(StateSpaceNonlinearModelBase& nonlinear_model)=0;
};
/**
* @brief Extended Kalman filter, the first order approximation-based estimator for stochastic nonlinear state space models.
*
* This implementation is specific for discrete time, continuous state models.
*/
class ExtendedKalmanFilter : public NonlinearBayesianFilter
{
public:
virtual ~ExtendedKalmanFilter() {std::cout << "\nExtendedKalmanFilter derived class destroyed\n";}
/**
* @brief Reset filter, first procedure before prediction/correction.
* @param[in] nonlinear_model \p StateSpaceNonlinearModelBase derived object for which the filter will run
*/
void Reset(StateSpaceNonlinearModelBase& nonlinear_model) override
{
BayesianFilter::Reset(nonlinear_model.X0, nonlinear_model.P0, nonlinear_model.Nx, nonlinear_model.Ny);
}
/**
* @brief Prediction procedure, which will update (in place) the internal member variables \p X and \p P based on input vector \p U and model \p nonlinear_model. This procedure calls methods of the derived class object \p nonlinear_model in order to update model parameters. So there is no need the user to do that.
* @param[in] U input vector of the linear system process model
* @param[in] t time variable, for time varying systems
* @param[in] nonlinear_model \p StateSpaceNonlinearModelBase derived object for which the filter will run. Model updating and parameter calculations implemented by the user in the \p StateSpaceNonlinearModelBase derived object are called from inside this method.
*/
Result Prediction(Eigen::VectorXd & U, double t, StateSpaceNonlinearModelBase& nonlinear_model) override
{
// Update model (linearized on previous correction state)
Eigen::VectorXd W = Eigen::MatrixXd::Zero(nonlinear_model.dfdW.rows(), 1);
nonlinear_model.UpdatedfdX(X,U,t,W);
nonlinear_model.UpdatedfdW(X,U,t,W);
// Predicao
Result res = nonlinear_model.ProcessModel(X,U,t,W);
if(!res.first) return res;
P = nonlinear_model.dfdX * P * nonlinear_model.dfdX.transpose() + nonlinear_model.dfdW*nonlinear_model.Q*nonlinear_model.dfdW.transpose();
return res;
}
/**
* @brief Correction procedure, which will update (in place) the internal member variables \p X and \p P based on measurement vector \p Y and model \p linear_model. In case of time varying systems, \p linear_model class members should be updated before calling this procedure.
* @param[in] Y measurement vector of the linear system process model
* @param[in] t time variable, for time varying systems
* @param[in] nonlinear_model \p StateSpaceNonlinearModelBase derived object for which the filter will run. Model updating and parameter calculations implemented by the user in the \p StateSpaceNonlinearModelBase derived object are called from inside this method.
*/
Result Correction(Eigen::VectorXd & Y, double t, StateSpaceNonlinearModelBase& nonlinear_model) override
{
Result res;
// Update model (linearized on predicted state)
Eigen::VectorXd V = Eigen::VectorXd::Zero(nonlinear_model.dhdV.rows(), 1);
nonlinear_model.UpdatedhdX(X,t,V);
nonlinear_model.UpdatedhdV(X,t,V);
// State correction:
res = nonlinear_model.MeasurementModel(Y_predicted,X,t,V);
if(!res.first) return res;
V_predicted = Y - Y_predicted;
for(int i=0; i < V_predicted.size(); ++i){
if(nonlinear_model.Y_isangle[i]) V_predicted(i) = atan2(sin(V_predicted(i)),cos(V_predicted(i)));
}
S_predicted = nonlinear_model.dhdX * P * nonlinear_model.dhdX.transpose() + nonlinear_model.dhdV * nonlinear_model.R * nonlinear_model.dhdV.transpose();
d2_predicted = (V_predicted.transpose() * S_predicted.inverse() * V_predicted)(0, 0);
Eigen::MatrixXd G = P * nonlinear_model.dhdX.transpose() * S_predicted.inverse();
Eigen::MatrixXd Gi = G * V_predicted;
for(int i=0; i < Gi.rows(); ++i){
if(nonlinear_model.X_isangle[i]) Gi(i) = atan2(sin(Gi(i)),cos(Gi(i)));
}
X = X + Gi;
// P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * nonlinear_model.dhdX) * P;
// More numericaly stable P update:
P = (Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * nonlinear_model.dhdX) * P *((Eigen::MatrixXd::Identity(X.rows(), X.rows()) - G * nonlinear_model.dhdX).transpose()) + G*nonlinear_model.R*G.transpose();
// Update model (linearized on corrected state)
V = Eigen::VectorXd::Zero(nonlinear_model.dhdV.rows(), 1);
nonlinear_model.UpdatedhdX(X,t,V);
nonlinear_model.UpdatedhdV(X,t,V);
// Generate corrected output, innovation and d^2:
res = nonlinear_model.MeasurementModel(Y_corrected,X,t,V);
if(!res.first) return res;
V_corrected = Y - Y_corrected;
for(int i=0; i < V_corrected.size(); ++i){
if(nonlinear_model.Y_isangle[i]) V_corrected(i) = atan2(sin(V_corrected(i)),cos(V_corrected(i)));
}
S_corrected = nonlinear_model.dhdX * P * nonlinear_model.dhdX.transpose() + nonlinear_model.dhdV * nonlinear_model.R * nonlinear_model.dhdV.transpose();
d2_corrected = (V_corrected.transpose() * S_corrected.inverse() * V_corrected)(0, 0);
return res;
}
};
/**
* @brief Unscented Kalman filter, the Unscented Transform (UT)-based estimator for stochastic nonlinear state space models.
*
* This implementation is specific for discrete time, continuous state models.
*/
class UnscentedKalmanFilter : public NonlinearBayesianFilter
{
private:
/**
* @brief Initialize sigma weights. For internal use.
* @param[out] Wsigma vector filled with weights
* @param[in] kappa parameter of weighting function
* @param[in] Nx dimension of state vector
*/
void InitializeWeights(Eigen::VectorXd & Wsigma, double & kappa, int Nx)
{
int Nsigma = 2*Nx + 1;
Wsigma.resize(Nsigma,1);
kappa = (3 > Nx-3) ? 3 : Nx-3;
Wsigma(0) = kappa/(Nx+kappa);
for (int i = 1; i < Wsigma.rows(); ++i) {
Wsigma(i) = 1/(2.0*(Nx+kappa));
}
Wsigma = Wsigma / Wsigma.sum(); // Garante soma 1.
}
public:
// Override virtual members:
virtual ~UnscentedKalmanFilter() {std::cout << "\nUnscentedKalmanFilter derived class destroyed\n";}
/**
* @brief Reset filter, first procedure before prediction/correction.
* @param[in] nonlinear_model \p StateSpaceNonlinearModelBase derived object for which the filter will run
*/
void Reset(StateSpaceNonlinearModelBase& nonlinear_model) override
{
BayesianFilter::Reset(nonlinear_model.X0, nonlinear_model.P0, nonlinear_model.Nx, nonlinear_model.Ny);
}
/**
* @brief Prediction procedure, which will update (in place) the internal member variables \p X and \p P based on input vector \p U and model \p nonlinear_model. This procedure calls methods of the derived class object \p nonlinear_model in order to update model parameters. So there is no need the user to do that.
* @param[in] U input vector of the linear system process model
* @param[in] t time variable, for time varying systems
* @param[in] nonlinear_model \p StateSpaceNonlinearModelBase derived object for which the filter will run. Model updating and parameter calculations implemented by the user in the \p StateSpaceNonlinearModelBase derived object are called from inside this method.
*/
Result Prediction(Eigen::VectorXd & U, double t, StateSpaceNonlinearModelBase& nonlinear_model) override
{
Result res = BayesianFiltering::ResultFormat(true,"");
// Prediction
// The function x(k) = f(x(k-1),u(k),w(k),t(k)) is converted to x(k) = f(xa(k),u(k),t(k)) with xa(k) = [x(k-1); w(k)]
int Nx = nonlinear_model.Nx;
int Nw = nonlinear_model.Nw;
int DimX = Nx + Nw;
int DimY = Nx;
Eigen::VectorXd Wsigma;
double kappa;
InitializeWeights(Wsigma, kappa, DimX);
int Nsigma = Wsigma.rows();
// Generate augmented Xa and Pa
Eigen::VectorXd Xa(DimX);
Xa << X, Eigen::VectorXd::Zero(Nw);
Eigen::MatrixXd Pa(DimX,DimX);
Pa << P, Eigen::MatrixXd::Zero(Nx,Nw),
Eigen::MatrixXd::Zero(Nw, Nx), nonlinear_model.Q;
// Generate sigma points:
Eigen::MatrixXd Xsigma(DimX,Nsigma);
Eigen::MatrixXd Ysigma(DimY,Nsigma);
Eigen::LLT<Eigen::MatrixXd> llt((DimX+kappa)*Pa);
Eigen::MatrixXd L = llt.matrixL();
Eigen::VectorXd Xi(Nx);
Eigen::VectorXd Wi(Nw);
for (int i = 0; i < Nsigma; ++i) {
if (i==0){
Xsigma.col(i) = Xa;
}
else if (i <= DimX){
Xsigma.col(i) = Xa + L.col(i-1);
}
else{
Xsigma.col(i) = Xa - L.col(i-1-DimX);
}
Xi = Xsigma.block(0 , i, Nx, 1);
Wi = Xsigma.block(Nx, i, Nw, 1);
Result res = nonlinear_model.ProcessModel(Xi,U,t,Wi);
if(!res.first) return res;
Ysigma.col(i) = Xi;
}
// Compute prediction
X = Eigen::VectorXd::Zero(Nx);
for (int i = 0; i < Nsigma; ++i) {
X += Wsigma(i)*Ysigma.col(i);
}