use crate::error::{SolverError, SolverResult};
use super::types::{OdeConfig, OdeSolution, OdeSystem};
use super::utils::{numerical_jacobian, solve_dense_system, validate_ode_inputs, vec_norm};
pub struct ImplicitEulerSolver;
impl ImplicitEulerSolver {
const MAX_NEWTON: usize = 50;
const NEWTON_TOL: f64 = 1e-12;
pub fn solve(
system: &dyn OdeSystem,
y0: &[f64],
config: &OdeConfig,
) -> SolverResult<OdeSolution> {
let n = system.dim();
validate_ode_inputs(n, y0, config)?;
let mut t = config.t_start;
let dt = config.dt;
let mut y = y0.to_vec();
let mut times = vec![t];
let mut states = vec![y.clone()];
let mut num_steps = 0_usize;
let mut num_rhs = 0_usize;
while t < config.t_end - dt * 1e-10 && num_steps < config.max_steps {
let h = dt.min(config.t_end - t);
let t_new = t + h;
let mut y_new = y.clone();
let mut f_val = vec![0.0; n];
system.rhs(t, &y, &mut f_val)?;
num_rhs += 1;
for i in 0..n {
y_new[i] = y[i] + h * f_val[i];
}
let mut converged = false;
for _ in 0..Self::MAX_NEWTON {
system.rhs(t_new, &y_new, &mut f_val)?;
num_rhs += 1;
let mut residual = vec![0.0; n];
for i in 0..n {
residual[i] = y_new[i] - y[i] - h * f_val[i];
}
let res_norm = vec_norm(&residual);
if res_norm < Self::NEWTON_TOL {
converged = true;
break;
}
let jac_f = numerical_jacobian(system, t_new, &y_new, 1e-8)?;
num_rhs += n;
let mut jac_g = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
jac_g[i][j] = -h * jac_f[i][j];
if i == j {
jac_g[i][j] += 1.0;
}
}
}
let delta =
solve_dense_system(&jac_g, &residual.iter().map(|r| -r).collect::<Vec<_>>())?;
for i in 0..n {
y_new[i] += delta[i];
}
}
if !converged {
return Err(SolverError::ConvergenceFailure {
iterations: Self::MAX_NEWTON as u32,
residual: {
system.rhs(t_new, &y_new, &mut f_val)?;
let mut res = vec![0.0; n];
for i in 0..n {
res[i] = y_new[i] - y[i] - h * f_val[i];
}
vec_norm(&res)
},
});
}
y.copy_from_slice(&y_new);
t = t_new;
num_steps += 1;
times.push(t);
states.push(y.clone());
}
Ok(OdeSolution {
times,
states,
num_steps,
num_rejected: 0,
num_rhs_evals: num_rhs,
})
}
}
pub struct Bdf2Solver;
impl Bdf2Solver {
const MAX_NEWTON: usize = 50;
const NEWTON_TOL: f64 = 1e-12;
pub fn solve(
system: &dyn OdeSystem,
y0: &[f64],
config: &OdeConfig,
) -> SolverResult<OdeSolution> {
let n = system.dim();
validate_ode_inputs(n, y0, config)?;
let dt = config.dt;
let mut t = config.t_start;
let mut y_prev = y0.to_vec();
let mut times = vec![t];
let mut states = vec![y_prev.clone()];
let mut num_steps = 0_usize;
let mut num_rhs = 0_usize;
let h = dt.min(config.t_end - t);
if t >= config.t_end - dt * 1e-10 {
return Ok(OdeSolution {
times,
states,
num_steps: 0,
num_rejected: 0,
num_rhs_evals: 0,
});
}
let mut y_cur = y_prev.clone();
let mut f_val = vec![0.0; n];
{
system.rhs(t, &y_prev, &mut f_val)?;
num_rhs += 1;
for i in 0..n {
y_cur[i] = y_prev[i] + h * f_val[i];
}
let t_new = t + h;
let mut converged = false;
for _ in 0..Self::MAX_NEWTON {
system.rhs(t_new, &y_cur, &mut f_val)?;
num_rhs += 1;
let mut residual = vec![0.0; n];
for i in 0..n {
residual[i] = y_cur[i] - y_prev[i] - h * f_val[i];
}
if vec_norm(&residual) < Self::NEWTON_TOL {
converged = true;
break;
}
let jac_f = numerical_jacobian(system, t_new, &y_cur, 1e-8)?;
num_rhs += n;
let mut jac_g = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
jac_g[i][j] = -h * jac_f[i][j];
if i == j {
jac_g[i][j] += 1.0;
}
}
}
let neg_res: Vec<f64> = residual.iter().map(|r| -r).collect();
let delta = solve_dense_system(&jac_g, &neg_res)?;
for i in 0..n {
y_cur[i] += delta[i];
}
}
if !converged {
return Err(SolverError::ConvergenceFailure {
iterations: Self::MAX_NEWTON as u32,
residual: 1.0,
});
}
t = t_new;
num_steps += 1;
times.push(t);
states.push(y_cur.clone());
}
while t < config.t_end - dt * 1e-10 && num_steps < config.max_steps {
let h_step = dt.min(config.t_end - t);
let t_new = t + h_step;
let coeff_h = 2.0 / 3.0 * h_step;
let mut y_new = vec![0.0; n];
for i in 0..n {
y_new[i] = 2.0 * y_cur[i] - y_prev[i];
}
let mut converged = false;
for _ in 0..Self::MAX_NEWTON {
system.rhs(t_new, &y_new, &mut f_val)?;
num_rhs += 1;
let mut residual = vec![0.0; n];
for i in 0..n {
residual[i] = y_new[i] - (4.0 / 3.0) * y_cur[i] + (1.0 / 3.0) * y_prev[i]
- coeff_h * f_val[i];
}
if vec_norm(&residual) < Self::NEWTON_TOL {
converged = true;
break;
}
let jac_f = numerical_jacobian(system, t_new, &y_new, 1e-8)?;
num_rhs += n;
let mut jac_g = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..n {
jac_g[i][j] = -coeff_h * jac_f[i][j];
if i == j {
jac_g[i][j] += 1.0;
}
}
}
let neg_res: Vec<f64> = residual.iter().map(|r| -r).collect();
let delta = solve_dense_system(&jac_g, &neg_res)?;
for i in 0..n {
y_new[i] += delta[i];
}
}
if !converged {
return Err(SolverError::ConvergenceFailure {
iterations: Self::MAX_NEWTON as u32,
residual: 1.0,
});
}
y_prev.copy_from_slice(&y_cur);
y_cur.copy_from_slice(&y_new);
t = t_new;
num_steps += 1;
times.push(t);
states.push(y_cur.clone());
}
Ok(OdeSolution {
times,
states,
num_steps,
num_rejected: 0,
num_rhs_evals: num_rhs,
})
}
}