#[derive(Clone, Copy, Debug)]
pub struct RiddersConfig {
pub initial_step: f64,
pub shrink: f64,
pub rungs: usize,
}
impl Default for RiddersConfig {
fn default() -> Self {
Self {
initial_step: 1.0e-2,
shrink: 2.0,
rungs: 12,
}
}
}
#[derive(Clone, Debug)]
pub struct FdDerivative {
pub value: f64,
pub uncertainty: f64,
pub step: f64,
pub order: usize,
pub ladder: Vec<(f64, f64)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FdVerdict {
Agree,
Disagree,
Unresolved,
}
impl FdDerivative {
pub fn self_band(&self, rel_tol: f64, abs_floor: f64) -> f64 {
rel_tol * self.value.abs().max(abs_floor)
}
pub fn band(&self, analytic: f64, rel_tol: f64, abs_floor: f64) -> f64 {
rel_tol * self.value.abs().max(analytic.abs()).max(abs_floor)
}
pub fn resolved(&self, rel_tol: f64, abs_floor: f64) -> bool {
self.uncertainty.is_finite() && self.uncertainty <= self.self_band(rel_tol, abs_floor)
}
pub fn agreement_bound(&self, analytic: f64, rel_tol: f64, abs_floor: f64) -> f64 {
self.band(analytic, rel_tol, abs_floor) + self.uncertainty
}
pub fn judge(&self, analytic: f64, rel_tol: f64, abs_floor: f64) -> FdVerdict {
if !self.value.is_finite() || !self.resolved(rel_tol, abs_floor) {
return FdVerdict::Unresolved;
}
if (analytic - self.value).abs() > self.agreement_bound(analytic, rel_tol, abs_floor) {
FdVerdict::Disagree
} else {
FdVerdict::Agree
}
}
pub fn ladder_report(&self) -> String {
self.ladder
.iter()
.map(|(h, d)| format!("h={h:.2e} D={d:+.10e}"))
.collect::<Vec<_>>()
.join(" ")
}
}
pub fn ridders_derivative<F>(mut f: F, config: RiddersConfig) -> FdDerivative
where
F: FnMut(f64) -> f64,
{
ridders_from_stencil(|h| (f(h) - f(-h)) / (2.0 * h), config)
}
pub fn ridders_from_stencil<F>(mut stencil: F, config: RiddersConfig) -> FdDerivative
where
F: FnMut(f64) -> f64,
{
assert!(
config.initial_step > 0.0 && config.initial_step.is_finite(),
"ridders_derivative: initial_step must be finite and positive"
);
assert!(
config.shrink > 1.0 && config.shrink.is_finite(),
"ridders_derivative: shrink must exceed 1"
);
assert!(
config.rungs >= 4,
"ridders_derivative: need at least 4 rungs"
);
let mut tableau: Vec<Vec<f64>> = Vec::with_capacity(config.rungs);
let mut ladder: Vec<(f64, f64)> = Vec::with_capacity(config.rungs);
let mut best = FdDerivative {
value: f64::NAN,
uncertainty: f64::INFINITY,
step: f64::NAN,
order: 0,
ladder: Vec::new(),
};
let ratio_sq = config.shrink * config.shrink;
let mut h = config.initial_step;
for i in 0..config.rungs {
let d = stencil(h);
ladder.push((h, d));
let mut row = vec![d];
if i > 0 {
let mut factor = ratio_sq;
for j in 1..=i {
let left = row[j - 1];
let up = tableau[i - 1][j - 1];
let extrapolant = (factor * left - up) / (factor - 1.0);
row.push(extrapolant);
let plateau = if j + 2 <= i {
Some((tableau[i - 1][j], tableau[i - 2][j]))
} else {
None
};
if let Some((previous, before_that)) = plateau {
let error = (extrapolant - left)
.abs()
.max((extrapolant - up).abs())
.max((extrapolant - previous).abs())
.max((extrapolant - before_that).abs());
if extrapolant.is_finite() && error < best.uncertainty {
best.value = extrapolant;
best.uncertainty = error;
best.step = h;
best.order = 2 * (j + 1);
}
}
factor *= ratio_sq;
}
}
tableau.push(row);
h /= config.shrink;
}
best.ladder = ladder;
best
}