blob: 357b7f55b9f7f0939bbf590a0459290b130b11d2 [file] [log] [blame]
Austin Schuhacd335a2017-01-01 16:20:54 -08001#include "frc971/control_loops/runge_kutta.h"
2
3#include "gtest/gtest.h"
4
Stephan Pleinesf63bde82024-01-13 15:59:33 -08005namespace frc971::control_loops::testing {
Austin Schuhacd335a2017-01-01 16:20:54 -08006
7// Tests that integrating dx/dt = e^x works.
8TEST(RungeKuttaTest, Exponential) {
9 ::Eigen::Matrix<double, 1, 1> y0;
Austin Schuhf7466732023-02-20 22:11:41 -080010 y0(0, 0) = 1.0;
Austin Schuhacd335a2017-01-01 16:20:54 -080011
12 ::Eigen::Matrix<double, 1, 1> y1 = RungeKutta(
13 [](::Eigen::Matrix<double, 1, 1> x) {
14 ::Eigen::Matrix<double, 1, 1> y;
15 y(0, 0) = ::std::exp(x(0, 0));
16 return y;
17 },
18 y0, 0.1);
Austin Schuhf7466732023-02-20 22:11:41 -080019 EXPECT_NEAR(y1(0, 0), -std::log(std::exp(-1.0) - 0.1), 1e-5);
20}
21
22// Now do it with sub steps.
23TEST(RungeKuttaTest, ExponentialSteps) {
24 ::Eigen::Matrix<double, 1, 1> y0;
25 y0(0, 0) = 1.0;
26
27 ::Eigen::Matrix<double, 1, 1> y1 = RungeKuttaSteps(
28 [](::Eigen::Matrix<double, 1, 1> x) {
29 ::Eigen::Matrix<double, 1, 1> y;
30 y(0, 0) = ::std::exp(x(0, 0));
31 return y;
32 },
33 y0, 0.1, 10);
34 EXPECT_NEAR(y1(0, 0), -std::log(std::exp(-1.0) - 0.1), 1e-8);
Austin Schuhacd335a2017-01-01 16:20:54 -080035}
36
Austin Schuh268a94f2018-02-17 17:10:19 -080037// Tests that integrating dx/dt = e^x works when we provide a U.
38TEST(RungeKuttaTest, ExponentialWithU) {
39 ::Eigen::Matrix<double, 1, 1> y0;
40 y0(0, 0) = 0.0;
41
Austin Schuh9edb5df2018-12-23 09:03:15 +110042 ::Eigen::Matrix<double, 1, 1> y1 = RungeKuttaU(
Austin Schuh268a94f2018-02-17 17:10:19 -080043 [](::Eigen::Matrix<double, 1, 1> x, ::Eigen::Matrix<double, 1, 1> u) {
44 ::Eigen::Matrix<double, 1, 1> y;
45 y(0, 0) = ::std::exp(u(0, 0) * x(0, 0));
46 return y;
47 },
48 y0, (::Eigen::Matrix<double, 1, 1>() << 1.0).finished(), 0.1);
49 EXPECT_NEAR(y1(0, 0), ::std::exp(0.1) - ::std::exp(0), 1e-3);
50}
51
Austin Schuhca52a242018-12-23 09:19:29 +110052::Eigen::Matrix<double, 1, 1> RungeKuttaTimeVaryingSolution(double t) {
53 return (::Eigen::Matrix<double, 1, 1>()
54 << 12.0 * ::std::exp(t) / (::std::pow(::std::exp(t) + 1.0, 2.0)))
55 .finished();
56}
57
58// Tests RungeKutta with a time varying solution.
59// Now, lets test RK4 with a time varying solution. From
60// http://www2.hawaii.edu/~jmcfatri/math407/RungeKuttaTest.html:
61// x' = x (2 / (e^t + 1) - 1)
62//
63// The true (analytical) solution is:
64//
65// x(t) = 12 * e^t / ((e^t + 1)^2)
66TEST(RungeKuttaTest, RungeKuttaTimeVarying) {
67 ::Eigen::Matrix<double, 1, 1> y0 = RungeKuttaTimeVaryingSolution(5.0);
68
69 ::Eigen::Matrix<double, 1, 1> y1 = RungeKutta(
70 [](double t, ::Eigen::Matrix<double, 1, 1> x) {
71 return (::Eigen::Matrix<double, 1, 1>()
72 << x(0, 0) * (2.0 / (::std::exp(t) + 1.0) - 1.0))
73 .finished();
74 },
75 y0, 5.0, 1.0);
76 EXPECT_NEAR(y1(0, 0), RungeKuttaTimeVaryingSolution(6.0)(0, 0), 1e-3);
77}
78
Austin Schuhf7466732023-02-20 22:11:41 -080079// Now do it with a ton of sub steps.
80TEST(RungeKuttaTest, RungeKuttaTimeVaryingSteps) {
81 ::Eigen::Matrix<double, 1, 1> y0 = RungeKuttaTimeVaryingSolution(5.0);
82
83 ::Eigen::Matrix<double, 1, 1> y1 = RungeKuttaSteps(
84 [](double t, ::Eigen::Matrix<double, 1, 1> x) {
85 return (::Eigen::Matrix<double, 1, 1>()
86 << x(0, 0) * (2.0 / (::std::exp(t) + 1.0) - 1.0))
87 .finished();
88 },
89 y0, 5.0, 1.0, 10);
90 EXPECT_NEAR(y1(0, 0), RungeKuttaTimeVaryingSolution(6.0)(0, 0), 1e-7);
91}
92
Austin Schuhb0bfaf82024-06-19 19:47:23 -070093// Tests that integrating dx/dt = e^x works with RK45
94TEST(RungeKuttaTest, RungeKuttaTimeVaryingAdaptive) {
95 ::Eigen::Matrix<double, 1, 1> y0 = RungeKuttaTimeVaryingSolution(5.0);
96
97 size_t count = 0;
98
99 ::Eigen::Matrix<double, 1, 1> y1 = AdaptiveRungeKutta(
100 [&](double t, ::Eigen::Matrix<double, 1, 1> x) {
101 ++count;
102 return (::Eigen::Matrix<double, 1, 1>()
103 << x(0, 0) * (2.0 / (::std::exp(t) + 1.0) - 1.0))
104 .finished();
105 },
106 y0, 5.0, 1.0, 1e-6, 1e-9);
107 EXPECT_NEAR(y1(0, 0), RungeKuttaTimeVaryingSolution(6.0)(0, 0), 1e-7);
108
109 // Using sympy as a benchmark, we should expect to see the function called 38
110 // times.
111 EXPECT_EQ(count, 38);
112
113 LOG(INFO) << "Got " << y1(0, 0) << " vs expected "
114 << RungeKuttaTimeVaryingSolution(6.0)(0, 0);
115}
116
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800117} // namespace frc971::control_loops::testing