RustQuant_math/
integration.rs

1// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2// RustQuant: A Rust library for quantitative finance tools.
3// Copyright (C) 2023 https://github.com/avhz
4// Dual licensed under Apache 2.0 and MIT.
5// See:
6//      - LICENSE-APACHE.md
7//      - LICENSE-MIT.md
8// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9
10//! This module contains functions for numerical integration.
11//!
12//! The Tanh-Sinh quadrature is used for the integration.
13//! This method uses the hyperbolic trig functions to transform
14//! the integral over $[-1, +1]$ to an integral over $\mathbb{R} = (-\infty, +\infty)$.
15//!
16//! We have the approximation:
17//!
18//! $$
19//! \int_{-1}^{+1} f(x) dx \approx \sum_{\mathbb{R}} w_k f(x_k)
20//! $$
21//!
22//! The abscissae and weights are calculated as follows:
23//!
24//! $$
25//! x_k = \tanh \left( \frac{1}{2} \pi \sinh(kh) \right)
26//! $$
27//!
28//! $$
29//! w_k = \frac{1}{2} h \pi \cosh(kh) \cosh^{-2} \left( \frac{1}{2} \pi \sinh(kh) \right)
30//! $$
31
32// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33// FUNCTIONS
34// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35
36/// Integrates a function from `a` to `b`.
37/// Uses the Tanh-Sinh quadrature over [-1, +1]
38/// and then transforms to an integral over [a, b].
39pub fn integrate<F>(f: F, a: f64, b: f64) -> f64
40where
41    F: Fn(f64) -> f64,
42{
43    // Apply a linear change of variables:
44    //
45    // x = c * t + d
46    //
47    // where:
48    //      c = 0.5 * (b - a)
49    //      d = 0.5 * (a + b)
50
51    let c = 0.5 * (b - a);
52    let d = 0.5 * (a + b);
53
54    c * tanhsinh(|t| {
55        let out = f(c * t + d);
56        if out.is_finite() {
57            out
58        } else {
59            0.0
60        }
61    })
62}
63
64// This method integrates the function f(c * x + d).
65fn tanhsinh<F>(f: F) -> f64
66where
67    F: Fn(f64) -> f64,
68{
69    let mut integral = 0.0;
70
71    for i in 0..100 {
72        integral += WEIGHTS[i] * f(ABSCISSAE[i]);
73    }
74
75    integral
76}
77
78// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79// ABSCISSAE & WEIGHTS
80// These are for the tanh-sinh quadrature.
81// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82
83// Nodes and weights from: https://keisan.casio.com/exec/system/1285151216
84
85/// Abscissae: the nodes for the sum evaluation.
86pub const ABSCISSAE: [f64; 100] = [
87    -1.0,
88    -1.0,
89    -1.0,
90    -1.0,
91    -1.0,
92    -1.0,
93    -1.0,
94    -1.0,
95    -1.0,
96    -1.0,
97    -1.0,
98    -1.0,
99    -1.0,
100    -1.0,
101    -1.0,
102    -1.0,
103    -0.999_999_999_999_999_999_999_999_92,
104    -0.999_999_999_999_999_999_999_999_95,
105    -0.999_999_999_999_999_999_999_999_98,
106    -0.999_999_999_999_999_999_999_999_91,
107    -0.999_999_999_999_999_999_999_999_95,
108    -0.999_999_999_999_999_999_999_998_77,
109    -0.999_999_999_999_999_999_999_103_26,
110    -0.999_999_999_999_999_999_703_932_4,
111    -0.999_999_999_999_999_950_531_716_2,
112    -0.999_999_999_999_995_4,
113    -0.999_999_999_999_755_4,
114    -0.999_999_999_991_728_4,
115    -0.999_999_999_814_670_3,
116    -0.999_999_997_111_343_6,
117    -0.999_999_967_297_845_9,
118    -0.999_999_720_654_543_6,
119    -0.999_998_137_805_321,
120    -0.999_990_019_143_856_7,
121    -0.999_955_840_978_693_4,
122    -0.999_834_913_162_265,
123    -0.999_467_635_803_999,
124    -0.998_491_938_731_602_9,
125    -0.996_186_771_585_243_7,
126    -0.991_272_560_236_550_9,
127    -0.981_701_565_973_876_9,
128    -0.964_495_379_931_111_4,
129    -0.935_708_688_740_139_8,
130    -0.890_613_153_970_636_9,
131    -0.824_190_972_468_412_2,
132    -0.731_982_954_977_568_5,
133    -0.611_228_933_047_550_6,
134    -0.462_072_342_445_637_1,
135    -0.288_435_163_890_398_6,
136    -0.098_120_697_049_078_77,
137    0.098_120_697_049_078_75,
138    0.288_435_163_890_398_64,
139    0.462_072_342_445_637_19,
140    0.611_228_933_047_550_7,
141    0.731_982_954_977_568_5,
142    0.824_190_972_468_412_2,
143    0.890_613_153_970_636_9,
144    0.935_708_688_740_139_9,
145    0.964_495_379_931_111_5,
146    0.981_701_565_973_876_9,
147    0.991_272_560_236_551,
148    0.996_186_771_585_243_7,
149    0.998_491_938_731_603,
150    0.999_467_635_803_999,
151    0.999_834_913_162_265,
152    0.999_955_840_978_693_5,
153    0.999_990_019_143_856_8,
154    0.999_998_137_805_321_1,
155    0.999_999_720_654_543_7,
156    0.999_999_967_297_846,
157    0.999_999_997_111_343_7,
158    0.999_999_999_814_670_2,
159    0.999_999_999_991_728_4,
160    0.999_999_999_999_755_4,
161    0.999_999_999_999_995_4,
162    0.999_999_999_999_999_950_531_74,
163    0.999_999_999_999_999_999_703_94,
164    0.999_999_999_999_999_999_999_156,
165    0.999_999_999_999_999_999_999_987,
166    0.999_999_999_999_999_999_999_985,
167    0.999_999_999_999_999_999_999_951,
168    0.999_999_999_999_999_999_999_958,
169    0.999_999_999_999_999_999_999_955,
170    0.999_999_999_999_999_999_999_912,
171    1.0,
172    1.0,
173    1.0,
174    1.0,
175    1.0,
176    1.0,
177    1.0,
178    1.0,
179    1.0,
180    1.0,
181    1.0,
182    1.0,
183    1.0,
184    1.0,
185    1.0,
186    1.0,
187];
188
189/// Weights for the sum evaluation.
190pub const WEIGHTS: [f64; 100] = [
191    0.0,
192    4.575_617_930_178_338e-295,
193    3.316_994_462_570_346e-260,
194    1.865_212_622_495_173_5e-229,
195    2.479_121_672_052_092e-202,
196    2.081_537_448_295_626e-178,
197    2.628_202_011_356_882_4e-157,
198    1.072_629_885_654_983_3e-138,
199    2.779_482_305_261_637e-122,
200    8.296_418_844_663_515e-108,
201    4.824_667_051_120_648e-95,
202    8.690_880_649_537_159e-84,
203    7.300_384_623_604_137e-74,
204    4.102_662_749_806_205e-65,
205    2.120_927_715_027_845e-57,
206    1.335_824_993_406_033_2e-50,
207    1.313_406_878_320_599_1e-44,
208    2.508_807_816_395_078_6e-39,
209    1.129_193_867_059_432_8e-34,
210    1.419_894_975_881_043_8e-30,
211    5.796_754_421_896_154_4e-27,
212    8.772_732_913_927_396e-24,
213    5.532_447_125_135_289e-21,
214    1.612_026_230_404_078_1e-18,
215    2.377_241_245_744_043_6e-16,
216    1.922_871_877_455_573e-14,
217    9.158_786_378_811_089e-13,
218    2.735_019_685_607_782_3e-11,
219    5.412_036_348_700_474e-10,
220    7.452_095_495_199_113e-9,
221    7.455_643_353_281_056e-8,
222    5.630_935_258_210_419e-7,
223    3.320_892_221_887_424e-6,
224    1.575_869_255_697_420_2e-5,
225    6.178_940_879_197_28e-5,
226    2.049_612_222_935_924_2e-4,
227    5.873_088_850_232_362e-4,
228    0.001_480_856_522_480_776_8,
229    0.003_339_108_906_155_36,
230    0.006_827_704_416_155_456,
231    0.012_809_851_589_261_179,
232    0.022_262_908_367_071_66,
233    0.036_106_623_280_003_48,
234    0.054_935_001_719_810_43,
235    0.078_672_104_392_875_16,
236    0.106_225_018_644_775_54,
237    0.135_274_854_478_869_08,
238    0.162_387_107_725_986_52,
239    0.183_570_841_955_285,
240    0.195_234_233_228_838_65,
241    0.195_234_233_228_838_65,
242    0.183_570_841_955_285,
243    0.162_387_107_725_986_52,
244    0.135_274_854_478_869_08,
245    0.106_225_018_644_775_54,
246    0.078_672_104_392_875_16,
247    0.054_935_001_719_810_43,
248    0.036_106_623_280_003_48,
249    0.022_262_908_367_071_66,
250    0.012_809_851_589_261_179,
251    0.006_827_704_416_155_456,
252    0.003_339_108_906_155_36,
253    0.001_480_856_522_480_776_8,
254    5.873_088_850_232_362e-4,
255    2.049_612_222_935_924_2e-4,
256    6.178_940_879_197_28e-5,
257    1.575_869_255_697_420_2e-5,
258    3.320_892_221_887_424e-6,
259    5.630_935_258_210_419e-7,
260    7.455_643_353_281_056e-8,
261    7.452_095_495_199_113e-9,
262    5.412_036_348_700_474e-10,
263    2.735_019_685_607_782_3e-11,
264    9.158_786_378_811_089e-13,
265    1.922_871_877_455_573e-14,
266    2.377_241_245_744_043_6e-16,
267    1.612_026_230_404_078_1e-18,
268    5.532_447_125_135_289e-21,
269    8.772_732_913_927_396e-24,
270    5.796_754_421_896_154_4e-27,
271    1.419_894_975_881_043_8e-30,
272    1.129_193_867_059_432_8e-34,
273    2.508_807_816_395_078_6e-39,
274    1.313_406_878_320_599_1e-44,
275    1.335_824_993_406_033_2e-50,
276    2.120_927_715_027_845e-57,
277    4.102_662_749_806_205e-65,
278    7.300_384_623_604_137e-74,
279    8.690_880_649_537_159e-84,
280    4.824_667_051_120_648e-95,
281    8.296_418_844_663_515e-108,
282    2.779_482_305_261_637e-122,
283    1.072_629_885_654_983_3e-138,
284    2.628_202_011_356_882_4e-157,
285    2.081_537_448_295_626e-178,
286    2.479_121_672_052_092e-202,
287    1.865_212_622_495_173_5e-229,
288    3.316_994_462_570_346e-260,
289    4.575_617_930_178_338e-295,
290    0.0,
291];
292
293// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
294// TESTS
295// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
296
297#[cfg(test)]
298mod tests_integration {
299    use super::*;
300
301    use RustQuant_utils::{assert_approx_equal, RUSTQUANT_EPSILON as EPS};
302
303    #[test]
304    fn test_quadrature() {
305        fn f(x: f64) -> f64 {
306            (x.sin()).exp()
307        }
308
309        let integral = integrate(f, 0.0, 5.0);
310
311        assert_approx_equal!(integral, 7.189_119_252_343_784, EPS);
312    }
313}