pub mod adaptive;
pub mod halton;
pub mod monte_carlo;
pub mod sobol;
pub mod sparse_grid;
pub mod tensor;
pub use adaptive::{adaptive_cubature, AdaptiveCubature};
pub use monte_carlo::{monte_carlo_integrate, MCMethod, MonteCarloIntegrator};
pub use sparse_grid::{SparseGrid, SparseGridBasis};
pub use tensor::TensorProductRule;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
#[derive(Debug, Clone)]
pub struct CubatureRule {
nodes: Vec<f64>,
weights: Vec<f64>,
dim: usize,
}
impl CubatureRule {
#[must_use]
pub fn new(nodes_flat: Vec<f64>, weights: Vec<f64>, dim: usize) -> Self {
assert_eq!(nodes_flat.len(), weights.len() * dim);
Self {
nodes: nodes_flat,
weights,
dim,
}
}
#[inline]
#[must_use]
pub fn num_points(&self) -> usize {
self.weights.len()
}
#[inline]
#[must_use]
pub fn dim(&self) -> usize {
self.dim
}
#[inline]
#[must_use]
pub fn node(&self, i: usize) -> &[f64] {
&self.nodes[i * self.dim..(i + 1) * self.dim]
}
#[inline]
#[must_use]
pub fn weights(&self) -> &[f64] {
&self.weights
}
#[inline]
pub fn integrate<G>(&self, f: G) -> f64
where
G: Fn(&[f64]) -> f64,
{
let mut sum = 0.0;
for i in 0..self.num_points() {
sum += self.weights[i] * f(self.node(i));
}
sum
}
pub fn integrate_box<G>(&self, lower: &[f64], upper: &[f64], f: G) -> f64
where
G: Fn(&[f64]) -> f64,
{
assert_eq!(lower.len(), self.dim);
assert_eq!(upper.len(), self.dim);
let d = self.dim;
let half_widths: Vec<f64> = (0..d).map(|j| 0.5 * (upper[j] - lower[j])).collect();
let midpoints: Vec<f64> = (0..d).map(|j| 0.5 * (lower[j] + upper[j])).collect();
let jacobian: f64 = half_widths.iter().product();
let mut x = vec![0.0; d];
let mut sum = 0.0;
for i in 0..self.num_points() {
let node = self.node(i);
for j in 0..d {
x[j] = half_widths[j] * node[j] + midpoints[j];
}
sum += self.weights[i] * f(&x);
}
sum * jacobian
}
#[cfg(feature = "parallel")]
pub fn integrate_par<G>(&self, f: G) -> f64
where
G: Fn(&[f64]) -> f64 + Sync,
{
use rayon::prelude::*;
let d = self.dim;
(0..self.num_points())
.into_par_iter()
.map(|i| self.weights[i] * f(&self.nodes[i * d..(i + 1) * d]))
.sum()
}
#[cfg(feature = "parallel")]
pub fn integrate_box_par<G>(&self, lower: &[f64], upper: &[f64], f: G) -> f64
where
G: Fn(&[f64]) -> f64 + Sync,
{
use rayon::prelude::*;
assert_eq!(lower.len(), self.dim);
assert_eq!(upper.len(), self.dim);
let d = self.dim;
let half_widths: Vec<f64> = (0..d).map(|j| 0.5 * (upper[j] - lower[j])).collect();
let midpoints: Vec<f64> = (0..d).map(|j| 0.5 * (lower[j] + upper[j])).collect();
let jacobian: f64 = half_widths.iter().product();
let sum: f64 = (0..self.num_points())
.into_par_iter()
.map(|i| {
let node = &self.nodes[i * d..(i + 1) * d];
let x: Vec<f64> = (0..d)
.map(|j| half_widths[j] * node[j] + midpoints[j])
.collect();
self.weights[i] * f(&x)
})
.sum();
sum * jacobian
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cubature_rule_basics() {
let rule = CubatureRule::new(vec![-1.0, 1.0], vec![1.0, 1.0], 1);
assert_eq!(rule.num_points(), 2);
assert_eq!(rule.dim(), 1);
assert_eq!(rule.node(0), &[-1.0]);
assert_eq!(rule.node(1), &[1.0]);
}
#[test]
fn integrate_box_constant() {
let rule = CubatureRule::new(vec![0.0, 0.0], vec![4.0], 2);
let result = rule.integrate_box(&[0.0, 0.0], &[2.0, 3.0], |_| 1.0);
assert!((result - 6.0).abs() < 1e-14); }
#[cfg(feature = "parallel")]
#[test]
fn integrate_par_matches_sequential() {
use crate::cubature::TensorProductRule;
use crate::GaussLegendre;
let gl = GaussLegendre::new(10).unwrap();
let tp = TensorProductRule::isotropic(gl.rule(), 3).unwrap();
let f = |x: &[f64]| (x[0] * x[1] + x[2]).sin();
let seq = tp.rule().integrate(&f);
let par = tp.rule().integrate_par(&f);
assert!((seq - par).abs() < 1e-14, "seq={seq}, par={par}");
}
#[cfg(feature = "parallel")]
#[test]
fn integrate_box_par_matches_sequential() {
use crate::cubature::TensorProductRule;
use crate::GaussLegendre;
let gl = GaussLegendre::new(10).unwrap();
let tp = TensorProductRule::isotropic(gl.rule(), 2).unwrap();
let f = |x: &[f64]| x[0] * x[1];
let seq = tp.rule().integrate_box(&[0.0, 0.0], &[1.0, 1.0], &f);
let par = tp.rule().integrate_box_par(&[0.0, 0.0], &[1.0, 1.0], &f);
assert!((seq - par).abs() < 1e-14, "seq={seq}, par={par}");
}
}