use crate::error::EmlError;
use crate::lower::LoweredOp;
const GL5_NODES: [f64; 5] = [
-0.906_179_845_938_664,
-0.538_469_310_105_683_1,
0.0,
0.538_469_310_105_683_1,
0.906_179_845_938_664,
];
const GL5_WEIGHTS: [f64; 5] = [
0.236_926_885_056_189,
0.478_628_670_499_366_5,
0.568_888_888_888_888_9,
0.478_628_670_499_366_5,
0.236_926_885_056_189,
];
#[derive(Clone, Copy, Debug)]
pub enum QuadNdMethod {
GaussLegendre,
MonteCarlo {
n_samples: usize,
},
}
#[derive(Clone, Copy, Debug)]
pub struct QuadNdOpts {
pub method: QuadNdMethod,
pub points_per_dim: usize,
}
impl Default for QuadNdOpts {
fn default() -> Self {
Self {
method: QuadNdMethod::GaussLegendre,
points_per_dim: 5,
}
}
}
fn halton(index: usize, base: usize) -> f64 {
let mut f = 1.0_f64;
let mut r = 0.0_f64;
let mut i = index;
while i > 0 {
f /= base as f64;
r += f * (i % base) as f64;
i /= base;
}
r
}
const HALTON_PRIMES: [usize; 16] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53];
pub fn quadrature_nd(
expr: &LoweredOp,
vars: &[usize],
lo: &[f64],
hi: &[f64],
opts: &QuadNdOpts,
) -> Result<f64, EmlError> {
let ndim = vars.len();
if ndim == 0 {
return Err(EmlError::InvalidParameter("vars must be non-empty"));
}
if lo.len() != ndim || hi.len() != ndim {
return Err(EmlError::DimensionMismatch(ndim, lo.len().min(hi.len())));
}
if ndim > HALTON_PRIMES.len() {
return Err(EmlError::InvalidParameter("too many dimensions (max 16)"));
}
match opts.method {
QuadNdMethod::GaussLegendre => gauss_legendre_nd(expr, vars, lo, hi),
QuadNdMethod::MonteCarlo { n_samples } => {
monte_carlo_halton_nd(expr, vars, lo, hi, n_samples)
}
}
}
fn gauss_legendre_nd(
expr: &LoweredOp,
vars: &[usize],
lo: &[f64],
hi: &[f64],
) -> Result<f64, EmlError> {
let ndim = vars.len();
let mut jacobian = 1.0_f64;
for i in 0..ndim {
jacobian *= (hi[i] - lo[i]) / 2.0;
}
let centers: Vec<f64> = (0..ndim).map(|i| (lo[i] + hi[i]) / 2.0).collect();
let half_widths: Vec<f64> = (0..ndim).map(|i| (hi[i] - lo[i]) / 2.0).collect();
let n_pts = GL5_NODES.len().pow(ndim as u32);
let mut sum = 0.0_f64;
for flat_idx in 0..n_pts {
let mut weight = 1.0_f64;
let mut x_vals: Vec<(usize, f64)> = Vec::with_capacity(ndim);
let mut idx = flat_idx;
for i in 0..ndim {
let node_idx = idx % GL5_NODES.len();
idx /= GL5_NODES.len();
let node = GL5_NODES[node_idx];
let w = GL5_WEIGHTS[node_idx];
let x = centers[i] + half_widths[i] * node;
weight *= w;
x_vals.push((vars[i], x));
}
let n_vars_total = vars.iter().copied().max().map(|m| m + 1).unwrap_or(1);
let mut var_vec = vec![0.0_f64; n_vars_total];
for (idx_var, val) in &x_vals {
if *idx_var < var_vec.len() {
var_vec[*idx_var] = *val;
}
}
let f_val = expr.eval(&var_vec);
sum += weight * f_val;
}
Ok(jacobian * sum)
}
fn monte_carlo_halton_nd(
expr: &LoweredOp,
vars: &[usize],
lo: &[f64],
hi: &[f64],
n_samples: usize,
) -> Result<f64, EmlError> {
if n_samples == 0 {
return Err(EmlError::InvalidParameter("n_samples must be > 0"));
}
let ndim = vars.len();
let volume: f64 = (0..ndim).map(|i| hi[i] - lo[i]).product();
let n_vars_total = vars.iter().copied().max().map(|m| m + 1).unwrap_or(1);
let mut sum = 0.0_f64;
for s in 0..n_samples {
let mut var_vec = vec![0.0_f64; n_vars_total];
for (dim, &var_idx) in vars.iter().enumerate() {
let h = halton(s + 1, HALTON_PRIMES[dim]);
let x = lo[dim] + h * (hi[dim] - lo[dim]);
if var_idx < var_vec.len() {
var_vec[var_idx] = x;
}
}
sum += expr.eval(&var_vec);
}
Ok(volume * sum / n_samples as f64)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lower::LoweredOp;
use std::sync::Arc;
#[test]
fn test_gauss_legendre_2d_linear() {
let expr = LoweredOp::Add(Arc::new(LoweredOp::Var(0)), Arc::new(LoweredOp::Var(1)));
let opts = QuadNdOpts {
method: QuadNdMethod::GaussLegendre,
points_per_dim: 5,
};
let result = quadrature_nd(&expr, &[0, 1], &[0.0, 0.0], &[1.0, 1.0], &opts).unwrap();
assert!((result - 1.0).abs() < 1e-10, "Expected 1.0, got {}", result);
}
#[test]
fn test_gauss_legendre_1d_cubic() {
let expr = LoweredOp::Pow(Arc::new(LoweredOp::Var(0)), Arc::new(LoweredOp::Const(3.0)));
let opts = QuadNdOpts {
method: QuadNdMethod::GaussLegendre,
points_per_dim: 5,
};
let result = quadrature_nd(&expr, &[0], &[-1.0], &[1.0], &opts).unwrap();
assert!(result.abs() < 1e-12, "Expected 0, got {}", result);
}
}