WPILibC++ 2027.0.0-alpha-3
Loading...
Searching...
No Matches
DARE.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <string_view>
8
9#include <Eigen/Cholesky>
10#include <Eigen/Core>
11#include <Eigen/LU>
12#include <wpi/expected>
13
14#include "frc/StateSpaceUtil.h"
15
16namespace frc {
17
18/**
19 * Errors the DARE solver can encounter.
20 */
21enum class DAREError {
22 /// Q was not symmetric.
24 /// Q was not positive semidefinite.
26 /// R was not symmetric.
28 /// R was not positive definite.
30 /// (A, B) pair was not stabilizable.
32 /// (A, C) pair where Q = CᵀC was not detectable.
34};
35
36/**
37 * Converts the given DAREError enum to a string.
38 */
39constexpr std::string_view to_string(const DAREError& error) {
40 switch (error) {
42 return "Q was not symmetric.";
44 return "Q was not positive semidefinite.";
46 return "R was not symmetric.";
48 return "R was not positive definite.";
50 return "(A, B) pair was not stabilizable.";
52 return "(A, C) pair where Q = CᵀC was not detectable.";
53 }
54
55 return "";
56}
57
58namespace detail {
59
60/**
61 * Computes the unique stabilizing solution X to the discrete-time algebraic
62 * Riccati equation:
63 *
64 * AᵀXA − X − AᵀXB(BᵀXB + R)⁻¹BᵀXA + Q = 0
65 *
66 * This internal function skips expensive precondition checks for increased
67 * performance. The solver may hang if any of the following occur:
68 * <ul>
69 * <li>Q isn't symmetric positive semidefinite</li>
70 * <li>R isn't symmetric positive definite</li>
71 * <li>The (A, B) pair isn't stabilizable</li>
72 * <li>The (A, C) pair where Q = CᵀC isn't detectable</li>
73 * </ul>
74 * Only use this function if you're sure the preconditions are met.
75 *
76 * @tparam States Number of states.
77 * @tparam Inputs Number of inputs.
78 * @param A The system matrix.
79 * @param B The input matrix.
80 * @param Q The state cost matrix.
81 * @param R_llt The LLT decomposition of the input cost matrix.
82 * @return Solution to the DARE.
83 */
84template <int States, int Inputs>
85Eigen::Matrix<double, States, States> DARE(
86 const Eigen::Matrix<double, States, States>& A,
87 const Eigen::Matrix<double, States, Inputs>& B,
88 const Eigen::Matrix<double, States, States>& Q,
89 const Eigen::LLT<Eigen::Matrix<double, Inputs, Inputs>>& R_llt) {
90 using StateMatrix = Eigen::Matrix<double, States, States>;
91
92 // Implements SDA algorithm on p. 5 of [1] (initial A, G, H are from (4)).
93 //
94 // [1] E. K.-W. Chu, H.-Y. Fan, W.-W. Lin & C.-S. Wang "Structure-Preserving
95 // Algorithms for Periodic Discrete-Time Algebraic Riccati Equations",
96 // International Journal of Control, 77:8, 767-788, 2004.
97 // DOI: 10.1080/00207170410001714988
98
99 // A₀ = A
100 // G₀ = BR⁻¹Bᵀ
101 // H₀ = Q
102 StateMatrix A_k = A;
103 StateMatrix G_k = B * R_llt.solve(B.transpose());
104 StateMatrix H_k;
105 StateMatrix H_k1 = Q;
106
107 do {
108 H_k = H_k1;
109
110 // W = I + GₖHₖ
111 StateMatrix W = StateMatrix::Identity(H_k.rows(), H_k.cols()) + G_k * H_k;
112
113 auto W_solver = W.lu();
114
115 // Solve WV₁ = Aₖ for V₁
116 StateMatrix V_1 = W_solver.solve(A_k);
117
118 // Solve V₂Wᵀ = Gₖ for V₂
119 //
120 // We want to put V₂Wᵀ = Gₖ into Ax = b form so we can solve it more
121 // efficiently.
122 //
123 // V₂Wᵀ = Gₖ
124 // (V₂Wᵀ)ᵀ = Gₖᵀ
125 // WV₂ᵀ = Gₖᵀ
126 //
127 // The solution of Ax = b can be found via x = A.solve(b).
128 //
129 // V₂ᵀ = W.solve(Gₖᵀ)
130 // V₂ = W.solve(Gₖᵀ)ᵀ
131 //
132 // Since W, Gₖ, and Hₖ are symmetric, drop the transposes on Gₖ and V₂.
133 //
134 // V₂ = W.solve(Gₖ)
135 StateMatrix V_2 = W_solver.solve(G_k);
136
137 // Gₖ₊₁ = Gₖ + AₖV₂Aₖᵀ
138 // Hₖ₊₁ = Hₖ + V₁ᵀHₖAₖ
139 // Aₖ₊₁ = AₖV₁
140 G_k += A_k * V_2 * A_k.transpose();
141 H_k1 = H_k + V_1.transpose() * H_k * A_k;
142 A_k *= V_1;
143
144 // while |Hₖ₊₁ − Hₖ| > ε |Hₖ₊₁|
145 } while ((H_k1 - H_k).norm() > 1e-10 * H_k1.norm());
146
147 return H_k1;
148}
149
150} // namespace detail
151
152/**
153 * Computes the unique stabilizing solution X to the discrete-time algebraic
154 * Riccati equation:
155 *
156 * AᵀXA − X − AᵀXB(BᵀXB + R)⁻¹BᵀXA + Q = 0
157 *
158 * @tparam States Number of states.
159 * @tparam Inputs Number of inputs.
160 * @param A The system matrix.
161 * @param B The input matrix.
162 * @param Q The state cost matrix.
163 * @param R The input cost matrix.
164 * @param checkPreconditions Whether to check preconditions (30% less time if
165 * user is sure precondtions are already met).
166 * @return Solution to the DARE on success, or DAREError on failure.
167 */
168template <int States, int Inputs>
170 const Eigen::Matrix<double, States, States>& A,
171 const Eigen::Matrix<double, States, Inputs>& B,
172 const Eigen::Matrix<double, States, States>& Q,
173 const Eigen::Matrix<double, Inputs, Inputs>& R,
174 bool checkPreconditions = true) {
175 if (checkPreconditions) {
176 // Require R be symmetric
177 if ((R - R.transpose()).norm() > 1e-10) {
179 }
180 }
181
182 // Require R be positive definite
183 auto R_llt = R.llt();
184 if (R_llt.info() != Eigen::Success) {
186 }
187
188 if (checkPreconditions) {
189 // Require Q be symmetric
190 if ((Q - Q.transpose()).norm() > 1e-10) {
192 }
193
194 // Require Q be positive semidefinite
195 //
196 // If Q is a symmetric matrix with a decomposition LDLᵀ, the number of
197 // positive, negative, and zero diagonal entries in D equals the number of
198 // positive, negative, and zero eigenvalues respectively in Q (see
199 // https://en.wikipedia.org/wiki/Sylvester's_law_of_inertia).
200 //
201 // Therefore, D having no negative diagonal entries is sufficient to prove Q
202 // is positive semidefinite.
203 auto Q_ldlt = Q.ldlt();
204 if (Q_ldlt.info() != Eigen::Success ||
205 (Q_ldlt.vectorD().array() < 0.0).any()) {
207 }
208
209 // Require (A, B) pair be stabilizable
212 }
213
214 // Require (A, C) pair be detectable where Q = CᵀC
215 //
216 // Q = CᵀC = PᵀLDLᵀP
217 // C = √(D)LᵀP
218 Eigen::Matrix<double, States, States> C =
219 Q_ldlt.vectorD().cwiseSqrt().asDiagonal() *
220 Eigen::Matrix<double, States, States>{Q_ldlt.matrixL().transpose()} *
221 Q_ldlt.transpositionsP();
222
223 if (!IsDetectable<States, States>(A, C)) {
225 }
226 }
227
228 return detail::DARE<States, Inputs>(A, B, Q, R_llt);
229}
230
231/**
232Computes the unique stabilizing solution X to the discrete-time algebraic
233Riccati equation:
234
235 AᵀXA − X − (AᵀXB + N)(BᵀXB + R)⁻¹(BᵀXA + Nᵀ) + Q = 0
236
237This is equivalent to solving the original DARE:
238
239 A₂ᵀXA₂ − X − A₂ᵀXB(BᵀXB + R)⁻¹BᵀXA₂ + Q₂ = 0
240
241where A₂ and Q₂ are a change of variables:
242
243 A₂ = A − BR⁻¹Nᵀ and Q₂ = Q − NR⁻¹Nᵀ
244
245This overload of the DARE is useful for finding the control law uₖ that
246minimizes the following cost function subject to xₖ₊₁ = Axₖ + Buₖ.
247
248@verbatim
249 ∞ [xₖ]ᵀ[Q N][xₖ]
250J = Σ [uₖ] [Nᵀ R][uₖ] ΔT
251 k=0
252@endverbatim
253
254This is a more general form of the following. The linear-quadratic regulator
255is the feedback control law uₖ that minimizes the following cost function
256subject to xₖ₊₁ = Axₖ + Buₖ:
257
258@verbatim
259
260J = Σ (xₖᵀQxₖ + uₖᵀRuₖ) ΔT
261 k=0
262@endverbatim
263
264This can be refactored as:
265
266@verbatim
267 ∞ [xₖ]ᵀ[Q 0][xₖ]
268J = Σ [uₖ] [0 R][uₖ] ΔT
269 k=0
270@endverbatim
271
272@tparam States Number of states.
273@tparam Inputs Number of inputs.
274@param A The system matrix.
275@param B The input matrix.
276@param Q The state cost matrix.
277@param R The input cost matrix.
278@param N The state-input cross cost matrix.
279@param checkPreconditions Whether to check preconditions (30% less time if user
280 is sure precondtions are already met).
281@return Solution to the DARE on success, or DAREError on failure.
282*/
283template <int States, int Inputs>
285 const Eigen::Matrix<double, States, States>& A,
286 const Eigen::Matrix<double, States, Inputs>& B,
287 const Eigen::Matrix<double, States, States>& Q,
288 const Eigen::Matrix<double, Inputs, Inputs>& R,
289 const Eigen::Matrix<double, States, Inputs>& N,
290 bool checkPreconditions = true) {
291 if (checkPreconditions) {
292 // Require R be symmetric
293 if ((R - R.transpose()).norm() > 1e-10) {
295 }
296 }
297
298 // Require R be positive definite
299 auto R_llt = R.llt();
300 if (R_llt.info() != Eigen::Success) {
302 }
303
304 // This is a change of variables to make the DARE that includes Q, R, and N
305 // cost matrices fit the form of the DARE that includes only Q and R cost
306 // matrices.
307 //
308 // This is equivalent to solving the original DARE:
309 //
310 // A₂ᵀXA₂ − X − A₂ᵀXB(BᵀXB + R)⁻¹BᵀXA₂ + Q₂ = 0
311 //
312 // where A₂ and Q₂ are a change of variables:
313 //
314 // A₂ = A − BR⁻¹Nᵀ and Q₂ = Q − NR⁻¹Nᵀ
315 Eigen::Matrix<double, States, States> A_2 =
316 A - B * R_llt.solve(N.transpose());
317 Eigen::Matrix<double, States, States> Q_2 =
318 Q - N * R_llt.solve(N.transpose());
319
320 if (checkPreconditions) {
321 // Require Q be symmetric
322 if ((Q_2 - Q_2.transpose()).norm() > 1e-10) {
324 }
325
326 // Require Q be positive semidefinite
327 //
328 // If Q is a symmetric matrix with a decomposition LDLᵀ, the number of
329 // positive, negative, and zero diagonal entries in D equals the number of
330 // positive, negative, and zero eigenvalues respectively in Q (see
331 // https://en.wikipedia.org/wiki/Sylvester's_law_of_inertia).
332 //
333 // Therefore, D having no negative diagonal entries is sufficient to prove Q
334 // is positive semidefinite.
335 auto Q_ldlt = Q_2.ldlt();
336 if (Q_ldlt.info() != Eigen::Success ||
337 (Q_ldlt.vectorD().array() < 0.0).any()) {
339 }
340
341 // Require (A, B) pair be stabilizable
342 if (!IsStabilizable<States, Inputs>(A_2, B)) {
344 }
345
346 // Require (A, C) pair be detectable where Q = CᵀC
347 //
348 // Q = CᵀC = PᵀLDLᵀP
349 // C = √(D)LᵀP
350 Eigen::Matrix<double, States, States> C =
351 Q_ldlt.vectorD().cwiseSqrt().asDiagonal() *
352 Eigen::Matrix<double, States, States>{Q_ldlt.matrixL().transpose()} *
353 Q_ldlt.transpositionsP();
354
355 if (!IsDetectable<States, States>(A_2, C)) {
357 }
358 }
359
360 return detail::DARE<States, Inputs>(A_2, B, Q_2, R_llt);
361}
362
363} // namespace frc
An expected<T, E> object is an object that contains the storage for another object and manages the li...
Definition expected:1250
Definition expected:142
Converts a string literal into a format string that will be parsed at compile time and converted into...
Definition printf.h:50
Eigen::Matrix< double, States, States > DARE(const Eigen::Matrix< double, States, States > &A, const Eigen::Matrix< double, States, Inputs > &B, const Eigen::Matrix< double, States, States > &Q, const Eigen::LLT< Eigen::Matrix< double, Inputs, Inputs > > &R_llt)
Computes the unique stabilizing solution X to the discrete-time algebraic Riccati equation:
Definition DARE.h:85
Definition SystemServer.h:9
bool IsStabilizable(const Matrixd< States, States > &A, const Matrixd< States, Inputs > &B)
Returns true if (A, B) is a stabilizable pair.
Definition StateSpaceUtil.h:292
wpi::expected< Eigen::Matrix< double, States, States >, DAREError > DARE(const Eigen::Matrix< double, States, States > &A, const Eigen::Matrix< double, States, Inputs > &B, const Eigen::Matrix< double, States, States > &Q, const Eigen::Matrix< double, Inputs, Inputs > &R, bool checkPreconditions=true)
Computes the unique stabilizing solution X to the discrete-time algebraic Riccati equation:
Definition DARE.h:169
constexpr std::string_view to_string(const DAREError &error)
Converts the given DAREError enum to a string.
Definition DARE.h:39
DAREError
Errors the DARE solver can encounter.
Definition DARE.h:21
@ QNotPositiveSemidefinite
Q was not positive semidefinite.
@ RNotSymmetric
R was not symmetric.
@ QNotSymmetric
Q was not symmetric.
@ ACNotDetectable
(A, C) pair where Q = CᵀC was not detectable.
@ RNotPositiveDefinite
R was not positive definite.
@ ABNotStabilizable
(A, B) pair was not stabilizable.
bool IsDetectable(const Matrixd< States, States > &A, const Matrixd< Outputs, States > &C)
Returns true if (A, C) is a detectable pair.
Definition StateSpaceUtil.h:351