scirs2_integrate/ode/utils/stiffness/
mod.rs1pub mod integration;
7
8use crate::IntegrateFloat;
9use scirs2_core::ndarray::{Array1, Array2};
10use std::fmt::Debug;
11use std::marker::PhantomData;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum StiffnessDetectionMethod {
16 Basic,
18 ErrorPattern,
20 StepPattern,
22 EigenvalueEstimation,
24 #[default]
26 Combined,
27}
28
29#[derive(Debug, Clone)]
31pub struct StiffnessDetectionConfig<F: IntegrateFloat> {
32 pub method: StiffnessDetectionMethod,
34 pub min_steps_before_switch: usize,
36 pub stiffness_threshold: usize,
38 pub non_stiffness_threshold: usize,
40 pub analysis_window: usize,
42 pub step_size_ratio_threshold: F,
44 pub error_ratio_threshold: F,
46 pub use_eigenvalue_estimation: bool,
48 pub eigenvalue_est_period: usize,
50 pub indicator_weights: IndicatorWeights<F>,
52 _phantom: PhantomData<F>,
54}
55
56impl<F: IntegrateFloat> Default for StiffnessDetectionConfig<F> {
57 fn default() -> Self {
58 StiffnessDetectionConfig {
59 method: StiffnessDetectionMethod::default(),
60 min_steps_before_switch: 5,
61 stiffness_threshold: 3,
62 non_stiffness_threshold: 5,
63 analysis_window: 10,
64 step_size_ratio_threshold: F::from_f64(0.1).unwrap(),
65 error_ratio_threshold: F::from_f64(10.0).unwrap(),
66 use_eigenvalue_estimation: false,
67 eigenvalue_est_period: 25,
68 indicator_weights: IndicatorWeights::default(),
69 _phantom: PhantomData,
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct IndicatorWeights<F: IntegrateFloat> {
77 pub error_pattern_weight: F,
79 pub step_pattern_weight: F,
81 pub newton_convergence_weight: F,
83 pub eigenvalue_weight: F,
85}
86
87impl<F: IntegrateFloat> Default for IndicatorWeights<F> {
88 fn default() -> Self {
89 IndicatorWeights {
90 error_pattern_weight: F::from_f64(1.0).unwrap(),
91 step_pattern_weight: F::from_f64(1.0).unwrap(),
92 newton_convergence_weight: F::from_f64(1.5).unwrap(),
93 eigenvalue_weight: F::from_f64(2.0).unwrap(),
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct StiffnessDetector<F: IntegrateFloat> {
101 config: StiffnessDetectionConfig<F>,
103 step_size_history: Vec<F>,
105 error_history: Vec<F>,
107 newton_iter_history: Vec<usize>,
109 rejected_step_history: Vec<bool>,
111 stiffness_ratio: F,
113 stiffness_indicators: usize,
115 non_stiffness_indicators: usize,
117 last_eigenvalue_est: usize,
119 stiffness_score: F,
121}
122
123impl<F: IntegrateFloat> Default for StiffnessDetector<F> {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl<F: IntegrateFloat> StiffnessDetector<F> {
130 pub fn new() -> Self {
132 Self::with_config(StiffnessDetectionConfig::default())
133 }
134
135 pub fn with_config(config: StiffnessDetectionConfig<F>) -> Self {
137 StiffnessDetector {
138 config,
139 step_size_history: Vec::with_capacity(20),
140 error_history: Vec::with_capacity(20),
141 newton_iter_history: Vec::with_capacity(20),
142 rejected_step_history: Vec::with_capacity(20),
143 stiffness_ratio: F::zero(),
144 stiffness_indicators: 0,
145 non_stiffness_indicators: 0,
146 last_eigenvalue_est: 0,
147 stiffness_score: F::zero(),
148 }
149 }
150
151 pub fn record_step(
153 &mut self,
154 step_size: F,
155 error: F,
156 newton_iterations: usize,
157 rejected: bool,
158 steps_taken: usize,
159 ) {
160 self.step_size_history.push(step_size);
162 self.error_history.push(error);
163 self.newton_iter_history.push(newton_iterations);
164 self.rejected_step_history.push(rejected);
165
166 let window = self.config.analysis_window;
168 if self.step_size_history.len() > window {
169 self.step_size_history.remove(0);
170 self.error_history.remove(0);
171 self.newton_iter_history.remove(0);
172 self.rejected_step_history.remove(0);
173 }
174
175 match self.config.method {
177 StiffnessDetectionMethod::Basic => self.analyze_basic(),
178 StiffnessDetectionMethod::ErrorPattern => self.analyze_error_pattern(),
179 StiffnessDetectionMethod::StepPattern => self.analyze_step_pattern(),
180 StiffnessDetectionMethod::EigenvalueEstimation => {
181 if steps_taken - self.last_eigenvalue_est >= self.config.eigenvalue_est_period {
183 self.last_eigenvalue_est = steps_taken;
186 }
187 }
188 StiffnessDetectionMethod::Combined => {
189 self.analyze_basic();
190 self.analyze_error_pattern();
191 self.analyze_step_pattern();
192
193 if self.config.use_eigenvalue_estimation
194 && steps_taken - self.last_eigenvalue_est >= self.config.eigenvalue_est_period
195 {
196 self.last_eigenvalue_est = steps_taken;
198 }
199
200 self.update_stiffness_score();
202 }
203 }
204 }
205
206 fn analyze_basic(&mut self) {
208 if self.step_size_history.is_empty() {
209 return;
210 }
211
212 let last_idx = self.step_size_history.len() - 1;
213
214 if self.error_history[last_idx] < F::from_f64(0.01).unwrap() {
216 self.non_stiffness_indicators += 1;
217 }
218
219 if self.error_history[last_idx] > self.config.error_ratio_threshold {
221 self.stiffness_indicators += 1;
222 }
223
224 if self.newton_iter_history[last_idx] <= 2 {
226 self.non_stiffness_indicators += 1;
227 }
228
229 if self.newton_iter_history[last_idx] >= 8 {
231 self.stiffness_indicators += 1;
232 }
233
234 if self.rejected_step_history[last_idx] {
236 self.stiffness_indicators += 1;
237 }
238 }
239
240 fn analyze_error_pattern(&mut self) {
242 if self.error_history.len() < 3 {
243 return;
244 }
245
246 let mut oscillating = true;
248 for i in 2..self.error_history.len() {
249 if (self.error_history[i] > self.error_history[i - 1]
250 && self.error_history[i - 1] > self.error_history[i - 2])
251 || (self.error_history[i] < self.error_history[i - 1]
252 && self.error_history[i - 1] < self.error_history[i - 2])
253 {
254 oscillating = false;
255 break;
256 }
257 }
258
259 if oscillating {
260 self.stiffness_indicators += 1;
261 }
262
263 let mut decreasing = true;
265 for i in 1..self.error_history.len() {
266 if self.error_history[i] > self.error_history[i - 1] {
267 decreasing = false;
268 break;
269 }
270 }
271
272 if decreasing && self.error_history.len() >= 3 {
273 self.non_stiffness_indicators += 1;
274 }
275 }
276
277 fn analyze_step_pattern(&mut self) {
279 if self.step_size_history.len() < 3 {
280 return;
281 }
282
283 let mut decreases = 0;
285 for i in 1..self.step_size_history.len() {
286 if self.step_size_history[i] < self.step_size_history[i - 1] {
287 decreases += 1;
288 }
289 }
290
291 let decrease_ratio = F::from_usize(decreases).unwrap()
293 / F::from_usize(self.step_size_history.len() - 1).unwrap();
294
295 if decrease_ratio > F::from_f64(0.7).unwrap() {
296 self.stiffness_indicators += 1;
297 }
298
299 let mut increasing = true;
301 for i in 1..self.step_size_history.len() {
302 if self.step_size_history[i] < self.step_size_history[i - 1] {
303 increasing = false;
304 break;
305 }
306 }
307
308 if increasing && self.step_size_history.len() >= 3 {
309 self.non_stiffness_indicators += 1;
310 }
311 }
312
313 fn update_stiffness_score(&mut self) {
315 let weights = &self.config.indicator_weights;
316
317 let stiff_score = F::from_usize(self.stiffness_indicators).unwrap()
319 * (weights.error_pattern_weight
320 + weights.step_pattern_weight
321 + weights.newton_convergence_weight);
322
323 let non_stiff_score = F::from_usize(self.non_stiffness_indicators).unwrap()
324 * (weights.error_pattern_weight
325 + weights.step_pattern_weight
326 + weights.newton_convergence_weight);
327
328 if stiff_score > F::zero() || non_stiff_score > F::zero() {
330 self.stiffness_score =
331 (stiff_score - non_stiff_score) / (stiff_score + non_stiff_score).max(F::one());
332 } else {
333 self.stiffness_score = F::zero();
334 }
335 }
336
337 pub fn is_stiff(&self, _current_method_is_stiff: bool, steps_sinceswitch: usize) -> bool {
339 if steps_sinceswitch < self.config.min_steps_before_switch {
341 return _current_method_is_stiff;
342 }
343
344 if self.config.method == StiffnessDetectionMethod::Combined {
346 if _current_method_is_stiff {
347 return self.stiffness_score > F::from_f64(-0.3).unwrap();
350 } else {
351 return self.stiffness_score > F::from_f64(0.2).unwrap();
354 }
355 }
356
357 if _current_method_is_stiff {
359 self.non_stiffness_indicators >= self.config.non_stiffness_threshold
361 } else {
362 self.stiffness_indicators >= self.config.stiffness_threshold
364 }
365 }
366
367 pub fn reset_after_switch(&mut self) {
369 self.stiffness_indicators = 0;
370 self.non_stiffness_indicators = 0;
371 self.stiffness_score = F::zero();
372 }
374
375 pub fn stiffness_score(&self) -> F {
377 self.stiffness_score
378 }
379
380 pub fn estimate_stiffness_from_jacobian(&mut self, jacobian: &Array2<F>) -> F {
382 let n = jacobian.nrows();
386 if n == 0 {
387 return F::one();
388 }
389
390 let max_eigenval_magnitude = self.estimate_largest_eigenvalue_magnitude(jacobian);
392
393 let min_eigenval_magnitude = self.estimate_smallest_eigenvalue_magnitude(jacobian);
395
396 let stiffness_ratio = if min_eigenval_magnitude > F::from_f64(1e-14).unwrap() {
398 max_eigenval_magnitude / min_eigenval_magnitude
399 } else {
400 F::from_f64(1e6).unwrap()
402 };
403
404 self.stiffness_ratio = stiffness_ratio;
405
406 if stiffness_ratio > F::from_f64(100.0).unwrap() {
408 self.stiffness_indicators += 1;
409 } else if stiffness_ratio < F::from_f64(10.0).unwrap() {
410 self.non_stiffness_indicators += 1;
411 }
412
413 stiffness_ratio
414 }
415
416 fn estimate_largest_eigenvalue_magnitude(&self, jacobian: &Array2<F>) -> F {
418 let n = jacobian.nrows();
419 let max_iterations = 20;
420 let tolerance = F::from_f64(1e-6).unwrap();
421
422 let mut v = Array1::<F>::from_elem(n, F::one());
424
425 let mut norm = (v.dot(&v)).sqrt();
427 if norm > F::from_f64(1e-14).unwrap() {
428 v = &v / norm;
429 }
430
431 let mut eigenvalue = F::zero();
432
433 for _ in 0..max_iterations {
434 let mut v_new = Array1::<F>::zeros(n);
436 for i in 0..n {
437 for j in 0..n {
438 v_new[i] += jacobian[[i, j]] * v[j];
439 }
440 }
441
442 let new_eigenvalue = v.dot(&v_new);
444
445 norm = (v_new.dot(&v_new)).sqrt();
447 if norm > F::from_f64(1e-14).unwrap() {
448 v_new = &v_new / norm;
449 }
450
451 if (new_eigenvalue - eigenvalue).abs() < tolerance {
453 eigenvalue = new_eigenvalue;
454 break;
455 }
456
457 eigenvalue = new_eigenvalue;
458 v = v_new;
459 }
460
461 eigenvalue.abs()
462 }
463
464 fn estimate_smallest_eigenvalue_magnitude(&self, jacobian: &Array2<F>) -> F {
466 let n = jacobian.nrows();
467 let max_iterations = 20;
468 let tolerance = F::from_f64(1e-6).unwrap();
469
470 let mut a_copy = jacobian.clone();
476
477 let regularization = F::from_f64(1e-12).unwrap();
479 for i in 0..n {
480 a_copy[[i, i]] += regularization;
481 }
482
483 let mut v = Array1::<F>::from_elem(n, F::one());
485
486 let mut norm = (v.dot(&v)).sqrt();
488 if norm > F::from_f64(1e-14).unwrap() {
489 v = &v / norm;
490 }
491
492 let mut eigenvalue = F::zero();
493
494 for _ in 0..max_iterations {
495 let mut v_new = v.clone();
498 let damping = F::from_f64(0.1).unwrap();
499
500 for _ in 0..5 {
502 let mut av = Array1::<F>::zeros(n);
503 for i in 0..n {
504 for j in 0..n {
505 av[i] += a_copy[[i, j]] * v_new[j];
506 }
507 }
508
509 for i in 0..n {
510 v_new[i] -= damping * (av[i] - v[i]);
511 }
512 }
513
514 let mut av_new = Array1::<F>::zeros(n);
516 for i in 0..n {
517 for j in 0..n {
518 av_new[i] += jacobian[[i, j]] * v_new[j];
519 }
520 }
521
522 let new_eigenvalue = v_new.dot(&av_new) / v_new.dot(&v_new);
523
524 norm = (v_new.dot(&v_new)).sqrt();
526 if norm > F::from_f64(1e-14).unwrap() {
527 v_new = &v_new / norm;
528 }
529
530 if (new_eigenvalue - eigenvalue).abs() < tolerance {
532 eigenvalue = new_eigenvalue;
533 break;
534 }
535
536 eigenvalue = new_eigenvalue;
537 v = v_new;
538 }
539
540 eigenvalue.abs()
541 }
542}
543
544#[derive(Debug, Clone)]
546pub struct MethodSwitchInfo<F: IntegrateFloat> {
547 pub nonstiff_to_stiff_switches: usize,
549 pub stiff_to_nonstiff_switches: usize,
551 pub stiffness_scores: Vec<F>,
553 pub switch_steps: Vec<usize>,
555 pub switch_reasons: Vec<String>,
557}
558
559impl<F: IntegrateFloat> Default for MethodSwitchInfo<F> {
560 fn default() -> Self {
561 Self::new()
562 }
563}
564
565impl<F: IntegrateFloat> MethodSwitchInfo<F> {
566 pub fn new() -> Self {
568 MethodSwitchInfo {
569 nonstiff_to_stiff_switches: 0,
570 stiff_to_nonstiff_switches: 0,
571 stiffness_scores: Vec::new(),
572 switch_steps: Vec::new(),
573 switch_reasons: Vec::new(),
574 }
575 }
576
577 pub fn record_switch(
579 &mut self,
580 from_stiff: bool,
581 step: usize,
582 stiffness_score: F,
583 reason: &str,
584 ) {
585 if from_stiff {
586 self.stiff_to_nonstiff_switches += 1;
587 } else {
588 self.nonstiff_to_stiff_switches += 1;
589 }
590
591 self.stiffness_scores.push(stiffness_score);
592 self.switch_steps.push(step);
593 self.switch_reasons.push(reason.to_string());
594 }
595
596 pub fn summary(&self) -> String {
598 format!(
599 "Method switching summary: {} non-stiff to stiff, {} stiff to non-stiff",
600 self.nonstiff_to_stiff_switches, self.stiff_to_nonstiff_switches
601 )
602 }
603}