Controlpp
Loading...
Searching...
No Matches
Estimators.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <fstream>
5
6#include <Eigen/Core>
7#include <Eigen/Dense>
8
9#include <tl/expected.hpp>
10
13
14namespace controlpp
15{
16
28 template<class T, int XRows, int XCols, int XOpt, int XMaxRows, int XMaxCols>
29 requires((XRows >= XCols) || (XRows == Eigen::Dynamic) || (XCols == Eigen::Dynamic))
30 Eigen::Vector<T, XCols> least_squares(const Eigen::Matrix<T, XRows, XCols, XOpt, XMaxRows, XMaxCols>& X, const Eigen::Vector<T, XRows>& y) {
31 Eigen::Vector<T, XCols> result = X.colPivHouseholderQr().solve(y).eval();
32 return result;
33 }
34
39
40 inline std::string_view to_string(dft_estimate_error err){
41 switch(err){
42 case dft_estimate_error::data_ranges_different_lenth : return "Data ranges have different length, but should have equal size";
43 case dft_estimate_error::data_range_too_small : return "Data ranges are too small for the system order";
44 default : return "dft_estimate_error::error";
45 }
46 }
47
48 inline std::ostream& operator<< (std::ostream& stream, dft_estimate_error err){
49 return stream << controlpp::to_string(err);
50 }
51
75 template<class T, int NumOrder, int DenOrder>
76 requires((NumOrder != Eigen::Dynamic) && (DenOrder != Eigen::Dynamic))
77 tl::expected<DiscreteTransferFunction<T, NumOrder, DenOrder>, dft_estimate_error>
79 const Eigen::Vector<T, Eigen::Dynamic>& u,
80 const Eigen::Vector<T, Eigen::Dynamic>& y,
81 const T& regularization = T(0),
83 ){
84 if(y.size() != u.size()){
86 }
87
88 constexpr int P = NumOrder + 1 + DenOrder;
89 Eigen::Vector<T, P> s;
90 s.setZero();
91
92 // input (u) partition
93 auto s_u = s.head(NumOrder + 1);
94
95 // output (y partition)
96 auto s_y = s.tail(DenOrder);
97
98 const std::size_t start = std::max(s_u.size()-1, s_y.size());
99
100 if(static_cast<std::size_t>(y.size()) <= start || static_cast<std::size_t>(u.size()) <= start){
101 return tl::unexpected(dft_estimate_error::data_range_too_small);
102 }
103
104 // fill s_u and s_y with start values
105 for(std::size_t k = 0; k < static_cast<std::size_t>(s_u.size()); ++k){
106 s_u(k) = u(start - k);
107 }
108 for(std::size_t k = 0; k < static_cast<std::size_t>(s_y.size()); ++k){
109 s_y(k) = -y(start - k - 1);
110 }
111
112 // sums
113 Eigen::Matrix<T, P, P> S;
114 S.setZero();
115
116 Eigen::Vector<T, P> r;
117 r.setZero();
118
119 // first iteration out of the loop
120 S.noalias() += s * s.transpose();
121 r.noalias() += y(start) * s;
122
123 // make an memory efficient sum to solve the least squares equation
124 for(std::size_t k = start+1; (k < static_cast<std::size_t>(y.size())) && (k < static_cast<std::size_t>(u.size())); ++k){
125 // push next input, output
126 controlpp::shift_up(s_u.data(), s_u.data() + s_u.size(), u(k));
127 controlpp::shift_up(s_y.data(), s_y.data() + s_y.size(), -y(k-1));
128
129 S.noalias() += s * s.transpose();
130 r.noalias() += y(k) * s;
131 }
132
133 // S should be symetric --> enforce symetry
134 S = (S + S.transpose()) * T(0.5);
135
136 // add regularisation to S
137 S.diagonal().array() += regularization;
138
139 // add hint penalty to r
140 const T hint_a0 = hint.den(0);
141
142 const Eigen::Vector<T, NumOrder + 1> hint_scaled_num = (hint.num().vector() / hint_a0);
143 const Eigen::Vector<T, DenOrder + 1> hint_scaled_den = (hint.den().vector() / hint_a0);
144 const Eigen::Vector<T, DenOrder> hint_scaled_den_tail = hint_scaled_den.template tail<DenOrder>();
145
146 const Eigen::Vector<T, P> hint_v = controlpp::join_to_vector(hint_scaled_num, hint_scaled_den_tail);
147 r += regularization * hint_v;
148
149 // solve r = S * param
150 // since S is symetric positive definite use llt
151 std::cout << "S:\n" << S << std::endl;
152 std::cout << "r:\n" << r.transpose() << std::endl;
153 const Eigen::Vector<T, P> params = S.ldlt().solve(r);
154
155 // partition result into numerator and denominator
156 const Eigen::Vector<T, NumOrder + 1> num = params.head(NumOrder + 1);
157 Eigen::Vector<T, DenOrder + 1> den;
158 den(0) = T(1);
159 den.tail(DenOrder) = params.tail(DenOrder);
160
161 // construct resulting transfer function
162 DiscreteTransferFunction<T, NumOrder, DenOrder> result(num, den);
163 return result;
164 }
165
194 template<class T, size_t NParams, size_t NMeasurements = 1>
196 private:
197
198 Eigen::Matrix<T, NParams, NParams> _cov;
199 Eigen::Vector<T, NParams> _param;
200 Eigen::Vector<T, NParams> _K;
201 T _memory = 0.98;
202 T _cov_regularisation = 1e-9;
203 T _gain_clamp = 10;
204
205
206 public:
207
250 const Eigen::Vector<T, NParams>& param_hint = Eigen::Vector<T, NParams>().setOne(),
251 T memory = 0.99,
252 T cov_regularisation = 1e-9,
253 T gain_clamp = 10
254 )
255 : _param(param_hint)
256 , _memory(memory)
257 , _cov_regularisation(cov_regularisation)
258 , _gain_clamp(gain_clamp)
259 {
260 assert(0 < memory);
261 assert(memory <= 1);
262 assert(cov_regularisation > 0);
263
264 this->_cov = (Eigen::Matrix<T, NParams, NParams>::Identity() * T(1000));
265 this->_K.setZero();
266 }
267
274 void input(const T& y, const Eigen::Matrix<T, NMeasurements, NParams>& s) {
275 this->_cov.diagonal().array() += this->_cov_regularisation;
276
277 // Gain
278 const auto A = (this->_cov * s.transpose()).eval();
279 const Eigen::Matrix<T, NMeasurements, NMeasurements> I_m = Eigen::Matrix<T, NMeasurements, NMeasurements>::Identity();
280 auto B1 = (s * this->_cov * s.transpose()).eval();
281 B1.diagonal().array() += this->_memory;
282 const auto B = ((B1 + B1.transpose())/2).eval(); // force symetry
283
284 // calculate: K = A * B^-1
285 this->_K = B.transpose().llt().solve(A.transpose()).transpose().eval();
286
287 // Limit the update gain
288 for(int i = 0; i < this->_K.size(); ++i){
289 this->_K.at(i) = std::clamp(this->_K.at(i), -this->_gain_clamp, this->_gain_clamp);
290 }
291
292 // Update
293 this->_param += this->_K * (y - s * this->_param);
294
295 // Covariance
296 this->_cov -= this->_K * s * this->_cov;
297 this->_cov /= this->_memory;
298 }
299
304 inline const Eigen::Vector<T, NParams>& estimate() const {return this->_param;}
305
313 [[nodiscard]] inline const Eigen::Matrix<T, NParams, NParams>& cov() const {return this->_cov;}
314
315 inline void set_cov(const Eigen::Matrix<T, NParams, NParams>& cov) {
316 this->_cov = cov;
317 }
318
326 inline void set_cov_regularisation(const T& reg){
327 this->_cov_regularisation = reg;
328 }
329
330 [[nodiscard]] inline T cov_regularisation() const {
331 return this->_cov_regularisation;
332 }
333
343 inline void set_memory(const T& memory){
344 this->_memory = memory;
345 }
346
356 [[nodiscard]] inline const T& memory() const {
357 return this->_memory;
358 }
359
370 inline void set_gain_clamp(const T& gain_clamp) {
371 this->_gain_clamp = gain_clamp;
372 }
373
378 [[nodiscard]] inline const T& gain_clamp(){
379 return this->_gain_clamp;
380 }
381
382
383
391 inline const Eigen::Vector<T, NParams>& gain() const {return this->_K;}
392 };
393
424 template<class T, size_t NParams>
425 class ReccursiveLeastSquares<T, NParams, 1>{
426 private:
427
428 Eigen::Matrix<T, NParams, NParams> _cov;
429 Eigen::Vector<T, NParams> _param;
430 Eigen::Vector<T, NParams> _K;
431 T _memory = 0.98;
432 T _cov_regularisation = 1e-9;
433 T _gain_clamp = 10;
434
435
436 public:
437
461 const Eigen::Vector<T, NParams>& param_hint = Eigen::Vector<T, NParams>().setOnes(),
462 const Eigen::Matrix<T, NParams, NParams>& cov_hint = (Eigen::Matrix<T, NParams, NParams>::Identity() * T(1000)),
463 T memory = 0.99,
464 T cov_regularisation = 1e-9
465 )
466 : _cov(cov_hint)
467 , _param(param_hint)
468 , _memory(memory)
469 , _cov_regularisation(cov_regularisation)
470 {
471 if(memory <= T(0) || memory > T(1)){
472 throw std::invalid_argument("Error: ReccursiveLeastSquares::ReccursiveLeastSquares(): memory has to be in the open-closed range of: (0, 1]");
473 }
474 this->_K.setZero();
475 }
476
477 inline void set_cov(const Eigen::Matrix<T, NParams, NParams>& cov){
478 this->_cov = cov;
479 }
480
481 inline void set_param(const Eigen::Vector<T, NParams>& param){
482 this->_param = param;
483 }
484
485 inline void set_memory(const T& memory){
486 this->_memory = memory;
487 }
488
489 inline void set_gain_clamp(const T& gain_clamp){
490 this->_gain_clamp = gain_clamp;
491 }
492
493 [[nodiscard]] inline const T& gain_clamp() const {
494 return this->_gain_clamp;
495 }
496
503 void input(const T& y, const Eigen::Vector<T, NParams>& s) {
504 this->_cov.diagonal().array() += this->_cov_regularisation;
505
506 // Gain
507 const auto A = (this->_cov * s).eval();
508 const T B = this->_memory + s.transpose() * this->_cov * s;
509 this->_K = A / B;
510
511 for(int i = 0; i < this->_K.size(); ++i){
512 this->_K(i) = std::clamp(this->_K(i), -this->_gain_clamp, this->_gain_clamp);
513 }
514
515 // Update
516 this->_param += this->_K * (y - s.transpose() * this->_param);
517
518 this->_cov -= this->_K * s.transpose() * this->_cov;
519 this->_cov /= this->_memory;
520 }
521
526 inline const Eigen::Vector<T, NParams>& estimate() const {return this->_param;}
527
535 inline const Eigen::Matrix<T, NParams, NParams>& cov() const {return this->_cov;}
536
544 inline const Eigen::Vector<T, NParams>& gain() const {return this->_K;}
545 };
546
567 template<class T, size_t NumOrder, size_t DenOrder, size_t Measurements=1>
569 private:
570
572
573 /*
574 TODO
575
576 Possible cause for numerical instability:
577
578 For early iterations, when uk and neg_yk are mostly zero, the system matrix s may be poorly conditioned (near-zero values), leading to numerical instability in the RLS update, especially in the gain calculation.
579
580 */
581 Eigen::Vector<T, NumOrder> uk = Eigen::Vector<T, NumOrder>::Zero();
582 Eigen::Vector<T, DenOrder> neg_yk = Eigen::Vector<T, DenOrder>::Zero();
583 T _memory = 0.98;
584
585 public:
586
597 const Eigen::Vector<T, NumOrder+1> NumeratorUncertainty,
598 const Eigen::Vector<T, DenOrder> DenominatorUncertainty,
599 const T& memory = 0.995)
600 {
601 T a_0 = hint.den().at(0);
602 if(a_0 != static_cast<T>(0)){
603 hint.num() /= a_0;
604 hint.den() /= a_0;
605 }
606
607 auto param = controlpp::join_to_vector<T, NumOrder+1, DenOrder>(hint.num().vector(), hint.den().vector().tail(DenOrder).eval());
608 this->rls.set_param(param);
609
610 auto cov = controlpp::join_to_diagonal<T, NumOrder+1, DenOrder>(NumeratorUncertainty, DenominatorUncertainty);
611 this->rls.set_cov(cov);
612
613 this->rls.set_memory(memory);
614
615 static_assert(NumOrder <= DenOrder, "The Discrete Transfer Function has to be propper. Meaning `NumOrder <= DenOrder` has to be true.");
616 }
617
620 const T& uncertainty = 1000,
621 const T& memory = 0.995)
622 : DtfEstimator(
623 hint,
624 Eigen::Vector<T, NumOrder+1>().setOnes()*T(uncertainty),
625 Eigen::Vector<T, DenOrder>().setOnes()*T(uncertainty),
626 memory){}
627
629 : DtfEstimator(
630 DiscreteTransferFunction<T, 0, 0>({T(1)}, {T(1)}), // hint
631 1000, // uncertainty
632 0.995 // memory
633 ){}
634
635
646 void input(const T& y, const T& u){
647 // add to the recursive least squares solver
648 Eigen::Vector<T, 1 + NumOrder + DenOrder> s;
649 s(0) = u;
650 s.segment(1, NumOrder) = this->uk;
651 s.tail(DenOrder) = this->neg_yk;
652 this->rls.input(y, s);
653
654 // update uk
655 std::copy_backward(this->uk.data(), this->uk.data()+this->uk.size(), this->uk.data()+1);
656 this->uk(0) = u;
657
658 // update yk
659 std::copy_backward(this->neg_yk.data(), this->neg_yk.data()+this->neg_yk.size(), this->neg_yk.data()+1);
660 this->neg_yk(0) = -y;
661 }
662
669 Eigen::Vector<T, NumOrder+1 + DenOrder> est = rls.estimate();
670 result.num().vector() = est.head(NumOrder+1);
671 result.den().vector()(0) = T(1);
672 result.den().vector().tail(DenOrder) = est.tail(DenOrder);
673 return result;
674 }
675
676 const Eigen::Matrix<T, NumOrder+1 + DenOrder, NumOrder+1 + DenOrder> cov(){
677 return this->rls.cov();
678 }
679
680 const Eigen::Vector<T, NumOrder+1 + DenOrder>& gain(){
681 return this->rls.gain();
682 }
683
684 void set_gain_clamp(const T& g){
685 this->rls.set_gain_clamp(g);
686 }
687
688 [[nodiscard]] const T& gain_clamp() const {
689 return this->rls.gain_clamp();
690 }
691
692 inline void set_cov_regularisation(const T& reg){
693 this->rls.cov_regularisation(reg);
694 }
695
696 [[nodiscard]] inline T cov_regularisation() const {
697 return this->rls.cov_regularisation();
698 }
699
700 };
701
702
703
704
705} // namespace controlpp
General algorithms that are used in multiple places in the library.
Continuous transfer functions in the s lapace plain.
Definition DiscreteTransferFunction.hpp:23
constexpr den_type & den()
Definition DiscreteTransferFunction.hpp:117
constexpr num_type & num()
Definition DiscreteTransferFunction.hpp:111
Estimates a discrete transfer function from online data points.
Definition Estimators.hpp:568
const Eigen::Vector< T, NumOrder+1+DenOrder > & gain()
Definition Estimators.hpp:680
DtfEstimator(DiscreteTransferFunction< T, NumOrder, DenOrder > hint, const T &uncertainty=1000, const T &memory=0.995)
Definition Estimators.hpp:618
const T & gain_clamp() const
Definition Estimators.hpp:688
DiscreteTransferFunction< T, NumOrder, DenOrder > estimate()
returns the current estimate
Definition Estimators.hpp:667
void set_cov_regularisation(const T &reg)
Definition Estimators.hpp:692
DtfEstimator()
Definition Estimators.hpp:628
void set_gain_clamp(const T &g)
Definition Estimators.hpp:684
const Eigen::Matrix< T, NumOrder+1+DenOrder, NumOrder+1+DenOrder > cov()
Definition Estimators.hpp:676
void input(const T &y, const T &u)
Adds an interation step to the estimate.
Definition Estimators.hpp:646
T cov_regularisation() const
Definition Estimators.hpp:696
DtfEstimator(DiscreteTransferFunction< T, NumOrder, DenOrder > hint, const Eigen::Vector< T, NumOrder+1 > NumeratorUncertainty, const Eigen::Vector< T, DenOrder > DenominatorUncertainty, const T &memory=0.995)
Initialises a discrete transfer function estimator with optional hints and memory/decay parameters.
Definition Estimators.hpp:595
ReccursiveLeastSquares(const Eigen::Vector< T, NParams > &param_hint=Eigen::Vector< T, NParams >().setOnes(), const Eigen::Matrix< T, NParams, NParams > &cov_hint=(Eigen::Matrix< T, NParams, NParams >::Identity() *T(1000)), T memory=0.99, T cov_regularisation=1e-9)
Creates a recursive least square object with start parameters/covariance and a memory factor.
Definition Estimators.hpp:460
const Eigen::Vector< T, NParams > & gain() const
Returns the gain used for the updata.
Definition Estimators.hpp:544
void input(const T &y, const Eigen::Vector< T, NParams > &s)
Adds a new input output pair that updates the estimate.
Definition Estimators.hpp:503
const T & gain_clamp() const
Definition Estimators.hpp:493
void set_cov(const Eigen::Matrix< T, NParams, NParams > &cov)
Definition Estimators.hpp:477
void set_gain_clamp(const T &gain_clamp)
Definition Estimators.hpp:489
void set_memory(const T &memory)
Definition Estimators.hpp:485
const Eigen::Vector< T, NParams > & estimate() const
returns the current best estimate
Definition Estimators.hpp:526
const Eigen::Matrix< T, NParams, NParams > & cov() const
returns the current covariance
Definition Estimators.hpp:535
void set_param(const Eigen::Vector< T, NParams > &param)
Definition Estimators.hpp:481
Calculates the recursive least square for online parameter estimation.
Definition Estimators.hpp:195
void set_gain_clamp(const T &gain_clamp)
Limits the update gain K.
Definition Estimators.hpp:370
void set_memory(const T &memory)
Sets the memory factor.
Definition Estimators.hpp:343
const Eigen::Matrix< T, NParams, NParams > & cov() const
returns the current covariance
Definition Estimators.hpp:313
T cov_regularisation() const
Definition Estimators.hpp:330
const Eigen::Vector< T, NParams > & gain() const
Returns the gain K used in the parameter update.
Definition Estimators.hpp:391
const T & memory() const
Returns the memory factor.
Definition Estimators.hpp:356
const T & gain_clamp()
Returns the currect gain clamp factor.
Definition Estimators.hpp:378
ReccursiveLeastSquares(const Eigen::Vector< T, NParams > &param_hint=Eigen::Vector< T, NParams >().setOne(), T memory=0.99, T cov_regularisation=1e-9, T gain_clamp=10)
Creates a recursive least square object with start parameters/covariance and a memory factor.
Definition Estimators.hpp:249
const Eigen::Vector< T, NParams > & estimate() const
returns the current best estimate
Definition Estimators.hpp:304
void input(const T &y, const Eigen::Matrix< T, NMeasurements, NParams > &s)
Adds a new input output pair that updates the estimate.
Definition Estimators.hpp:274
void set_cov_regularisation(const T &reg)
Set the value for the covariant regularisation.
Definition Estimators.hpp:326
void set_cov(const Eigen::Matrix< T, NParams, NParams > &cov)
Definition Estimators.hpp:315
Definition Polynom.hpp:1012
The main namespace for the Control++ library.
Definition Bode.cpp:3
void shift_up(Iterator first, Iterator last, const T &v0=T(0))
Shifts the values in a range up by one position and inserts a new value (copy operation) at the begin...
Definition algorithm.hpp:68
std::ostream & operator<<(std::ostream &stream, EBodeCsvReadError val)
Definition Bode.cpp:5
Eigen::Vector< T, XCols > least_squares(const Eigen::Matrix< T, XRows, XCols, XOpt, XMaxRows, XMaxCols > &X, const Eigen::Vector< T, XRows > &y)
Solves the overdefined system for p.
Definition Estimators.hpp:30
Eigen::Vector< T, LSize+RSize > join_to_vector(const Eigen::Vector< T, LSize > &l, const Eigen::Vector< T, RSize > &r)
Definition math.hpp:225
std::string_view to_string(dft_estimate_error err)
Definition Estimators.hpp:40
tl::expected< DiscreteTransferFunction< T, NumOrder, DenOrder >, dft_estimate_error > dft_estimate(const Eigen::Vector< T, Eigen::Dynamic > &u, const Eigen::Vector< T, Eigen::Dynamic > &y, const T &regularization=T(0), const DiscreteTransferFunction< T, NumOrder, DenOrder > &hint=DiscreteTransferFunction< T, NumOrder, DenOrder >({T(0)}, {T(1)}))
Estimates a discrete time transfer function of a specific order for the input (u) and output (y) data...
Definition Estimators.hpp:78
dft_estimate_error
Definition Estimators.hpp:35