Icarus
Vehicle Simulation as a Transformable Computational Graph, built on Vulcan and Janus
Loading...
Searching...
No Matches
RK45Integrator.hpp
Go to the documentation of this file.
1#pragma once
2
9
10#include <algorithm>
11#include <cmath>
12#include <cstddef>
13#include <icarus/core/Error.hpp>
15#include <janus/math/Arithmetic.hpp>
16#include <janus/math/IntegratorStep.hpp>
17#include <janus/math/Logic.hpp>
18#include <type_traits>
19
20namespace icarus {
21
36template <typename Scalar> class RK45Integrator : public AdaptiveIntegrator<Scalar> {
37 public:
39
46 explicit RK45Integrator(Scalar abs_tol = Scalar{1e-6}, Scalar rel_tol = Scalar{1e-6})
47 : abs_tol_(abs_tol), rel_tol_(rel_tol) {}
48
55 JanusVector<Scalar> Step(const DerivativeFunc &f, const JanusVector<Scalar> &x, Scalar t,
56 Scalar dt) override {
57 auto result = janus::rk45_step(f, x, t, dt);
58 return result.y5;
59 }
60
75 AdaptiveStepResult<Scalar> AdaptiveStep(const DerivativeFunc &f, const JanusVector<Scalar> &x,
76 Scalar t, Scalar dt) override {
77 Scalar current_dt = dt;
78
79 // Retry loop with step size reduction
80 constexpr int max_attempts = 20; // Prevent infinite loops
81 for (int attempt = 0; attempt < max_attempts; ++attempt) {
82 // Compute RK45 step
83 auto result = janus::rk45_step(f, x, t, current_dt);
84
85 // Compute error tolerance
86 Scalar tol = ComputeTolerance(x, result.y5);
87
88 // Check if step is accepted
89 bool accepted = LessThanOrEqual(result.error, tol);
90
91 if (accepted) {
92 stats_.accepted_steps++;
93 return AdaptiveStepResult<Scalar>{result.y5, current_dt, result.error, true};
94 }
95
96 // Step rejected - reduce dt and retry
97 stats_.rejected_steps++;
98
99 // Compute new step size
100 Scalar dt_new = SuggestDt(current_dt, result.error, tol);
101
102 // Check for minimum step size (only in numeric mode)
103 if constexpr (std::is_same_v<Scalar, double>) {
104 if (dt_new <= min_dt_) {
105 throw StepSizeTooSmallError(static_cast<double>(min_dt_));
106 }
107 }
108
109 current_dt = dt_new;
110 }
111
112 // Max attempts reached - return last result as rejected
113 auto result = janus::rk45_step(f, x, t, current_dt);
114 return AdaptiveStepResult<Scalar>{result.y5, current_dt, result.error, false};
115 }
116
117 // Tolerance setters/getters
118 void SetAbsTol(Scalar tol) override { abs_tol_ = tol; }
119 void SetRelTol(Scalar tol) override { rel_tol_ = tol; }
120 [[nodiscard]] Scalar GetAbsTol() const override { return abs_tol_; }
121 [[nodiscard]] Scalar GetRelTol() const override { return rel_tol_; }
122
123 [[nodiscard]] std::string Name() const override { return "RK45"; }
124 [[nodiscard]] int Order() const override { return 5; }
125 [[nodiscard]] IntegratorType Type() const override { return IntegratorType::RK45; }
126
127 // Step size control parameters
128 void SetMinDt(Scalar min_dt) { min_dt_ = min_dt; }
129 void SetMaxDt(Scalar max_dt) { max_dt_ = max_dt; }
130 void SetSafetyFactor(Scalar factor) { safety_ = factor; }
131
132 [[nodiscard]] Scalar GetMinDt() const { return min_dt_; }
133 [[nodiscard]] Scalar GetMaxDt() const { return max_dt_; }
134
143 [[nodiscard]] Scalar SuggestDt(Scalar dt, Scalar error, Scalar tol) const {
144 // Optimal step size formula: dt_new = dt * safety * (tol/error)^(1/5)
145 // Use 5th root for RK45 (5th order method)
146 Scalar ratio = tol / (error + Scalar{1e-15}); // Avoid division by zero
147 Scalar factor = safety_ * janus::pow(ratio, Scalar{0.2});
148
149 // Limit growth/shrinkage
150 factor = janus::max(Scalar{0.1}, janus::min(Scalar{5.0}, factor));
151
152 Scalar dt_new = dt * factor;
153
154 // Clamp to min/max
155 return janus::max(min_dt_, janus::min(max_dt_, dt_new));
156 }
157
161 struct Statistics {
162 std::size_t accepted_steps = 0;
163 std::size_t rejected_steps = 0;
164
165 void Reset() {
166 accepted_steps = 0;
167 rejected_steps = 0;
168 }
169
170 [[nodiscard]] double AcceptanceRate() const {
171 auto total = accepted_steps + rejected_steps;
172 return total > 0 ? static_cast<double>(accepted_steps) / static_cast<double>(total)
173 : 1.0;
174 }
175 };
176
177 [[nodiscard]] const Statistics &GetStatistics() const { return stats_; }
178 void ResetStatistics() { stats_.Reset(); }
179
180 private:
181 Scalar abs_tol_;
182 Scalar rel_tol_;
183 Scalar min_dt_{1e-10};
184 Scalar max_dt_{1.0};
185 Scalar safety_{0.9};
186 Statistics stats_;
187
193 [[nodiscard]] Scalar ComputeTolerance(const JanusVector<Scalar> &x,
194 const JanusVector<Scalar> &x_new) const {
195 Scalar max_norm = Scalar{0};
196 for (Eigen::Index i = 0; i < x.size(); ++i) {
197 Scalar xi_abs = janus::abs(x[i]);
198 Scalar xi_new_abs = janus::abs(x_new[i]);
199 max_norm = janus::max(max_norm, janus::max(xi_abs, xi_new_abs));
200 }
201 return abs_tol_ + rel_tol_ * max_norm;
202 }
203
207 [[nodiscard]] bool LessThanOrEqual(Scalar a, Scalar b) const {
208 // For numeric mode, direct comparison
209 // For symbolic mode, this is only used in statistics (not traced)
210 if constexpr (std::is_same_v<Scalar, double>) {
211 return a <= b;
212 } else {
213 // For symbolic, evaluate numerically if possible
214 // This branch only used in adaptive control, not traced
215 return true; // Accept in symbolic mode
216 }
217 }
218};
219
220} // namespace icarus
Consolidated error handling for Icarus.
Abstract interface for numerical integrators.
Interface for adaptive step integrators.
Definition Integrator.hpp:101
std::function< JanusVector< Scalar >(Scalar t, const JanusVector< Scalar > &x)> DerivativeFunc
Derivative function signature.
Definition Integrator.hpp:54
Scalar GetMinDt() const
Definition RK45Integrator.hpp:132
IntegratorType Type() const override
Get integrator type.
Definition RK45Integrator.hpp:125
const Statistics & GetStatistics() const
Definition RK45Integrator.hpp:177
RK45Integrator(Scalar abs_tol=Scalar{1e-6}, Scalar rel_tol=Scalar{1e-6})
Construct with default tolerances.
Definition RK45Integrator.hpp:46
Scalar GetRelTol() const override
Get current relative tolerance.
Definition RK45Integrator.hpp:121
void ResetStatistics()
Definition RK45Integrator.hpp:178
void SetAbsTol(Scalar tol) override
Set absolute tolerance.
Definition RK45Integrator.hpp:118
std::string Name() const override
Get integrator name for logging.
Definition RK45Integrator.hpp:123
Scalar GetAbsTol() const override
Get current absolute tolerance.
Definition RK45Integrator.hpp:120
AdaptiveStepResult< Scalar > AdaptiveStep(const DerivativeFunc &f, const JanusVector< Scalar > &x, Scalar t, Scalar dt) override
Adaptive step with automatic error control.
Definition RK45Integrator.hpp:75
void SetRelTol(Scalar tol) override
Set relative tolerance.
Definition RK45Integrator.hpp:119
void SetSafetyFactor(Scalar factor)
Definition RK45Integrator.hpp:130
void SetMaxDt(Scalar max_dt)
Definition RK45Integrator.hpp:129
Scalar GetMaxDt() const
Definition RK45Integrator.hpp:133
void SetMinDt(Scalar min_dt)
Definition RK45Integrator.hpp:128
Scalar SuggestDt(Scalar dt, Scalar error, Scalar tol) const
Suggest next step size based on error.
Definition RK45Integrator.hpp:143
JanusVector< Scalar > Step(const DerivativeFunc &f, const JanusVector< Scalar > &x, Scalar t, Scalar dt) override
Fixed-step interface (uses requested dt directly).
Definition RK45Integrator.hpp:55
int Order() const override
Get integrator order (for error analysis).
Definition RK45Integrator.hpp:124
Definition Error.hpp:476
Definition AggregationTypes.hpp:13
IntegratorType
Available integrator methods.
Definition IntegratorTypes.hpp:27
@ RK45
Dormand-Prince adaptive (5th order, 7 evals) - janus::rk45_step.
Definition IntegratorTypes.hpp:31
Result from adaptive step integrators.
Definition Integrator.hpp:26
Statistics for adaptive stepping.
Definition RK45Integrator.hpp:161
void Reset()
Definition RK45Integrator.hpp:165
std::size_t accepted_steps
Definition RK45Integrator.hpp:162
std::size_t rejected_steps
Definition RK45Integrator.hpp:163
double AcceptanceRate() const
Definition RK45Integrator.hpp:170