blob: b6f4198f75232959a17e72be45a7bb31165c6bf1 [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>
James Kuszmaul59a5c612019-01-22 07:56:08 -08007// We need to include MatrixFunctions for the matrix exponential.
8#include "unsupported/Eigen/MatrixFunctions"
Austin Schuh9b881ae2019-01-04 07:29:20 +11009
10namespace frc971 {
11namespace controls {
12
13template <typename Scalar, int num_states, int num_inputs>
14void C2D(const ::Eigen::Matrix<Scalar, num_states, num_states> &A_continuous,
15 const ::Eigen::Matrix<Scalar, num_states, num_inputs> &B_continuous,
16 ::std::chrono::nanoseconds dt,
17 ::Eigen::Matrix<Scalar, num_states, num_states> *A,
18 ::Eigen::Matrix<Scalar, num_states, num_inputs> *B) {
19 // Trick from
20 // https://en.wikipedia.org/wiki/Discretization#Discretization_of_linear_state_space_models
21 // to solve for A and B more efficiently.
22 Eigen::Matrix<Scalar, num_states + num_inputs, num_states + num_inputs>
23 M_state_continuous;
24 M_state_continuous.setZero();
25 M_state_continuous.template block<num_states, num_states>(0, 0) =
26 A_continuous *
27 ::std::chrono::duration_cast<::std::chrono::duration<Scalar>>(dt).count();
28 M_state_continuous.template block<num_states, num_inputs>(0, num_states) =
29 B_continuous *
30 ::std::chrono::duration_cast<::std::chrono::duration<Scalar>>(dt).count();
31
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
54 Eigen::Matrix<Scalar, 2 * num_states, 2 *num_states> phi =
55 (M_gain *
56 ::std::chrono::duration_cast<::std::chrono::duration<Scalar>>(dt)
57 .count()).exp();
58
59 // Phi12 = phi[0:num_states, num_states:2*num_states]
60 // Phi22 = phi[num_states:2*num_states,
61 // num_states:2*num_states]
62 Eigen::Matrix<Scalar, num_states, num_states> phi12 =
63 phi.block(0, num_states, num_states, num_states);
64 Eigen::Matrix<Scalar, num_states, num_states> phi22 =
65 phi.block(num_states, num_states, num_states, num_states);
66
James Kuszmaulb2a2f352019-03-02 16:59:34 -080067 Qtemp = phi22.transpose() * phi12;
68 *Q_d = (Qtemp + Qtemp.transpose()) / static_cast<Scalar>(2.0);
69}
70
71// Does a faster approximation for the discretizing A/Q, for if solving a 2Nx2N
72// matrix exponential is too expensive.
73// Basic reasoning/method:
74// The original algorithm does a matrix exponential on a 2N x 2N matrix (the
75// block matrix made of of A and Q). This is extremely expensive for larg-ish
76// matrices. This function takes advantage of the structure of the matrix
77// we are taking the exponential and notes that we care about two things:
78// 1) The exponential of A*t, which is only NxN and so is relatively cheap.
79// 2) The upper-right quarter of the 2Nx2N matrix, which we can approximate
80// using a taylor series to several terms and still be substantially cheaper
81// than taking the big exponential.
82template <typename Scalar, int num_states>
83void DiscretizeQAFast(
84 const Eigen::Matrix<Scalar, num_states, num_states> &Q_continuous,
85 const Eigen::Matrix<Scalar, num_states, num_states> &A_continuous,
86 ::std::chrono::nanoseconds dt,
87 Eigen::Matrix<Scalar, num_states, num_states> *Q_d,
88 Eigen::Matrix<Scalar, num_states, num_states> *A_d) {
89 Eigen::Matrix<Scalar, num_states, num_states> Qtemp =
90 (Q_continuous + Q_continuous.transpose()) / static_cast<Scalar>(2.0);
91 Scalar dt_d =
92 ::std::chrono::duration_cast<::std::chrono::duration<Scalar>>(dt).count();
93 Eigen::Matrix<Scalar, num_states, num_states> last_term = Qtemp;
94 double last_coeff = dt_d;
95 const Eigen::Matrix<Scalar, num_states, num_states> At =
96 A_continuous.transpose();
97 Eigen::Matrix<Scalar, num_states, num_states> Atn = At;
98 Eigen::Matrix<Scalar, num_states, num_states> phi12 = last_term * last_coeff;
99 Eigen::Matrix<Scalar, num_states, num_states> phi22 =
100 At.Identity() + Atn * last_coeff;
101 // TODO(james): Tune this once we have the robot up; ii < 6 is enough to get
102 // beyond any real numerical issues. 5 should be fine, but just happened to
103 // kick a test over to failing. 4 would probably work.
104 for (int ii = 2; ii < 6; ++ii) {
105 Eigen::Matrix<Scalar, num_states, num_states> next_term =
106 -A_continuous * last_term + Qtemp * Atn;
107 last_coeff *= dt_d / static_cast<Scalar>(ii);
108 phi12 += next_term * last_coeff;
109
110 last_term = next_term;
111
112 Atn *= At;
113 phi22 += last_coeff * Atn;
114 }
115 Eigen::Matrix<Scalar, num_states, num_states> phi22t =
116 phi22.transpose();
117
118 Qtemp = phi22t * phi12;
119 *Q_d = (Qtemp + Qtemp.transpose()) / static_cast<Scalar>(2.0);
120 *A_d = phi22t;
James Kuszmaul59a5c612019-01-22 07:56:08 -0800121}
122
Austin Schuh9b881ae2019-01-04 07:29:20 +1100123} // namespace controls
124} // namespace frc971
125
126#endif // FRC971_CONTROL_LOOPS_C2D_H_