use ndarray::{array, ArrayView1};
use scirs2_integrate::{
gaussian::gauss_legendre,
monte_carlo::{monte_carlo, MonteCarloOptions},
ode::{solve_ivp, ODEMethod, ODEOptions},
quad::{quad, simpson, trapezoid},
romberg::{self, romberg},
};
use std::marker::PhantomData;
use std::time::Instant;
/// A helper function to time and report the result of an integration method
#[allow(dead_code)]
fn time_integration<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
let elapsed = start.elapsed();
println!("{_name}: {elapsed:?}");
result
}
#[allow(dead_code)]
fn main() {
// Define a test function with a known analytical result
// f(x) = x^2, which has the integral ∫x^2 dx = x^3/3
// Over [0,1], the integral equals 1/3
let f_1d = |x: f64| x * x;
// Analytical result
let exact_result_1d = 1.0 / 3.0;
println!("Integrating f(x) = x^2 over [0,1]");
println!("Exact result: {exact_result_1d}");
println!("\nPerformance and accuracy comparison:");
// Test different methods
let trap_result = time_integration("Trapezoid rule (n=1000)", || {
trapezoid(f_1d, 0.0, 1.0, 1000)
});
println!(
" Result: {}, Error: {:.2e}",
trap_result,
(trap_result - exact_result_1d).abs()
);
let simp_result = time_integration("Simpson's rule (n=1000)", || {
simpson(f_1d, 0.0, 1.0, 1000).unwrap()
});
println!(
" Result: {}, Error: {:.2e}",
simp_result,
(simp_result - exact_result_1d).abs()
);
let gauss_result = time_integration("Gauss-Legendre (n=5)", || {
gauss_legendre(f_1d, 0.0, 1.0, 5).unwrap()
});
println!(
" Result: {}, Error: {:.2e}",
gauss_result,
(gauss_result - exact_result_1d).abs()
);
let romberg_result = time_integration("Romberg", || romberg(f_1d, 0.0, 1.0, None).unwrap());
println!(
" Result: {}, Error: {:.2e}",
romberg_result.value,
(romberg_result.value - exact_result_1d).abs()
);
// Adaptive quadrature is not available as a public API yet
// let adaptive_result = time_integration("Adaptive quadrature", || {
// adaptive_quad(f_1d, 0.0, 1.0, 1e-10, 1e-10, None).unwrap()
// });
// println!(" Result: {}, Error: {:.2e}",
// adaptive_result.0, (adaptive_result.0 - exact_result_1d).abs());
let quad_result = time_integration("General-purpose quad", || {
quad(f_1d, 0.0, 1.0, None).unwrap()
});
println!(
" Result: {}, Error: {:.2e}",
quad_result.value,
(quad_result.value - exact_result_1d).abs()
);
// Monte Carlo integration
println!("\nMonte Carlo integration with increasing samples:");
let options_base = MonteCarloOptions {
seed: Some(42), // For reproducibility
_phantom: PhantomData,
..Default::default()
};
for n_samples in [1000, 10_000, 100_000, 1_000_000] {
let options = MonteCarloOptions {
n_samples,
..options_base.clone()
};
let monte_result = time_integration(&format!("Monte Carlo (n={n_samples})"), || {
monte_carlo(
|x: ArrayView1<f64>| x[0] * x[0],
&[(0.0, 1.0)],
Some(options),
)
.unwrap()
});
println!(
" Result: {}, Error: {:.2e}, Std Error: {:.2e}",
monte_result.value,
(monte_result.value - exact_result_1d).abs(),
monte_result.std_error
);
}
// 2D integration example
println!("\nMultidimensional integration example:");
println!("Integrating f(x,y) = x^2 + y^2 over [0,1]×[0,1], exact result = 2/3");
let f_2d = |x: ArrayView1<f64>| x[0] * x[0] + x[1] * x[1];
let exact_result_2d = 2.0 / 3.0;
let mc_2d_result = time_integration("Monte Carlo 2D (n=100,000)", || {
let options = MonteCarloOptions {
n_samples: 100_000,
seed: Some(42),
_phantom: PhantomData,
..Default::default()
};
monte_carlo(f_2d, &[(0.0, 1.0), (0.0, 1.0)], Some(options)).unwrap()
});
println!(
" Result: {}, Error: {:.2e}, Std Error: {:.2e}",
mc_2d_result.value,
(mc_2d_result.value - exact_result_2d).abs(),
mc_2d_result.std_error
);
let romberg_2d_result = time_integration("Romberg 2D", || {
romberg::multi_romberg(f_2d, &[(0.0, 1.0), (0.0, 1.0)], None).unwrap()
});
println!(
" Result: {}, Error: {:.2e}",
romberg_2d_result,
(romberg_2d_result - exact_result_2d).abs()
);
// ODE solver comparison
println!("\nODE solver comparison:");
println!("Solving y' = -y, y(0) = 1, exact solution: y(t) = exp(-t)");
println!("Comparing at t = 10, exact = 4.5399e-5");
let exact_ode_result = (-10.0_f64).exp();
let decay_system = |_t: f64, y: ArrayView1<f64>| array![-y[0]];
// Create a map of methods to test
let methods = [
("RK23", ODEMethod::RK23),
("RK45", ODEMethod::RK45),
("DOP853", ODEMethod::DOP853),
("BDF", ODEMethod::Bdf),
("Radau (experimental)", ODEMethod::Radau),
("LSODA (experimental)", ODEMethod::LSODA),
];
for (method_name, method) in methods {
let result = time_integration(method_name, || {
solve_ivp(
decay_system,
[0.0, 10.0],
array![1.0],
Some(ODEOptions {
method,
rtol: 1e-6,
atol: 1e-8,
max_steps: 1000,
..Default::default()
}),
)
});
match result {
Ok(res) => {
let y_final = res.y.last().unwrap()[0];
println!(
" Result: {:.6e}, Error: {:.2e}, Steps: {}, Function evals: {}",
y_final,
(y_final - exact_ode_result).abs(),
res.n_steps,
res.n_eval
);
if method == ODEMethod::LSODA || method == ODEMethod::Radau {
if let Some(msg) = res.message {
println!(" Message: {msg}");
}
}
}
Err(e) => {
println!(" Failed: {e}");
if method == ODEMethod::LSODA || method == ODEMethod::Radau {
println!(" Note: This method is still experimental");
}
}
}
}
}