use crate::StrError;
const STEPSIZE_CENTRAL5: f64 = 1e-3;
fn deriv1_and_errors_central5<F, A>(at_x: f64, args: &mut A, h: f64, mut f: F) -> Result<(f64, f64, f64), StrError>
where
F: FnMut(f64, &mut A) -> Result<f64, StrError>,
{
let fm1 = f(at_x - h, args)?;
let fp1 = f(at_x + h, args)?;
let fmh = f(at_x - h / 2.0, args)?;
let fph = f(at_x + h / 2.0, args)?;
let r3 = 0.5 * (fp1 - fm1);
let r5 = (4.0 / 3.0) * (fph - fmh) - (1.0 / 3.0) * r3;
let dfdx = r5 / h;
let e3 = (f64::abs(fp1) + f64::abs(fm1)) * f64::EPSILON;
let e5 = 2.0 * (f64::abs(fph) + f64::abs(fmh)) * f64::EPSILON + e3;
let dy = f64::max(f64::abs(r3 / h), f64::abs(r5 / h)) * (f64::abs(at_x) / h) * f64::EPSILON;
let abs_trunc_err = f64::abs((r5 - r3) / h);
let abs_round_err = f64::abs(e5 / h) + dy;
Ok((dfdx, abs_trunc_err, abs_round_err))
}
pub fn deriv1_central5<F, A>(at_x: f64, args: &mut A, mut f: F) -> Result<f64, StrError>
where
F: FnMut(f64, &mut A) -> Result<f64, StrError>,
{
let h = STEPSIZE_CENTRAL5;
let (dfdx, err, rerr) = deriv1_and_errors_central5(at_x, args, h, &mut f)?;
let err_total = err + rerr;
if err == 0.0 || rerr == 0.0 {
return Ok(dfdx);
}
if err < rerr {
return Ok(dfdx);
}
let h_improv = h * f64::powf(rerr / (2.0 * err), 1.0 / 3.0);
let (dfdx_improv, err_improv, rerr_improv) = deriv1_and_errors_central5(at_x, args, h_improv, &mut f)?;
let err_total_improv = err_improv + rerr_improv;
if err_total_improv > err_total {
return Ok(dfdx);
}
if f64::abs(dfdx_improv - dfdx) > 4.0 * err_total {
return Ok(dfdx);
}
Ok(dfdx_improv)
}
#[cfg(test)]
mod tests {
use super::{deriv1_and_errors_central5, deriv1_central5};
use crate::check::testing;
#[test]
fn deriv1_and_errors_central5_works() {
let tests = testing::get_functions();
println!(
"{:>10}{:>15}{:>22}{:>11}{:>10}{:>10}",
"function", "numerical", "analytical", "|num-ana|", "err", "rerr"
);
for test in &tests {
let args = &mut 0;
let (d, err, rerr) = deriv1_and_errors_central5(test.at_x, args, 1e-3, test.f).unwrap();
let d_correct = (test.g)(test.at_x, args).unwrap();
println!(
"{:>10}{:15.9}{:22}{:11.2e}{:10.2e}{:10.2e}",
test.name,
d,
d_correct,
f64::abs(d - d_correct),
err,
rerr,
);
assert!(f64::abs(d - d_correct) < test.tol_g);
assert!(err < test.tol_g_err);
assert!(rerr < test.tol_g_rerr);
}
}
#[test]
fn deriv1_central5_works() {
let tests = testing::get_functions();
println!(
"{:>10}{:>15}{:>22}{:>11}",
"function", "numerical", "analytical", "|num-ana|"
);
for test in &tests {
let args = &mut 0;
let d = deriv1_central5(test.at_x, args, test.f).unwrap();
let d_correct = (test.g)(test.at_x, args).unwrap();
println!(
"{:>10}{:15.9}{:22}{:11.2e}",
test.name,
d,
d_correct,
f64::abs(d - d_correct),
);
assert!(f64::abs(d - d_correct) < test.improv_tol_g_diff);
}
}
}