use crate::Vector;
pub fn legendre_pn(n: usize, x: f64) -> f64 {
if n == 0 {
return 1.0;
}
if n == 1 {
return x;
}
let mut p_prev = 1.0; let mut p_curr = x; for k in 1..n {
let kf = k as f64;
let p_next = ((2.0 * kf + 1.0) * x * p_curr - kf * p_prev) / (kf + 1.0);
p_prev = p_curr;
p_curr = p_next;
}
p_curr
}
pub fn legendre_pn_deriv1(n: usize, x: f64) -> f64 {
if n == 0 {
return 0.0;
}
if (x - 1.0).abs() < 1e-15 {
let nf = n as f64;
return nf * (nf + 1.0) / 2.0;
}
if (x + 1.0).abs() < 1e-15 {
let nf = n as f64;
let val = nf * (nf + 1.0) / 2.0;
return if n % 2 == 0 { -val } else { val };
}
let pn = legendre_pn(n, x);
let pn1 = legendre_pn(n - 1, x);
(n as f64) * (x * pn - pn1) / (x * x - 1.0)
}
pub fn legendre_pn_deriv2(n: usize, x: f64) -> f64 {
if n <= 1 {
return 0.0;
}
let nf = n as f64;
let nn1 = nf * (nf + 1.0);
if (x - 1.0).abs() < 1e-15 {
return (nf - 1.0) * nf * (nf + 1.0) * (nf + 2.0) / 8.0;
}
if (x + 1.0).abs() < 1e-15 {
let val = (nf - 1.0) * nf * (nf + 1.0) * (nf + 2.0) / 8.0;
return if n % 2 == 0 { val } else { -val };
}
let dpn = legendre_pn_deriv1(n, x);
let pn = legendre_pn(n, x);
(2.0 * x * dpn - nn1 * pn) / (1.0 - x * x)
}
pub fn legendre_gauss_points(nn: usize) -> Vector {
let mut xx = Vector::new(nn + 1);
if nn == 0 {
xx[0] = 0.0;
return xx;
}
let n = nn + 1; for k in 0..=nn {
let angle = std::f64::consts::PI * ((4 * k + 3) as f64) / ((4 * n + 2) as f64);
let mut x = angle.cos();
for _ in 0..100 {
let p = legendre_pn(n, x);
let dp = legendre_pn_deriv1(n, x);
if dp.abs() < 1e-30 {
break;
}
let dx = -p / dp;
x += dx;
if dx.abs() < 1e-15 {
break;
}
}
xx[k] = x;
}
let mut data = xx.as_data().to_vec();
data.sort_by(|a, b| a.partial_cmp(b).unwrap());
for (i, &val) in data.iter().enumerate() {
xx[i] = val;
}
xx
}
pub fn legendre_gauss_weights(nn: usize) -> Vector {
let xx = legendre_gauss_points(nn);
let mut ww = Vector::new(nn + 1);
let n = nn + 1; for k in 0..=nn {
let x = xx[k];
let dp = legendre_pn_deriv1(n, x);
ww[k] = 2.0 / ((1.0 - x * x) * dp * dp);
}
ww
}
pub fn legendre_lobatto_points(nn: usize) -> Vector {
let mut xx = Vector::new(nn + 1);
if nn == 0 {
return xx; }
xx[0] = -1.0;
xx[nn] = 1.0;
if nn < 3 {
if nn == 2 {
xx[1] = 0.0;
}
return xx;
}
let n = nn;
for k in 1..nn {
let angle = std::f64::consts::PI * (k as f64) / (nn as f64);
let mut x = -angle.cos();
for _ in 0..200 {
let dp = legendre_pn_deriv1(n, x);
let ddp = legendre_pn_deriv2(n, x);
if ddp.abs() < 1e-30 {
break;
}
let dx = -dp / ddp;
x += dx;
if dx.abs() < 1e-15 {
break;
}
}
xx[k] = x;
}
xx
}
pub fn legendre_lobatto_weights(nn: usize) -> Vector {
let mut ww = Vector::new(nn + 1);
if nn < 2 {
return ww;
}
let np1 = (nn + 1) as f64;
let w_endpoint = 2.0 / (np1 * (np1 - 1.0));
ww[0] = w_endpoint;
ww[nn] = w_endpoint;
let xx = legendre_lobatto_points(nn);
for k in 1..nn {
let x = xx[k];
let pn1 = legendre_pn(nn, x);
ww[k] = 2.0 / (np1 * (np1 - 1.0) * pn1 * pn1);
}
ww
}
#[cfg(test)]
fn gauss_points_with_guess(nn: usize, x0: f64) -> f64 {
let n = nn + 1;
let mut x = x0;
for _ in 0..100 {
let p = legendre_pn(n, x);
let dp = legendre_pn_deriv1(n, x);
if dp.abs() < 1e-30 {
break;
}
let dx = -p / dp;
x += dx;
if dx.abs() < 1e-15 {
break;
}
}
x
}
#[cfg(test)]
fn lobatto_points_with_guess(nn: usize, x0: f64) -> f64 {
let n = nn;
let mut x = x0;
for _ in 0..200 {
let dp = legendre_pn_deriv1(n, x);
let ddp = legendre_pn_deriv2(n, x);
if ddp.abs() < 1e-30 {
break;
}
let dx = -dp / ddp;
x += dx;
if dx.abs() < 1e-15 {
break;
}
}
x
}
#[cfg(test)]
mod tests {
use super::*;
use crate::approx_eq;
#[test]
fn pn_base_cases() {
let xx = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5];
for &x in &xx {
assert_eq!(legendre_pn(0, x), 1.0);
assert_eq!(legendre_pn(1, x), x);
}
}
#[test]
fn pn_exact_polynomials_n2_to_n5() {
let xx = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5];
for &x in &xx {
let x2 = x * x;
let x3 = x2 * x;
let x4 = x3 * x;
let x5 = x4 * x;
approx_eq(legendre_pn(2, x), (-1.0 + 3.0 * x2) / 2.0, 1e-14);
approx_eq(legendre_pn(3, x), (-3.0 * x + 5.0 * x3) / 2.0, 1e-14);
approx_eq(legendre_pn(4, x), (3.0 - 30.0 * x2 + 35.0 * x4) / 8.0, 1e-14);
approx_eq(legendre_pn(5, x), (15.0 * x - 70.0 * x3 + 63.0 * x5) / 8.0, 1e-13);
}
}
#[test]
fn pn_special_values() {
for n in 0..20 {
assert_eq!(legendre_pn(n, 1.0), 1.0);
let expected_at_neg1 = if n % 2 == 0 { 1.0 } else { -1.0 };
approx_eq(legendre_pn(n, -1.0), expected_at_neg1, 1e-14);
if n % 2 != 0 {
assert_eq!(legendre_pn(n, 0.0), 0.0);
}
}
}
#[test]
fn pn_symmetry() {
let xx = [0.0, 0.25, 0.5, 0.75, 1.0, 1.5];
for &x in &xx {
for n in 0..11 {
let pn_x = legendre_pn(n, x);
let pn_neg_x = legendre_pn(n, -x);
let expected = if n % 2 == 0 { pn_x } else { -pn_x };
approx_eq(pn_neg_x, expected, 1e-14);
}
}
}
#[test]
fn pn_bonnet_recursion() {
let xx = [-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0];
for &x in &xx {
for n in 1..10 {
let pn = legendre_pn(n, x);
let pn1 = legendre_pn(n + 1, x);
let pm1 = legendre_pn(n - 1, x);
let nf = n as f64;
let lhs = (nf + 1.0) * pn1;
let rhs = (2.0 * nf + 1.0) * x * pn - nf * pm1;
approx_eq(lhs, rhs, 1e-12);
}
}
}
#[test]
fn pn_outside_domain() {
for &x in &[-3.0, -2.0, 2.0, 3.0] {
for n in 0..10 {
let pn = legendre_pn(n, x);
if n >= 1 {
let pn1 = legendre_pn(n + 1, x);
let pm1 = legendre_pn(n - 1, x);
let nf = n as f64;
let lhs = (nf + 1.0) * pn1;
let rhs = (2.0 * nf + 1.0) * x * pn - nf * pm1;
approx_eq(lhs, rhs, 1e-10);
}
}
}
}
#[test]
fn pn_high_degree() {
for &n in &[20, 50] {
let x = 0.3;
let pn = legendre_pn(n, x);
let pn1 = legendre_pn(n + 1, x);
let pm1 = legendre_pn(n - 1, x);
let nf = n as f64;
let lhs = (nf + 1.0) * pn1;
let rhs = (2.0 * nf + 1.0) * x * pn - nf * pm1;
approx_eq(lhs, rhs, 1e-8);
approx_eq(legendre_pn(n, 1.0), 1.0, 1e-14);
}
}
#[test]
fn deriv1_base_cases() {
let xx = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5];
for &x in &xx {
assert_eq!(legendre_pn_deriv1(0, x), 0.0);
assert_eq!(legendre_pn_deriv1(1, x), 1.0);
}
}
#[test]
fn deriv1_exact_formulas_n2_to_n5() {
let xx = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5];
for &x in &xx {
let x2 = x * x;
let x3 = x2 * x;
approx_eq(legendre_pn_deriv1(2, x), 3.0 * x, 1e-13);
approx_eq(legendre_pn_deriv1(3, x), (-3.0 + 15.0 * x2) / 2.0, 1e-13);
approx_eq(legendre_pn_deriv1(4, x), (-60.0 * x + 140.0 * x3) / 8.0, 1e-12);
let x4 = x3 * x;
approx_eq(legendre_pn_deriv1(5, x), (15.0 - 210.0 * x2 + 315.0 * x4) / 8.0, 1e-12);
}
}
#[test]
fn deriv1_boundary_x_eq_1() {
for n in 1..11 {
let expected = (n as f64) * (n as f64 + 1.0) / 2.0;
approx_eq(legendre_pn_deriv1(n, 1.0), expected, 1e-14);
}
}
#[test]
fn deriv1_boundary_x_eq_neg1() {
for n in 1..11 {
let val = (n as f64) * (n as f64 + 1.0) / 2.0;
let expected = if n % 2 == 0 { -val } else { val };
approx_eq(legendre_pn_deriv1(n, -1.0), expected, 1e-14);
}
}
#[test]
fn deriv1_numerical_finite_difference() {
let h = 1e-7;
let xx = [-0.99, -0.5, 0.0, 0.5, 0.99];
for &x in &xx {
for n in 0..9 {
let pn_h1 = legendre_pn(n, x + h);
let pn_h2 = legendre_pn(n, x - h);
let numerical = (pn_h1 - pn_h2) / (2.0 * h);
let analytical = legendre_pn_deriv1(n, x);
approx_eq(analytical, numerical, 1e-7);
}
}
}
#[test]
fn deriv1_outside_domain() {
for &x in &[-3.0, -2.0, 2.0, 3.0] {
let h = 1e-6;
for n in 1..9 {
let pn_h1 = legendre_pn(n, x + h);
let pn_h2 = legendre_pn(n, x - h);
let numerical = (pn_h1 - pn_h2) / (2.0 * h);
let analytical = legendre_pn_deriv1(n, x);
approx_eq(analytical, numerical, 1e-4);
}
}
}
#[test]
fn deriv1_ode_relation() {
let xx = [-0.9, -0.5, 0.0, 0.5, 0.9];
for &x in &xx {
for n in 2..9 {
let dpn = legendre_pn_deriv1(n, x);
let pn = legendre_pn(n, x);
let pn1 = legendre_pn(n - 1, x);
let lhs = (1.0 - x * x) * dpn;
let rhs = (n as f64) * (pn1 - x * pn);
approx_eq(lhs, rhs, 1e-12);
}
}
}
#[test]
fn deriv2_base_cases() {
let xx = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5];
for &x in &xx {
assert_eq!(legendre_pn_deriv2(0, x), 0.0);
assert_eq!(legendre_pn_deriv2(1, x), 0.0);
}
for &x in &xx {
assert_eq!(legendre_pn_deriv2(2, x), 3.0);
approx_eq(legendre_pn_deriv2(3, x), 15.0 * x, 1e-13);
}
}
#[test]
fn deriv2_exact_formulas_n2_to_n5() {
let xx = [-1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5];
for &x in &xx {
let x2 = x * x;
let x3 = x2 * x;
approx_eq(legendre_pn_deriv2(2, x), 3.0, 1e-13);
approx_eq(legendre_pn_deriv2(3, x), 15.0 * x, 1e-13);
approx_eq(legendre_pn_deriv2(4, x), (-60.0 + 420.0 * x2) / 8.0, 1e-12);
approx_eq(legendre_pn_deriv2(5, x), (-420.0 * x + 1260.0 * x3) / 8.0, 1e-12);
}
}
#[test]
fn deriv2_legendre_ode() {
let xx = [-0.99, -0.5, 0.0, 0.5, 0.99];
for &x in &xx {
for n in 0..9 {
let pn = legendre_pn(n, x);
let dpn = legendre_pn_deriv1(n, x);
let ddpn = legendre_pn_deriv2(n, x);
let residual = (1.0 - x * x) * ddpn - 2.0 * x * dpn + (n as f64) * ((n as f64) + 1.0) * pn;
approx_eq(residual, 0.0, 1e-10);
}
}
}
#[test]
fn deriv2_numerical_finite_difference() {
let h = 1e-5;
let xx = [-0.99, -0.5, 0.0, 0.5, 0.99];
for &x in &xx {
for n in 0..9 {
let dpn_h1 = legendre_pn_deriv1(n, x + h);
let dpn_h2 = legendre_pn_deriv1(n, x - h);
let numerical = (dpn_h1 - dpn_h2) / (2.0 * h);
let analytical = legendre_pn_deriv2(n, x);
approx_eq(analytical, numerical, 1e-5);
}
}
}
#[test]
fn deriv2_boundary_x_eq_1() {
for n in 2..9 {
let nf = n as f64;
let expected = nf * (nf + 1.0) * (nf - 1.0) * (nf + 2.0) / 8.0;
approx_eq(legendre_pn_deriv2(n, 1.0), expected, 1e-12);
}
}
#[test]
fn deriv2_boundary_x_eq_neg1() {
for n in 2..9 {
let nf = n as f64;
let val = nf * (nf + 1.0) * (nf - 1.0) * (nf + 2.0) / 8.0;
let expected = if n % 2 == 0 { val } else { -val };
approx_eq(legendre_pn_deriv2(n, -1.0), expected, 1e-12);
}
}
#[test]
fn deriv2_consistency_with_deriv1() {
let h = 1e-5;
let xx = [-0.99, -0.5, 0.0, 0.5, 0.99];
for &x in &xx {
for n in 2..9 {
let dpn_h1 = legendre_pn_deriv1(n, x + h);
let dpn_h2 = legendre_pn_deriv1(n, x - h);
let numerical = (dpn_h1 - dpn_h2) / (2.0 * h);
let analytical = legendre_pn_deriv2(n, x);
approx_eq(analytical, numerical, 1e-5);
}
}
}
#[test]
fn gauss_points_n0() {
let xx = legendre_gauss_points(0);
assert_eq!(xx.dim(), 1);
approx_eq(xx[0], 0.0, 1e-15);
}
#[test]
fn gauss_points_n1_to_n9() {
for n in 1..=9 {
let xx = legendre_gauss_points(n);
assert_eq!(xx.dim(), n + 1);
for k in 0..=n {
let pn = legendre_pn(n + 1, xx[k]);
approx_eq(pn, 0.0, 1e-14);
}
}
}
#[test]
fn gauss_points_are_roots() {
for n in 1..=15 {
let xx = legendre_gauss_points(n);
for k in 0..=n {
let pn = legendre_pn(n + 1, xx[k]);
assert!(pn.abs() < 1e-12, "P_{}({}) = {} not zero", n + 1, xx[k], pn);
}
}
}
#[test]
fn gauss_points_symmetry() {
for n in 1..=15 {
let xx = legendre_gauss_points(n);
for k in 0..=n {
approx_eq(xx[k], -xx[n - k], 1e-15);
}
}
}
#[test]
fn gauss_points_sorted_ascending() {
for n in 1..=15 {
let xx = legendre_gauss_points(n);
for k in 1..=n {
assert!(
xx[k] >= xx[k - 1],
"Points not sorted for n={}: x[{}]={} < x[{}]={}",
n,
k,
xx[k],
k - 1,
xx[k - 1]
);
}
}
}
#[test]
fn gauss_points_known_values() {
let xx2 = legendre_gauss_points(2);
let sqrt3_5 = (3.0_f64 / 5.0).sqrt();
approx_eq(xx2[0], -sqrt3_5, 1e-14);
approx_eq(xx2[1], 0.0, 1e-15);
approx_eq(xx2[2], sqrt3_5, 1e-14);
let xx3 = legendre_gauss_points(3);
approx_eq(xx3[0], -0.8611363115940526, 1e-14);
approx_eq(xx3[1], -0.3399810435848563, 1e-14);
approx_eq(xx3[2], 0.3399810435848563, 1e-14);
approx_eq(xx3[3], 0.8611363115940526, 1e-14);
}
#[test]
fn gauss_points_domain() {
for n in 1..=15 {
let xx = legendre_gauss_points(n);
for k in 0..=n {
assert!(
xx[k] > -1.0 && xx[k] < 1.0,
"Point x[{}]={} out of (-1,1) for n={}",
k,
xx[k],
n
);
}
}
}
#[test]
fn gauss_weights_sum_to_two() {
for n in 1..=15 {
let ww = legendre_gauss_weights(n);
let sum: f64 = ww.as_data().iter().sum();
approx_eq(sum, 2.0, 1e-14);
}
}
#[test]
fn gauss_weights_positive() {
for n in 1..=15 {
let ww = legendre_gauss_weights(n);
for k in 0..=n {
assert!(ww[k] > 0.0, "Weight w[{}]={} not positive for n={}", k, ww[k], n);
}
}
}
#[test]
fn gauss_weights_symmetry() {
for n in 1..=15 {
let ww = legendre_gauss_weights(n);
for k in 0..=n {
approx_eq(ww[k], ww[n - k], 1e-15);
}
}
}
#[test]
fn gauss_weights_known_values() {
let ww1 = legendre_gauss_weights(1);
approx_eq(ww1[0], 1.0, 1e-14);
approx_eq(ww1[1], 1.0, 1e-14);
let ww2 = legendre_gauss_weights(2);
approx_eq(ww2[0], 5.0 / 9.0, 1e-14);
approx_eq(ww2[1], 8.0 / 9.0, 1e-14);
approx_eq(ww2[2], 5.0 / 9.0, 1e-14);
}
#[test]
fn gauss_quadrature_exact_linear() {
let xx = legendre_gauss_points(1);
let ww = legendre_gauss_weights(1);
let mut result = 0.0;
for k in 0..=1 {
result += ww[k] * xx[k];
}
approx_eq(result, 0.0, 1e-15);
}
#[test]
fn gauss_quadrature_exact_quadratic() {
let xx = legendre_gauss_points(2);
let ww = legendre_gauss_weights(2);
let mut result = 0.0;
for k in 0..=2 {
result += ww[k] * xx[k] * xx[k];
}
approx_eq(result, 2.0 / 3.0, 1e-15);
}
#[test]
fn gauss_quadrature_exact_degree_2n_minus_1() {
for n in 1..=8 {
let xx = legendre_gauss_points(n);
let ww = legendre_gauss_weights(n);
let deg_odd = 2 * n - 1;
let mut result_odd = 0.0;
for k in 0..=n {
result_odd += ww[k] * xx[k].powi(deg_odd as i32);
}
approx_eq(result_odd, 0.0, 1e-12);
let deg_even = 2 * n - 2;
let mut result_even = 0.0;
for k in 0..=n {
result_even += ww[k] * xx[k].powi(deg_even as i32);
}
approx_eq(result_even, 2.0 / (2 * n - 1) as f64, 1e-12);
}
}
#[test]
fn gauss_quadrature_trig_function() {
let n = 10;
let xx = legendre_gauss_points(n);
let ww = legendre_gauss_weights(n);
let mut result = 0.0;
for k in 0..=n {
result += ww[k] * xx[k].cos();
}
approx_eq(result, 2.0 * 1.0_f64.sin(), 1e-12);
}
#[test]
fn lobatto_points_n2() {
let xx = legendre_lobatto_points(2);
assert_eq!(xx.dim(), 3);
approx_eq(xx[0], -1.0, 1e-15);
approx_eq(xx[1], 0.0, 1e-15);
approx_eq(xx[2], 1.0, 1e-15);
}
#[test]
fn lobatto_points_endpoints() {
for n in 2..=15 {
let xx = legendre_lobatto_points(n);
assert_eq!(xx[0], -1.0);
assert_eq!(xx[n], 1.0);
}
}
#[test]
fn lobatto_points_interior_are_critical_points() {
for n in 3..=15 {
let xx = legendre_lobatto_points(n);
for k in 1..n {
let dp = legendre_pn_deriv1(n, xx[k]);
assert!(dp.abs() < 1e-10, "P'_{}({}) = {} not zero for n={}", n, xx[k], dp, n);
}
}
}
#[test]
fn lobatto_points_symmetry() {
for n in 2..=15 {
let xx = legendre_lobatto_points(n);
for k in 0..=n {
approx_eq(xx[k], -xx[n - k], 1e-15);
}
}
}
#[test]
fn lobatto_points_sorted() {
for n in 2..=15 {
let xx = legendre_lobatto_points(n);
for k in 1..=n {
assert!(xx[k] > xx[k - 1], "Points not sorted for n={}", n);
}
}
}
#[test]
fn lobatto_points_known_values() {
let xx3 = legendre_lobatto_points(3);
let inv_sqrt5 = 1.0 / 5.0_f64.sqrt();
approx_eq(xx3[0], -1.0, 1e-15);
approx_eq(xx3[1], -inv_sqrt5, 1e-14);
approx_eq(xx3[2], inv_sqrt5, 1e-14);
approx_eq(xx3[3], 1.0, 1e-15);
}
#[test]
fn lobatto_points_domain() {
for n in 2..=15 {
let xx = legendre_lobatto_points(n);
for k in 0..=n {
assert!(xx[k] >= -1.0 && xx[k] <= 1.0, "Point out of [-1,1] for n={}", n);
}
}
}
#[test]
fn lobatto_weights_sum() {
for n in 2..=15 {
let ww = legendre_lobatto_weights(n);
let sum: f64 = ww.as_data().iter().sum();
approx_eq(sum, 2.0, 1e-13);
}
}
#[test]
fn lobatto_weights_positive() {
for n in 2..=15 {
let ww = legendre_lobatto_weights(n);
for k in 0..=n {
assert!(ww[k] > 0.0, "Weight w[{}] not positive for n={}", k, n);
}
}
}
#[test]
fn lobatto_weights_symmetry() {
for n in 2..=15 {
let ww = legendre_lobatto_weights(n);
for k in 0..=n {
approx_eq(ww[k], ww[n - k], 1e-15);
}
}
}
#[test]
fn lobatto_weights_endpoint_values() {
for n in 3..=10 {
let ww = legendre_lobatto_weights(n);
let np1 = (n + 1) as f64;
let expected = 2.0 / (np1 * (np1 - 1.0));
approx_eq(ww[0], expected, 1e-14);
approx_eq(ww[n], expected, 1e-14);
}
}
#[test]
fn lobatto_weights_known_values() {
let ww3 = legendre_lobatto_weights(3);
approx_eq(ww3[0], 1.0 / 6.0, 1e-14);
approx_eq(ww3[1], 5.0 / 6.0, 1e-14);
approx_eq(ww3[2], 5.0 / 6.0, 1e-14);
approx_eq(ww3[3], 1.0 / 6.0, 1e-14);
}
#[test]
fn lobatto_quadrature_exact_low_degree() {
for n in 3..=10 {
let xx = legendre_lobatto_points(n);
let ww = legendre_lobatto_weights(n);
let mut r0 = 0.0;
for k in 0..=n {
r0 += ww[k];
}
approx_eq(r0, 2.0, 1e-14);
let mut r1 = 0.0;
for k in 0..=n {
r1 += ww[k] * xx[k];
}
approx_eq(r1, 0.0, 1e-14);
let mut r2 = 0.0;
for k in 0..=n {
r2 += ww[k] * xx[k] * xx[k];
}
approx_eq(r2, 2.0 / 3.0, 1e-14);
}
}
#[test]
fn lobatto_quadrature_trig_function() {
let n = 12;
let xx = legendre_lobatto_points(n);
let ww = legendre_lobatto_weights(n);
let mut result = 0.0;
for k in 0..=n {
result += ww[k] * xx[k].cos();
}
approx_eq(result, 2.0 * 1.0_f64.sin(), 1e-12);
}
#[test]
fn pn_large_n_stability() {
let pn100 = legendre_pn(100, 0.5);
assert!(pn100.abs() < 1.0, "P_100(0.5) = {} too large", pn100);
}
#[test]
fn deriv1_equals_numerical_derivative() {
let h = 1e-7;
let xx = [-0.99, -0.5, 0.0, 0.5, 0.99];
for &x in &xx {
for n in 0..11 {
let pn_h1 = legendre_pn(n, x + h);
let pn_h2 = legendre_pn(n, x - h);
let numerical = (pn_h1 - pn_h2) / (2.0 * h);
let analytical = legendre_pn_deriv1(n, x);
approx_eq(analytical, numerical, 1e-7);
}
}
}
#[test]
fn deriv2_equals_numerical_second_derivative() {
let h = 1e-5;
let xx = [-0.9, -0.5, 0.0, 0.5, 0.9];
for &x in &xx {
for n in 0..11 {
let pn_h1 = legendre_pn(n, x + h);
let pn = legendre_pn(n, x);
let pn_h2 = legendre_pn(n, x - h);
let numerical = (pn_h1 - 2.0 * pn + pn_h2) / (h * h);
let analytical = legendre_pn_deriv2(n, x);
approx_eq(analytical, numerical, 1e-4);
}
}
}
#[test]
fn gauss_points_high_degree() {
for &n in &[20, 50] {
let xx = legendre_gauss_points(n);
for k in 0..=n {
let pn = legendre_pn(n + 1, xx[k]);
assert!(pn.abs() < 1e-10, "P_{}({}) = {} not zero", n + 1, xx[k], pn);
}
}
}
#[test]
fn lobatto_points_high_degree() {
let n = 20;
let xx = legendre_lobatto_points(n);
for k in 1..n {
let dp = legendre_pn_deriv1(n, xx[k]);
assert!(dp.abs() < 1e-8, "P'_{}({}) = {} not zero", n, xx[k], dp);
}
}
#[test]
fn gauss_points_newton_dp_zero_safety_break() {
let result = gauss_points_with_guess(3, 0.0);
approx_eq(result, 0.0, 1e-15);
}
#[test]
fn lobatto_points_newton_ddp_zero_safety_break() {
let x0 = (1.0_f64 / 7.0).sqrt();
let result = lobatto_points_with_guess(4, x0);
assert!((result - x0).abs() < 1e-10 || result.is_finite());
}
#[test]
fn lobatto_weights_returns_empty_for_nn_0() {
let ww = legendre_lobatto_weights(0);
assert_eq!(ww.dim(), 1);
}
#[test]
fn lobatto_weights_returns_empty_for_nn_1() {
let ww = legendre_lobatto_weights(1);
assert_eq!(ww.dim(), 2);
}
}