WPILibC++ 2023.4.3
NumericalIntegration.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
8
9#include <algorithm>
10#include <array>
11
12#include "Eigen/Core"
13#include "units/time.h"
14
15namespace frc {
16
17/**
18 * Performs 4th order Runge-Kutta integration of dx/dt = f(x) for dt.
19 *
20 * @param f The function to integrate. It must take one argument x.
21 * @param x The initial value of x.
22 * @param dt The time over which to integrate.
23 */
24template <typename F, typename T>
25T RK4(F&& f, T x, units::second_t dt) {
26 const auto h = dt.value();
27
28 T k1 = f(x);
29 T k2 = f(x + h * 0.5 * k1);
30 T k3 = f(x + h * 0.5 * k2);
31 T k4 = f(x + h * k3);
32
33 return x + h / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
34}
35
36/**
37 * Performs 4th order Runge-Kutta integration of dx/dt = f(x, u) for dt.
38 *
39 * @param f The function to integrate. It must take two arguments x and u.
40 * @param x The initial value of x.
41 * @param u The value u held constant over the integration period.
42 * @param dt The time over which to integrate.
43 */
44template <typename F, typename T, typename U>
45T RK4(F&& f, T x, U u, units::second_t dt) {
46 const auto h = dt.value();
47
48 T k1 = f(x, u);
49 T k2 = f(x + h * 0.5 * k1, u);
50 T k3 = f(x + h * 0.5 * k2, u);
51 T k4 = f(x + h * k3, u);
52
53 return x + h / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
54}
55
56/**
57 * Performs adaptive Dormand-Prince integration of dx/dt = f(x, u) for dt.
58 *
59 * @param f The function to integrate. It must take two arguments x and
60 * u.
61 * @param x The initial value of x.
62 * @param u The value u held constant over the integration period.
63 * @param dt The time over which to integrate.
64 * @param maxError The maximum acceptable truncation error. Usually a small
65 * number like 1e-6.
66 */
67template <typename F, typename T, typename U>
68T RKDP(F&& f, T x, U u, units::second_t dt, double maxError = 1e-6) {
69 // See https://en.wikipedia.org/wiki/Dormand%E2%80%93Prince_method for the
70 // Butcher tableau the following arrays came from.
71
72 constexpr int kDim = 7;
73
74 // clang-format off
75 constexpr double A[kDim - 1][kDim - 1]{
76 { 1.0 / 5.0},
77 { 3.0 / 40.0, 9.0 / 40.0},
78 { 44.0 / 45.0, -56.0 / 15.0, 32.0 / 9.0},
79 {19372.0 / 6561.0, -25360.0 / 2187.0, 64448.0 / 6561.0, -212.0 / 729.0},
80 { 9017.0 / 3168.0, -355.0 / 33.0, 46732.0 / 5247.0, 49.0 / 176.0, -5103.0 / 18656.0},
81 { 35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0, 11.0 / 84.0}};
82 // clang-format on
83
84 constexpr std::array<double, kDim> b1{
85 35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0,
86 11.0 / 84.0, 0.0};
87 constexpr std::array<double, kDim> b2{5179.0 / 57600.0, 0.0,
88 7571.0 / 16695.0, 393.0 / 640.0,
89 -92097.0 / 339200.0, 187.0 / 2100.0,
90 1.0 / 40.0};
91
92 T newX;
93 double truncationError;
94
95 double dtElapsed = 0.0;
96 double h = dt.value();
97
98 // Loop until we've gotten to our desired dt
99 while (dtElapsed < dt.value()) {
100 do {
101 // Only allow us to advance up to the dt remaining
102 h = (std::min)(h, dt.value() - dtElapsed);
103
104 // clang-format off
105 T k1 = f(x, u);
106 T k2 = f(x + h * (A[0][0] * k1), u);
107 T k3 = f(x + h * (A[1][0] * k1 + A[1][1] * k2), u);
108 T k4 = f(x + h * (A[2][0] * k1 + A[2][1] * k2 + A[2][2] * k3), u);
109 T k5 = f(x + h * (A[3][0] * k1 + A[3][1] * k2 + A[3][2] * k3 + A[3][3] * k4), u);
110 T k6 = f(x + h * (A[4][0] * k1 + A[4][1] * k2 + A[4][2] * k3 + A[4][3] * k4 + A[4][4] * k5), u);
111 // clang-format on
112
113 // Since the final row of A and the array b1 have the same coefficients
114 // and k7 has no effect on newX, we can reuse the calculation.
115 newX = x + h * (A[5][0] * k1 + A[5][1] * k2 + A[5][2] * k3 +
116 A[5][3] * k4 + A[5][4] * k5 + A[5][5] * k6);
117 T k7 = f(newX, u);
118
119 truncationError = (h * ((b1[0] - b2[0]) * k1 + (b1[1] - b2[1]) * k2 +
120 (b1[2] - b2[2]) * k3 + (b1[3] - b2[3]) * k4 +
121 (b1[4] - b2[4]) * k5 + (b1[5] - b2[5]) * k6 +
122 (b1[6] - b2[6]) * k7))
123 .norm();
124
125 if (truncationError == 0.0) {
126 h = dt.value() - dtElapsed;
127 } else {
128 h *= 0.9 * std::pow(maxError / truncationError, 1.0 / 5.0);
129 }
130 } while (truncationError > maxError);
131
132 dtElapsed += h;
133 x = newX;
134 }
135
136 return x;
137}
138
139} // namespace frc
constexpr common_t< T1, T2 > min(const T1 x, const T2 y) noexcept
Compile-time pairwise minimum function.
Definition: min.hpp:35
Definition: AprilTagFieldLayout.h:22
T RK4(F &&f, T x, units::second_t dt)
Performs 4th order Runge-Kutta integration of dx/dt = f(x) for dt.
Definition: NumericalIntegration.h:25
T RKDP(F &&f, T x, U u, units::second_t dt, double maxError=1e-6)
Performs adaptive Dormand-Prince integration of dx/dt = f(x, u) for dt.
Definition: NumericalIntegration.h:68
static constexpr const unit_t< compound_unit< energy::joule, time::seconds > > h(6.626070040e-34)
Planck constant.
static constexpr const unit_t< compound_unit< charge::coulomb, inverse< substance::mol > > > F(N_A *e)
Faraday constant.
static constexpr const charge::coulomb_t e(1.6021766208e-19)
elementary charge.
static constexpr uint64_t k1
Definition: Hashing.h:171
static constexpr uint64_t k3
Definition: Hashing.h:173
static constexpr uint64_t k2
Definition: Hashing.h:172
constexpr common_t< T1, T2 > pow(const T1 base, const T2 exp_term) noexcept
Compile-time power function.
Definition: pow.hpp:76