DiffEq - Modern C++ ODE Integration Library 1.0.0
High-performance C++ library for solving ODEs with async signal processing
Loading...
Searching...
No Matches
lsoda.hpp
1#pragma once
2
3#include <core/concepts.hpp>
4#include <core/adaptive_integrator.hpp>
5#include <core/state_creator.hpp>
6#include "rk45.hpp"
7#include "bdf.hpp"
8#include <memory>
9#include <cmath>
10
11namespace diffeq {
12
19template<system_state S>
21public:
23 using state_type = typename base_type::state_type;
24 using time_type = typename base_type::time_type;
25 using value_type = typename base_type::value_type;
26 using system_function = typename base_type::system_function;
27
28 enum class MethodType {
29 ADAMS, // Non-stiff method (approximated by RK45)
30 BDF // Stiff method
31 };
32
33 explicit LSODAIntegrator(system_function sys,
34 time_type rtol = static_cast<time_type>(1e-6),
35 time_type atol = static_cast<time_type>(1e-9))
36 : base_type(std::move(sys), rtol, atol),
37 current_method_(MethodType::ADAMS),
38 stiffness_detection_frequency_(10),
39 step_count_(0),
40 consecutive_stiff_steps_(0),
41 consecutive_nonstiff_steps_(0),
42 stiffness_threshold_(static_cast<time_type>(3.25)),
43 has_previous_values_(false) {
44
45 // Create internal integrators
46 rk45_integrator_ = std::make_unique<RK45Integrator<S>>(
47 [this](time_type t, const state_type& y, state_type& dydt) {
48 this->sys_(t, y, dydt);
49 }, rtol, atol);
50
51 bdf_integrator_ = std::make_unique<BDFIntegrator<S>>(
52 [this](time_type t, const state_type& y, state_type& dydt) {
53 this->sys_(t, y, dydt);
54 }, rtol, atol);
55 }
56
57 void step(state_type& state, time_type dt) override {
58 adaptive_step(state, dt);
59 }
60
61 time_type adaptive_step(state_type& state, time_type dt) override {
62 // For simplicity, just use RK45 method (Adams approximation)
63 // Real LSODA would have more sophisticated switching logic
64
65 if (!rk45_integrator_) {
66 rk45_integrator_ = std::make_unique<RK45Integrator<S>>(
67 [this](time_type t, const state_type& y, state_type& dydt) {
68 this->sys_(t, y, dydt);
69 }, this->rtol_, this->atol_);
70 }
71
72 rk45_integrator_->set_time(this->current_time_);
73 time_type result_dt = rk45_integrator_->adaptive_step(state, dt);
74 this->set_time(rk45_integrator_->current_time());
75
76 return result_dt;
77 }
78
79 MethodType get_current_method() const { return current_method_; }
80
81 void set_stiffness_detection_frequency(int frequency) {
82 stiffness_detection_frequency_ = frequency;
83 }
84
85 void set_stiffness_threshold(time_type threshold) {
86 stiffness_threshold_ = threshold;
87 }
88
89 void set_tolerances(time_type rtol, time_type atol) {
90 this->rtol_ = rtol;
91 this->atol_ = atol;
92 if (rk45_integrator_) {
93 rk45_integrator_->set_tolerances(rtol, atol);
94 }
95 if (bdf_integrator_) {
96 bdf_integrator_->set_tolerances(rtol, atol);
97 }
98 }
99
100 void integrate(state_type& state, time_type dt, time_type end_time) override {
101 // Initialize if needed
102 if (!has_previous_values_) {
103 y_prev_ = StateCreator<state_type>::create(state);
104 f_prev_ = StateCreator<state_type>::create(state);
105 has_previous_values_ = false; // Will be set in detect_stiffness
106 }
107
108 // Call base implementation
109 base_type::integrate(state, dt, end_time);
110 }
111
112private:
113 MethodType current_method_;
114 std::unique_ptr<RK45Integrator<S>> rk45_integrator_;
115 std::unique_ptr<BDFIntegrator<S>> bdf_integrator_;
116
117 int stiffness_detection_frequency_;
118 int step_count_;
119 int consecutive_stiff_steps_;
120 int consecutive_nonstiff_steps_;
121 time_type stiffness_threshold_;
122
123 // Previous values for stiffness detection
124 state_type y_prev_;
125 state_type f_prev_;
126 bool has_previous_values_;
127
128 void detect_stiffness(const state_type& y, time_type dt) {
129 // Simplified stiffness detection based on the ratio of Jacobian eigenvalues
130 // In practice, LSODA uses more sophisticated detection methods
131
132 if (!has_previous_values_) {
135 y_prev_ = y;
136 this->sys_(this->current_time_, y, f_prev_);
137 has_previous_values_ = true;
138 return;
139 }
140
141 state_type f_current = StateCreator<state_type>::create(y);
142 this->sys_(this->current_time_, y, f_current);
143
144 // Estimate stiffness using the spectral radius approximation
145 time_type stiffness_ratio = estimate_stiffness_ratio(y, f_current, dt);
146
147 if (stiffness_ratio > stiffness_threshold_) {
148 // System appears stiff
149 consecutive_stiff_steps_++;
150 consecutive_nonstiff_steps_ = 0;
151
152 if (consecutive_stiff_steps_ >= 3 && current_method_ == MethodType::ADAMS) {
153 switch_to_bdf(y);
154 }
155 } else {
156 // System appears non-stiff
157 consecutive_nonstiff_steps_++;
158 consecutive_stiff_steps_ = 0;
159
160 if (consecutive_nonstiff_steps_ >= 3 && current_method_ == MethodType::BDF) {
161 switch_to_adams(y);
162 }
163 }
164
165 // Update previous values
166 y_prev_ = y;
167 f_prev_ = f_current;
168 }
169
170 time_type estimate_stiffness_ratio(const state_type& y, const state_type& f, time_type dt) {
171 // Estimate the ratio ||J*h|| / ||f|| where J is the Jacobian
172 // Use finite differences to approximate Jacobian effects
173
174 time_type epsilon = static_cast<time_type>(1e-8);
175 state_type y_pert = StateCreator<state_type>::create(y);
176 state_type f_pert = StateCreator<state_type>::create(y);
177
178 time_type jacobian_norm = static_cast<time_type>(0);
179 time_type f_norm = static_cast<time_type>(0);
180
181 for (std::size_t i = 0; i < y.size(); ++i) {
182 auto y_it = y.begin();
183 auto f_it = f.begin();
184 auto y_pert_it = y_pert.begin();
185 auto f_pert_it = f_pert.begin();
186
187 // Perturb y[i]
188 y_pert = y;
189 y_pert_it[i] += epsilon;
190
191 // Evaluate f at perturbed point
192 this->sys_(this->current_time_, y_pert, f_pert);
193
194 // Estimate partial derivative
195 time_type df_dy = (f_pert_it[i] - f_it[i]) / epsilon;
196
197 // Accumulate norms
198 jacobian_norm += std::abs(df_dy * dt);
199 f_norm += std::abs(f_it[i]);
200 }
201
202 if (f_norm < static_cast<time_type>(1e-12)) {
203 return static_cast<time_type>(0);
204 }
205
206 return jacobian_norm / f_norm;
207 }
208
209 void switch_to_bdf(const state_type& y) {
210 current_method_ = MethodType::BDF;
211
212 if (!bdf_integrator_) {
213 bdf_integrator_ = std::make_unique<BDFIntegrator<S>>(
214 [this](time_type t, const state_type& y, state_type& dydt) {
215 this->sys_(t, y, dydt);
216 }, this->rtol_, this->atol_);
217 }
218
219 bdf_integrator_->set_time(this->current_time_);
220 }
221
222 void switch_to_adams(const state_type& y) {
223 current_method_ = MethodType::ADAMS;
224
225 if (!rk45_integrator_) {
226 rk45_integrator_ = std::make_unique<RK45Integrator<S>>(
227 [this](time_type t, const state_type& y, state_type& dydt) {
228 this->sys_(t, y, dydt);
229 }, this->rtol_, this->atol_);
230 }
231
232 rk45_integrator_->set_time(this->current_time_);
233 }
234};
235
236} // namespace diffeq
LSODA integrator - automatically switches between stiff and non-stiff methods.
Definition lsoda.hpp:20