use numra_core::Scalar;
use crate::adaptive::QuadResult;
use crate::error::IntegrationError;
pub fn trapezoid<S: Scalar>(y: &[S], dx: S) -> S {
assert!(y.len() >= 2, "trapezoid requires at least 2 data points");
let n = y.len();
let mut sum = y[0] + y[n - 1];
for yi in &y[1..n - 1] {
sum += S::TWO * *yi;
}
sum * dx * S::HALF
}
pub fn trapezoid_nonuniform<S: Scalar>(x: &[S], y: &[S]) -> S {
assert_eq!(x.len(), y.len(), "x and y must have the same length");
assert!(x.len() >= 2, "trapezoid requires at least 2 data points");
let mut sum = S::ZERO;
for i in 0..x.len() - 1 {
sum += (x[i + 1] - x[i]) * (y[i] + y[i + 1]) * S::HALF;
}
sum
}
pub fn simpson<S: Scalar>(y: &[S], dx: S) -> S {
assert!(y.len() >= 3, "simpson requires at least 3 data points");
let n = y.len();
if n % 2 == 0 {
let simp = simpson_odd(&y[..n - 1], dx);
let trap = (y[n - 2] + y[n - 1]) * dx * S::HALF;
return simp + trap;
}
simpson_odd(y, dx)
}
fn simpson_odd<S: Scalar>(y: &[S], dx: S) -> S {
let n = y.len();
let three = S::from_f64(3.0);
let four = S::from_f64(4.0);
let mut sum = y[0] + y[n - 1];
for i in (1..n - 1).step_by(2) {
sum += four * y[i];
}
for i in (2..n - 1).step_by(2) {
sum += S::TWO * y[i];
}
sum * dx / three
}
pub fn cumulative_trapezoid<S: Scalar>(y: &[S], dx: S) -> Vec<S> {
assert!(
y.len() >= 2,
"cumulative_trapezoid requires at least 2 data points"
);
let mut result = Vec::with_capacity(y.len() - 1);
let mut running = S::ZERO;
for i in 0..y.len() - 1 {
running += (y[i] + y[i + 1]) * dx * S::HALF;
result.push(running);
}
result
}
pub fn romberg<S, F>(f: F, a: S, b: S, tol: S) -> Result<QuadResult<S>, IntegrationError>
where
S: Scalar,
F: Fn(S) -> S,
{
let max_order = 20;
let mut r_prev: Vec<S> = Vec::with_capacity(max_order);
let mut r_curr: Vec<S> = Vec::with_capacity(max_order);
let h = b - a;
let mut n_eval = 2usize;
r_prev.push((f(a) + f(b)) * h * S::HALF);
let mut h_k = h;
for k in 1..max_order {
h_k *= S::HALF;
let n_new = 1usize << (k - 1);
let mut midpoint_sum = S::ZERO;
for i in 0..n_new {
let x = a + h_k * S::from_usize(2 * i + 1);
midpoint_sum += f(x);
}
n_eval += n_new;
r_curr.clear();
r_curr.push(r_prev[0] * S::HALF + h_k * midpoint_sum);
let mut four_j = S::from_f64(4.0);
for j in 1..=k {
let val = (four_j * r_curr[j - 1] - r_prev[j - 1]) / (four_j - S::ONE);
r_curr.push(val);
four_j *= S::from_f64(4.0);
}
let error = (r_curr[k] - r_prev[k - 1]).abs();
if error < tol {
return Ok(QuadResult {
value: r_curr[k],
error_estimate: error,
n_evaluations: n_eval,
n_subdivisions: 1 << k,
});
}
core::mem::swap(&mut r_prev, &mut r_curr);
}
Err(IntegrationError::MaxSubdivisions {
subdivisions: 1 << (max_order - 1),
error_estimate: (r_prev[max_order - 1] - r_prev[max_order - 2])
.abs()
.to_f64(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_trapezoid_linear() {
let y = vec![0.0, 1.0, 2.0];
let result = trapezoid(&y, 1.0);
assert_relative_eq!(result, 2.0, epsilon = 1e-12);
}
#[test]
fn test_trapezoid_constant() {
let y = vec![3.0, 3.0, 3.0, 3.0, 3.0];
let result = trapezoid(&y, 0.5);
assert_relative_eq!(result, 6.0, epsilon = 1e-12);
}
#[test]
fn test_trapezoid_nonuniform() {
let x = vec![0.0, 1.0, 3.0];
let y = vec![0.0, 1.0, 3.0];
let result = trapezoid_nonuniform(&x, &y);
assert_relative_eq!(result, 4.5, epsilon = 1e-12);
}
#[test]
fn test_simpson_quadratic() {
let y: Vec<f64> = (0..5).map(|i| (i as f64).powi(2)).collect();
let result = simpson(&y, 1.0);
assert_relative_eq!(result, 64.0 / 3.0, epsilon = 1e-12);
}
#[test]
fn test_simpson_cubic() {
let y: Vec<f64> = (0..5).map(|i| (i as f64 * 0.5).powi(3)).collect();
let result = simpson(&y, 0.5);
assert_relative_eq!(result, 4.0, epsilon = 1e-12);
}
#[test]
fn test_simpson_even_points() {
let y: Vec<f64> = (0..4).map(|i| (i as f64).powi(2)).collect();
let result = simpson(&y, 1.0);
assert_relative_eq!(result, 55.0 / 6.0, epsilon = 1e-12);
}
#[test]
fn test_cumulative_trapezoid() {
let y = vec![1.0, 1.0, 1.0, 1.0];
let result = cumulative_trapezoid(&y, 1.0);
assert_eq!(result.len(), 3);
assert_relative_eq!(result[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(result[1], 2.0, epsilon = 1e-12);
assert_relative_eq!(result[2], 3.0, epsilon = 1e-12);
}
#[test]
fn test_cumulative_trapezoid_quadratic() {
let y = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let result = cumulative_trapezoid(&y, 1.0);
assert_eq!(result.len(), 4);
assert_relative_eq!(result[0], 0.5, epsilon = 1e-12);
assert_relative_eq!(result[1], 2.0, epsilon = 1e-12);
assert_relative_eq!(result[2], 4.5, epsilon = 1e-12);
assert_relative_eq!(result[3], 8.0, epsilon = 1e-12);
}
#[test]
fn test_romberg_x_squared() {
let result = romberg(|x: f64| x * x, 0.0, 1.0, 1e-10).unwrap();
assert_relative_eq!(result.value, 1.0 / 3.0, epsilon = 1e-10);
assert!(result.error_estimate < 1e-10);
}
#[test]
fn test_romberg_exp() {
let result = romberg(|x: f64| x.exp(), 0.0, 1.0, 1e-12).unwrap();
let expected = core::f64::consts::E - 1.0;
assert_relative_eq!(result.value, expected, epsilon = 1e-12);
}
#[test]
fn test_romberg_sin() {
let result = romberg(|x: f64| x.sin(), 0.0, core::f64::consts::PI, 1e-12).unwrap();
assert_relative_eq!(result.value, 2.0, epsilon = 1e-12);
}
}