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