use num::{Float, ToPrimitive, Unsigned};
use crate::utils::checkers::check_newton_method_args;
pub fn romberg_method<Func, F1, F2, U>(
func: Func,
lower_limit: F1,
upper_limit: F1,
n_columns: U,
) -> f64
where
F1: Float,
F2: Float,
U: Unsigned + ToPrimitive + Copy,
Func: Fn(F1) -> F2,
{
check_newton_method_args(lower_limit, upper_limit, n_columns);
let n = n_columns.to_usize().unwrap();
let a = lower_limit.to_f64().unwrap();
let b = upper_limit.to_f64().unwrap();
let h0 = b - a;
let f = |x: f64| func(F1::from(x).unwrap()).to_f64().unwrap();
let mut prev = vec![0.0_f64; n];
let mut curr = vec![0.0_f64; n];
prev[0] = 0.5 * h0 * (f(a) + f(b));
for i in 1..n {
let num_new = 1_usize << (i - 1); let h = h0 / (2 * num_new) as f64;
let new_sum: f64 = (0..num_new).map(|k| f(a + (2 * k + 1) as f64 * h)).sum();
curr[0] = 0.5 * prev[0] + h * new_sum;
let mut factor = 4.0_f64; for j in 1..=i {
curr[j] = (factor * curr[j - 1] - prev[j - 1]) / (factor - 1.0);
factor *= 4.0;
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n - 1]
}
#[cfg(test)]
mod tests {
use std::ops::Div;
use super::*;
const EPSILON: f64 = 10e-5;
const NUM_STEPS: usize = 10;
#[test]
fn test_integral_value() {
fn square(x: f64) -> f64 {
x.powi(2)
}
let a = 0.0;
let b = 1.0;
let integral = romberg_method(square, a, b, NUM_STEPS);
let analytic_result: f64 = 1.0.div(3.0);
assert!((integral - analytic_result).abs() < EPSILON);
}
#[test]
fn test_f32_to_f64() {
fn square(x: f32) -> f64 {
x.powi(2) as f64
}
let a = 0.0;
let b = 1.0;
let integral = romberg_method(square, a, b, NUM_STEPS);
let analytic_result: f64 = 1.0.div(3.0);
assert!((integral - analytic_result).abs() < EPSILON);
}
#[test]
fn test_f64_to_f32() {
fn square(x: f64) -> f32 {
x.powi(2) as f32
}
let a = 0.0;
let b = 1.0;
let integral = romberg_method(square, a, b, NUM_STEPS);
let analytic_result: f64 = 1.0.div(3.0);
assert!((integral - analytic_result).abs() < EPSILON);
}
#[test]
fn test_f32_to_f32() {
fn square(x: f32) -> f32 {
x.powi(2)
}
let a = 0.0;
let b = 1.0;
let integral = romberg_method(square, a, b, NUM_STEPS);
let analytic_result: f64 = 1.0.div(3.0);
assert!((integral - analytic_result).abs() < EPSILON);
}
#[test]
#[should_panic(expected = "Integral limits a and b can't be NaN")]
fn test_lower_limit_nan() {
fn f(x: f64) -> f64 {
x
}
let _ = romberg_method(f, f64::NAN, 1.0, 1usize);
}
#[test]
#[should_panic(expected = "Integral limits a and b can't be NaN")]
fn test_upper_limit_nan() {
fn f(x: f64) -> f64 {
x
}
let _ = romberg_method(f, 0.0, f64::NAN, 1usize);
}
}