schur.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 
25 #include <corbo-numerics/schur.h>
26 
29 
30 #include <corbo-core/console.h>
31 #include <corbo-core/utilities.h>
32 #include <corbo-numerics/sylvester_continuous.h> // for swap_blocks_schur_form()
33 
34 #include <Eigen/Eigenvalues>
35 #include <Eigen/Householder>
36 #include <Eigen/QR>
37 
38 #include <algorithm>
39 #include <cmath>
40 #include <limits>
41 
42 namespace corbo {
43 
45 {
46  assert(U.rows() == 2 && U.cols() == 2);
47  assert(have_equal_size(T, U));
48 
49  double c = T(1, 0);
50 
51  if (approx_zero(c, 1e-30))
52  {
53  U.setIdentity();
54  return;
55  }
56 
57  double a = T(0, 0);
58  double b = T(0, 1);
59  double d = T(1, 1);
60 
61  if (approx_zero(b, 1e-30))
62  {
63  // swap rows and columns
64  T(0, 0) = d;
65  T(0, 1) = -c;
66  T(1, 0) = 0;
67  T(1, 1) = a;
68 
69  // 2d rotation matrix with cos=0 and sin=1
70  U(0, 0) = 0;
71  U(0, 1) = -1;
72  U(1, 0) = 1;
73  U(1, 1) = 0;
74  return;
75  }
76 
77  if (approx_zero(a - d, 1e-30) && util::sign(1, b) != util::sign(1, c))
78  {
79  U.setIdentity();
80  return;
81  }
82 
83  double a_d = a - d;
84  double p = 0.5 * a_d;
85  double bc_max = std::max(std::abs(b), std::abs(c));
86  double bc_mis = std::min(std::abs(b), std::abs(c)) * util::sign(1, b) * util::sign(1, c);
87  double scale = std::max(std::abs(p), bc_max);
88  double z = (p / scale) * p + (bc_max / scale) * bc_mis;
89 
91  {
92  // real eigenvalues, we need elements a and d
93  z = p + util::sign(std::sqrt(scale) * std::sqrt(z), p);
94  T(0, 0) = d + z;
95  T(1, 1) = d - (bc_max / z) * bc_mis;
96  // compute b and U
97  T(0, 1) = b - c;
98  T(1, 0) = 0;
99 
100  double tau = util::l2_norm_2d(c, z);
101  U(0, 0) = z / tau;
102  U(0, 1) = -c / tau;
103  U(1, 0) = c / tau;
104  U(1, 1) = z / tau;
105  }
106  else
107  {
108  // complex eigenvalues (or real (almost) equal)
109  // start with making diagonal elements equal
110  double sigma = b + c;
111  double tau = util::l2_norm_2d(sigma, a_d);
112  double u_cos = std::sqrt(0.5 * (1 + std::abs(sigma) / tau));
113  double u_sin = -(p / (tau * u_cos)) * util::sign(1, sigma);
114  U(0, 0) = u_cos;
115  U(0, 1) = -u_sin;
116  U(1, 0) = u_sin;
117  U(1, 1) = u_cos;
118 
119  T = U.transpose() * T * U;
120 
121  double temp = 0.5 * (T(0, 0) + T(1, 1)); // symmetry
122  T(0, 0) = temp;
123  T(1, 1) = temp;
124 
125  a = T(0, 0);
126  b = T(0, 1);
127  c = T(1, 0);
128  d = T(1, 1);
129 
130  if (!approx_zero(c, 1e-30)) // c!= 0
131  {
132  if (!approx_zero(b, 1e-30)) // b != 0
133  {
134  if (util::sign(1, b) == util::sign(1, c)) // sign(b) == sign(c)
135  {
136  // real eigenvalues -> just reduce to upper triangular form
137  double sab = std::sqrt(std::abs(b));
138  double sac = std::sqrt(std::abs(c));
139  p = util::sign(sab * sac, c);
140  tau = 1.0 / std::sqrt(std::abs(b + c));
141  T(0, 0) = temp + p;
142  T(1, 1) = temp - p;
143  T(0, 1) = b - c;
144  T(1, 0) = 0;
145 
146  double cos1 = sab * tau;
147  double sin1 = sac * tau;
148 
149  temp = U(0, 0) * cos1 - U(1, 0) * sin1;
150  U(1, 0) = U(0, 0) * sin1 + U(1, 0) * cos1;
151  U(0, 1) = -U(1, 0);
152  U(0, 0) = temp;
153  U(1, 1) = temp;
154  }
155  }
156  else
157  {
158  T(0, 1) = -b;
159  T(1, 0) = 0;
160  temp = U(0, 0);
161  U(0, 0) = U(0, 1);
162  U(1, 1) = U(0, 1);
163  U(0, 1) = -temp;
164  U(1, 0) = temp;
165  }
166  }
167  }
168 }
169 
170 bool swap_schur_blocks(Eigen::Ref<Eigen::MatrixXd> T, int ra11, int p, int q, Eigen::Ref<Eigen::MatrixXd> Q, bool standardize)
171 {
172  assert(is_square(T));
173  assert(have_equal_size(T, Q));
174  assert(p > 0 && p < 3);
175  assert(q > 0 && q < 3);
176  assert(ra11 + p + q <= T.rows());
177 
178  bool ret_val = true;
179 
180  Eigen::MatrixXd BACKUP = T;
181 
182  const int na = p + q;
183 
184  // make sure that both eigenvalues differ,
185  // otherwise the sylvester equation does not have a unique solution
186  if (na == 2)
187  {
188  if (essentially_equal(T(ra11, ra11), T(ra11 + 1, ra11 + 1), 1e-30)) return true; // same eigenvalues
189  }
190 
191  int ra22 = ra11 + p;
192 
193  if (na == 4)
194  {
195  // check if eigen values are equal
196  if (essentially_equal(T(ra11, ra11), T(ra22, ra22), 1e-30)) // real part
197  {
198  double imag1 = std::sqrt(std::abs(T(ra11, ra11 + 1) * T(ra11 + 1, ra11)));
199  double imag2 = std::sqrt(std::abs(T(ra22, ra22 + 1) * T(ra22 + 1, ra22)));
200  if (essentially_equal(imag1, imag2, 1e-30)) return true; // same eigenvalues
201  }
202  }
203 
204  // Solve sylvester equation A11 X - X A22 = \gamma A12
205 
206  // [1] suggests to use a small value close to machine precision times the norm of the matrix
207  // in cases if there is are small diagonal values while solving.
208  const double gamma = 1e3 * std::numeric_limits<double>::epsilon() * T.block(ra11, ra11, na, na).norm();
209 
210  Eigen::MatrixXd X;
211  if (!SylvesterContinuous::solve(T.block(ra11, ra11, p, p), -T.block(ra22, ra22, q, q), -gamma * T.block(ra11, ra22, p, q), X)) return false;
212 
213  Eigen::MatrixXd G(p + q, q);
214  G.block(0, 0, p, q) = -X;
215  G.block(p, 0, q, q).setIdentity();
216  G.block(p, 0, q, q) *= gamma;
217 
218  // store norm for later stability check
219  double A_infnorm = T.block(ra11, ra11, na, na).lpNorm<Eigen::Infinity>();
220 
221  Eigen::HouseholderQR<Eigen::MatrixXd> qr_dec(G); // TODO(roesmann) FullPivHouseholder?
222 
223  // perform swap on complete A
224  // this code might be used with a different temporary matrix in order to avoid destruction in T
225  // if the stability criterion fails:
226  // T.block(ra11, ra11, p + q, T.rows() - ra11).applyOnTheLeft(qr_dec.householderQ().transpose());
227  // T.block(0, ra11, na, na).applyOnTheRight(qr_dec.householderQ());
228  // Q.block(ra11, ra11, na, na) = qr_dec.householderQ();
229 
230  // perform swap on complete T
231  T.block(ra11, ra11, na, T.cols() - ra11).applyOnTheLeft(qr_dec.householderQ().transpose());
232  T.block(0, ra11, ra22 + q, na).applyOnTheRight(qr_dec.householderQ());
233  Q.block(0, ra11, Q.rows(), na).applyOnTheRight(qr_dec.householderQ());
234 
235  // a11 and a22 are now swapped
236  ra22 = ra11 + q;
237 
238  constexpr const double threshold = std::numeric_limits<double>::epsilon() * 2;
239  const double stability_crit = 10.0 * threshold * A_infnorm; // see [1]
240  double A21_norm = T.block(ra22, ra11, p, q).lpNorm<Eigen::Infinity>();
241  if (A21_norm < stability_crit)
242  {
243  // set lower left part to zero
244  T.block(ra22, ra11, p, q).setZero(); // this is now p x q due to swap
245  }
246  else
247  {
248  ret_val = false;
249  PRINT_WARNING("swap_blocks_schur_form(): numerical instability detected: " << A21_norm << "/" << stability_crit << ",\nA21:\n"
250  << T.block(ra22, ra11, q, p));
251  }
252 
253  // standardize complex blocks if desired
254  if (standardize)
255  {
256  if (q == 2)
257  { // this block is now in the top left corner
258  Eigen::Matrix2d U;
259  schur_decomposition_2d(T.block(ra11, ra11, q, q), U);
260 
261  // also update T from left to right starting right of the current block
262  T.block(ra11, ra22, q, T.cols() - ra22).applyOnTheLeft(U.transpose());
263 
264  // also update T from up to down ending above the current block
265  for (int i = 0; i < ra11; ++i)
266  {
267  // We use a map in following to treat 2d row vector as 2d column vector without transposing etc.
268  // With the current formulation, we can only handle a single row at once (hence the for-loop).
270  map.applyOnTheLeft(U.transpose());
271  }
272 
273  // update unitary matrix Q
274  for (int i = 0; i < Q.rows(); ++i)
275  {
277  map.applyOnTheLeft(U.transpose());
278  }
279  }
280 
281  // transform new 2 x 2 block to standard-form
282  if (p == 2)
283  { // this block is now in the bottom right corner
284  Eigen::Matrix2d U;
285  schur_decomposition_2d(T.block(ra22, ra22, p, p), U);
286 
287  // also update T from left to right starting right of the current block
288  if (T.cols() > ra22 + p) T.block(ra22, ra22 + p, p, T.cols() - ra22 - p).applyOnTheLeft(U.transpose());
289 
290  // also update T from up to down ending above the current block
291  for (int i = 0; i < ra22; ++i)
292  {
293  // We use a map in following to treat 2d row vector as 2d column vector without transposing etc.
294  // With the current formulation, we can only handle a single row at once (hence the for-loop).
296  map.applyOnTheLeft(U.transpose());
297  }
298 
299  // update unitary matrix Q
300  for (int i = 0; i < Q.rows(); ++i)
301  {
303  map.applyOnTheLeft(U.transpose());
304  }
305  }
306  }
307  return ret_val;
308 }
309 
310 } // namespace corbo
#define max(a, b)
Definition: datatypes.h:20
EIGEN_DEVICE_FUNC Index outerStride() const
Definition: Ref.h:74
bool have_equal_size(const Eigen::MatrixBase< DerivedA > &matrix1, const Eigen::MatrixBase< DerivedB > &matrix2)
Determine if two matrices exhibit equal sizes/dimensions.
#define PRINT_WARNING(msg)
Print msg-stream.
Definition: console.h:145
#define min(a, b)
Definition: datatypes.h:19
A matrix or vector expression mapping an existing array of data.
Definition: Map.h:94
EIGEN_DEVICE_FUNC const SqrtReturnType sqrt() const
RealScalar c
bool swap_schur_blocks(Eigen::Ref< Eigen::MatrixXd > T, int ra11, int p, int q, Eigen::Ref< Eigen::MatrixXd > Q, bool standardize)
Definition: schur.cpp:170
bool is_square(const Eigen::MatrixBase< Derived > &matrix)
Determine if a given matrix is square.
static bool solve(const Eigen::Ref< const Eigen::MatrixXd > &A, const Eigen::Ref< const Eigen::MatrixXd > &B, const Eigen::Ref< const Eigen::MatrixXd > &C, Eigen::MatrixXd &X)
Solve continuous-time Sylvester equation.
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const AbsReturnType abs() const
void schur_decomposition_2d(Eigen::Ref< Eigen::Matrix2d > T, Eigen::Ref< Eigen::Matrix2d > U)
Perform the 2D Real Schur decompositionIn contrast to Eigen::RealSchur this function enforces diagona...
Definition: schur.cpp:44
double l2_norm_2d(double a, double b)
Compute the l2 norm of a 2d vector [a,b] as sqrt(a*a+b*b)
Convenience specialization of Stride to specify only an inner stride See class Map for some examples...
Definition: Stride.h:90
EIGEN_DEVICE_FUNC const Scalar & q
Scalar * b
Definition: cholesky.cpp:56
A matrix or vector expression mapping an existing expression.
Definition: Ref.h:192
Scalar * a
Definition: cholesky.cpp:26
Householder QR decomposition of a matrix.
bool approx_zero(double val, double epsilon=1e-10)
Check if a double is appoximately zero.
bool essentially_equal(double a, double b, double epsilon=1e-6)
Check if two doubles are essentially equal.
#define X
Definition: icosphere.cpp:20
int sign(T val)
Signum function.
const int Infinity
Definition: Constants.h:31


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Mon Feb 28 2022 22:07:16