1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//! Module for working with integrals.
//!
//! This module has functions for estimating the values of
//! integrals through numeric integration techniques.

pub use super::func::*;

/// The default precision constant used in `integrate`.
///
/// This value can be thought of as the number of subintervals to use
/// for each integral interval in the region `[a, b`].
pub const DEFAULT_PRECISION: u64 = 4;

/// Estimate the value of the integral of `f` over `[a, b]` using
/// `p` subintervals.
///
/// This function works by applying Simpson's rule to the function
/// over the specified interval, using `p` subintervals.
///
/// Note that a higher `p` will increase the accuracy of the result,
/// but also increase the time the computation takes. `p` should be chosen
/// to ensure that a good estimate can be made without drastically 
/// increasing the computational complexity.
///
/// If `a` is equal to `b` or `p` equals zero, `zero` will be
/// returned.
///
/// # Examples
///
/// ```
/// #[macro_use] extern crate reikna;
/// # fn main() {
/// use reikna::integral::*;
/// 
/// let f = func!(|x| x + 4.0);
/// assert_eq!(integrate_wp(&f, 0.0, 0.0, 10), 0.0);
/// assert_eq!(integrate_wp(&f, 0.0, 1.0, 10), 4.5);
///# }
/// ```
pub fn integrate_wp(f: &Function, a: f64, b: f64, p: u64) -> f64 {
    if (a - b).abs() < ::std::f64::EPSILON || p == 0 {
        return 0.0;
    }

    let delta = (b - a) / p as f64;

    let mut integral = f(a) + f(b);
    let mut pos = a;
    for i in 1..p {
        pos += delta;
        if i & 0x01 == 0 {
            integral += 2.0 * f(pos);
        } else {
            integral += 4.0 * f(pos);
        }
    }

    integral * delta / 3.0
}

/// Estimate the value of the integral of `f` over `[a, b]`.
///
/// This is a helper function that calls `integrate_wp()` using
/// a `p` value calculated depending on the size of `[a, b]`. See
/// the documentation for `integrate_wp()` for more information.
///
/// The value of `p` is calculated by the following formula:
///
/// ``` text
/// p = round(|b - a|) * precision
/// ```
///
/// Where `precision` is the constant `DEFAULT_PRECISION`.
///
/// Note -- because of the way the precision is calculated, the
/// computational complexity of this function grows linearly with
/// the size of the interval. Very large intervals will have very
/// large precision values, which can slow down computation while not
/// providing a large improvement to accuracy. For very large intervals,
/// is is better to use `integrate_wp()` directly, so the precision value
/// can be set to a more reasonable target.
///
/// # Examples
///
/// ```
/// #[macro_use] extern crate reikna;
/// # fn main() {
/// use reikna::integral::*;
/// 
/// let f = func!(|x| x + 4.0);
/// assert_eq!(integrate(&f, 0.0, 0.0), 0.0);
/// assert_eq!(integrate(&f, 0.0, 1.0), 4.5);
///# }
/// ```
pub fn integrate(f: &Function, a: f64, b: f64) -> f64 {
    let p = (b - a).abs().round() as u64 * DEFAULT_PRECISION;
    integrate_wp(f, a, b, p)
}

