blob: 7c1bf813766c8a092827f8243887383fdf313eed [file] [log] [blame]
Austin Schuh9b881ae2019-01-04 07:29:20 +11001#ifndef FRC971_CONTROL_LOOPS_C2D_H_
2#define FRC971_CONTROL_LOOPS_C2D_H_
3
4#include <chrono>
5
6#include <Eigen/Dense>
Philipp Schrader790cb542023-07-05 21:06:52 -07007
James Kuszmaul651fc3f2019-05-15 21:14:25 -07008#include "aos/time/time.h"
James Kuszmaul59a5c612019-01-22 07:56:08 -08009// We need to include MatrixFunctions for the matrix exponential.
10#include "unsupported/Eigen/MatrixFunctions"
Austin Schuh9b881ae2019-01-04 07:29:20 +110011
12namespace frc971 {
13namespace controls {
14
15template <typename Scalar, int num_states, int num_inputs>
16void C2D(const ::Eigen::Matrix<Scalar, num_states, num_states> &A_continuous,
17 const ::Eigen::Matrix<Scalar, num_states, num_inputs> &B_continuous,
18 ::std::chrono::nanoseconds dt,
19 ::Eigen::Matrix<Scalar, num_states, num_states> *A,
20 ::Eigen::Matrix<Scalar, num_states, num_inputs> *B) {
21 // Trick from
22 // https://en.wikipedia.org/wiki/Discretization#Discretization_of_linear_state_space_models
23 // to solve for A and B more efficiently.
24 Eigen::Matrix<Scalar, num_states + num_inputs, num_states + num_inputs>
25 M_state_continuous;
26 M_state_continuous.setZero();
27 M_state_continuous.template block<num_states, num_states>(0, 0) =
James Kuszmaul651fc3f2019-05-15 21:14:25 -070028 A_continuous * ::aos::time::TypedDurationInSeconds<Scalar>(dt);
Austin Schuh9b881ae2019-01-04 07:29:20 +110029 M_state_continuous.template block<num_states, num_inputs>(0, num_states) =
James Kuszmaul651fc3f2019-05-15 21:14:25 -070030 B_continuous * ::aos::time::TypedDurationInSeconds<Scalar>(dt);
Austin Schuh9b881ae2019-01-04 07:29:20 +110031
32 Eigen::Matrix<Scalar, num_states + num_inputs, num_states + num_inputs>
33 M_state = M_state_continuous.exp();
34 *A = M_state.template block<num_states, num_states>(0, 0);
35 *B = M_state.template block<num_states, num_inputs>(0, num_states);
36}
37
James Kuszmaul59a5c612019-01-22 07:56:08 -080038template <typename Scalar, int num_states>
39void DiscretizeQ(
40 const Eigen::Matrix<Scalar, num_states, num_states> &Q_continuous,
41 const Eigen::Matrix<Scalar, num_states, num_states> &A_continuous,
42 ::std::chrono::nanoseconds dt,
43 Eigen::Matrix<Scalar, num_states, num_states> *Q_d) {
44 Eigen::Matrix<Scalar, num_states, num_states> Qtemp =
45 (Q_continuous + Q_continuous.transpose()) / static_cast<Scalar>(2.0);
46 Eigen::Matrix<Scalar, 2 * num_states, 2 * num_states> M_gain;
47 M_gain.setZero();
48 // Set up the matrix M = [[-A, Q], [0, A.T]]
49 M_gain.template block<num_states, num_states>(0, 0) = -A_continuous;
50 M_gain.template block<num_states, num_states>(0, num_states) = Qtemp;
51 M_gain.template block<num_states, num_states>(num_states, num_states) =
52 A_continuous.transpose();
53
Austin Schuh612d95d2023-10-21 00:01:40 -070054 Eigen::Matrix<Scalar, 2 * num_states, 2 * num_states> phi =
James Kuszmaul651fc3f2019-05-15 21:14:25 -070055 (M_gain * ::aos::time::TypedDurationInSeconds<Scalar>(dt)).exp();
James Kuszmaul59a5c612019-01-22 07:56:08 -080056
57 // Phi12 = phi[0:num_states, num_states:2*num_states]
58 // Phi22 = phi[num_states:2*num_states,
59 // num_states:2*num_states]
60 Eigen::Matrix<Scalar, num_states, num_states> phi12 =
61 phi.block(0, num_states, num_states, num_states);
62 Eigen::Matrix<Scalar, num_states, num_states> phi22 =
63 phi.block(num_states, num_states, num_states, num_states);
64
James Kuszmaulb2a2f352019-03-02 16:59:34 -080065 Qtemp = phi22.transpose() * phi12;
66 *Q_d = (Qtemp + Qtemp.transpose()) / static_cast<Scalar>(2.0);
67}
68
69// Does a faster approximation for the discretizing A/Q, for if solving a 2Nx2N
70// matrix exponential is too expensive.
71// Basic reasoning/method:
72// The original algorithm does a matrix exponential on a 2N x 2N matrix (the
73// block matrix made of of A and Q). This is extremely expensive for larg-ish
74// matrices. This function takes advantage of the structure of the matrix
75// we are taking the exponential and notes that we care about two things:
76// 1) The exponential of A*t, which is only NxN and so is relatively cheap.
77// 2) The upper-right quarter of the 2Nx2N matrix, which we can approximate
78// using a taylor series to several terms and still be substantially cheaper
79// than taking the big exponential.
80template <typename Scalar, int num_states>
81void DiscretizeQAFast(
82 const Eigen::Matrix<Scalar, num_states, num_states> &Q_continuous,
83 const Eigen::Matrix<Scalar, num_states, num_states> &A_continuous,
84 ::std::chrono::nanoseconds dt,
85 Eigen::Matrix<Scalar, num_states, num_states> *Q_d,
86 Eigen::Matrix<Scalar, num_states, num_states> *A_d) {
87 Eigen::Matrix<Scalar, num_states, num_states> Qtemp =
88 (Q_continuous + Q_continuous.transpose()) / static_cast<Scalar>(2.0);
James Kuszmaul651fc3f2019-05-15 21:14:25 -070089 Scalar dt_d = ::aos::time::TypedDurationInSeconds<Scalar>(dt);
James Kuszmaulb2a2f352019-03-02 16:59:34 -080090 Eigen::Matrix<Scalar, num_states, num_states> last_term = Qtemp;
91 double last_coeff = dt_d;
92 const Eigen::Matrix<Scalar, num_states, num_states> At =
93 A_continuous.transpose();
94 Eigen::Matrix<Scalar, num_states, num_states> Atn = At;
95 Eigen::Matrix<Scalar, num_states, num_states> phi12 = last_term * last_coeff;
96 Eigen::Matrix<Scalar, num_states, num_states> phi22 =
97 At.Identity() + Atn * last_coeff;
98 // TODO(james): Tune this once we have the robot up; ii < 6 is enough to get
99 // beyond any real numerical issues. 5 should be fine, but just happened to
100 // kick a test over to failing. 4 would probably work.
101 for (int ii = 2; ii < 6; ++ii) {
102 Eigen::Matrix<Scalar, num_states, num_states> next_term =
103 -A_continuous * last_term + Qtemp * Atn;
104 last_coeff *= dt_d / static_cast<Scalar>(ii);
105 phi12 += next_term * last_coeff;
106
107 last_term = next_term;
108
109 Atn *= At;
110 phi22 += last_coeff * Atn;
111 }
James Kuszmaul651fc3f2019-05-15 21:14:25 -0700112 Eigen::Matrix<Scalar, num_states, num_states> phi22t = phi22.transpose();
James Kuszmaulb2a2f352019-03-02 16:59:34 -0800113
114 Qtemp = phi22t * phi12;
115 *Q_d = (Qtemp + Qtemp.transpose()) / static_cast<Scalar>(2.0);
116 *A_d = phi22t;
James Kuszmaul59a5c612019-01-22 07:56:08 -0800117}
118
Austin Schuh9b881ae2019-01-04 07:29:20 +1100119} // namespace controls
120} // namespace frc971
121
122#endif // FRC971_CONTROL_LOOPS_C2D_H_