rint/multi/basic.rs
1use crate::multi::{Integrator, Rule};
2use crate::IntegralEstimate;
3use crate::Integrand;
4use crate::Limits;
5use crate::{InitialisationError, InitialisationErrorKind};
6
7/// A non-adaptive multi-dimensional integrator.
8///
9/// The [`Basic`] integrator applies a fully-symmetric integration [`Rule`] to approximate the
10/// integral of an $N$-dimensional function. It is non-adaptive: it runs exactly once on the input
11/// function. Thus, it is only suitable for the integration of smooth functions with no problematic
12/// regions in the integration region. If higher accuracy is required then the [`Adaptive`].
13///
14/// [`Adaptive`]: crate::multi::Adaptive
15///
16/// # Example
17///
18/// Here we present a calculation of [Catalan's constant] $G$ using the integral representation:
19/// $$
20/// G = \int_{0}^{1} \int_{0}^{1} \frac{1}{1 + x^{2} y^{2}} dy dx
21/// $$
22///
23/// [Catalan's constant]: <https://en.wikipedia.org/wiki/Catalan%27s_constant>
24///```rust
25/// use rint::{Integrand, Limits};
26/// use rint::multi::{Basic, Rule13};
27///
28/// const G: f64 = 0.915_965_594_177_219_015_054_603_514_932_384_110_774;
29/// const N: usize = 2;
30///
31/// struct Catalan;
32///
33/// impl Integrand for Catalan {
34/// type Point = [f64; N];
35/// type Scalar = f64;
36///
37/// fn evaluate(&self, coordinate: &[f64; N]) -> Self::Scalar {
38/// let x = coordinate[0];
39/// let y = coordinate[1];
40///
41/// 1.0 / (1.0 + x.powi(2) * y.powi(2))
42/// }
43/// }
44///
45/// # use std::error::Error;
46/// # fn main() -> Result<(), Box<dyn Error>> {
47/// let catalan = Catalan;
48/// let limits = [Limits::new(0.0,1.0)?,Limits::new(0.0,1.0)?];
49/// let rule = Rule13::generate();
50/// let integral = Basic::new(&catalan, &rule, limits)?.integrate();
51///
52/// let result = integral.result();
53/// let error = integral.error();
54/// let abs_actual_error = (G - result).abs();
55/// let iters = integral.iterations();
56/// assert_eq!(iters, 1);
57/// assert!(abs_actual_error < error);
58/// # Ok(())
59/// # }
60///```
61#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
62pub struct Basic<'a, I, R, const N: usize> {
63 function: &'a I,
64 rule: &'a R,
65 limits: [Limits; N],
66}
67
68impl<'a, I, const N: usize, const FINAL: usize, const TOTAL: usize>
69 Basic<'a, I, Rule<N, FINAL, TOTAL>, N>
70where
71 I: Integrand<Point = [f64; N]>,
72{
73 /// Create a new [`Basic`] multi-dimensional integrator.
74 ///
75 /// The user first defines a `function` which is something implementing the [`Integrand`] trait
76 /// and selects a fully-symmetric multi-dimensional integration [`Rule`], `rule`, to integrate
77 /// the function in the hypercube formed by the [`Limits`], `limits` in each of the $N$
78 /// integration directions.
79 ///
80 /// # Errors
81 ///
82 /// Will fail if $N < 2$ or $N > 15$. The routines probided in this module are developed
83 /// for dimensionalities between $2 \le N \le 15$.
84 pub fn new(
85 function: &'a I,
86 rule: &'a Rule<N, FINAL, TOTAL>,
87 limits: [Limits; N],
88 ) -> Result<Self, InitialisationError> {
89 if N < 2 || N > 15 {
90 return Err(InitialisationError::new(
91 InitialisationErrorKind::InvalidDimension(N),
92 ));
93 }
94 Ok(Self {
95 function,
96 rule,
97 limits,
98 })
99 }
100
101 /// Integrate the given function.
102 ///
103 /// Applies the user supplied integration rule to obtain an [`IntegralEstimate`], which is the
104 /// numerically evaluated estimate of the integral value and error, as well as the number of
105 /// function evaluations and integration routine iterations. Note: for the [`Basic`] integrator
106 /// the number of iterations is 1.
107 #[must_use]
108 pub fn integrate(&self) -> IntegralEstimate<I::Scalar> {
109 let integral = self.integrator().integrate();
110 IntegralEstimate::new()
111 .with_result(integral.result())
112 .with_error(integral.error())
113 .with_iterations(1)
114 .with_evaluations(self.rule.evaluations())
115 }
116
117 /// Create an [`Integrator`] from the integration data stored in a [`Basic`].
118 const fn integrator(&self) -> Integrator<'_, I, N, FINAL, TOTAL> {
119 Integrator::new(self.function, self.rule, self.limits)
120 }
121}