number_diff/functions/
integration.rs

1use crate::{Elementary, Error, Func, Function, Round};
2
3/// types that implement the [Integrate](crate::Integrate) trait can safely be integrated within
4/// the domain ℝ.
5pub trait Integrate {
6    /// Method will return an instance of [Integral](crate::Integral) that can be taylored to
7    /// specific usecases.
8    ///
9    /// Example:
10    /// ```rust
11    /// let function = Function::from("sin(x)");
12    ///
13    /// let mut integral = function.integrate();
14
15    /// // specify bounds and precision for the integral
16    /// integral
17    ///     .set_lower_bound(0.)
18    ///     .set_upper_bound(PI / 2.)
19    ///     .set_precision(20000);
20    ///
21    /// // evaluate the integral
22    /// let value = integral.evaluate().unwrap();
23    /// // note that the value of the evaluated integral must be unwrapped if using the `integrate()`
24    /// // method because the method cannot guarantee that bounds have been set at the point of
25    /// // evaluating. The evaluate_integral() method which is implemented for any instance with the
26    /// // Integrate trait is safer and is guaranteed to yield a valid result.
27    ///
28    /// // round the value to 10 decimal places
29    /// value.round_to(10);
30    ///
31    /// assert_eq!(value, 1.);
32    /// ```
33    /// Also note that if the precision is not specified, it will default to 1000.
34    fn integrate(&self) -> Integral;
35
36    /// Method will return the value of the definite integral of the function evaluated from the
37    /// provided lower and upper bound.
38    ///
39    /// Example:
40    /// ```
41    /// let function = Function::from("cos(x)");
42    ///
43    /// // the evaluate_integral() method will automatically round the result to five decimal points.
44    /// // This is because higher precision cannot be guaranteed with using the standard precision set
45    /// // for the method. Provided that the function is defined for all values between the lower and
46    /// // upper bounds, the method will always return a valid result.
47    /// let value = function.evaluate_integral(0., PI);
48    ///
49    /// assert_eq!(value, 0.);
50    /// ```
51    fn evaluate_integral(&self, lower_bound: f64, upper_bound: f64) -> f64;
52}
53
54const STANDARD_PRECISION: usize = 1000;
55
56/// See [Integrate documentation](crate::Integrate) for usage and examples
57pub struct Integral {
58    function: Func,
59    lower_bound: Option<f64>,
60    upper_bound: Option<f64>,
61    precision: usize,
62}
63
64impl Integral {
65    pub fn vacant(function: Func) -> Self {
66        Self {
67            function,
68            lower_bound: None,
69            upper_bound: None,
70            precision: STANDARD_PRECISION,
71        }
72    }
73
74    pub fn set_lower_bound(&mut self, lower_bound: f64) -> &mut Self {
75        self.lower_bound = Some(lower_bound);
76        self
77    }
78
79    pub fn set_upper_bound(&mut self, upper_bound: f64) -> &mut Self {
80        self.upper_bound = Some(upper_bound);
81        self
82    }
83
84    pub fn set_precision(&mut self, precision: usize) -> &mut Self {
85        self.precision = precision;
86        self
87    }
88
89    pub fn evaluate(&self) -> Result<f64, Error> {
90        if let (Some(lower_bound), Some(upper_bound)) = (self.lower_bound, self.upper_bound) {
91            Ok(simpsons_rule(
92                &self.function,
93                lower_bound,
94                upper_bound,
95                self.precision,
96            ))
97        } else {
98            Err(Error::InternalError(String::from(
99                "Bounds of integration must be set in order to evaluate the integral",
100            )))
101        }
102    }
103}
104
105/// See [Integrate](crate::Integrate) for usage and examples.
106impl Integrate for Elementary {
107    fn integrate(&self) -> Integral {
108        Integral::vacant(self.clone().call())
109    }
110    /// Evaluating the integral gives a value of the integral with eight decimal places
111    fn evaluate_integral(&self, lower_bound: f64, upper_bound: f64) -> f64 {
112        unsafe {
113            let mut value = self
114                .integrate()
115                .set_lower_bound(lower_bound)
116                .set_upper_bound(upper_bound)
117                .evaluate()
118                .unwrap_unchecked(); // this unwrap will never fail because the upper and lower bounds
119                                     // will always be set
120            value.with_significant_figures(5)
121        }
122    }
123}
124
125/// See [Integrate](crate::Integrate) for usage and examples.
126impl Integrate for Function {
127    fn integrate(&self) -> Integral {
128        self.elementary().integrate()
129    }
130    fn evaluate_integral(&self, lower_bound: f64, upper_bound: f64) -> f64 {
131        self.elementary()
132            .evaluate_integral(lower_bound, upper_bound)
133    }
134}
135
136fn simpsons_rule(funciton: &Func, lower_bound: f64, upper_bound: f64, precision: usize) -> f64 {
137    // note that n must be an even number for Simpson's rule to work
138    let n = precision * 2;
139    let dx = (upper_bound - lower_bound) / n as f64;
140    let mut sum: f64 = (1..n)
141        .map(|x| {
142            if x % 2 == 0 {
143                2. * funciton(lower_bound + x as f64 * dx)
144            } else {
145                4. * funciton(lower_bound + x as f64 * dx)
146            }
147        })
148        .sum();
149    sum += funciton(lower_bound) + funciton(upper_bound);
150
151    sum * dx / 3.
152}