/// Return a `Function` that estimates the `n`th integral of `f`, using a
/// constant of `c` and a positive precision constant of `p`.
///
/// The integration itself is done by `integrate_wp()`, see the
/// documentation for `integrate_wp()` for more information.
///
/// The precision value passed to `integrate_wp()` is calculated with the
/// following formula:
///
/// ``` text
/// precision = round(|x|) * p
/// ```
///
/// Where `p` is the precision constant supplied to this function.
///
/// Note -- the computational complexity of the resulting function grows
/// exponentially based on the value of `n`, IE:
///
/// ``` text
/// nth_integral(1, f, c, p)(x) -> O(1^x)
/// nth_integral(2, f, c, p)(x) -> O(2^x)
/// nth_integral(3, f, c, p)(x) -> O(3^x)
/// ```
/// 
/// where `x` is the value of `x` supplied to the resulting function.
/// For this reason it is not recommended to use values of `n` greater than
/// three or four, as larger values will cause computational complexity to
/// rapidly inflate.
///
/// # Panics
///
/// Panics if `p` equals zero.
/// 
/// # Examples
///
/// ```
/// #[macro_use] extern crate reikna;
/// # fn main() {
/// use reikna::integral::*;
/// 
/// let f = func!(|x| x * x);
/// let integral = nth_integral(1, &f, 1.0, DEFAULT_PRECISION);
/// 
/// println!("integral({}) = {}",  0.0, integral( 0.0));
/// println!("integral({}) = {}",  1.0, integral( 1.0));
/// println!("integral({}) = {}", -2.0, integral(-2.0));
///# }
/// ```
///
/// Outputs:
///
/// ```text
/// integral(0.0) = 1.0
/// integral(1.0) = 1.3333333333333333
/// integral(-2.0) = -1.6666666666666665
/// ```
pub fn nth_integral(n: u64, f: &Function, c: f64, p: u64) -> Function {
    assert!(p != 0, "Precision constant must be positive!");

    let f_copy = f.clone();
    let integral: Function = func!(
        move |x: f64| {
            let prec = x.abs().round() as u64 * p;
            integrate_wp(&f_copy, 0.0, x, prec) + c
    });

    match n {
        0 => f.clone(),
        1 => integral,
        _ => nth_integral(n - 1, &integral, c, p),
    }
}

/// Return a `Function` that estimates the integral of `f`, using a
/// constant of `c` and a positive precision constant of `p`.
///
/// This is a helper function that calls `nth_integral()` with an
/// `n` value of `1`. See the documentation for `nth_integral()` for
/// more information.
///
/// # Panics
///
/// Panics if `nth_integral()` panics. See the documentation of
/// `nth_integral()` for more information.
///
/// # Examples
///
/// ```
/// #[macro_use] extern crate reikna;
/// # fn main() {
/// use reikna::integral::*;
/// 
/// let f = func!(|x| x * x);
/// let integral = integral(&f, 1.0, DEFAULT_PRECISION);
/// 
/// println!("integral({}) = {}",  0.0, integral( 0.0));
/// println!("integral({}) = {}",  1.0, integral( 1.0));
/// println!("integral({}) = {}", -2.0, integral(-2.0));
///# }
/// ```
///
/// Outputs:
///
/// ```text
/// integral(0.0) = 1.0
/// integral(1.0) = 1.3333333333333333
/// integral(-2.0) = -1.6666666666666665
/// ```
pub fn integral(f: &Function, c: f64, p: u64) -> Function {
    nth_integral(1, f, c, p)
}

#[cfg(test)]
mod tests {
    use super::*;

#[test]
    fn t_integrate() {
        let f = func!(|x: f64| x * x);
        assert_fp!(integrate(&f,  0.0, 0.0),  0.0);
        assert_fp!(integrate(&f, -1.0, 1.0),  2.0 / 3.0);
        assert_fp!(integrate(&f,  0.0, 1.0),  1.0 / 3.0);
        assert_fp!(integrate(&f, -1.0, 0.0),  1.0 / 3.0);
        assert_fp!(integrate(&f,  1.0, 0.0), -1.0 / 3.0);

        assert_fp!(integrate(&f,  0.0, 1000.0), integrate(&f, -1000.0, 0.0));
        assert_fp!(integrate(&f,  13.0, 0.0),  -integrate(&f, 0.0, 13.0));

        let f_int = nth_integral(1, &f, 0.0, 2);
        assert_fp!(f_int( 0.0),  0.0);
        assert_fp!(f_int( 1.0),  1.0 / 3.0);
        assert_fp!(f_int(-1.0), -1.0 / 3.0);

        let f_int = nth_integral(1, &f, 1.0, 2);
        assert_fp!(f_int( 0.0),  1.0);
        assert_fp!(f_int( 1.0),  4.0 / 3.0);
        assert_fp!(f_int(-1.0),  2.0 / 3.0);

        let f_int = nth_integral(2, &f, 0.0, 2);
        assert_fp!(f_int( 0.0),  0.0);
        assert_fp!(f_int( 1.0), 1.0 / 12.0);
        assert_fp!(f_int(-1.0), 1.0 / 12.0);
    }

#[test]
#[should_panic]
    fn t_integrate_panic() {
        let f = func!(|x: f64| x * x);
        nth_integral(1, &f, 1.0, 0);
    }
}