gauss_quad/jacobi/
mod.rs

1//! Numerical integration using the Gauss-Jacobi quadrature rule.
2//!
3//! This rule can integrate expressions of the form (1 - x)^alpha * (1 + x)^beta * f(x),
4//! where f(x) is a smooth function on a finite domain, alpha > -1 and beta > -1, and where f(x) is transformed from the domain [a, b] to the domain [-1, 1].
5//! This enables the approximation of integrals with singularities at the end points of the domain.
6//!
7//! # Example
8//! ```
9//! use gauss_quad::jacobi::GaussJacobi;
10//! # use gauss_quad::jacobi::GaussJacobiError;
11//! use approx::assert_abs_diff_eq;
12//!
13//! let quad = GaussJacobi::new(10, 0.0, -1.0 / 3.0)?;
14//!
15//! // numerically integrate sin(x) / (1 + x)^(1/3), a function with a singularity at x = -1.
16//! let integral = quad.integrate(-1.0, 1.0, |x| x.sin());
17//!
18//! assert_abs_diff_eq!(integral, -0.4207987746500829, epsilon = 1e-14);
19//! # Ok::<(), GaussJacobiError>(())
20//! ```
21
22#[cfg(feature = "rayon")]
23use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
24
25use crate::gamma::gamma;
26use crate::{
27    DMatrixf64, GaussChebyshevFirstKind, GaussChebyshevSecondKind, GaussLegendre, Node, Weight,
28    __impl_node_weight_rule,
29};
30
31use std::backtrace::Backtrace;
32
33/// A Gauss-Jacobi quadrature scheme.
34///
35/// This rule can integrate expressions of the form (1 - x)^alpha * (1 + x)^beta * f(x),
36/// where f(x) is a smooth function on a finite domain, alpha > -1 and beta > -1,
37/// and where f(x) is transformed from the domain [a, b] to the domain [-1, 1].
38/// This enables the approximation of integrals with singularities at the end points of the domain.
39///
40/// # Examples
41/// ```
42/// # use gauss_quad::jacobi::{GaussJacobi, GaussJacobiError};
43/// # use approx::assert_abs_diff_eq;
44/// # use core::f64::consts::E;
45/// // initialize the quadrature rule.
46/// let quad = GaussJacobi::new(10, -0.5, 0.0)?;
47///
48/// let integral = quad.integrate(0.0, 2.0, |x| (-x).exp());
49///
50/// assert_abs_diff_eq!(integral, 0.9050798148074449, epsilon = 1e-14);
51/// # Ok::<(), GaussJacobiError>(())
52/// ```
53#[derive(Debug, Clone, PartialEq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct GaussJacobi {
56    node_weight_pairs: Vec<(Node, Weight)>,
57    alpha: f64,
58    beta: f64,
59}
60
61impl GaussJacobi {
62    /// Initializes Gauss-Jacobi quadrature rule of the given degree by computing the nodes and weights
63    /// needed for the given parameters. `alpha` is the exponent of the (1 - x) factor and `beta` is the
64    /// exponent of the (1 + x) factor.
65    ///
66    /// A rule of degree n can integrate polynomials of degree 2n-1 exactly.
67    ///
68    /// Applies the Golub-Welsch algorithm to determine Gauss-Jacobi nodes & weights.
69    /// See Gil, Segura, Temme - Numerical Methods for Special Functions
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if `deg` is smaller than 2, and/or if `alpha` and/or `beta` are smaller than or equal to -1.
74    pub fn new(deg: usize, alpha: f64, beta: f64) -> Result<Self, GaussJacobiError> {
75        match (
76            deg >= 2,
77            (alpha.is_finite() && alpha > -1.0),
78            (beta.is_finite() && beta > -1.0),
79        ) {
80            (true, true, true) => Ok(()),
81            (false, true, true) => Err(GaussJacobiErrorReason::Degree),
82            (true, false, true) => Err(GaussJacobiErrorReason::Alpha),
83            (true, true, false) => Err(GaussJacobiErrorReason::Beta),
84            (true, false, false) => Err(GaussJacobiErrorReason::AlphaBeta),
85            (false, false, true) => Err(GaussJacobiErrorReason::DegreeAlpha),
86            (false, true, false) => Err(GaussJacobiErrorReason::DegreeBeta),
87            (false, false, false) => Err(GaussJacobiErrorReason::DegreeAlphaBeta),
88        }
89        .map_err(GaussJacobiError::new)?;
90
91        // Delegate the computation of nodes and weights when they have special values
92        // that are equivalent to other rules that have faster implementations.
93        //
94        // UNWRAP: We have already verified that the degree is 2 or larger above.
95        // Since that is the only possible error cause for these quadrature rules
96        // this code can not fail, so we just `unwrap` the result.
97        match (alpha, beta) {
98            (0.0, 0.0) => return Ok(GaussLegendre::new(deg).unwrap().into()),
99            (-0.5, -0.5) => return Ok(GaussChebyshevFirstKind::new(deg).unwrap().into()),
100            (0.5, 0.5) => return Ok(GaussChebyshevSecondKind::new(deg).unwrap().into()),
101            _ => (),
102        }
103
104        let mut companion_matrix = DMatrixf64::from_element(deg, deg, 0.0);
105
106        let mut diag = (beta - alpha) / (2.0 + beta + alpha);
107        // Initialize symmetric companion matrix
108        for idx in 0..deg - 1 {
109            let idx_f64 = idx as f64;
110            let idx_p1 = idx_f64 + 1.0;
111            let denom_sum = 2.0 * idx_p1 + alpha + beta;
112            let off_diag = 2.0 / denom_sum
113                * (idx_p1 * (idx_p1 + alpha) * (idx_p1 + beta) * (idx_p1 + alpha + beta)
114                    / ((denom_sum + 1.0) * (denom_sum - 1.0)))
115                    .sqrt();
116            unsafe {
117                *companion_matrix.get_unchecked_mut((idx, idx)) = diag;
118                *companion_matrix.get_unchecked_mut((idx, idx + 1)) = off_diag;
119                *companion_matrix.get_unchecked_mut((idx + 1, idx)) = off_diag;
120            }
121            diag = (beta * beta - alpha * alpha) / (denom_sum * (denom_sum + 2.0));
122        }
123        unsafe {
124            *companion_matrix.get_unchecked_mut((deg - 1, deg - 1)) = diag;
125        }
126        // calculate eigenvalues & vectors
127        let eigen = companion_matrix.symmetric_eigen();
128
129        let scale_factor =
130            (2.0f64).powf(alpha + beta + 1.0) * gamma(alpha + 1.0) * gamma(beta + 1.0)
131                / gamma(alpha + beta + 1.0)
132                / (alpha + beta + 1.0);
133
134        // zip together the iterator over nodes with the one over weights and return as Vec<(f64, f64)>
135        let mut node_weight_pairs: Vec<(f64, f64)> = eigen
136            .eigenvalues
137            .iter()
138            .copied()
139            .zip(
140                eigen
141                    .eigenvectors
142                    .row(0)
143                    .iter()
144                    .map(|x| x * x * scale_factor),
145            )
146            .collect();
147
148        node_weight_pairs.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
149
150        // TO FIX: implement correction
151        // eigenvalue algorithm has problem to get the zero eigenvalue for odd degrees
152        // for now... manual correction seems to do the trick
153        if deg % 2 == 1 {
154            node_weight_pairs[deg / 2].0 = 0.0;
155        }
156
157        Ok(Self {
158            node_weight_pairs,
159            alpha,
160            beta,
161        })
162    }
163
164    fn argument_transformation(x: f64, a: f64, b: f64) -> f64 {
165        0.5 * ((b - a) * x + (b + a))
166    }
167
168    fn scale_factor(a: f64, b: f64) -> f64 {
169        0.5 * (b - a)
170    }
171
172    /// Perform quadrature of integrand from `a` to `b`. This will integrate  
173    /// (1 - x)^`alpha` * (1 + x)^`beta` * `integrand`(x)  
174    /// where `alpha` and `beta` were given in the call to [`new`](Self::new), and the integrand is transformed from the domain [a, b] to the domain [-1, 1].
175    pub fn integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
176    where
177        F: Fn(f64) -> f64,
178    {
179        let result: f64 = self
180            .node_weight_pairs
181            .iter()
182            .map(|(x_val, w_val)| integrand(Self::argument_transformation(*x_val, a, b)) * w_val)
183            .sum();
184        Self::scale_factor(a, b) * result
185    }
186
187    #[cfg(feature = "rayon")]
188    /// Same as [`integrate`](GaussJacobi::integrate) but runs in parallel.
189    pub fn par_integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
190    where
191        F: Fn(f64) -> f64 + Sync,
192    {
193        let result: f64 = self
194            .node_weight_pairs
195            .par_iter()
196            .map(|(x_val, w_val)| integrand(Self::argument_transformation(*x_val, a, b)) * w_val)
197            .sum();
198        Self::scale_factor(a, b) * result
199    }
200
201    /// Returns the value of the `alpha` parameter.
202    #[inline]
203    pub const fn alpha(&self) -> f64 {
204        self.alpha
205    }
206
207    /// Returns the value of the `beta` parameter.
208    #[inline]
209    pub const fn beta(&self) -> f64 {
210        self.beta
211    }
212}
213
214__impl_node_weight_rule! {GaussJacobi, GaussJacobiNodes, GaussJacobiWeights, GaussJacobiIter, GaussJacobiIntoIter}
215
216/// The error returned by [`GaussJacobi::new`] if given a degree, `deg`, less than 2
217/// and/or an `alpha` and/or `beta` less than or equal to -1.
218#[derive(Debug)]
219pub struct GaussJacobiError {
220    reason: GaussJacobiErrorReason,
221    backtrace: Backtrace,
222}
223
224impl GaussJacobiError {
225    /// Captures a backtrace and creates a new GaussJacobiError with the given reason.
226    #[inline]
227    pub(crate) fn new(reason: GaussJacobiErrorReason) -> Self {
228        Self {
229            reason,
230            backtrace: Backtrace::capture(),
231        }
232    }
233
234    /// Returns the reason for the error.
235    #[inline]
236    pub fn reason(&self) -> GaussJacobiErrorReason {
237        self.reason
238    }
239
240    /// Returns a [`Backtrace`] to where the error was created.
241    ///
242    /// This backtrace is captured with [`Backtrace::capture`], see it for more information about how to make it display information when printed.
243    #[inline]
244    pub fn backtrace(&self) -> &Backtrace {
245        &self.backtrace
246    }
247}
248
249use core::fmt;
250impl fmt::Display for GaussJacobiError {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        const DEGREE_LIMIT: &str = "must be at least 2";
253        const EXPONENT_LIMIT: &str = "must be finite and larger than -1.0";
254        match self.reason() {
255            GaussJacobiErrorReason::Degree => write!(f, "degree {DEGREE_LIMIT}"),
256            GaussJacobiErrorReason::Alpha => write!(f, "alpha {EXPONENT_LIMIT}"),
257            GaussJacobiErrorReason::Beta => write!(f, "beta {EXPONENT_LIMIT}"),
258            GaussJacobiErrorReason::AlphaBeta => write!(f, "alpha and beta {EXPONENT_LIMIT}"),
259            GaussJacobiErrorReason::DegreeAlpha => {
260                write!(f, "degree {DEGREE_LIMIT} and alpha {EXPONENT_LIMIT}")
261            }
262            GaussJacobiErrorReason::DegreeBeta => {
263                write!(f, "degree {DEGREE_LIMIT} and beta {EXPONENT_LIMIT}")
264            }
265            GaussJacobiErrorReason::DegreeAlphaBeta => {
266                write!(f, "degree {DEGREE_LIMIT}, alpha and beta {EXPONENT_LIMIT}")
267            }
268        }
269    }
270}
271
272impl std::error::Error for GaussJacobiError {}
273
274/// Gauss-Legendre quadrature is equivalent to Gauss-Jacobi quadrature with `alpha` = `beta` = 0.
275impl From<GaussLegendre> for GaussJacobi {
276    fn from(value: GaussLegendre) -> Self {
277        let mut node_weight_pairs = value.into_node_weight_pairs();
278        // Gauss-Legendre nodes are generated in reverse sorted order.
279        // This corrects for that since Gauss-Jacobi nodes are currently always sorted
280        // in ascending order.
281        node_weight_pairs.reverse();
282        Self {
283            node_weight_pairs,
284            alpha: 0.0,
285            beta: 0.0,
286        }
287    }
288}
289
290/// Gauss-Chebyshev quadrature of the first kind is equivalent to Gauss-Jacobi quadrature with `alpha` = `beta` = -0.5.
291impl From<GaussChebyshevFirstKind> for GaussJacobi {
292    fn from(value: GaussChebyshevFirstKind) -> Self {
293        Self {
294            node_weight_pairs: value.into_node_weight_pairs(),
295            alpha: -0.5,
296            beta: -0.5,
297        }
298    }
299}
300
301/// Gauss-Chebyshev quadrature of the second kind is equivalent to Gauss-Jacobi quadrature with `alpha` = `beta` = 0.5.
302impl From<GaussChebyshevSecondKind> for GaussJacobi {
303    fn from(value: GaussChebyshevSecondKind) -> Self {
304        Self {
305            node_weight_pairs: value.into_node_weight_pairs(),
306            alpha: 0.5,
307            beta: 0.5,
308        }
309    }
310}
311
312/// The reason for the `GaussJacobiError`, returned by the [`GaussJacobiError::reason`] function.
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
314#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
315pub enum GaussJacobiErrorReason {
316    /// The degree was less than 2.
317    Degree,
318    /// The `alpha` exponent was less than or equal to -1.
319    Alpha,
320    /// The `beta` exponent was less than or equal to -1.
321    Beta,
322    /// Both the `alpha` and `beta` exponents were less than or equal to -1.
323    AlphaBeta,
324    /// The degree was less than 2 and the `alpha` exponent was less than or equal to -1.
325    DegreeAlpha,
326    /// The degree was less than 2 and the `beta` exponent was less than or equal to -1.
327    DegreeBeta,
328    /// The degree was less than 2 and both the `alpha` and `beta` exponents were less than or equal to -1.
329    DegreeAlphaBeta,
330}
331
332impl GaussJacobiErrorReason {
333    /// Returns true if the given degree, `deg`, was bad.
334    #[inline]
335    pub fn was_bad_degree(&self) -> bool {
336        matches!(
337            self,
338            Self::Degree | Self::DegreeAlpha | Self::DegreeBeta | Self::DegreeAlphaBeta
339        )
340    }
341
342    /// Returns true if the given `alpha` exponent was bad.
343    #[inline]
344    pub fn was_bad_alpha(&self) -> bool {
345        matches!(
346            self,
347            Self::Alpha | Self::DegreeAlpha | Self::AlphaBeta | Self::DegreeAlphaBeta
348        )
349    }
350
351    /// Returns true if the given `beta` exponent was bad.
352    #[inline]
353    pub fn was_bad_beta(&self) -> bool {
354        matches!(
355            self,
356            Self::Beta | Self::DegreeBeta | Self::AlphaBeta | Self::DegreeAlphaBeta
357        )
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use approx::assert_abs_diff_eq;
364
365    use super::*;
366
367    #[test]
368    fn sanity_check_chebyshev_delegation() {
369        const DEG: usize = 200;
370        let jrule = GaussJacobi::new(DEG, -0.5, -0.5).unwrap();
371        let crule1 = GaussChebyshevFirstKind::new(DEG).unwrap();
372
373        assert_eq!(jrule.as_node_weight_pairs(), crule1.as_node_weight_pairs());
374
375        let jrule = GaussJacobi::new(DEG, 0.5, 0.5).unwrap();
376        let crule2 = GaussChebyshevSecondKind::new(DEG).unwrap();
377
378        assert_eq!(jrule.as_node_weight_pairs(), crule2.as_node_weight_pairs())
379    }
380
381    #[test]
382    fn sanity_check_legendre_delegation() {
383        const DEG: usize = 200;
384        let jrule = GaussJacobi::new(DEG, 0.0, 0.0).unwrap();
385        let lrule = GaussLegendre::new(DEG).unwrap();
386
387        assert_eq!(
388            jrule.as_node_weight_pairs(),
389            lrule.into_iter().rev().collect::<Vec<_>>(),
390        );
391    }
392
393    #[test]
394    fn check_alpha_beta_bounds() {
395        assert!(GaussJacobi::new(10, -1.0, -1.0).is_err());
396    }
397
398    #[test]
399    fn golub_welsch_5_alpha_0_beta_0() {
400        let (x, w): (Vec<_>, Vec<_>) = GaussJacobi::new(5, 0.0, 0.0).unwrap().into_iter().unzip();
401        let x_should = [
402            -0.906_179_845_938_664,
403            -0.538_469_310_105_683_1,
404            0.0,
405            0.538_469_310_105_683_1,
406            0.906_179_845_938_664,
407        ];
408        let w_should = [
409            0.236_926_885_056_189_08,
410            0.478_628_670_499_366_47,
411            0.568_888_888_888_888_9,
412            0.478_628_670_499_366_47,
413            0.236_926_885_056_189_08,
414        ];
415        for (i, x_val) in x_should.iter().enumerate() {
416            assert_abs_diff_eq!(*x_val, x[i], epsilon = 1e-15);
417        }
418        for (i, w_val) in w_should.iter().enumerate() {
419            assert_abs_diff_eq!(*w_val, w[i], epsilon = 1e-15);
420        }
421    }
422
423    #[test]
424    fn golub_welsch_2_alpha_1_beta_0() {
425        let (x, w): (Vec<_>, Vec<_>) = GaussJacobi::new(2, 1.0, 0.0).unwrap().into_iter().unzip();
426        let x_should = [-0.689_897_948_556_635_7, 0.289_897_948_556_635_64];
427        let w_should = [1.272_165_526_975_908_7, 0.727_834_473_024_091_3];
428        for (i, x_val) in x_should.iter().enumerate() {
429            assert_abs_diff_eq!(*x_val, x[i], epsilon = 1e-14);
430        }
431        for (i, w_val) in w_should.iter().enumerate() {
432            assert_abs_diff_eq!(*w_val, w[i], epsilon = 1e-14);
433        }
434    }
435
436    #[test]
437    fn golub_welsch_5_alpha_1_beta_0() {
438        let (x, w): (Vec<_>, Vec<_>) = GaussJacobi::new(5, 1.0, 0.0).unwrap().into_iter().unzip();
439        let x_should = [
440            -0.920_380_285_897_062_6,
441            -0.603_973_164_252_783_7,
442            0.0,
443            0.390_928_546_707_272_2,
444            0.802_929_828_402_347_2,
445        ];
446        let w_should = [
447            0.387_126_360_906_606_74,
448            0.668_698_552_377_478_2,
449            0.585_547_948_338_679_2,
450            0.295_635_480_290_466_66,
451            0.062_991_658_086_769_1,
452        ];
453        for (i, x_val) in x_should.iter().enumerate() {
454            assert_abs_diff_eq!(*x_val, x[i], epsilon = 1e-14);
455        }
456        for (i, w_val) in w_should.iter().enumerate() {
457            assert_abs_diff_eq!(*w_val, w[i], epsilon = 1e-14);
458        }
459    }
460
461    #[test]
462    fn golub_welsch_5_alpha_0_beta_1() {
463        let (x, w): (Vec<_>, Vec<_>) = GaussJacobi::new(5, 0.0, 1.0).unwrap().into_iter().unzip();
464        let x_should = [
465            -0.802_929_828_402_347_2,
466            -0.390_928_546_707_272_2,
467            0.0,
468            0.603_973_164_252_783_7,
469            0.920_380_285_897_062_6,
470        ];
471        let w_should = [
472            0.062_991_658_086_769_1,
473            0.295_635_480_290_466_66,
474            0.585_547_948_338_679_2,
475            0.668_698_552_377_478_2,
476            0.387_126_360_906_606_74,
477        ];
478        for (i, x_val) in x_should.iter().enumerate() {
479            assert_abs_diff_eq!(*x_val, x[i], epsilon = 1e-14);
480        }
481        for (i, w_val) in w_should.iter().enumerate() {
482            assert_abs_diff_eq!(*w_val, w[i], epsilon = 1e-14);
483        }
484    }
485
486    #[test]
487    fn golub_welsch_50_alpha_42_beta_23() {
488        let (x, w): (Vec<_>, Vec<_>) = GaussJacobi::new(50, 42.0, 23.0)
489            .unwrap()
490            .into_iter()
491            .unzip();
492        let x_should = [
493            -0.936_528_233_152_541_2,
494            -0.914_340_864_546_088_5,
495            -0.892_159_904_972_709_7,
496            -0.869_216_909_221_225_6,
497            -0.845_277_228_769_225_6,
498            -0.820_252_766_348_056_8,
499            -0.794_113_540_498_529_6,
500            -0.766_857_786_572_463_5,
501            -0.738_499_459_607_423_4,
502            -0.709_062_235_514_446_8,
503            -0.678_576_327_905_629_3,
504            -0.647_076_661_181_635_3,
505            -0.614_601_751_027_635_6,
506            -0.581_192_977_458_508_4,
507            -0.546_894_086_695_451_9,
508            -0.511_750_831_826_105_3,
509            -0.475_810_700_347_493_84,
510            -0.439_122_697_460_417_9,
511            -0.401_737_165_777_708_5,
512            -0.363_705_629_046_518_04,
513            -0.325_080_651_686_135_1,
514            -0.285_915_708_544_232_9,
515            -0.246_265_060_906_733_86,
516            -0.206_183_635_819_408_85,
517            -0.165_726_906_401_709_62,
518            -0.124_950_771_176_147_79,
519            -0.083_911_430_566_871_42,
520            -0.042_665_258_670_068_65,
521            -0.001_268_668_170_195_549_6,
522            0.040_222_034_151_539_98,
523            0.081_750_804_545_872_01,
524            0.123_262_036_301_197_46,
525            0.164_700_756_351_269_24,
526            0.206_012_852_393_607_17,
527            0.247_145_341_670_134_97,
528            0.288_046_697_452_241,
529            0.328_667_256_796_052_5,
530            0.368_959_744_983_174_2,
531            0.408_879_971_241_114_4,
532            0.448_387_782_372_734_86,
533            0.487_448_416_419_391_24,
534            0.526_034_498_798_180_8,
535            0.564_129_114_046_126_2,
536            0.601_730_771_388_207_7,
537            0.638_861_919_860_897_4,
538            0.675_584_668_752_041_4,
539            0.712_032_766_455_434_9,
540            0.748_486_131_436_470_7,
541            0.785_585_184_777_517_6,
542            0.825_241_342_102_355_2,
543        ];
544        let w_should = [
545            7.48575322545471E-18,
546            4.368160045795394E-15,
547            5.475_092_226_093_74E-13,
548            2.883_802_894_000_164_4E-11,
549            8.375_974_400_943_034E-10,
550            1.551_169_281_097_026_6E-8,
551            2.002_752_126_655_06E-7,
552            1.914_052_885_645_138E-6,
553            1.412_973_977_680_798E-5,
554            8.315_281_580_948_582E-5,
555            3.996_349_769_672_429E-4,
556            0.001_598_442_290_393_378_4,
557            0.005_401_484_462_492_892,
558            0.015_609_515_951_961_325,
559            0.038_960_859_894_776_14,
560            0.084_675_992_815_357_84,
561            0.161_320_272_041_780_37,
562            0.270_895_707_022_142,
563            0.402_766_052_144_190_03,
564            0.532_134_840_644_357_2,
565            0.626_561_850_396_477_3,
566            0.658_939_504_140_677_5,
567            0.619_968_794_555_102,
568            0.522_392_634_872_676_4,
569            0.394_418_806_923_720_8,
570            0.266_845_588_852_137_27,
571            0.161_693_943_297_351_4,
572            0.087_665_230_931_323_02,
573            0.042_462_146_242_945_82,
574            0.018_336_610_588_859_478,
575            0.007_040_822_524_198_700_5,
576            0.002_395_953_515_750_436_4,
577            7.196_709_691_248_771E-4,
578            1.898_822_582_266_401E-4,
579            4.375_352_582_937_183E-5,
580            8.744_218_873_447_381E-6,
581            1.503_255_708_913_270_4E-6,
582            2.201_263_417_180_834_2E-7,
583            2.713_269_374_479_116_4E-8,
584            2.774_921_681_532_996E-9,
585            2.313_546_085_591_984_2E-10,
586            1.538_220_559_204_994_4E-11,
587            7.931_012_545_002_62E-13,
588            3.057_666_218_185_739E-14,
589            8.393_076_986_026_449E-16,
590            1.531_180_072_630_389E-17,
591            1.675_381_720_821_777_5E-19,
592            9.300_961_857_933_663E-22,
593            1.912_538_194_408_499_4E-24,
594            6.645_776_758_516_211E-28,
595        ];
596        for (i, x_val) in x_should.iter().enumerate() {
597            assert_abs_diff_eq!(*x_val, x[i], epsilon = 1e-10);
598        }
599        for (i, w_val) in w_should.iter().enumerate() {
600            assert_abs_diff_eq!(*w_val, w[i], epsilon = 1e-10);
601        }
602    }
603
604    #[test]
605    fn check_derives() {
606        let quad = GaussJacobi::new(10, 0.0, 1.0).unwrap();
607        let quad_clone = quad.clone();
608        assert_eq!(quad, quad_clone);
609        let other_quad = GaussJacobi::new(10, 1.0, 0.0).unwrap();
610        assert_ne!(quad, other_quad);
611    }
612
613    #[test]
614    fn check_jacobi_error() {
615        let jacobi_rule = GaussJacobi::new(3, -2.0, 1.0);
616        assert!(jacobi_rule
617            .as_ref()
618            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::Alpha));
619        assert_eq!(
620            format!("{}", jacobi_rule.err().unwrap()),
621            "alpha must be finite and larger than -1.0"
622        );
623
624        let jacobi_rule = GaussJacobi::new(3, 1.0, -2.0);
625        assert!(jacobi_rule
626            .as_ref()
627            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::Beta));
628        assert_eq!(
629            format!("{}", jacobi_rule.err().unwrap()),
630            "beta must be finite and larger than -1.0"
631        );
632
633        let jacobi_rule = GaussJacobi::new(3, -2.0, -2.0);
634        assert!(jacobi_rule
635            .as_ref()
636            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::AlphaBeta));
637        assert_eq!(
638            format!("{}", jacobi_rule.err().unwrap()),
639            "alpha and beta must be finite and larger than -1.0"
640        );
641        assert_eq!(
642            GaussJacobi::new(3, -2.0, 1.0).map_err(|e| e.reason()),
643            Err(GaussJacobiErrorReason::Alpha)
644        );
645
646        assert_eq!(
647            GaussJacobi::new(3, -1.0, 1.0).map_err(|e| e.reason()),
648            Err(GaussJacobiErrorReason::Alpha)
649        );
650
651        assert_eq!(
652            GaussJacobi::new(3, 1.0, -2.0).map_err(|e| e.reason()),
653            Err(GaussJacobiErrorReason::Beta)
654        );
655
656        assert_eq!(
657            GaussJacobi::new(3, -1.0, -1.0).map_err(|e| e.reason()),
658            Err(GaussJacobiErrorReason::AlphaBeta)
659        );
660
661        let jacobi_rule = GaussJacobi::new(0, 0.5, 0.5);
662        assert!(jacobi_rule
663            .as_ref()
664            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::Degree));
665        assert_eq!(
666            format!("{}", jacobi_rule.err().unwrap()),
667            "degree must be at least 2"
668        );
669        assert_eq!(
670            GaussJacobi::new(1, 0.5, 0.5).map_err(|e| e.reason()),
671            Err(GaussJacobiErrorReason::Degree)
672        );
673
674        let jacobi_rule = GaussJacobi::new(0, -1.0, 0.5);
675        assert!(jacobi_rule
676            .as_ref()
677            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::DegreeAlpha));
678        assert_eq!(
679            format!("{}", jacobi_rule.err().unwrap()),
680            "degree must be at least 2 and alpha must be finite and larger than -1.0"
681        );
682        assert_eq!(
683            GaussJacobi::new(0, -2.0, 0.5).map_err(|e| e.reason()),
684            Err(GaussJacobiErrorReason::DegreeAlpha)
685        );
686        assert_eq!(
687            GaussJacobi::new(1, -1.0, 0.5).map_err(|e| e.reason()),
688            Err(GaussJacobiErrorReason::DegreeAlpha)
689        );
690
691        let jacobi_rule = GaussJacobi::new(0, 0.5, -1.0);
692        assert!(jacobi_rule
693            .as_ref()
694            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::DegreeBeta));
695        assert_eq!(
696            format!("{}", jacobi_rule.err().unwrap()),
697            "degree must be at least 2 and beta must be finite and larger than -1.0"
698        );
699        assert_eq!(
700            GaussJacobi::new(3, -2.0, -2.0).map_err(|e| e.reason()),
701            Err(GaussJacobiErrorReason::AlphaBeta)
702        );
703        assert_eq!(
704            GaussJacobi::new(3, -1.0, -2.0).map_err(|e| e.reason()),
705            Err(GaussJacobiErrorReason::AlphaBeta)
706        );
707
708        let jacobi_rule = GaussJacobi::new(0, -1.0, -1.0);
709        assert!(jacobi_rule
710            .as_ref()
711            .is_err_and(|x| x.reason() == GaussJacobiErrorReason::DegreeAlphaBeta));
712        assert_eq!(
713            format!("{}", jacobi_rule.err().unwrap()),
714            "degree must be at least 2, alpha and beta must be finite and larger than -1.0"
715        );
716        assert_eq!(
717            GaussJacobi::new(3, -2.0, -1.0).map_err(|e| e.reason()),
718            Err(GaussJacobiErrorReason::AlphaBeta)
719        );
720        assert_eq!(
721            GaussJacobi::new(3, -1.0, -1.0).map_err(|e| e.reason()),
722            Err(GaussJacobiErrorReason::AlphaBeta)
723        );
724
725        assert_eq!(
726            GaussJacobi::new(0, 0.5, 0.5).map_err(|e| e.reason()),
727            Err(GaussJacobiErrorReason::Degree)
728        );
729        assert_eq!(
730            GaussJacobi::new(1, 0.5, 0.5).map_err(|e| e.reason()),
731            Err(GaussJacobiErrorReason::Degree)
732        );
733
734        assert_eq!(
735            GaussJacobi::new(0, -1.0, 0.5).map_err(|e| e.reason()),
736            Err(GaussJacobiErrorReason::DegreeAlpha)
737        );
738        assert_eq!(
739            GaussJacobi::new(0, -2.0, 0.5).map_err(|e| e.reason()),
740            Err(GaussJacobiErrorReason::DegreeAlpha)
741        );
742        assert_eq!(
743            GaussJacobi::new(1, -1.0, 0.5).map_err(|e| e.reason()),
744            Err(GaussJacobiErrorReason::DegreeAlpha)
745        );
746        assert_eq!(
747            GaussJacobi::new(1, -2.0, 0.5).map_err(|e| e.reason()),
748            Err(GaussJacobiErrorReason::DegreeAlpha)
749        );
750
751        assert_eq!(
752            GaussJacobi::new(0, 0.5, -1.0).map_err(|e| e.reason()),
753            Err(GaussJacobiErrorReason::DegreeBeta)
754        );
755        assert_eq!(
756            GaussJacobi::new(0, 0.5, -2.0).map_err(|e| e.reason()),
757            Err(GaussJacobiErrorReason::DegreeBeta)
758        );
759        assert_eq!(
760            GaussJacobi::new(1, 0.5, -1.0).map_err(|e| e.reason()),
761            Err(GaussJacobiErrorReason::DegreeBeta)
762        );
763        assert_eq!(
764            GaussJacobi::new(1, 0.5, -2.0).map_err(|e| e.reason()),
765            Err(GaussJacobiErrorReason::DegreeBeta)
766        );
767
768        assert_eq!(
769            GaussJacobi::new(0, -1.0, -1.0).map_err(|e| e.reason()),
770            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
771        );
772        assert_eq!(
773            GaussJacobi::new(0, -2.0, -2.0).map_err(|e| e.reason()),
774            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
775        );
776        assert_eq!(
777            GaussJacobi::new(1, -1.0, -1.0).map_err(|e| e.reason()),
778            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
779        );
780        assert_eq!(
781            GaussJacobi::new(1, -2.0, -2.0).map_err(|e| e.reason()),
782            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
783        );
784        assert_eq!(
785            GaussJacobi::new(0, -1.0, -2.0).map_err(|e| e.reason()),
786            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
787        );
788        assert_eq!(
789            GaussJacobi::new(0, -2.0, -1.0).map_err(|e| e.reason()),
790            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
791        );
792        assert_eq!(
793            GaussJacobi::new(1, -1.0, -2.0).map_err(|e| e.reason()),
794            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
795        );
796        assert_eq!(
797            GaussJacobi::new(1, -2.0, -1.0).map_err(|e| e.reason()),
798            Err(GaussJacobiErrorReason::DegreeAlphaBeta)
799        );
800    }
801
802    #[test]
803    fn check_iterators() {
804        let rule = GaussJacobi::new(2, -0.25, -0.5).unwrap();
805        // Answer taken from Wolfram Alpha <https://www.wolframalpha.com/input?i2d=true&i=Integrate%5BDivide%5BPower%5Bx%2C2%5D%2CPower%5B%5C%2840%291-x%5C%2841%29%2CDivide%5B1%2C4%5D%5DPower%5B%5C%2840%291%2Bx%5C%2841%29%2CDivide%5B1%2C2%5D%5D%5D%2C%7Bx%2C-1%2C1%7D%5D>
806        let ans = 1.3298477657906902;
807
808        assert_abs_diff_eq!(
809            ans,
810            rule.iter().fold(0.0, |tot, (n, w)| tot + n * n * w),
811            epsilon = 1e-14
812        );
813
814        assert_abs_diff_eq!(
815            ans,
816            rule.nodes()
817                .zip(rule.weights())
818                .fold(0.0, |tot, (n, w)| tot + n * n * w),
819            epsilon = 1e-14
820        );
821
822        assert_abs_diff_eq!(
823            ans,
824            rule.into_iter().fold(0.0, |tot, (n, w)| tot + n * n * w),
825            epsilon = 1e-14
826        );
827    }
828
829    #[test]
830    fn check_some_integrals() {
831        let rule = GaussJacobi::new(10, -0.5, -0.25).unwrap();
832
833        assert_abs_diff_eq!(
834            rule.integrate(-1.0, 1.0, |x| x * x),
835            1.3298477657906902,
836            epsilon = 1e-14
837        );
838
839        assert_abs_diff_eq!(
840            rule.integrate(-1.0, 1.0, |x| x.cos()),
841            2.2239,
842            epsilon = 1e-5
843        );
844    }
845
846    #[cfg(feature = "rayon")]
847    #[test]
848    fn par_check_some_integrals() {
849        let rule = GaussJacobi::new(10, -0.5, -0.25).unwrap();
850
851        assert_abs_diff_eq!(
852            rule.par_integrate(-1.0, 1.0, |x| x * x),
853            1.3298477657906902,
854            epsilon = 1e-14
855        );
856
857        assert_abs_diff_eq!(
858            rule.par_integrate(-1.0, 1.0, |x| x.cos()),
859            2.2239,
860            epsilon = 1e-5
861        );
862    }
863}