#[derive(Clone, Copy, Debug)]
pub struct GaussOptions {
pub half_range: bool,
pub add_endpoints: bool,
}
impl Default for GaussOptions {
fn default() -> Self {
Self {
half_range: false,
add_endpoints: false,
}
}
}
pub fn gauss_legendre(n: usize, opts: GaussOptions) -> (Vec<f64>, Vec<f64>) {
assert!(n > 0, "gauss_legendre requires n >= 1");
let m = (n + 1) / 2;
let mut x = vec![0.0f64; n];
let mut w = vec![0.0f64; n];
let eps = 1.0e-15;
let pi = std::f64::consts::PI;
for i in 1..=m {
let mut z = (pi * (i as f64 - 0.25) / (n as f64 + 0.5)).cos();
let mut pp;
loop {
let mut p1 = 1.0f64;
let mut p2 = 0.0f64;
for k in 1..=n {
let p3 = p2;
p2 = p1;
let kf = k as f64;
p1 = ((2.0 * kf - 1.0) * z * p2 - (kf - 1.0) * p3) / kf;
}
pp = (n as f64) * (z * p1 - p2) / (z * z - 1.0);
let z1 = z;
z -= p1 / pp;
if (z - z1).abs() < eps {
break;
}
}
x[i - 1] = -z;
x[n - i] = z;
let wi = 2.0 / ((1.0 - z * z) * pp * pp);
w[i - 1] = wi;
w[n - i] = wi;
}
if opts.half_range {
let mut xh = Vec::with_capacity(n);
let mut wh = Vec::with_capacity(n);
for (&xi, &wi) in x.iter().zip(w.iter()) {
if xi > 0.0 {
xh.push(xi);
wh.push(wi);
}
}
(xh, wh)
} else if opts.add_endpoints {
let mut xe = Vec::with_capacity(n + 2);
let mut we = Vec::with_capacity(n + 2);
xe.push(-1.0);
we.push(0.0);
xe.extend_from_slice(&x);
we.extend_from_slice(&w);
xe.push(1.0);
we.push(0.0);
(xe, we)
} else {
(x, w)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn gauss_n5_matches_known_values() {
let (x, w) = gauss_legendre(5, GaussOptions::default());
let ref_x = [
-0.9061798459386640,
-0.5384693101056831,
0.0,
0.5384693101056831,
0.9061798459386640,
];
let ref_w = [
0.2369268850561891,
0.4786286704993665,
0.5688888888888889,
0.4786286704993665,
0.2369268850561891,
];
for (a, b) in x.iter().zip(ref_x.iter()) {
assert_abs_diff_eq!(*a, *b, epsilon = 1e-12);
}
for (a, b) in w.iter().zip(ref_w.iter()) {
assert_abs_diff_eq!(*a, *b, epsilon = 1e-12);
}
}
#[test]
fn gauss_integrates_polynomials_exactly() {
for n in 2..10 {
let (x, w) = gauss_legendre(n, GaussOptions::default());
for k in 0..2 * n {
let approx: f64 = x
.iter()
.zip(w.iter())
.map(|(&xi, &wi)| wi * xi.powi(k as i32))
.sum();
let exact = if k % 2 == 1 {
0.0
} else {
2.0 / (k as f64 + 1.0)
};
assert_abs_diff_eq!(approx, exact, epsilon = 1e-12);
}
}
}
#[test]
fn weights_sum_to_two() {
for n in [3, 8, 16, 32, 64] {
let (_, w) = gauss_legendre(n, GaussOptions::default());
let s: f64 = w.iter().sum();
assert_abs_diff_eq!(s, 2.0, epsilon = 1e-13);
}
}
#[test]
fn half_range_has_positive_nodes_only() {
let (x, _) = gauss_legendre(
8,
GaussOptions {
half_range: true,
add_endpoints: false,
},
);
assert_eq!(x.len(), 4);
for xi in x {
assert!(xi > 0.0);
}
}
}