Skip to main content

oxicuda_solver/dense/ode_pde/
implicit.rs

1//! Implicit ODE solvers: Backward Euler, BDF2.
2
3use crate::error::{SolverError, SolverResult};
4
5use super::types::{OdeConfig, OdeSolution, OdeSystem};
6use super::utils::{numerical_jacobian, solve_dense_system, validate_ode_inputs, vec_norm};
7
8// =========================================================================
9// Implicit Euler
10// =========================================================================
11
12/// Backward (implicit) Euler solver — suitable for stiff systems.
13pub struct ImplicitEulerSolver;
14
15impl ImplicitEulerSolver {
16    /// Maximum Newton iterations per time step.
17    const MAX_NEWTON: usize = 50;
18    /// Newton convergence tolerance.
19    const NEWTON_TOL: f64 = 1e-12;
20
21    /// Integrate using the backward Euler method with Newton iteration.
22    pub fn solve(
23        system: &dyn OdeSystem,
24        y0: &[f64],
25        config: &OdeConfig,
26    ) -> SolverResult<OdeSolution> {
27        let n = system.dim();
28        validate_ode_inputs(n, y0, config)?;
29
30        let mut t = config.t_start;
31        let dt = config.dt;
32        let mut y = y0.to_vec();
33
34        let mut times = vec![t];
35        let mut states = vec![y.clone()];
36        let mut num_steps = 0_usize;
37        let mut num_rhs = 0_usize;
38
39        while t < config.t_end - dt * 1e-10 && num_steps < config.max_steps {
40            let h = dt.min(config.t_end - t);
41            let t_new = t + h;
42
43            // Newton iteration to solve: y_new - y - h*f(t_new, y_new) = 0
44            // Initial guess: forward Euler
45            let mut y_new = y.clone();
46            let mut f_val = vec![0.0; n];
47            system.rhs(t, &y, &mut f_val)?;
48            num_rhs += 1;
49            for i in 0..n {
50                y_new[i] = y[i] + h * f_val[i];
51            }
52
53            let mut converged = false;
54            for _ in 0..Self::MAX_NEWTON {
55                // Evaluate residual: G(y_new) = y_new - y - h*f(t_new, y_new)
56                system.rhs(t_new, &y_new, &mut f_val)?;
57                num_rhs += 1;
58
59                let mut residual = vec![0.0; n];
60                for i in 0..n {
61                    residual[i] = y_new[i] - y[i] - h * f_val[i];
62                }
63
64                let res_norm = vec_norm(&residual);
65                if res_norm < Self::NEWTON_TOL {
66                    converged = true;
67                    break;
68                }
69
70                // Numerical Jacobian of G: J_G = I - h * J_f
71                let jac_f = numerical_jacobian(system, t_new, &y_new, 1e-8)?;
72                num_rhs += n; // Jacobian evaluates rhs n times
73
74                // Build J_G = I - h*J_f
75                let mut jac_g = vec![vec![0.0; n]; n];
76                for i in 0..n {
77                    for j in 0..n {
78                        jac_g[i][j] = -h * jac_f[i][j];
79                        if i == j {
80                            jac_g[i][j] += 1.0;
81                        }
82                    }
83                }
84
85                // Solve J_G * delta = -residual using Gaussian elimination
86                let delta =
87                    solve_dense_system(&jac_g, &residual.iter().map(|r| -r).collect::<Vec<_>>())?;
88
89                for i in 0..n {
90                    y_new[i] += delta[i];
91                }
92            }
93
94            if !converged {
95                return Err(SolverError::ConvergenceFailure {
96                    iterations: Self::MAX_NEWTON as u32,
97                    residual: {
98                        system.rhs(t_new, &y_new, &mut f_val)?;
99                        let mut res = vec![0.0; n];
100                        for i in 0..n {
101                            res[i] = y_new[i] - y[i] - h * f_val[i];
102                        }
103                        vec_norm(&res)
104                    },
105                });
106            }
107
108            y.copy_from_slice(&y_new);
109            t = t_new;
110            num_steps += 1;
111
112            times.push(t);
113            states.push(y.clone());
114        }
115
116        Ok(OdeSolution {
117            times,
118            states,
119            num_steps,
120            num_rejected: 0,
121            num_rhs_evals: num_rhs,
122        })
123    }
124}
125
126// =========================================================================
127// BDF2
128// =========================================================================
129
130/// Second-order backward differentiation formula (BDF2) solver.
131pub struct Bdf2Solver;
132
133impl Bdf2Solver {
134    /// Maximum Newton iterations per step.
135    const MAX_NEWTON: usize = 50;
136    /// Newton convergence tolerance.
137    const NEWTON_TOL: f64 = 1e-12;
138
139    /// Integrate using BDF2. The first step is bootstrapped with implicit Euler.
140    ///
141    /// BDF2 formula: y_{n+1} = (4/3)*y_n - (1/3)*y_{n-1} + (2/3)*h*f(t_{n+1}, y_{n+1})
142    pub fn solve(
143        system: &dyn OdeSystem,
144        y0: &[f64],
145        config: &OdeConfig,
146    ) -> SolverResult<OdeSolution> {
147        let n = system.dim();
148        validate_ode_inputs(n, y0, config)?;
149
150        let dt = config.dt;
151        let mut t = config.t_start;
152        let mut y_prev = y0.to_vec();
153
154        let mut times = vec![t];
155        let mut states = vec![y_prev.clone()];
156        let mut num_steps = 0_usize;
157        let mut num_rhs = 0_usize;
158
159        // Bootstrap: take one implicit Euler step to get y_1
160        let h = dt.min(config.t_end - t);
161        if t >= config.t_end - dt * 1e-10 {
162            return Ok(OdeSolution {
163                times,
164                states,
165                num_steps: 0,
166                num_rejected: 0,
167                num_rhs_evals: 0,
168            });
169        }
170
171        let mut y_cur = y_prev.clone();
172        let mut f_val = vec![0.0; n];
173        {
174            // Forward Euler initial guess
175            system.rhs(t, &y_prev, &mut f_val)?;
176            num_rhs += 1;
177            for i in 0..n {
178                y_cur[i] = y_prev[i] + h * f_val[i];
179            }
180
181            let t_new = t + h;
182            let mut converged = false;
183            for _ in 0..Self::MAX_NEWTON {
184                system.rhs(t_new, &y_cur, &mut f_val)?;
185                num_rhs += 1;
186
187                let mut residual = vec![0.0; n];
188                for i in 0..n {
189                    residual[i] = y_cur[i] - y_prev[i] - h * f_val[i];
190                }
191
192                if vec_norm(&residual) < Self::NEWTON_TOL {
193                    converged = true;
194                    break;
195                }
196
197                let jac_f = numerical_jacobian(system, t_new, &y_cur, 1e-8)?;
198                num_rhs += n;
199
200                let mut jac_g = vec![vec![0.0; n]; n];
201                for i in 0..n {
202                    for j in 0..n {
203                        jac_g[i][j] = -h * jac_f[i][j];
204                        if i == j {
205                            jac_g[i][j] += 1.0;
206                        }
207                    }
208                }
209
210                let neg_res: Vec<f64> = residual.iter().map(|r| -r).collect();
211                let delta = solve_dense_system(&jac_g, &neg_res)?;
212                for i in 0..n {
213                    y_cur[i] += delta[i];
214                }
215            }
216
217            if !converged {
218                return Err(SolverError::ConvergenceFailure {
219                    iterations: Self::MAX_NEWTON as u32,
220                    residual: 1.0,
221                });
222            }
223
224            t = t_new;
225            num_steps += 1;
226            times.push(t);
227            states.push(y_cur.clone());
228        }
229
230        // BDF2 steps
231        while t < config.t_end - dt * 1e-10 && num_steps < config.max_steps {
232            let h_step = dt.min(config.t_end - t);
233            let t_new = t + h_step;
234
235            // BDF2: y_{n+1} = (4/3)*y_n - (1/3)*y_{n-1} + (2/3)*h*f(t_{n+1}, y_{n+1})
236            // Residual: y_{n+1} - (4/3)*y_n + (1/3)*y_{n-1} - (2/3)*h*f(t_{n+1}, y_{n+1}) = 0
237            let coeff_h = 2.0 / 3.0 * h_step;
238
239            // Initial guess: extrapolation
240            let mut y_new = vec![0.0; n];
241            for i in 0..n {
242                y_new[i] = 2.0 * y_cur[i] - y_prev[i];
243            }
244
245            let mut converged = false;
246            for _ in 0..Self::MAX_NEWTON {
247                system.rhs(t_new, &y_new, &mut f_val)?;
248                num_rhs += 1;
249
250                let mut residual = vec![0.0; n];
251                for i in 0..n {
252                    residual[i] = y_new[i] - (4.0 / 3.0) * y_cur[i] + (1.0 / 3.0) * y_prev[i]
253                        - coeff_h * f_val[i];
254                }
255
256                if vec_norm(&residual) < Self::NEWTON_TOL {
257                    converged = true;
258                    break;
259                }
260
261                let jac_f = numerical_jacobian(system, t_new, &y_new, 1e-8)?;
262                num_rhs += n;
263
264                let mut jac_g = vec![vec![0.0; n]; n];
265                for i in 0..n {
266                    for j in 0..n {
267                        jac_g[i][j] = -coeff_h * jac_f[i][j];
268                        if i == j {
269                            jac_g[i][j] += 1.0;
270                        }
271                    }
272                }
273
274                let neg_res: Vec<f64> = residual.iter().map(|r| -r).collect();
275                let delta = solve_dense_system(&jac_g, &neg_res)?;
276                for i in 0..n {
277                    y_new[i] += delta[i];
278                }
279            }
280
281            if !converged {
282                return Err(SolverError::ConvergenceFailure {
283                    iterations: Self::MAX_NEWTON as u32,
284                    residual: 1.0,
285                });
286            }
287
288            y_prev.copy_from_slice(&y_cur);
289            y_cur.copy_from_slice(&y_new);
290            t = t_new;
291            num_steps += 1;
292            times.push(t);
293            states.push(y_cur.clone());
294        }
295
296        Ok(OdeSolution {
297            times,
298            states,
299            num_steps,
300            num_rejected: 0,
301            num_rhs_evals: num_rhs,
302        })
303    }
304}