blob: 47a87a0ea33a7fe28b3b04843cee5eddac85da8e [file] [log] [blame]
James Kuszmaul59a5c612019-01-22 07:56:08 -08001#include "frc971/control_loops/c2d.h"
2
3#include <functional>
4
James Kuszmaul59a5c612019-01-22 07:56:08 -08005#include "gtest/gtest.h"
6
Philipp Schrader790cb542023-07-05 21:06:52 -07007#include "frc971/control_loops/runge_kutta.h"
8
Stephan Pleinesf63bde82024-01-13 15:59:33 -08009namespace frc971::controls::testing {
James Kuszmaul59a5c612019-01-22 07:56:08 -080010
11class C2DTest : public ::testing::Test {
12 public:
13 C2DTest() {
14 // Create a trivial second-order system.
15 A_continuous << 0, 1, 0, 0;
16 B_continuous << 0, 1;
17 Q_continuous << 1, 0, 0, 1;
18 }
19
20 protected:
21 Eigen::Matrix<double, 2, 2> A_continuous;
22 Eigen::Matrix<double, 2, 1> B_continuous;
23 Eigen::Matrix<double, 2, 2> Q_continuous;
24};
25
26// Check that for a simple second-order system that we can easily analyze
27// analytically, C2D creates valid A/B matrices.
28TEST_F(C2DTest, DiscretizeAB) {
29 Eigen::Matrix<double, 2, 1> X0;
30 X0 << 1, 1;
31 Eigen::Matrix<double, 1, 1> U;
32 U << 1;
33 Eigen::Matrix<double, 2, 2> A_d;
34 Eigen::Matrix<double, 2, 1> B_d;
35
36 C2D(A_continuous, B_continuous, ::std::chrono::seconds(1), &A_d, &B_d);
37 Eigen::Matrix<double, 2, 1> X1_discrete = A_d * X0 + B_d * U;
38 // We now have pos = vel = accel = 1, which should give us:
39 Eigen::Matrix<double, 2, 1> X1_truth;
40 X1_truth(1, 0) = X0(1, 0) + 1.0 * U(0, 0);
41 X1_truth(0, 0) = X0(0, 0) + 1.0 * X0(1, 0) + 0.5 * U(0, 0);
42 EXPECT_EQ(X1_truth, X1_discrete);
43}
44
45// Test that the discrete approximation of Q is roughly equal to
46// integral from 0 to dt of e^(A tau) Q e^(A.T tau) dtau
47TEST_F(C2DTest, DiscretizeQ) {
48 Eigen::Matrix<double, 2, 2> Q_d;
49 const auto dt = ::std::chrono::seconds(1);
50 DiscretizeQ(Q_continuous, A_continuous, dt, &Q_d);
51 // TODO(james): Using Runge Kutta for this is a bit silly as f is just a
52 // function of t, not Q, but I don't want to rewrite any of our math
53 // utilities.
54 // Note that we are being very explicit about the types of everything in this
55 // integration because otherwise it doesn't compile very well.
56 Eigen::Matrix<double, 2, 2> Q_d_integrated = control_loops::RungeKutta<
57 ::std::function<Eigen::Matrix<double, 2, 2>(
58 const double, const Eigen::Matrix<double, 2, 2> &)>,
59 Eigen::Matrix<double, 2, 2>>(
60 [this](const double t, const Eigen::Matrix<double, 2, 2> &) {
61 return Eigen::Matrix<double, 2, 2>(
62 (A_continuous * t).exp() * Q_continuous *
63 (A_continuous.transpose() * t).exp());
64 },
65 Eigen::Matrix<double, 2, 2>::Zero(), 0, 1.0);
66 EXPECT_LT((Q_d_integrated - Q_d).norm(), 1e-10)
Philipp Schrader790cb542023-07-05 21:06:52 -070067 << "Expected these to be nearly equal:\nQ_d:\n"
68 << Q_d << "\nQ_d_integrated:\n"
69 << Q_d_integrated;
James Kuszmaul59a5c612019-01-22 07:56:08 -080070}
71
James Kuszmaulb2a2f352019-03-02 16:59:34 -080072// Tests that the "fast" discretization produces nearly identical results.
73TEST_F(C2DTest, DiscretizeQAFast) {
74 Eigen::Matrix<double, 2, 2> Q_d;
75 Eigen::Matrix<double, 2, 2> Q_d_fast;
76 Eigen::Matrix<double, 2, 2> A_d;
77 Eigen::Matrix<double, 2, 2> A_d_fast;
78 Eigen::Matrix<double, 2, 1> B_d;
79 const auto dt = ::std::chrono::seconds(1);
80 DiscretizeQ(Q_continuous, A_continuous, dt, &Q_d);
81 C2D(A_continuous, B_continuous, dt, &A_d, &B_d);
82 DiscretizeQAFast(Q_continuous, A_continuous, dt, &Q_d_fast, &A_d_fast);
83 EXPECT_LT((Q_d - Q_d_fast).norm(), 1e-20);
84 EXPECT_LT((A_d - A_d_fast).norm(), 1e-20);
85}
86
Stephan Pleinesf63bde82024-01-13 15:59:33 -080087} // namespace frc971::controls::testing