differential_equations/methods/irk/radau/mod.rs
1//! IRK with Newton solves and adaptive step size.
2
3mod algebraic;
4mod initialize;
5mod interpolate;
6mod ordinary;
7
8use std::marker::PhantomData;
9
10use crate::{
11 linalg::Matrix,
12 methods::{ImplicitRungeKutta, Radau},
13 status::Status,
14 tolerance::Tolerance,
15 traits::{Real, State},
16 utils::constrain_step_size,
17};
18
19/// Constructor for Radau5
20impl<E, T: Real, Y: State<T>> ImplicitRungeKutta<E, Radau, T, Y, 5, 3, 3> {
21 /// Creates a new Radau IIA 3-stage implicit Runge-Kutta method of order 5.
22 ///
23 /// For full usage details, DAE index handling, tuning notes and examples,
24 /// see the documentation on the [`Radau5`] type.
25 pub fn radau5() -> Radau5<E, T, Y> {
26 Radau5::default()
27 }
28}
29
30/// Radau IIA 5th-order implicit Runge–Kutta (3-stage) with Newton solves,
31/// adaptive step-size control and dense (continuous) output.
32///
33/// # Overview
34/// - Solves stiff ODEs and DAEs expressed in the form M·y' = f(t, y).
35/// - Uses a 3-stage Radau IIA collocation of order 5 with embedded error
36/// estimation and optional Gustafsson predictive step controller.
37///
38/// # DAE support and index handling
39/// - This implementation supports index-1, index-2 and index-3 DAE systems.
40/// - For index-2 and index-3 problems the solver needs to know which
41/// equations are algebraic (constraints). Use the builder helpers
42/// `.index2_equations([...])` and `.index3_equations([...])` to
43/// declare the equation indices that correspond to higher-index algebraic
44/// constraints. Supplying this information changes how the mass/jacobian
45/// rows are treated and prevents step-size collapse on higher-index DAEs.
46/// - Indices are 0-based and refer to positions in the state vector `y`.
47///
48/// # Examples
49/// - `examples/dae/01_amplifier` - Amplifier circuit of index-1
50/// - `examples/dae/02_robertson` - stiff chemical kinetics DAE benchmark
51/// - `examples/dae/03_pendulum` - constrained pendulum of index-2
52/// - `examples/ode/13_vanderpol` - Very Stiff Van der Pol oscillator
53///
54/// # Notes
55/// - The real and absolute tolerances are modified during initialization
56/// to ensure proper error control based on other settings. Thus an
57/// inputted `atol` of `1e-6` will become a different value to reflect
58/// the desired accuracy. This matches the original Radau5 implementation.
59///
60/// # References
61/// - Hairer, E., & Wanner, G. (1996). "Solving Ordinary Differential Equations II."
62pub struct Radau5<E, T: Real, Y: State<T>> {
63 // Configuration
64 /// Relative error tolerance for adaptive step size control
65 pub rtol: Tolerance<T>,
66 /// Absolute error tolerance for adaptive step size control
67 pub atol: Tolerance<T>,
68 /// Initial step size (computed automatically if zero)
69 pub h0: T,
70 /// Minimum allowed step size
71 pub h_min: T,
72 /// Maximum allowed step size
73 pub h_max: T,
74 /// Maximum number of integration steps
75 pub max_steps: usize,
76 /// Maximum number of consecutive step rejections
77 pub max_rejects: usize,
78 /// Newton iteration convergence tolerance
79 pub newton_tol: T,
80 /// Maximum Newton iterations per step
81 pub max_newton_iter: usize,
82 /// Safety factor for step size control (typically 0.8-0.9)
83 pub safety_factor: T,
84 /// Minimum step size scaling factor
85 pub min_scale: T,
86 /// Maximum step size scaling factor
87 pub max_scale: T,
88 /// Enable predictive (Gustafsson) step-size controller
89 pub predictive: bool,
90
91 // State
92 /// Current time
93 t: T,
94 /// Current solution vector
95 y: Y,
96 /// Current derivative vector dy/dt
97 dydt: Y,
98 /// Previous time (for interpolation)
99 t_prev: T,
100 /// Previous solution vector
101 y_prev: Y,
102 /// Previous derivative vector
103 dydt_prev: Y,
104 /// Current step size
105 h: T,
106 /// Previous step size
107 h_prev: T,
108 /// Final integration time
109 tf: T,
110
111 // Method constants
112 /// First collocation point: c₁ = (4-√6)/10
113 c1: T,
114 /// Second collocation point: c₂ = (4+√6)/10
115 c2: T,
116 /// Helper constant: c₁ - 1
117 c1m1: T,
118 /// Helper constant: c₂ - 1
119 c2m1: T,
120 /// Helper constant: c₁ - c₂
121 c1mc2: T,
122 /// Error estimation coefficient 1
123 dd1: T,
124 /// Error estimation coefficient 2
125 dd2: T,
126 /// Error estimation coefficient 3
127 dd3: T,
128 /// Real system coefficient: 1/u₁
129 u1: T,
130 /// Complex system real part coefficient
131 alph: T,
132 /// Complex system imaginary part coefficient
133 beta: T,
134
135 /// Transformation matrix T (3×3) for stage variables
136 tmat: Matrix<T>,
137 /// Inverse transformation matrix T⁻¹ (3×3)
138 tinv: Matrix<T>,
139
140 // Workspace
141 /// Stage solution vectors: y + Zᵢ at collocation points
142 z: [Y; 3],
143 /// Stage derivative estimates
144 k: [Y; 3],
145 /// Right-hand side evaluations at stages
146 f: [Y; 3],
147 /// Jacobian matrix ∂f/∂y
148 jacobian: Matrix<T>,
149 /// Age of current Jacobian
150 jacobian_age: usize,
151 /// Solving linear system workspaces
152 /// a: n2 x n2 dense matrix, b: length-n2 RHS vector
153 a: Matrix<T>,
154 b: Vec<T>,
155
156 // Newton convergence control
157 /// Newton convergence factor
158 faccon: T,
159 /// Previous Newton norm
160 dynold: T,
161 /// Newton convergence rate
162 theta: T,
163 /// Decides whether the jacobian should be recomputed;
164 thet: T,
165 /// Previous convergence quotient
166 thqold: T,
167
168 // Sophisticated step size control
169 /// Composite safety factor used in step-size control: safety_factor * (1 + 2*max_newton_iter)
170 /// Users can override this; default is derived from safety_factor and max_newton_iter.
171 cfac: T,
172 /// Minimum clamp factor for step-size change (e.g., 1/8).
173 facr: T,
174 /// Maximum clamp factor for step-size change (e.g., 5.0).
175 facl: T,
176 /// Rounding Unit
177 uround: T,
178 /// Error scale, scal = atol + rtol * abs(y)
179 scal: Y,
180 /// quot1 < hnew/hold < quot2
181 quot1: T,
182 quot2: T,
183 /// Factor for new step size
184 hhfac: T,
185
186 // Linear system matrices
187 /// Real system matrix: (M - h*u₁*J) for first linear system
188 e1: Matrix<T>,
189 /// Complex system real part: (M - h*α*J)
190 e2r: Matrix<T>,
191 /// Complex system imaginary part: (-h*β*J)
192 e2i: Matrix<T>,
193
194 // Pivot vectors from LU decomposition
195 /// Pivot vector for real system E1
196 ip1: Vec<usize>,
197 /// Pivot vector for complex system E2
198 ip2: Vec<usize>,
199
200 /// Mass matrix M(t,y) for DAE systems
201 mass: Matrix<T>,
202 /// Indexs in state vector at which index two algebraic equations are located
203 index2: Vec<usize>,
204 /// Indexs in state vector at which index three algebraic equations are located
205 index3: Vec<usize>,
206
207 // Statistics
208 /// Count of consecutive singular matrix encounters
209 singular_count: usize,
210 /// Total integration steps taken
211 steps: usize,
212 /// Total step rejections
213 rejects: usize,
214 /// Total accepted steps (for Gustafsson's controller)
215 n_accepted: usize,
216 /// Current solver status
217 status: Status<T, Y>,
218
219 // Dense output
220 /// Continuous output coefficients for dense output polynomial
221 cont: [Y; 4],
222
223 // Control flags
224 /// True for the first integration step
225 first: bool,
226 /// True when last step was rejected
227 reject: bool,
228 /// Routing flag for Jacobian computation
229 call_jac: bool,
230 /// Routing flag for Jacobian decomposition
231 call_decomp: bool,
232
233 // Gustafsson controller
234 /// Last accepted step size HACC
235 h_acc: T,
236 /// Last accepted error ERRACC
237 err_acc: T,
238
239 /// Equation type
240 equation: PhantomData<E>,
241}
242
243impl<E, T: Real, Y: State<T>> Default for Radau5<E, T, Y> {
244 fn default() -> Self {
245 // Radau IIA(5) constants
246 let c1_t = T::from_f64(0.155_051_025_721_682_2).unwrap();
247 let c2_t = T::from_f64(0.644_948_974_278_317_8).unwrap();
248 let c1m1 = T::from_f64(-0.844_948_974_278_317_8).unwrap();
249 let c2m1 = T::from_f64(-0.355_051_025_721_682_2).unwrap();
250 let c1mc2 = T::from_f64(-0.489_897_948_556_635_6).unwrap();
251
252 let dd1 = T::from_f64(-10.048_809_399_827_416).unwrap();
253 let dd2 = T::from_f64(1.382_142_733_160_749).unwrap();
254 let dd3 = T::from_f64(-0.333_333_333_333_333_3).unwrap();
255
256 let u1 = T::from_f64(3.637_834_252_744_496).unwrap();
257 let alph = T::from_f64(2.681_082_873_627_752_3).unwrap();
258 let beta = T::from_f64(3.050_430_199_247_410_5).unwrap();
259
260 // Transformation matrices
261 let mut tmat = Matrix::zeros(3, 3);
262 tmat[(0, 0)] = T::from_f64(9.123_239_487_089_295E-2).unwrap();
263 tmat[(0, 1)] = T::from_f64(-1.412_552_950_209_542E-1).unwrap();
264 tmat[(0, 2)] = T::from_f64(-3.002_919_410_514_742_4E-2).unwrap();
265 tmat[(1, 0)] = T::from_f64(2.417_179_327_071_07E-1).unwrap();
266 tmat[(1, 1)] = T::from_f64(2.041_293_522_937_999_4E-1).unwrap();
267 tmat[(1, 2)] = T::from_f64(3.829_421_127_572_619E-1).unwrap();
268 tmat[(2, 0)] = T::from_f64(9.660_481_826_150_93E-1).unwrap();
269
270 let mut tinv = Matrix::zeros(3, 3);
271 tinv[(0, 0)] = T::from_f64(4.325_579_890_063_155).unwrap();
272 tinv[(0, 1)] = T::from_f64(3.391_992_518_158_098_4E-1).unwrap();
273 tinv[(0, 2)] = T::from_f64(5.417_705_399_358_749E-1).unwrap();
274 tinv[(1, 0)] = T::from_f64(-4.178_718_591_551_905).unwrap();
275 tinv[(1, 1)] = T::from_f64(-3.276_828_207_610_623_7E-1).unwrap();
276 tinv[(1, 2)] = T::from_f64(4.766_235_545_005_504_4E-1).unwrap();
277 tinv[(2, 0)] = T::from_f64(-5.028_726_349_457_868E-1).unwrap();
278 tinv[(2, 1)] = T::from_f64(2.571_926_949_855_605).unwrap();
279 tinv[(2, 2)] = T::from_f64(-5.960_392_048_282_249E-1).unwrap();
280
281 // Step-size controller and Newton tolerance
282 let safety_factor = T::from_f64(0.9).unwrap();
283 let max_newton_iter_usize: usize = 7;
284 let cfac_default = T::from_f64(13.5).unwrap();
285 let facl_default = T::from_f64(5.0).unwrap();
286 let facr_default = T::from_f64(0.125).unwrap();
287
288 let rtol_default = T::from_f64(0.000001).unwrap();
289 let atol_default = T::from_f64(0.000001).unwrap();
290 let uround = T::from_f64(1e-16).unwrap();
291 let newton_tol_default = T::from_f64(0.003_162_277_660_168_379_4).unwrap();
292
293 Self {
294 // Settings
295 rtol: Tolerance::Scalar(rtol_default),
296 atol: Tolerance::Scalar(atol_default),
297 h0: T::zero(),
298 h_min: T::zero(),
299 h_max: T::infinity(),
300 max_steps: 100_000,
301 max_rejects: 20,
302 newton_tol: newton_tol_default,
303 max_newton_iter: max_newton_iter_usize,
304 safety_factor,
305 cfac: cfac_default,
306 facl: facl_default,
307 facr: facr_default,
308 min_scale: T::from_f64(0.2).unwrap(),
309 max_scale: T::from_f64(8.0).unwrap(),
310
311 // Algorithm toggles
312 predictive: true,
313
314 // State
315 t: T::zero(),
316 y: Y::zeros(),
317 dydt: Y::zeros(),
318 t_prev: T::zero(),
319 y_prev: Y::zeros(),
320 dydt_prev: Y::zeros(),
321 h: T::zero(),
322 h_prev: T::zero(),
323 tf: T::zero(),
324
325 // Method constants
326 c1: c1_t,
327 c2: c2_t,
328 c1m1,
329 c2m1,
330 c1mc2,
331 dd1,
332 dd2,
333 dd3,
334 u1,
335 alph,
336 beta,
337 tmat,
338 tinv,
339
340 // Workspace
341 z: [Y::zeros(); 3],
342 k: [Y::zeros(); 3],
343 f: [Y::zeros(); 3],
344 jacobian: Matrix::zeros(0, 0),
345 jacobian_age: 0,
346 a: Matrix::zeros(0, 0),
347 b: Vec::new(),
348
349 // Newton convergence control
350 faccon: T::one(),
351 dynold: T::from_f64(1e-16).unwrap(),
352 theta: T::zero(),
353 thet: T::from_f64(0.001).unwrap(),
354 thqold: T::one(),
355
356 // Step size control variables
357 uround,
358 scal: Y::zeros(),
359 quot1: T::one(),
360 quot2: T::from_f64(1.2).unwrap(),
361 hhfac: T::zero(),
362
363 // Linear system matrices
364 e1: Matrix::zeros(0, 0),
365 e2r: Matrix::zeros(0, 0),
366 e2i: Matrix::zeros(0, 0),
367
368 // Pivot vectors
369 ip1: Vec::new(),
370 ip2: Vec::new(),
371
372 // Mass matrix
373 mass: Matrix::identity(0),
374 index2: Vec::new(),
375 index3: Vec::new(),
376
377 // Dense output coefficients
378 cont: [Y::zeros(); 4],
379
380 // Error recovery
381 singular_count: 0,
382
383 // Statistics
384 steps: 0,
385 rejects: 0,
386 n_accepted: 0,
387 status: Status::Uninitialized,
388
389 // Control flags
390 first: true,
391 reject: false,
392 call_jac: true,
393 call_decomp: true,
394
395 // Predictive controller defaults
396 h_acc: T::zero(),
397 err_acc: T::from_f64(1e-2).unwrap(),
398
399 // Equation type
400 equation: PhantomData,
401 }
402 }
403}
404
405impl<E, T: Real, Y: State<T>> Radau5<E, T, Y> {
406 // Builder methods
407 /// Set the relative tolerance for the solver.
408 pub fn rtol<V: Into<Tolerance<T>>>(mut self, rtol: V) -> Self {
409 self.rtol = rtol.into();
410 self
411 }
412
413 /// Set the absolute tolerance for the solver.
414 pub fn atol<V: Into<Tolerance<T>>>(mut self, atol: V) -> Self {
415 self.atol = atol.into();
416 self
417 }
418
419 /// Set the initial step size for the solver.
420 pub fn h0(mut self, h0: T) -> Self {
421 self.h0 = h0;
422 self
423 }
424
425 /// Set the minimum step size for the solver.
426 pub fn h_min(mut self, h_min: T) -> Self {
427 self.h_min = h_min;
428 self
429 }
430
431 /// Set the maximum step size for the solver.
432 pub fn h_max(mut self, h_max: T) -> Self {
433 self.h_max = h_max;
434 self
435 }
436
437 /// Set the minimum scale factor for the solver.
438 pub fn min_scale(mut self, min_scale: T) -> Self {
439 self.min_scale = min_scale;
440 self.facl = T::one() / min_scale;
441 self
442 }
443
444 /// Set the maximum scale factor for the solver.
445 pub fn max_scale(mut self, max_scale: T) -> Self {
446 self.max_scale = max_scale;
447 self.facr = T::one() / max_scale;
448 self
449 }
450
451 /// Enable/disable predictive (Gustafsson) step-size controller.
452 pub fn predictive(mut self, enabled: bool) -> Self {
453 self.predictive = enabled;
454 self
455 }
456
457 /// Set the maximum number of steps for the solver.
458 pub fn max_steps(mut self, n: usize) -> Self {
459 self.max_steps = n;
460 self
461 }
462
463 /// Set the maximum number of rejected steps for the solver.
464 pub fn max_rejects(mut self, n: usize) -> Self {
465 self.max_rejects = n;
466 self
467 }
468
469 /// Set the Newton tolerance for the solver.
470 pub fn newton_tol(mut self, tol: T) -> Self {
471 self.newton_tol = tol;
472 self
473 }
474
475 /// Set the safety factor for the solver.
476 pub fn safety_factor(mut self, sf: T) -> Self {
477 self.safety_factor = sf;
478 self
479 }
480
481 /// Set the maximum number of Newton iterations for the solver.
482 pub fn max_newton_iter(mut self, n: usize) -> Self {
483 self.max_newton_iter = n;
484 self
485 }
486
487 /// Indexes in the state vector of the index two algebraic equations
488 /// If this isn not set DAE's with index two equation will likely
489 /// cause step-size/error issues leading to a failed solve.
490 pub fn index2_equations<Idxs>(mut self, idxs: Idxs) -> Self
491 where
492 Idxs: Into<Vec<usize>>,
493 {
494 self.index2 = idxs.into();
495 self
496 }
497
498 /// Indexes in the state vector of the index three algebraic equations
499 /// If this isn not set DAE's with index three equation will likely
500 /// cause step-size/error issues leading to a failed solve.
501 pub fn index3_equations<Idxs>(mut self, idxs: Idxs) -> Self
502 where
503 Idxs: Into<Vec<usize>>,
504 {
505 self.index3 = idxs.into();
506 self
507 }
508
509 /// Handle unexpected step rejection
510 fn unexpected_step_rejection(&mut self) {
511 self.hhfac = T::from_f64(0.5).unwrap();
512 self.h = constrain_step_size(self.h * self.hhfac, self.h_min, self.h_max);
513 self.reject = true;
514 self.status = Status::RejectedStep;
515 }
516}