algebraic_riccati_continuous.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement
4  *
5  * Copyright (c) 2020,
6  * TU Dortmund - Institute of Control Theory and Systems Engineering.
7  * All rights reserved.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  *
22  * Authors: Christoph Rösmann
23  *********************************************************************/
24 
26 
29 #include <corbo-numerics/schur.h>
30 
31 #include <corbo-core/console.h>
32 
33 #include <Eigen/Cholesky>
34 #include <Eigen/Core>
35 #include <Eigen/Dense>
36 #include <algorithm>
37 
38 namespace corbo {
39 
41  const Eigen::Ref<const Eigen::MatrixXd>& Q, const Eigen::Ref<const Eigen::MatrixXd>& R, Eigen::MatrixXd& X,
42  Eigen::MatrixXd* G)
43 {
44  assert(is_square(A));
45  assert(have_equal_size(A, Q));
46  assert(A.rows() == B.rows());
47  assert(R.rows() == B.cols());
48  assert(is_square(R));
49 
50  const int p = A.rows(); // dim_states
51 
52  Eigen::LDLT<Eigen::MatrixXd> R_cholesky(R);
53  Eigen::MatrixXd R_inv_B_t = R_cholesky.solve(B.transpose());
54 
55  // Construct Hamiltonian matrix
56  // H = [ A -B R^-1 B^T ]
57  // [-Q -A^T ]
58  Eigen::MatrixXd H(2 * p, 2 * p);
59  H << A, -B * R_inv_B_t, -Q, -A.transpose();
60 
61 #ifndef NDEBUG
63  "Eigenvalues of the Hamiltonian are close to the imaginary axis. Numerically unstable results are expected.");
64 #endif
65 
66  bool success = solveRiccatiHamiltonianSchur(H, X);
67 
68  if (G && success) *G = R_inv_B_t * X;
69  return success;
70 }
71 
72 bool AlgebraicRiccatiContinuous::solveRiccatiHamiltonianSchur(const Eigen::MatrixXd& H, Eigen::MatrixXd& X)
73 {
74  assert(is_square(H));
75  assert(H.rows() % 2 == 0);
76 
77  // perform schur to find an orthogonal transformation that reduces H to real Schur form
79 
80  Eigen::MatrixXd T = schur.matrixT();
81  Eigen::MatrixXd U = schur.matrixU();
82 
83  // reorder Schur form such that all negative eigenvalues are located on the upper left blocks.
84  int subspace_dim;
85  bool success = reorder_schur_blocks(T, U, AlgebraicRiccatiContinuous::hasNegativeRealPart, &subspace_dim, false);
86 
87  const int p = H.rows() / 2;
88 
89  if (!success || p != subspace_dim) return false;
90 
91  // U = [U1; U2
92  // U2; U3]
93 
94  // solution X can be obtained by X = U2 * U1^-1
95  // since U1.inverse() is not recomended due to numerical effects
96  // we must rewrite the equation in order to facilitate solving with Eigen:
97  // X^T = (U2 * U1^-1)^T = (U1^T)^(-1) U2^T
98  Eigen::JacobiSVD<Eigen::MatrixXd> U1_svd(U.block(0, 0, p, p).transpose(), Eigen::ComputeThinU | Eigen::ComputeThinV);
99  X = U1_svd.solve(U.block(p, 0, p, p).transpose()); // X is symmetric, hence no final transpose
100  return true;
101 }
102 
104 {
105  Eigen::VectorXcd eig = matrix.eigenvalues();
106  for (int i = 0; i < eig.size(); ++i)
107  {
108  if (std::abs(eig[i].real()) <= 1e-12)
109  {
110  return true;
111  }
112  }
113  return false;
114 }
115 
116 /*
117 void AlgebraicRiccatiContinuous::sovleRiccatiHamiltonianMatrixSign(Eigen::MatrixXd& Z, Eigen::MatrixXd& X)
118 {
119  // TODO(roesmann): WE HAVE CHANGED THE ORDER OF ELEMENTS IN THE HAMILTONIAN, but we haven't
120  // applied it in this method.
121 
122  assert(is_square(Z));
123  assert(Z.rows() % 2 == 0);
124 
125  const int p = Z.rows() / 2;
126 
127  Eigen::MatrixXd Z_old;
128 
129  const double tolerance = 1e-9;
130  const double max_iterations = 100;
131 
132  double relative_norm = CORBO_INF_DBL;
133  std::size_t iteration = 0;
134 
135  double dp = double(2 * p);
136 
137  do
138  {
139  Z_old = Z;
140  // R. Byers. Solving the algebraic Riccati equation with the matrix sign function. Linear Algebra Appl., 85:267–279, 1987
141  // Added determinant scaling to improve convergence (converges in rough half the iterations with this)
142  double ck = std::pow(std::abs(Z.determinant()), -1.0 / dp); // reciprocal of the geometric mean of the eigenvalues
143  // TODO(roesmann): here is place for improving efficiency: compute Z.determinant() as a by-product of Z^-1
144  Z *= ck;
145  Z = Z - 0.5 * (Z - Z.inverse());
146  relative_norm = (Z - Z_old).norm();
147  ++iteration;
148  } while (iteration < max_iterations && relative_norm > tolerance);
149 
150  // Eigen::MatrixXd W11 = Z.block(0, 0, p, p); // -> A
151  // Eigen::MatrixXd W12 = Z.block(0, p, p, p); // -> Q
152  // Eigen::MatrixXd W21 = Z.block(p, 0, p, p); // -> B R^-1 B^T
153  // Eigen::MatrixXd W22 = Z.block(p, p, p, p); // -> -A^T
154 
155  Eigen::MatrixXd lhs(2 * p, p);
156  Eigen::MatrixXd rhs(2 * p, p);
157  // Eigen::MatrixXd eye = Eigen::MatrixXd::Identity(p, p);
158  // lhs << W12, W22 + eye;
159  // rhs << W11 + eye, W21;
160  // lhs << Z.block(0, p, p, p), Z.block(p, p, p, p) + Eigen::MatrixXd::Identity(p, p);
161  // rhs << Z.block(0, 0, p, p) + Eigen::MatrixXd::Identity(p, p), Z.block(p, 0, p, p);
162 
163  // lhs << W11 - eye, -W12;
164  // rhs << W21, eye - W22;
165  lhs << Z.block(0, 0, p, p) - Eigen::MatrixXd::Identity(p, p), -Z.block(0, p, p, p);
166  rhs << Z.block(p, 0, p, p), Eigen::MatrixXd::Identity(p, p) - Z.block(p, p, p, p);
167 
168  // TODO(roesmann) the identity matrix seems to be redundant but we need it to get suitable results (maybe for numerical stability in
169  // case A==0?)
170 
171  Eigen::JacobiSVD<Eigen::MatrixXd> svd(lhs, Eigen::ComputeThinU | Eigen::ComputeThinV);
172 
173  X = svd.solve(rhs);
174 
175  // exploit symmetry to reduce numerical error
176  X = (X + X.transpose()) * 0.5;
177 }
178 */
179 
182 {
183  assert(is_square(A));
184  assert(A.rows() == B.rows());
185  assert(A.rows() == G.cols());
186  assert(G.rows() == B.cols());
187 
188  Eigen::VectorXcd eig = (A - B * G).eigenvalues();
189  for (int i = 0; i < eig.size(); ++i)
190  {
191  if (eig[i].real() >= 0) return false;
192  }
193  return true;
194 }
195 
198 {
199  assert(is_square(A));
200  assert(have_equal_size(A, Q));
201  assert(A.rows() == B.rows());
202  assert(R.rows() == B.cols());
203  assert(is_square(R));
204 
205  const int p = A.rows(); // dim_states
206 
207  Eigen::LDLT<Eigen::MatrixXd> R_cholesky(R);
208  Eigen::MatrixXd R_inv_B_t = R_cholesky.solve(B.transpose());
209 
210  Eigen::MatrixXd H(2 * p, 2 * p);
211  H << A, -B * R_inv_B_t, -Q, -A.transpose();
212  return !hasRealPartsCloseToZero(H);
213 }
214 
215 } // namespace corbo
PRINT_WARNING_COND_NAMED
#define PRINT_WARNING_COND_NAMED(cond, msg)
Definition: console.h:257
corbo::have_equal_size
bool have_equal_size(const Eigen::MatrixBase< DerivedA > &matrix1, const Eigen::MatrixBase< DerivedB > &matrix2)
Determine if two matrices exhibit equal sizes/dimensions.
Definition: matrix_utilities.h:93
corbo
Definition: communication/include/corbo-communication/utilities.h:37
corbo::AlgebraicRiccatiContinuous::hasRealPartsCloseToZero
static bool hasRealPartsCloseToZero(const Eigen::Ref< const Eigen::MatrixXd > &matrix)
Check if the real parts of the provided matrix are close to zero.
Definition: algebraic_riccati_continuous.cpp:125
corbo::AlgebraicRiccatiContinuous::isNumericallyStable
static bool isNumericallyStable(const Eigen::Ref< const Eigen::MatrixXd > &A, const Eigen::Ref< const Eigen::MatrixXd > &B, const Eigen::Ref< const Eigen::MatrixXd > &Q, const Eigen::Ref< const Eigen::MatrixXd > &R)
Determine if solving the riccati equation seems to be numerically stable.
Definition: algebraic_riccati_continuous.cpp:218
real
float real
Definition: datatypes.h:10
X
#define X
Definition: icosphere.cpp:20
corbo::AlgebraicRiccatiContinuous::isClosedLoopStable
static bool isClosedLoopStable(const Eigen::Ref< const Eigen::MatrixXd > &A, const Eigen::Ref< const Eigen::MatrixXd > &B, const Eigen::Ref< const Eigen::MatrixXd > &G)
Determine if the closed-loop system is stable.
Definition: algebraic_riccati_continuous.cpp:202
console.h
matrix
Map< Matrix< T, Dynamic, Dynamic, ColMajor >, 0, OuterStride<> > matrix(T *data, int rows, int cols, int stride)
Definition: common.h:102
value_comparison.h
corbo::AlgebraicRiccatiContinuous::hasNegativeRealPart
static bool hasNegativeRealPart(const Eigen::Ref< const Eigen::MatrixXd > &block)
Predicate for Schur block ordering (see solveRiccatiHamiltonianSchur())
Definition: algebraic_riccati_continuous.h:197
abs
EIGEN_DEVICE_FUNC const EIGEN_STRONG_INLINE AbsReturnType abs() const
Definition: ArrayCwiseUnaryOps.h:43
Eigen::RealSchur
Performs a real Schur decomposition of a square matrix.
Definition: RealSchur.h:54
corbo::is_square
bool is_square(const Eigen::MatrixBase< Derived > &matrix)
Determine if a given matrix is square.
Definition: matrix_utilities.h:65
B
MatrixType B(b, *n, *nrhs, *ldb)
Eigen::ComputeThinU
@ ComputeThinU
Definition: Constants.h:385
matrix_utilities.h
Eigen::LDLT
Robust Cholesky decomposition of a matrix with pivoting.
Definition: LDLT.h:50
eig
SelfAdjointEigenSolver< PlainMatrixType > eig(mat, computeVectors?ComputeEigenvectors:EigenvaluesOnly)
algebraic_riccati_continuous.h
Eigen::JacobiSVD
Two-sided Jacobi SVD decomposition of a rectangular matrix.
Definition: ForwardDeclarations.h:258
Eigen::ComputeThinV
@ ComputeThinV
Definition: Constants.h:389
Eigen::Ref
A matrix or vector expression mapping an existing expression.
Definition: Ref.h:192
schur.h
corbo::AlgebraicRiccatiContinuous::solveRiccatiHamiltonianSchur
static bool solveRiccatiHamiltonianSchur(const Eigen::MatrixXd &H, Eigen::MatrixXd &X)
Solve Hamiltonian via Schur method.
Definition: algebraic_riccati_continuous.cpp:94
corbo::AlgebraicRiccatiContinuous::solve
static bool solve(const Eigen::Ref< const Eigen::MatrixXd > &A, const Eigen::Ref< const Eigen::MatrixXd > &B, const Eigen::Ref< const Eigen::MatrixXd > &Q, const Eigen::Ref< const Eigen::MatrixXd > &R, Eigen::MatrixXd &X, Eigen::MatrixXd *G=nullptr)
Solve continuous-time algebraic Riccati equation.
Definition: algebraic_riccati_continuous.cpp:62
A
MatrixType A(a, *n, *n, *lda)
corbo::reorder_schur_blocks
bool reorder_schur_blocks(Eigen::Ref< Eigen::MatrixXd > T, Eigen::Ref< Eigen::MatrixXd > Q, Predicate predicate, int *subspace_dim, bool standardize)
Definition: schur.hpp:57


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Wed Mar 2 2022 00:05:36