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}