Controlpp
Loading...
Searching...
No Matches
math.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <Eigen/Core>
4#include <Eigen/Eigenvalues>
5
6#include <cstdint>
7
8namespace controlpp{
9
17 template<class T>
18 constexpr T product_over(T from, T to){
19 unsigned long product = 1;
20 for(; from < to; ++from){
21 product *= from;
22 }
23 return product;
24 }
25
29 template<class T>
30 constexpr T pow(const T& base, const int& exp){
31 T result = static_cast<T>(1);
32 if(exp >= 0){
33 for(int i = 0; i < exp; ++i){
34 result *= base;
35 }
36 }else{
37 for(int i = 0; i < -exp; ++i){
38 result /= base;
39 }
40 }
41 return result;
42 }
43
54 template<class T, int N>
55 Eigen::Vector<T, N> unwrap(const Eigen::Vector<T, N>& y, const T& modulo){
56 Eigen::Vector<T, N> result;
57 if constexpr (N == Eigen::Dynamic) result.resize(y.size());
58 T offset = 0;
59 result(0) = y(0);
60 for(int i = 1; i < y.size(); ++i){
61 while(((y(i) + offset) - result(i-1)) > (modulo/2)){
62 offset -= modulo;
63 }
64 while(((y(i) + offset) - result(i-1)) < (-modulo/2)){
65 offset += modulo;
66 }
67 result(i) = y(i) + offset;
68 }
69 return result;
70 }
71
79 template<class T, int N>
80 Eigen::Vector<T, N> unwrap_rad(const Eigen::Vector<T, N>& phases){
81 const T modulo = std::numbers::pi_v<T> * static_cast<T>(2);
82 return unwrap(phases, modulo);
83 }
84
92 template<class T, int N>
93 Eigen::Vector<T, N> unwrap_deg(const Eigen::Vector<T, N>& phases){
94 const T modulo = 360;
95 return unwrap(phases, modulo);
96 }
97
98
114 template<class T = double>
115 constexpr T pade_param_reccursice(T P_k, std::int32_t m, std::int32_t n, std::int32_t k){
116 const std::int64_t num = (m - k);
117 const std::int64_t den = (k + 1) * (m + n - k);
118 const double f = static_cast<T>(num) / static_cast<T>(den);
119 return P_k * f;
120 }
121
130 template<class T = double>
131 constexpr void pade_params(T* params, std::size_t size, std::uint32_t m, std::uint32_t n){
132 params[0] = T(1.0);
133 for(std::size_t k = 1; k < size; ++k){
134 params[k] = pade_param_reccursice(params[k-1], m, n, k-1);
135 }
136 }
137
164 template<class T = double>
165 constexpr T pade_num_param(std::int32_t m, std::int32_t n, std::int32_t k){
166 double P_k = 1.0;
167 for(std::int32_t i = 0; i < k; ++i){
168 P_k = pade_param_reccursice(P_k, m, n, i);
169 }
170 return P_k;
171 }
172
199 template<class T = double>
200 constexpr T pade_den_param(unsigned long m, unsigned long n, unsigned long k){
201 return pade_num_param(n, m, k);
202 }
203
204 template<class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
205 Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols> identity_like([[maybe_unused]]const Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>& unused){
206 if constexpr (Rows != Eigen::Dynamic && Cols != Eigen::Dynamic){
207 return Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>::Identity();
208 }else{
209 Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols> result;
210 result.setIdentity();
211 return result;
212 }
213 }
214
215 template<class T, int LSize, int RSize>
216 Eigen::Matrix<T, LSize + RSize, LSize + RSize> join_to_diagonal(const Eigen::Vector<T, LSize>& l, const Eigen::Vector<T, RSize>& r){
217 Eigen::Matrix<T, LSize + RSize, LSize + RSize> result = Eigen::Matrix<T, LSize + RSize, LSize + RSize>::Zero();
218 result.diagonal().head(LSize) = l;
219 result.diagonal().tail(RSize) = r;
220 return result;
221 }
222
223 template<class T, int LSize, int RSize>
224 requires(LSize != Eigen::Dynamic && RSize != Eigen::Dynamic)
225 Eigen::Vector<T, LSize + RSize> join_to_vector(const Eigen::Vector<T, LSize>& l, const Eigen::Vector<T, RSize>& r){
226 Eigen::Vector<T, LSize + RSize> result;
227 result.head(LSize) = l;
228 result.tail(RSize) = r;
229 return result;
230 }
231
232 template<class T, int LSize, int RSize>
233 requires(!(LSize != Eigen::Dynamic && RSize != Eigen::Dynamic))
234 Eigen::Vector<T, Eigen::Dynamic> join_to_vector(const Eigen::Vector<T, LSize>& l, const Eigen::Vector<T, RSize>& r){
235 Eigen::Vector<T, Eigen::Dynamic> result(l.size() + r.size());
236 result.head(l.size()) = l;
237 result.tail(r.size()) = r;
238 return result;
239 }
240
247 template<class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
248 Eigen::Matrix<T, Rows-1, Cols-1> minor(const Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>& A, size_t ex_row, size_t ex_col){
249 Eigen::Matrix<T, Rows-1, Cols-1> result;
250 size_t A_row = 0;
251 size_t A_col = 0;
252 for(size_t result_row = 0; result_row < (Rows-1); ++result_row, (void)++A_row){
253 for(size_t result_col = 0; result_col < (Cols-1); ++result_col, (void)++A_col){
254 A_row += (A_row == ex_row) ? 1 : 0;
255 A_col += (A_col == ex_col) ? 1 : 0;
256 result(result_row, result_col) = A(A_row, A_col);
257 }
258 }
259 return result;
260 }
261
262 template<class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
263 Eigen::Matrix<T, Rows, Cols> adj(const Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>& A){
264 Eigen::Matrix<T, Rows, Cols> result;
265 for(int row = 0; row < Rows; ++row){
266 const bool row_sign = ((row & 1) == 1); // even are positive, odd are negative
267 for(int col = 0; col < Cols; ++col){
268 const bool col_sign = ((col & 1) == 1); // even are positive, odd are negative
269 const bool value_sign = row_sign != col_sign; // sign of the value (both positive/negative: value is positive) (different signs: value is negative)
270 const auto min = minor(A, row, col);
271 const T det = min.determinant();
272 if(value_sign){
273 // negative sign
274 result(col, row) = -det;
275 }else{
276 // positive sign
277 result(col, row) = det;
278 }
279 }
280 }
281 return result;
282 }
283
313 template<class T, int N>
314 Eigen::Matrix<T, N-1, N-1> companion(const Eigen::Vector<T, N>& v){
315 Eigen::Matrix<T, N-1, N-1> result;
316 result.template block<N-2, N-2>(0, 1) = Eigen::Matrix<T, N-2, N-2>::Identity();
317 result.col(0).setZero();
318 result.row(N-2) = -v.head(N-1) / v(N-1);
319 return result;
320 }
321
341 template<class T, int N>
342 requires(N % 2 == 0)
343 Eigen::Matrix<T, N/2, N/2> hamilton_solver(
344 const Eigen::Matrix<T, N, N> H
345 ){
346 // 2. Compute stable eigenvectors
347 Eigen::ComplexEigenSolver<Eigen::Matrix<T, N, N>> ces;
348 ces.compute(H);
349 const auto& H_eigvals = ces.eigenvalues();
350 const auto& H_eigvecs = ces.eigenvectors();
351
352 // in a 2n hamilton matrix are exactly n stable ones
353 Eigen::Matrix<std::complex<T>, N, N/2> StableEigenVecs;
354 int si = 0; // stable eigenvalue iterator
355 int ei = 0; // eigen value iterator
356 for(; (si < N/2) && (ei < N); ++ei){
357 if(H_eigvals(ei).real() <= 0){// only stable ones
358 StableEigenVecs.col(si) = H_eigvecs.col(ei);
359 ++si;
360 }
361 }
362
363 // 3. Repartition
364 const auto TopPartition = StableEigenVecs.template block<N/2, N/2>(0, 0);
365 const auto BottomPartition = StableEigenVecs.template block<N/2, N/2>(N/2, 0);
366
367 // 4. Recover Solution (BottomPartition * TopPartition^-1)
368 const Eigen::Matrix<std::complex<T>, N/2, N/2> X = TopPartition.transpose().partialPivLu().solve(BottomPartition.transpose()).transpose();
369
370 // The result is real, make sure it is real because it should be
371 const Eigen::Matrix<T, N/2, N/2> realX = X.real();
372
373 // force the result X to be symetric to combat small numerical errors
374 const Eigen::Matrix<T, N/2, N/2> result = static_cast<T>(0.5) * (realX + realX.transpose());
375 return result;
376 }
377
397 template<class T, int N>
398 Eigen::Matrix<T, N/2, N/2> symplectic_solver(
399 const Eigen::Matrix<T, N, N> S
400 ){
401 // 2. Compute stable eigenvectors
402 Eigen::ComplexEigenSolver<Eigen::Matrix<T, N, N>> ces;
403 ces.compute(S);
404 const auto& eigvals = ces.eigenvalues();
405 const auto& eigvecs = ces.eigenvectors();
406
407 // in a 2n hamilton matrix are exactly n stable ones
408 Eigen::Matrix<std::complex<T>, N, N/2> StableEigenVecs;
409 StableEigenVecs.setZero();
410 int si = 0; // stable eigenvalue iterator
411 int ei = 0; // eigen value iterator
412 for(; (si < N/2) && (ei < N); ++ei){
413 if(std::abs(eigvals(ei)) < 1){// only stable ones
414 StableEigenVecs.col(si) = eigvecs.col(ei);
415 ++si;
416 }
417 }
418
419 // 3. Repartition
420 const auto TopPartition = StableEigenVecs.template block<N/2, N/2>(0, 0);
421 const auto BottomPartition = StableEigenVecs.template block<N/2, N/2>(N/2, 0);
422
423 // 4. Recover Solution (BottomPartition * TopPartition^-1)
424 const Eigen::Matrix<std::complex<T>, N/2, N/2> X = TopPartition.transpose().partialPivLu().solve(BottomPartition.transpose()).transpose();
425
426 // The result is real, make sure it is real because it should be
427 const Eigen::Matrix<T, N/2, N/2> realX = X.real();
428
429 // force the result X to be symetric to combat small numerical errors
430 const Eigen::Matrix<T, N/2, N/2> result = static_cast<T>(0.5) * (realX + realX.transpose());
431
432 return result;
433 }
434
465 template<class T, int NStates,
466 int AOpt, int AMaxR, int AMaxC,
467 int QOpt, int QMaxR, int QMaxC
468 >
469 Eigen::Matrix<T, NStates, NStates> lyapunov_solver(
470 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
471 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q
472 ){
473 // 1. Build the hamilton matrix
474 Eigen::Matrix<T, 2*NStates, 2*NStates> H;
475 H.topLeftCorner(NStates, NStates) = A;
476 H.topRightCorner(NStates, NStates).setZero();
477 H.bottomLeftCorner(NStates, NStates) = - Q;
478 H.bottomRightCorner(NStates, NStates) = -A.transpose();
479 return hamilton_solver(H);
480 }
481
506 template<class ValueType, int NStates, int NInputs, int NOutputs>
507 std::tuple<Eigen::Matrix<ValueType, NStates, NStates>, Eigen::Matrix<ValueType, NStates, NStates>> energy_scaling_matrix(
508 const Eigen::Matrix<ValueType, NStates, NStates> A,
509 const Eigen::Matrix<ValueType, NStates, NInputs> B,
510 const Eigen::Matrix<ValueType, NOutputs, NStates> C
511 ){
512 // Calculate the two gramians Wc and Wo
513 const Eigen::Matrix<ValueType, NStates, NStates> Bsqr = B * B.transpose();
514 const Eigen::Matrix<ValueType, NStates, NStates> Wc = lyapunov_solver(A.transpose(), Bsqr);
515
516 const Eigen::Matrix<ValueType, NStates, NStates> Csqr = C.transpose() * C;
517 const Eigen::Matrix<ValueType, NStates, NStates> Wo = lyapunov_solver(A, Csqr);
518
519 // force symetry and add some to the diagonal for numerical stability
520 const ValueType eps = std::numeric_limits<ValueType>::epsilon();
521
522 Eigen::Matrix<ValueType, NStates, NStates> Wc_sym = (Wc + Wc.transpose()) * static_cast<ValueType>(0.5);
523 {
524 ValueType jitter = std::max(ValueType(1), Wc_sym.diagonal().cwiseAbs().maxCoeff());
525 Wc_sym.diagonal().array() += jitter * eps;
526 }
527 Eigen::Matrix<ValueType, NStates, NStates> Wo_sym = (Wo + Wo.transpose()) * static_cast<ValueType>(0.5);
528 {
529 ValueType jitter = std::max(ValueType(1), Wo_sym.diagonal().cwiseAbs().maxCoeff());
530 Wo_sym.diagonal().array() += jitter * eps;
531 }
532
533 // calculate the cholesky factors using LLT
534 const Eigen::LLT<Eigen::Matrix<ValueType, NStates, NStates>> Wc_colesky(Wc_sym);
535 const Eigen::LLT<Eigen::Matrix<ValueType, NStates, NStates>> Wo_colesky(Wo_sym);
536
537 // partition into triangle forms
538 const Eigen::Matrix<ValueType, NStates, NStates> Rc = Wc_colesky.matrixU();
539 const Eigen::Matrix<ValueType, NStates, NStates> Ro = Wo_colesky.matrixL();
540
541 // calculate the SVD
542 const Eigen::Matrix<ValueType, NStates, NStates> M = Ro * Rc;
543 const Eigen::JacobiSVD<Eigen::Matrix<ValueType, NStates, NStates>> svd(M, Eigen::ComputeThinU | Eigen::ComputeThinV);
544 const Eigen::Matrix<ValueType, NStates, NStates> U = svd.matrixU();
545 const Eigen::Matrix<ValueType, NStates, 1> s = svd.singularValues();
546 const Eigen::Matrix<ValueType, NStates, NStates> V = svd.matrixV();
547
548
549 const Eigen::Matrix<ValueType, NStates, 1> s_sqrt = (s.array().max(eps)).sqrt().matrix();
550 const Eigen::Matrix<ValueType, NStates, NStates> S_sqrt = s_sqrt.asDiagonal();
551
552 // Finally!!! we can calculate the resulting transformation matrices
553 const Eigen::Matrix<ValueType, NStates, NStates> T = Rc.transpose().template triangularView<Eigen::Lower>().solve((S_sqrt * V.transpose()).transpose()).transpose();
554 const Eigen::Matrix<ValueType, NStates, NStates> T_inverse = Ro.template triangularView<Eigen::Lower>().solve(U * S_sqrt);
555
556 return {T, T_inverse};
557 }
558
559
560
602 template<class T, int NStates, int NInputs,
603 int AOpt, int AMaxR, int AMaxC,
604 int BOpt, int BMaxR, int BMaxC,
605 int ROpt, int RMaxR, int RMaxC,
606 int QOpt, int QMaxR, int QMaxC
607 >
608 Eigen::Matrix<T, NStates, NStates> care_solver(
609 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
610 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B,
611 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q,
612 const Eigen::Matrix<T, NInputs, NInputs, ROpt, RMaxR, RMaxC>& R
613 ){
614 Eigen::Matrix<T, 2*NStates, 2*NStates> H;
615 H.topLeftCorner(NStates, NStates) = A;
616 H.topRightCorner(NStates, NStates) = -B * R.ldlt().solve(B.transpose());
617 H.bottomLeftCorner(NStates, NStates) = -Q;
618 H.bottomRightCorner(NStates, NStates) = -A;
619 return hamilton_solver(H);
620 }
621
664 template<class T, int NStates, int NInputs,
665 int AOpt, int AMaxR, int AMaxC,
666 int BOpt, int BMaxR, int BMaxC,
667 int ROpt, int RMaxR, int RMaxC,
668 int QOpt, int QMaxR, int QMaxC
669 >
670 Eigen::Matrix<T, NStates, NStates> dare_eigenvector_solver(
671 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
672 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B,
673 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q,
674 const Eigen::Matrix<T, NInputs, NInputs, ROpt, RMaxR, RMaxC>& R
675 ){
676 const Eigen::Matrix<T, NStates, NStates> brb = B * R.llt().solve(B.transpose());
677 Eigen::ColPivHouseholderQR<Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>> At_qr(A.transpose());
678 const Eigen::Matrix<T, NStates, NStates> aq = At_qr.solve(Q);
679 Eigen::Matrix<T, 2*NStates, 2*NStates> S;
680 S.topLeftCorner(NStates, NStates) = A + brb * aq;
681 S.topRightCorner(NStates, NStates) = -A.colPivHouseholderQr().solve(brb.transpose()).transpose();
682 S.bottomLeftCorner(NStates, NStates) = - aq;
683 S.bottomRightCorner(NStates, NStates) = At_qr.solve(identity_like(A));
684 return symplectic_solver(S);
685 }
686
725 template<class T, int NStates, int NInputs,
726 int AOpt, int AMaxR, int AMaxC,
727 int BOpt, int BMaxR, int BMaxC,
728 int ROpt, int RMaxR, int RMaxC,
729 int QOpt, int QMaxR, int QMaxC
730 >
731 Eigen::Matrix<T, 2*NStates + NInputs, 2*NStates + NInputs> build_dare_symplectic_pencil_H(
732 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
733 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B,
734 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q,
735 const Eigen::Matrix<T, NInputs, NInputs, ROpt, RMaxR, RMaxC>& R
736 ){
737 constexpr int H_rows = 2*NStates + NInputs;
738 constexpr int H_cols = 2*NStates + NInputs;
739
740 // build the matrix:
741 // [ A 0 B ]
742 // [ -Q I 0 ]
743 // [ 0 0 R ]
744 Eigen::Matrix<T, H_rows, H_cols> H;
745 H.setZero();
746 H.template block<NStates, NStates>(0, 0) = A;
747 H.template block<NStates, NInputs>(0, 2 * NStates) = B;
748 H.template block<NStates, NStates>(NStates, 0) = -Q;
749 H.template block<NStates, NStates>(NStates, NStates).setIdentity();
750 H.template block<NInputs, NInputs>(2 * NStates, 2 * NStates) = R;
751
752 return H;
753 }
754
793 template<class T, int NStates, int NInputs,
794 int AOpt, int AMaxR, int AMaxC,
795 int BOpt, int BMaxR, int BMaxC
796 >
797 Eigen::Matrix<T, 2*NStates + NInputs, 2*NStates + NInputs> build_dare_symplectic_pencil_J(
798 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
799 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B
800 ){
801 constexpr int J_rows = NStates + NStates + NInputs;
802 constexpr int J_cols = NStates + NStates + NInputs;
803
804 // build the matrix:
805 // [ I 0 B]
806 // [ 0 A^T 0]
807 // [ 0 -B^T 0]
808 Eigen::Matrix<T, J_rows, J_cols> J;
809 J.setZero();
810 J.template block<NStates, NStates>(0, 0).setIdentity();
811 J.template block<NStates, NInputs>(0, 2 * NStates) = B;
812 J.template block<NStates, NStates>(NStates, NStates) = A.transpose();
813 J.template block<NInputs, NStates>(2 * NStates, NStates) = -B.transpose();
814
815 return J;
816 }
817
834 /* DEVELOPEMENT
835 template<class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols>
836 int pencil_block_size(const Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>& S, int block_start){
837 assert((0 <= block_start) && (block_start < S.rows() - 1) && "block_start is out of bounds for the matrix S.");
838
839 int block_size = 1;
840
841 // determine the blocksize (1x1 or 2x2)
842 const T sub_diag = S(block_start + 1, block_start);
843
844 // zero threshold for numerical stability
845 const T local_diagonal = std::abs(S(block_start, block_start)) + std::abs(S(block_start + 1, block_start + 1));
846 const T epsilon = std::numeric_limits<T>::epsilon();
847 const T zero_threshold = epsilon * std::max(local_diagonal, T(1)) * T(1024);
848
849 if(block_start + 1 < PencilSize){
850 if(std::abs(sub_diag) > zero_threshold){
851 block_size = 2;
852 }else{
853 block_size = 1;
854 }
855 }else{
856 block_size = 1;
857 }
858
859 return block_size;
860 }
861 */
862
880 /* DEVELOPEMENT
881 template<class DerivedS, class DerivedT>
882 bool is_stable_pencil_block(
883 const Eigen::MatrixBase<DerivedS>& Sb,
884 const Eigen::MatrixBase<DerivedT>& Tb
885 ){
886 assert(Sb.rows() == Sb.cols());
887 assert(Tb.rows() == Tb.cols());
888 assert(Sb.rows() == Tb.rows());
889 assert(Sb.rows() >= 1 && Sb.rows() <= 2);
890
891 if(Sb.rows() == 1){
892 // blocksize is 1
893
894 // check if the eigenvalue is stable (inside the unit circle)
895 const auto s = Sb(0, 0);
896 const auto t = Tb(0, 0);
897
898 const auto scale = std::max({
899 std::abs(t),
900 std::abs(s),
901 Scalar(1)});
902
903 const auto tolerance = std::numeric_limits<Scalar>::epsilon() * scale * Scalar(128);
904
905 // avoid checks against zero for numerical stability
906 if((std::abs(s) <= tolerance) && (std::abs(t) <= tolerance)){
907 // both s and t are zero --> indeterminante
908 throw std::runtime_error("Controlpp: Indeterminate pencil block: both S and T are zero.");
909 }
910
911 // avoid division by zero
912 const bool stable = (std::abs(s) < std::abs(t));
913 return stable;
914 }else{
915 // blocksize is 2
916
917 // solve det(S - lambda T) = 0 for the 2x2 block to find the eigenvalues
918 // build the quadratic polynomial coefficients
919 //
920 //
921 // det | (S00 - lambda T00) (S01 - lambda T01) |
922 // | (S10) (S11 - lambda T11) |
923 auto a = Tb(0, 0) * Tb(1, 1);
924 auto b = Sb(1, 0) * Tb(0, 1) - Sb(0, 0) * Tb(1, 1) - Sb(1, 1) * Tb(0, 0);
925 auto c = Sb(0, 0) * Sb(1, 1) - Sb(0, 1) * Sb(1, 0);
926
927 { // assert that the block is determinate
928 const auto scale = std::max({
929 std::abs(a),
930 std::abs(b),
931 std::abs(c)
932 });
933
934 if(scale == Scalar(0)){
935 // all coefficients are zero --> indeterminante
936 throw std::runtime_error("Controlpp: Indeterminate pencil block: All coefficients are zero.");
937 }
938
939 // normalize the coefficients to avoid numerical issues
940 a /= scale;
941 b /= scale;
942 c /= scale;
943
944 const auto tolerance = std::numeric_limits<Scalar>::epsilon() * Scalar(128);
945
946 // avoid checks against zero for numerical stability
947 if(std::abs(a) <= tolerance){
948 if(std::abs(b) <= tolerance){
949 if(std::abs(c) <= tolerance){
950 // all coefficients are zero --> indeterminante
951 throw std::runtime_error("Controlpp: Indeterminate pencil block: Zero eigenvalues.");
952 }
953 // constant equation c = 0 --> no solution --> infinite eigenvalues --> unstable
954 return false;
955 }
956 // linear equation b lambda + c = 0 --> lambda = -c/b
957 // one finite eigenvalue and one infinite eigenvalue --> unstable
958 return false;
959 }
960 }
961
962 // solve the quadratic equation a lambda^2 + b lambda + c = 0
963 // lambda_1,2 = (-b +- sqrt(b^2 - 4ac)) / 2a
964 // | lambda_1,2 | < 1 ... for stability
965 // --> | (-b +- sqrt(b^2 - 4ac)) | < | 2a | ... for stability
966 const auto discriminant = b * b - 4 * a * c;
967 if(discriminant >= 0){
968 // real eigenvalues
969 const auto lhs_1 = std::abs(-b + std::sqrt(discriminant));
970 const auto lhs_2 = std::abs(-b - std::sqrt(discriminant));
971 const auto rhs = std::abs(2 * a);
972
973 { // assert that the block is determinate
974 // avoid checks against zero for numerical stability
975 const auto scale = std::max({
976 std::abs(lhs_1),
977 std::abs(lhs_2),
978 std::abs(rhs),
979 Scalar(1)});
980
981 const auto tolerance = std::numeric_limits<Scalar>::epsilon() * scale * Scalar(128);
982
983 if(std::abs(lhs_1) == DerivedS::Scalar(0) && std::abs(rhs) == DerivedT::Scalar(0)){
984 // both lhs and rhs are zero --> indeterminante
985 throw std::runtime_error("Controlpp: Indeterminate pencil block: Eigenvalues are zero.");
986 }
987
988 if(std::abs(lhs_2) == DerivedS::Scalar(0) && std::abs(rhs) == DerivedT::Scalar(0)){
989 // both lhs and rhs are zero --> indeterminante
990 throw std::runtime_error("Controlpp: Indeterminate pencil block: Eigenvalues are zero.");
991 }
992 }
993
994 const bool stable_1 = (lhs_1 < rhs);
995 const bool stable_2 = (lhs_2 < rhs);
996 const bool stable = stable_1 && stable_2;
997 return stable;
998 }else{
999 // complex eigenvalues
1000 const auto lhs = std::sqrt(b * b - discriminant);
1001 const auto rhs = std::abs(2 * a);
1002 { // assert that the block is determinate
1003 // avoid checks against zero for numerical stability
1004 const auto scale = std::max({
1005 std::abs(lhs),
1006 std::abs(rhs),
1007 Scalar(1)});
1008
1009 const auto tolerance = std::numeric_limits<Scalar>::epsilon() * scale * Scalar(128);
1010
1011 if(std::abs(lhs) == DerivedS::Scalar(0) && std::abs(rhs) == DerivedT::Scalar(0)){
1012 // both lhs and rhs are zero --> indeterminante
1013 throw std::runtime_error("Controlpp: Indeterminate pencil block: Eigenvalues are zero.");
1014 }
1015 }
1016
1017 const bool stable = (lhs < rhs);
1018 return stable;
1019 }
1020 }
1021 }
1022 */
1023
1050 /* DEVELOPEMENT
1051 template<class T, int NStates, int NInputs,
1052 int AOpt, int AMaxR, int AMaxC,
1053 int BOpt, int BMaxR, int BMaxC,
1054 int ROpt, int RMaxR, int RMaxC,
1055 int QOpt, int QMaxR, int QMaxC
1056 >
1057 Eigen::Matrix<T, NStates, NStates> dare_qz_solver(
1058 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
1059 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B,
1060 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q,
1061 const Eigen::Matrix<T, NInputs, NInputs, ROpt, RMaxR, RMaxC>& R
1062 ){
1063 // 1. Build the symplectic pencil matrices H and J
1064 constexpr int PencilSize = 2 * NStates + NInputs;
1065 using Pencil = Eigen::Matrix<T, PencilSize, PencilSize>;
1066
1067 const Pencil H = build_dare_symplectic_pencil_H(A, B, Q, R);
1068
1069 const Pencil J = build_dare_symplectic_pencil_J(A, B);
1070
1071 // 2. Solve the generalized eigenvalue problem H v = lambda J v
1072 Eigen::RealQZ<Pencil> qz;
1073 qz.compute(H, J);
1074
1075 if(qz.info() != Eigne::Success){
1076 // QZ failed to converge
1077 throw std::runtime_error("Controlpp: dare_qz_solver(): QZ failed to converge.");
1078 }
1079
1080 Pencil S = qz.matrixS(); // is quasi-upper-triangular
1081 Pencil Tmat = qz.matrixT(); // is upper-triangular
1082 Pencil Qz = qz.matrixQ(); // contains the left generalized Schur vectors
1083 Pencil Z = qz.matrixZ(); // contains the right generalized Schur vectors
1084
1085 // 3. Reorder
1086 int block_start = 0;
1087 while(block_start < PencilSize){
1088
1089 const int block_size = pencil_block_size(S, block_start);
1090
1091 // map to eigen::map for easier access
1092 auto Sb = S.block(block_start, block_start, block_size, block_size);
1093 auto Tb = Tmat.block(block_start, block_start, block_size, block_size);
1094
1095 const bool is_stable = is_stable_pencil_block(Sb, Tb);
1096
1097 // if the block is not stable, we need to swap it with a stable block
1098 // so that all stable blocks are in the top left corner of the matrix
1099 // @todo
1100
1101
1102 block_start += block_size;
1103 }
1104
1105 }
1106 */
1107
1108 template<class T, int NStates, int NInputs,
1109 int AOpt, int AMaxR, int AMaxC,
1110 int BOpt, int BMaxR, int BMaxC,
1111 int ROpt, int RMaxR, int RMaxC,
1112 int QOpt, int QMaxR, int QMaxC
1113 >
1114 Eigen::Matrix<T, NStates, NStates> dare_solver(
1115 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
1116 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B,
1117 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q,
1118 const Eigen::Matrix<T, NInputs, NInputs, ROpt, RMaxR, RMaxC>& R
1119 ){
1120 return dare_eigenvector_solver(A, B, Q, R);
1121 }
1122
1165 template<class T, int NStates, int NInputs,
1166 int AOpt, int AMaxR, int AMaxC,
1167 int BOpt, int BMaxR, int BMaxC,
1168 int ROpt, int RMaxR, int RMaxC,
1169 int QOpt, int QMaxR, int QMaxC,
1170 int NOpt, int NMaxR, int NMaxC
1171 >
1172 Eigen::Matrix<T, NStates, NStates> care_solver(
1173 const Eigen::Matrix<T, NStates, NStates, AOpt, AMaxR, AMaxC>& A,
1174 const Eigen::Matrix<T, NStates, NInputs, BOpt, BMaxR, BMaxC>& B,
1175 const Eigen::Matrix<T, NStates, NStates, QOpt, QMaxR, QMaxC>& Q,
1176 const Eigen::Matrix<T, NInputs, NInputs, ROpt, RMaxR, RMaxC>& R,
1177 const Eigen::Matrix<T, NStates, NInputs, NOpt, NMaxR, NMaxC>& N
1178 ){
1179 // 1. Build the hamilton matrix
1180 const Eigen::Matrix<T, NInputs, NStates> R_inv_DT_C = R.ldlt().solve((N.transpose()));
1181 const Eigen::Matrix<T, NStates, NStates> A_ = A - B * R_inv_DT_C;
1182
1183 Eigen::Matrix<T, 2*NStates, 2*NStates> H;
1184 H.topLeftCorner(NStates, NStates) = A_;
1185 H.topRightCorner(NStates, NStates) = - B * R.ldlt().solve(B.transpose());
1186 H.bottomLeftCorner(NStates, NStates) = - (Q - N * R_inv_DT_C);
1187 H.bottomRightCorner(NStates, NStates) = -A_.transpose();
1188 return hamilton_solver(H);
1189 }
1190
1191}
Eigen::Vector< T, Eigen::Dynamic > phases(const Bode< T > &bode)
Creates a vector of phases in rad.
Definition Bode.hpp:572
Definition Polynom.hpp:1012
The main namespace for the Control++ library.
Definition Bode.cpp:3
constexpr T product_over(T from, T to)
Calculates the product of all numbers in the closed open range [from, to)
Definition math.hpp:18
std::tuple< Eigen::Matrix< ValueType, NStates, NStates >, Eigen::Matrix< ValueType, NStates, NStates > > energy_scaling_matrix(const Eigen::Matrix< ValueType, NStates, NStates > A, const Eigen::Matrix< ValueType, NStates, NInputs > B, const Eigen::Matrix< ValueType, NOutputs, NStates > C)
Calculates the energy (Gramian) scaling via Lyapunov equations.
Definition math.hpp:507
Eigen::Matrix< T, NStates, NStates > dare_eigenvector_solver(const Eigen::Matrix< T, NStates, NStates, AOpt, AMaxR, AMaxC > &A, const Eigen::Matrix< T, NStates, NInputs, BOpt, BMaxR, BMaxC > &B, const Eigen::Matrix< T, NStates, NStates, QOpt, QMaxR, QMaxC > &Q, const Eigen::Matrix< T, NInputs, NInputs, ROpt, RMaxR, RMaxC > &R)
Solves the discrete time riccati equation (DARE)
Definition math.hpp:670
Eigen::Matrix< T, N/2, N/2 > hamilton_solver(const Eigen::Matrix< T, N, N > H)
Solves the hamilton matrix.
Definition math.hpp:343
constexpr T pade_den_param(unsigned long m, unsigned long n, unsigned long k)
Calculates the numerator parameters of the pade approximation.
Definition math.hpp:200
Eigen::Matrix< T, LSize+RSize, LSize+RSize > join_to_diagonal(const Eigen::Vector< T, LSize > &l, const Eigen::Vector< T, RSize > &r)
Definition math.hpp:216
constexpr T pade_param_reccursice(T P_k, std::int32_t m, std::int32_t n, std::int32_t k)
Calculates the next pade parameter recursively.
Definition math.hpp:115
Eigen::Matrix< T, 2 *NStates+NInputs, 2 *NStates+NInputs > build_dare_symplectic_pencil_H(const Eigen::Matrix< T, NStates, NStates, AOpt, AMaxR, AMaxC > &A, const Eigen::Matrix< T, NStates, NInputs, BOpt, BMaxR, BMaxC > &B, const Eigen::Matrix< T, NStates, NStates, QOpt, QMaxR, QMaxC > &Q, const Eigen::Matrix< T, NInputs, NInputs, ROpt, RMaxR, RMaxC > &R)
Builds the symplectic pencil matrix H for the discrete algebraic riccati equation (DARE)
Definition math.hpp:731
Eigen::Matrix< T, Rows, Cols > adj(const Eigen::Matrix< T, Rows, Cols, Options, MaxRows, MaxCols > &A)
Definition math.hpp:263
Eigen::Matrix< T, Rows, Cols, Options, MaxRows, MaxCols > identity_like(const Eigen::Matrix< T, Rows, Cols, Options, MaxRows, MaxCols > &unused)
Definition math.hpp:205
constexpr T pade_num_param(std::int32_t m, std::int32_t n, std::int32_t k)
Calculates the numerator parameters of the pade approximation.
Definition math.hpp:165
Eigen::Vector< T, N > unwrap(const Eigen::Vector< T, N > &y, const T &modulo)
Unwinds modulo jumps.
Definition math.hpp:55
Eigen::Matrix< T, N/2, N/2 > symplectic_solver(const Eigen::Matrix< T, N, N > S)
Solves the hamilton matrix.
Definition math.hpp:398
Eigen::Vector< T, N > unwrap_deg(const Eigen::Vector< T, N > &phases)
Unwinds phase jumps of in degrees.
Definition math.hpp:93
Eigen::Vector< T, LSize+RSize > join_to_vector(const Eigen::Vector< T, LSize > &l, const Eigen::Vector< T, RSize > &r)
Definition math.hpp:225
Eigen::Matrix< T, 2 *NStates+NInputs, 2 *NStates+NInputs > build_dare_symplectic_pencil_J(const Eigen::Matrix< T, NStates, NStates, AOpt, AMaxR, AMaxC > &A, const Eigen::Matrix< T, NStates, NInputs, BOpt, BMaxR, BMaxC > &B)
Builds the symplectic pencil matrix J for the discrete algebraic riccati equation (DARE)
Definition math.hpp:797
Eigen::Matrix< T, NStates, NStates > dare_solver(const Eigen::Matrix< T, NStates, NStates, AOpt, AMaxR, AMaxC > &A, const Eigen::Matrix< T, NStates, NInputs, BOpt, BMaxR, BMaxC > &B, const Eigen::Matrix< T, NStates, NStates, QOpt, QMaxR, QMaxC > &Q, const Eigen::Matrix< T, NInputs, NInputs, ROpt, RMaxR, RMaxC > &R)
Determines the block size of a quasi-upper-triangular matrix S at a given block start index.
Definition math.hpp:1114
constexpr T pow(const T &base, const int &exp)
Power function for integral exponents.
Definition math.hpp:30
constexpr void pade_params(T *params, std::size_t size, std::uint32_t m, std::uint32_t n)
Calculates all pade parameters up to the given size.
Definition math.hpp:131
Eigen::Matrix< T, Rows-1, Cols-1 > minor(const Eigen::Matrix< T, Rows, Cols, Options, MaxRows, MaxCols > &A, size_t ex_row, size_t ex_col)
returns the minor matrix excluding the provided column and row
Definition math.hpp:248
Eigen::Matrix< T, NStates, NStates > lyapunov_solver(const Eigen::Matrix< T, NStates, NStates, AOpt, AMaxR, AMaxC > &A, const Eigen::Matrix< T, NStates, NStates, QOpt, QMaxR, QMaxC > &Q)
Solves the continuous time Lyapunov equation.
Definition math.hpp:469
Eigen::Matrix< T, NStates, NStates > care_solver(const Eigen::Matrix< T, NStates, NStates, AOpt, AMaxR, AMaxC > &A, const Eigen::Matrix< T, NStates, NInputs, BOpt, BMaxR, BMaxC > &B, const Eigen::Matrix< T, NStates, NStates, QOpt, QMaxR, QMaxC > &Q, const Eigen::Matrix< T, NInputs, NInputs, ROpt, RMaxR, RMaxC > &R)
Solves the continuous time riccati equation (CARE)
Definition math.hpp:608
Eigen::Matrix< T, N-1, N-1 > companion(const Eigen::Vector< T, N > &v)
Creates a companion matrix from a vector.
Definition math.hpp:314
Eigen::Vector< T, N > unwrap_rad(const Eigen::Vector< T, N > &phases)
Unwinds phase jumps of in radiants.
Definition math.hpp:80