use crate::{
__impl_node_weight_rule, Node, Weight,
math::{cos, sin},
};
use alloc::boxed::Box;
use core::{f64::consts::PI, num::NonZeroUsize};
#[cfg(feature = "rayon")]
use rayon::iter::{
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator,
};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GaussChebyshevFirstKind {
node_weight_pairs: Box<[(Node, Weight)]>,
}
impl GaussChebyshevFirstKind {
pub fn new(degree: NonZeroUsize) -> Self {
let n = degree.get() as f64;
Self {
node_weight_pairs: (1..degree.get() + 1)
.rev()
.map(|i| (cos(PI * (2.0 * (i as f64) - 1.0) / (2.0 * n)), PI / n))
.collect(),
}
}
fn argument_transformation(x: f64, a: f64, b: f64) -> f64 {
0.5 * ((b - a) * x + (b + a))
}
fn scale_factor(a: f64, b: f64) -> f64 {
0.5 * (b - a)
}
#[cfg(feature = "rayon")]
pub fn par_new(degree: NonZeroUsize) -> Self {
let n = degree.get() as f64;
Self {
node_weight_pairs: (1..degree.get() + 1)
.into_par_iter()
.rev()
.map(|i| (cos(PI * (2.0 * (i as f64) - 1.0) / (2.0 * n)), PI / n))
.collect(),
}
}
pub fn integrate<F>(&self, a: f64, b: f64, mut integrand: F) -> f64
where
F: FnMut(f64) -> f64,
{
let result: f64 = self
.node_weight_pairs
.iter()
.map(|(x, w)| integrand(Self::argument_transformation(*x, a, b)) * w)
.sum();
result * Self::scale_factor(a, b)
}
#[cfg(feature = "rayon")]
pub fn par_integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
where
F: Sync + Fn(f64) -> f64,
{
let result: f64 = self
.node_weight_pairs
.par_iter()
.map(|(x, w)| integrand(Self::argument_transformation(*x, a, b)) * w)
.sum();
result * Self::scale_factor(a, b)
}
}
__impl_node_weight_rule! {GaussChebyshevFirstKind, GaussChebyshevFirstKindNodes, GaussChebyshevFirstKindWeights, GaussChebyshevFirstKindIter, GaussChebyshevFirstKindIntoIter}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GaussChebyshevSecondKind {
node_weight_pairs: Box<[(Node, Weight)]>,
}
impl GaussChebyshevSecondKind {
pub fn new(degree: NonZeroUsize) -> Self {
let n = degree.get() as f64;
Self {
node_weight_pairs: (1..degree.get() + 1)
.rev()
.map(|i| {
let over_n_plus_1 = 1.0 / (n + 1.0);
let sin_val = sin(PI * i as f64 * over_n_plus_1);
(
cos(PI * i as f64 * over_n_plus_1),
PI * over_n_plus_1 * sin_val * sin_val,
)
})
.collect(),
}
}
#[cfg(feature = "rayon")]
pub fn par_new(degree: NonZeroUsize) -> Self {
let n = degree.get() as f64;
Self {
node_weight_pairs: (1..degree.get() + 1)
.into_par_iter()
.rev()
.map(|i| {
let over_n_plus_1 = 1.0 / (n + 1.0);
let sin_val = sin(PI * i as f64 * over_n_plus_1);
(
cos(PI * i as f64 * over_n_plus_1),
PI * over_n_plus_1 * sin_val * sin_val,
)
})
.collect(),
}
}
fn argument_transformation(x: f64, a: f64, b: f64) -> f64 {
0.5 * ((b - a) * x + (b + a))
}
fn scale_factor(a: f64, b: f64) -> f64 {
0.5 * (b - a)
}
pub fn integrate<F>(&self, a: f64, b: f64, mut integrand: F) -> f64
where
F: FnMut(f64) -> f64,
{
let result: f64 = self
.node_weight_pairs
.iter()
.map(|(x, w)| integrand(Self::argument_transformation(*x, a, b)) * w)
.sum();
result * Self::scale_factor(a, b)
}
#[cfg(feature = "rayon")]
pub fn par_integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
where
F: Fn(f64) -> f64 + Sync,
{
let result: f64 = self
.node_weight_pairs
.par_iter()
.map(|(x, w)| integrand(Self::argument_transformation(*x, a, b)) * w)
.sum();
result * Self::scale_factor(a, b)
}
}
__impl_node_weight_rule! {GaussChebyshevSecondKind, GaussChebyshevSecondKindNodes, GaussChebyshevSecondKindWeights, GaussChebyshevSecondKindIter, GaussChebyshevSecondKindIntoIter}
#[cfg(test)]
mod test {
use crate::math::{cos, sin};
use approx::assert_abs_diff_eq;
use super::{GaussChebyshevFirstKind, GaussChebyshevSecondKind};
use core::{f64::consts::PI, num::NonZeroUsize};
#[test]
fn check_sorted() {
for deg in (2..100).step_by(10) {
let rule1 = GaussChebyshevFirstKind::new(deg.try_into().unwrap());
assert!(rule1.as_node_weight_pairs().is_sorted());
let rule2 = GaussChebyshevSecondKind::new(deg.try_into().unwrap());
assert!(rule2.as_node_weight_pairs().is_sorted());
}
}
#[cfg(feature = "rayon")]
#[test]
fn check_par_sorted() {
for deg in (2..100).step_by(10) {
let rule1 = GaussChebyshevFirstKind::par_new(deg.try_into().unwrap());
assert!(rule1.as_node_weight_pairs().is_sorted());
let rule2 = GaussChebyshevSecondKind::par_new(deg.try_into().unwrap());
assert!(rule2.as_node_weight_pairs().is_sorted());
}
}
#[test]
fn check_degree_1() {
let rule = GaussChebyshevFirstKind::new(1.try_into().unwrap());
assert_abs_diff_eq!(*rule.nodes().next().unwrap(), 0.0);
assert_abs_diff_eq!(*rule.weights().next().unwrap(), PI);
assert_abs_diff_eq!(rule.integrate(-1.0, 1.0, |x| x), 0.0);
assert_abs_diff_eq!(rule.integrate(-1.0, 1.0, |x| x + 1.0), PI);
assert_abs_diff_eq!(rule.integrate(0.0, 1.0, |x| x), PI / 4.0);
let rule = GaussChebyshevSecondKind::new(1.try_into().unwrap());
assert_abs_diff_eq!(*rule.nodes().next().unwrap(), 0.0);
assert_abs_diff_eq!(*rule.weights().next().unwrap(), PI / 2.0);
assert_abs_diff_eq!(rule.integrate(-1.0, 1.0, |x| x), 0.0);
assert_abs_diff_eq!(rule.integrate(0.0, 1.0, |x| x), PI / 8.0);
}
#[test]
fn check_chebyshev_1st_deg_5() {
let ans = [
(-0.5 * (0.5 * (5.0 + f64::sqrt(5.0))).sqrt(), PI / 5.0),
(-0.5 * (0.5 * (5.0 - f64::sqrt(5.0))).sqrt(), PI / 5.0),
(0.0, PI / 5.0),
(0.5 * (0.5 * (5.0 - f64::sqrt(5.0))).sqrt(), PI / 5.0),
(0.5 * (0.5 * (5.0 + f64::sqrt(5.0))).sqrt(), PI / 5.0),
];
let rule = GaussChebyshevFirstKind::new(5.try_into().unwrap());
for ((x, w), (x_should, w_should)) in rule.into_iter().zip(ans.into_iter()) {
assert_abs_diff_eq!(x, x_should);
assert_abs_diff_eq!(w, w_should);
}
}
#[test]
fn check_chebyshev_2nd_deg_5() {
let deg = NonZeroUsize::new(5).unwrap();
let rule = GaussChebyshevSecondKind::new(deg);
let deg = deg.get() as f64;
for (i, (x, w)) in rule.into_iter().enumerate() {
let ii = deg - i as f64;
let x_should = cos(ii * PI / (deg + 1.0));
let w_should = PI / (deg + 1.0) * sin(ii * PI / (deg + 1.0)).powi(2);
assert_abs_diff_eq!(x, x_should);
assert_abs_diff_eq!(w, w_should);
}
}
#[test]
fn check_integral_of_line() {
let rule = GaussChebyshevFirstKind::new(2.try_into().unwrap());
assert_abs_diff_eq!(rule.integrate(0.0, 2.0, |x| x), PI);
}
#[test]
fn check_integral_of_legendre_2() {
let deg = NonZeroUsize::new(2).unwrap();
let rule1 = GaussChebyshevFirstKind::new(deg);
let rule2 = GaussChebyshevSecondKind::new(deg);
fn legendre_2(x: f64) -> f64 {
1.5 * x * x - 0.5
}
assert_abs_diff_eq!(rule1.integrate(-1.0, 1.0, legendre_2), PI / 4.0);
assert_abs_diff_eq!(rule2.integrate(-1.0, 1.0, legendre_2), -PI / 16.0);
}
#[test]
fn check_integral_of_parabola() {
let rule = GaussChebyshevSecondKind::new(2.try_into().unwrap());
assert_abs_diff_eq!(rule.integrate(-1.0, 1.0, |x| x * x), PI / 8.0);
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_integrate() {
let rule1 = GaussChebyshevFirstKind::par_new(2.try_into().unwrap());
let rule2 = GaussChebyshevSecondKind::par_new(2.try_into().unwrap());
assert_abs_diff_eq!(rule1.par_integrate(0.0, 2.0, |x| x), PI);
assert_abs_diff_eq!(rule2.par_integrate(-1.0, 1.0, |x| x * x), PI / 8.0);
}
}