use core::f64::consts::PI;
use crate::error::QuadratureError;
use crate::rule::QuadratureRule;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use num_traits::Float as _;
#[derive(Debug, Clone)]
pub struct GaussChebyshevFirstKind {
rule: QuadratureRule<f64>,
}
impl GaussChebyshevFirstKind {
#[allow(clippy::cast_precision_loss)] pub fn new(n: usize) -> Result<Self, QuadratureError> {
if n == 0 {
return Err(QuadratureError::ZeroOrder);
}
let w = PI / n as f64;
let mut nodes = Vec::with_capacity(n);
let mut weights = Vec::with_capacity(n);
for k in 1..=n {
let theta = (2 * k - 1) as f64 * PI / (2 * n) as f64;
nodes.push(theta.cos());
weights.push(w);
}
nodes.reverse();
weights.reverse();
Ok(Self {
rule: QuadratureRule { nodes, weights },
})
}
}
impl_rule_accessors!(GaussChebyshevFirstKind, nodes_doc: "Returns the nodes on \\[-1, 1\\].");
#[derive(Debug, Clone)]
pub struct GaussChebyshevSecondKind {
rule: QuadratureRule<f64>,
}
impl GaussChebyshevSecondKind {
#[allow(clippy::cast_precision_loss)] pub fn new(n: usize) -> Result<Self, QuadratureError> {
if n == 0 {
return Err(QuadratureError::ZeroOrder);
}
let n1 = (n + 1) as f64;
let mut nodes = Vec::with_capacity(n);
let mut weights = Vec::with_capacity(n);
for k in 1..=n {
let theta = k as f64 * PI / n1;
nodes.push(theta.cos());
let s = theta.sin();
weights.push(PI / n1 * s * s);
}
nodes.reverse();
weights.reverse();
Ok(Self {
rule: QuadratureRule { nodes, weights },
})
}
}
impl_rule_accessors!(GaussChebyshevSecondKind, nodes_doc: "Returns the nodes on \\[-1, 1\\].");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_i_zero_order() {
assert!(GaussChebyshevFirstKind::new(0).is_err());
}
#[test]
fn type_i_uniform_weights() {
let gc = GaussChebyshevFirstKind::new(20).unwrap();
let expected_w = PI / 20.0;
for &w in gc.weights() {
assert!((w - expected_w).abs() < 1e-14);
}
}
#[test]
fn type_i_nodes_sorted_and_bounded() {
let gc = GaussChebyshevFirstKind::new(50).unwrap();
for i in 0..gc.order() - 1 {
assert!(gc.nodes()[i] < gc.nodes()[i + 1]);
}
assert!(gc.nodes()[0] > -1.0);
assert!(*gc.nodes().last().unwrap() < 1.0);
}
#[test]
fn type_i_weight_sum() {
let gc = GaussChebyshevFirstKind::new(100).unwrap();
let sum: f64 = gc.weights().iter().sum();
assert!((sum - PI).abs() < 1e-12, "sum={sum}");
}
#[test]
fn type_i_polynomial_exactness() {
let n = 10;
let gc = GaussChebyshevFirstKind::new(n).unwrap();
let r: f64 = gc.nodes().iter().zip(gc.weights()).map(|(_, &w)| w).sum();
assert!((r - PI).abs() < 1e-12);
let r: f64 = gc
.nodes()
.iter()
.zip(gc.weights())
.map(|(&x, &w)| x * w)
.sum();
assert!(r.abs() < 1e-12);
}
#[test]
fn type_ii_zero_order() {
assert!(GaussChebyshevSecondKind::new(0).is_err());
}
#[test]
fn type_ii_weight_sum() {
let gc = GaussChebyshevSecondKind::new(100).unwrap();
let sum: f64 = gc.weights().iter().sum();
assert!((sum - PI / 2.0).abs() < 1e-12, "sum={sum}");
}
#[test]
fn type_ii_nodes_sorted_and_bounded() {
let gc = GaussChebyshevSecondKind::new(50).unwrap();
for i in 0..gc.order() - 1 {
assert!(gc.nodes()[i] < gc.nodes()[i + 1]);
}
assert!(gc.nodes()[0] > -1.0);
assert!(*gc.nodes().last().unwrap() < 1.0);
}
#[test]
fn type_ii_symmetry() {
let gc = GaussChebyshevSecondKind::new(21).unwrap();
let n = gc.order();
for i in 0..n / 2 {
assert!((gc.nodes()[i] + gc.nodes()[n - 1 - i]).abs() < 1e-14);
assert!((gc.weights()[i] - gc.weights()[n - 1 - i]).abs() < 1e-14);
}
}
}