#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IntegrationMethod {
#[default]
ImplicitAdamsNonStiff,
ImplicitBackDiffStiff,
}
impl IntegrationMethod {
pub fn max_method_order(self) -> usize {
match self {
IntegrationMethod::ImplicitAdamsNonStiff => 12,
IntegrationMethod::ImplicitBackDiffStiff => 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CorrectorMethod {
#[default]
FunctionalIteration,
NewtonIterInternalJac,
JacobiNewtonInternalJac,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LsodeConfig {
pub method: IntegrationMethod,
pub corrector: CorrectorMethod,
pub max_order: usize,
pub rel_tolerance: f64,
pub abs_tolerance: f64,
pub min_step_size: f64,
pub max_step_size: f64,
pub initial_step_size: f64,
pub max_num_steps: usize,
pub max_correction_iters: usize,
pub max_num_conv_failure: usize,
pub allow_non_convergence: bool,
}
impl Default for LsodeConfig {
fn default() -> Self {
Self {
method: IntegrationMethod::ImplicitAdamsNonStiff,
corrector: CorrectorMethod::FunctionalIteration,
max_order: 12,
rel_tolerance: 1.0e-9,
abs_tolerance: 1.0e-9,
min_step_size: 0.0,
max_step_size: 0.0,
initial_step_size: 0.0,
max_num_steps: 500,
max_correction_iters: 3,
max_num_conv_failure: 10,
allow_non_convergence: false,
}
}
}
impl LsodeConfig {
pub fn effective_max_order(&self) -> usize {
self.max_order.min(self.method.max_method_order()).max(1)
}
pub fn check(&self) {
assert!(
self.max_order >= 1 && self.max_order <= self.method.max_method_order(),
"LsodeConfig.max_order = {} out of range [1, {}] for {:?}: set max_order within \
the method family's cap (Adams 12 / BDF 5).",
self.max_order,
self.method.max_method_order(),
self.method
);
assert!(
self.rel_tolerance.is_finite() && self.rel_tolerance >= 0.0,
"LsodeConfig.rel_tolerance = {} must be finite and ≥ 0.",
self.rel_tolerance
);
assert!(
self.abs_tolerance.is_finite() && self.abs_tolerance >= 0.0,
"LsodeConfig.abs_tolerance = {} must be finite and ≥ 0.",
self.abs_tolerance
);
assert!(
self.rel_tolerance > 0.0 || self.abs_tolerance > 0.0,
"LsodeConfig: at least one of rel_tolerance / abs_tolerance must be > 0 \
(both zero makes the error weight identically zero)."
);
assert!(
self.method == IntegrationMethod::ImplicitAdamsNonStiff
|| self.corrector != CorrectorMethod::FunctionalIteration,
"LsodeConfig: the stiff BDF family requires a Newton corrector (with a Jacobian), \
not FunctionalIteration. Choose NewtonIterInternalJac, \
or use the ImplicitAdamsNonStiff family for functional iteration."
);
assert!(
self.corrector != CorrectorMethod::JacobiNewtonInternalJac,
"LsodeConfig: the diagonal Jacobi-Newton corrector (MITER=3, \
JacobiNewtonInternalJac) is not yet ported. Use NewtonIterInternalJac (dense \
finite-difference Newton) for the stiff BDF family, or the ImplicitAdamsNonStiff \
family with FunctionalIteration."
);
for (name, v) in [
("min_step_size", self.min_step_size),
("max_step_size", self.max_step_size),
("initial_step_size", self.initial_step_size),
] {
assert!(
v.is_finite() && v >= 0.0,
"LsodeConfig.{name} = {v} must be finite and ≥ 0 (0 means unset)."
);
}
assert!(
self.max_num_steps >= 1 && self.max_correction_iters >= 1,
"LsodeConfig: max_num_steps ({}) and max_correction_iters ({}) must be ≥ 1.",
self.max_num_steps,
self.max_correction_iters
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "max_order")]
fn ig_17_rejects_out_of_range_order() {
LsodeConfig {
max_order: 0,
..LsodeConfig::default()
}
.check();
}
#[test]
#[should_panic(expected = "at least one")]
fn ig_20_rejects_both_tolerances_zero() {
LsodeConfig {
rel_tolerance: 0.0,
abs_tolerance: 0.0,
..LsodeConfig::default()
}
.check();
}
#[test]
fn run_lsode_config_validates() {
LsodeConfig::default().check();
LsodeConfig {
rel_tolerance: 2.3e-16,
abs_tolerance: 0.0,
..LsodeConfig::default()
}
.check();
}
}