1 // Written in the D programming language.
2 
3 /**
4 This module provides numerical integration by double-exponential (DE) formula.
5 */
6 module deint;
7 
8 import std.algorithm;
9 import std.array;
10 import std.functional;
11 import std.math;
12 import std.typecons;
13 
14 
15 /** A struct for performing numerical integration by double-exponential (DE) formula.
16  * It is also known as "Tanh-sinh quadrature".
17  * In DE formula, the integration (1) is converted to (2).
18  * (1) int_{xa}^{xb} f(x) w(x) dx
19  * (2) int_{ta}^{tb} f(g(t)) w(g(t)) g'(t) dt
20  * 
21  * This struct calculates reusable state in advance.
22  * The type of DE formula is automatically decided from the given interval of the integration.
23  *
24  * Reference:
25  * $(HTTP en.wikipedia.org/wiki/Tanh-sinh_quadrature)
26  */
27 struct DEInt(F, W = F)
28 {
29     private static
30     W returnConst1(F x) { return W(1); }
31 
32 
33     /** Initialize an object for computing DE integration.
34      * Params:
35      *      xa = starting value of original integration.
36      *      xb = end value of original integration.
37      *      weightFn = weight function. The default value of this parameter is `(F x) => F(1)`.
38      *      isExpDecay = if the integration is formed as int_a^b f(x) exp(-x) dx, this value is Yes. otherwise No.
39      *      trapN = division points of trapezoidal quadrature.
40      *      ta = starting value of integration transformed by DE-formula.
41      *      tb = starting value of integration transformed by DE-formula.
42      */
43     this(
44         F xa, F xb,
45         scope W delegate(F) weightFn = toDelegate(&returnConst1),
46         Flag!"isExpDecay" isExpDecay = No.isExpDecay,
47         size_t trapN = 100,
48         F ta = -5, F tb = 5)
49     {
50         setParams(xa, xb, weightFn, isExpDecay, trapN, ta, tb);
51     }
52 
53 
54     /// ditto
55     void setParams(
56         F xa, F xb,
57         scope W delegate(F) weightFn = toDelegate(&returnConst1),
58         Flag!"isExpDecay" isExpDecay = No.isExpDecay,
59         size_t trapN = 100,
60         F ta = -5, F tb = 5)
61     in{
62         if(isExpDecay)
63             assert(_xa != -F.infinity);
64     }
65     do {
66         if(xa > xb) {
67             this.setParams(xb, xa, weightFn, isExpDecay, trapN, ta, tb);
68             _weights = _weights.map!"cast(immutable)(-a)".array;
69         } else if(xa == -F.infinity && xb != F.infinity) {
70             //assert(!isExpDecay);
71             this.setParams(-xb, F.infinity, (F x) => weightFn(-x), isExpDecay, trapN, ta, tb);
72             _xs = _xs.map!"cast(immutable)(-a)".array;
73         }
74 
75         _xa = xa;
76         _xb = xb;
77         _ta = ta;
78         _tb = tb;
79         _trapN = trapN;
80         _isExpDecay = cast(bool)isExpDecay;
81 
82         if(_xs !is null)
83             return;
84 
85         if(xa == -F.infinity && xb == F.infinity){
86             assert(!isExpDecay);
87 
88             auto params = _makeParamsImpl(ta, tb, trapN, weightFn, delegate(F t){
89                 immutable F
90                     sinht = sinh(t),
91                     x = sinh(PI / 2 * sinht),
92                     dx = cosh(PI / 2 * sinht) * PI / 2 * cosh(t);
93 
94                 return cast(F[2])[x, dx];
95             });
96 
97 
98             _xs = params[0];
99             _weights = params[1];
100             _intType = isExpDecay ? "IIE" : "II";
101         }else if(xb == F.infinity) {
102             auto params = _makeParamsImpl(ta, tb, trapN, weightFn, delegate(F t){
103                 if(!isExpDecay){
104                     real x = exp(PI / 2 * sinh(t)),
105                          dx = x * PI / 2 * cosh(t);
106 
107                     return cast(F[2])[x + xa, dx];
108                 }else{
109                     real expmt = exp(-t),
110                          x = exp(t - expmt),
111                          dx = (1 + expmt) * x;
112 
113 
114                     return cast(F[2])[x + xa, dx]; 
115                 }
116             });
117 
118             _xs = params[0];
119             _weights = params[1];
120             _intType = isExpDecay ? "FIE" : "FI";
121         }else{
122             immutable F diff2 = (xb - xa) / 2,
123                         avg2 = (xb + xa) / 2;
124 
125             auto params = _makeParamsImpl(ta, tb, trapN, weightFn, delegate(F t){
126                 immutable F
127                     cosht = cosh(t),
128                     sinht = sinh(t),
129                     x = tanh(PI / 2 * sinht) * diff2 + avg2,
130                     cosh2 = cosh(PI / 2 * sinht)^^2,
131                     dx = PI / 2 * cosht / cosh2;
132 
133                 return cast(F[2])[x, dx * diff2];
134             });
135 
136             _xs = params[0];
137             _weights = params[1];
138             _intType = isExpDecay ? "FFE" : "FF";
139         }
140     }
141 
142 
143 
144     /** Execute integration of func by DE formula.
145     */
146     T integrate(T = W, Fn)(scope Fn func) const
147     {
148         T sum = 0;
149         foreach(i; 0 .. _xs.length)
150             sum += func(_xs[i]) * _weights[i];
151 
152         return sum;
153     }
154 
155 
156   @property const
157   {
158   	/** Return type of integration.
159   	 * The First and second charactor of the return value are explain the the starting value or end value of integration is finite value ('F') or infinity 'I'.
160   	 * If the return value has the third character and its value is 'E', the integration is formed as int_{xa}^{xb} f(x) exp(-x) dx 
161   	 */
162     string type() { return _intType; }
163 
164     /// Return the set value
165     F xa() { return _xa; }
166 
167     /// ditto
168     F xb() { return _xb; }
169 
170     /// ditto
171     F ta() { return _ta; }
172 
173     /// ditto
174     F tb() { return _tb; }
175 
176     /// ditto
177     size_t trapN() { return _trapN; }
178 
179     /// ditto
180     bool isExpDecay() { return _isExpDecay; }
181 
182     /// Return division points (computing points) of trapezoidal quadrature for DE formula.
183     immutable(F)[] xs() { return _xs; }
184 
185     /// Return weights of each division points.
186     immutable(W)[] weights() { return _weights; }
187   }
188 
189 
190   private:
191     string _intType;
192     F _xa, _xb, _ta, _tb;
193     size_t _trapN;
194     bool _isExpDecay;
195     immutable(F)[] _xs;
196     immutable(W)[] _weights;
197 
198     static
199     Tuple!(immutable(F)[], immutable(W)[]) _makeParamsImpl(F ta, F tb, size_t trapN, scope W delegate(F) weightFn, scope F[2] delegate(F) fn)
200     {
201         immutable(F)[] xs;
202         immutable(W)[] weights;
203         immutable F h = (tb - ta) / (trapN-1);
204         foreach(i; 0 .. trapN) {
205             immutable xWt = fn(i * h + ta);
206             xs ~= xWt[0];
207             weights ~= xWt[1] * h * weightFn(xWt[0]);
208         }
209 
210         return tuple(xs, weights);
211     }
212 }
213 
214 ///
215 unittest
216 {
217 	// integration on [0, 1]
218 	auto int01 = DEInt!real(0, 1);
219 	assert(int01.type == "FF");
220 
221 	// int_0^1 x dx = 0.5
222 	assert(int01.integrate((real x) => x).approxEqual(0.5));
223 
224 	// int_0^1 x^^2 dx = 1/3
225 	assert(int01.integrate((real x) => x^^2).approxEqual(1/3.0));
226 
227 
228 	// integration on [-inf, inf]
229 	auto intII = DEInt!real(-real.infinity, real.infinity);
230 	assert(intII.type == "II");
231 
232 	// Gaussian integral
233 	assert(intII.integrate((real x) => exp(-x^^2)).approxEqual(sqrt(PI)));
234 
235 	import std.mathspecial;
236 	// integration int_1^inf x * exp(-x) dx = Gamma(2, 1)
237 	auto intFI = DEInt!real(1, real.infinity, (real x) => exp(-x), Yes.isExpDecay);
238 	assert(intFI.type == "FIE");
239 
240 	// incomplete gamma function
241 	assert(intFI.integrate((real x) => x).approxEqual(gammaIncompleteCompl(2, 1) * gamma(2)));
242 }
243 
244 // Test of \int_a^b exp(x) dx
245 unittest 
246 {
247     // [a, b]
248     real[2][] dataset = [
249         [0.0, 1.0],
250         [-1.0, 1.0],
251         [0.0, -2.0],
252         [2.0, 0.0],
253         [-10.0, 0.0],
254         [0.0, 10.0]
255     ];
256 
257     foreach(data; dataset) {
258         real intDE = DEInt!real(data[0], data[1]).integrate((real x) => exp(x));
259         real truevalue = exp(data[1]) - exp(data[0]);
260 
261         assert(approxEqual(intDE, truevalue));
262     }
263 
264 
265     // reverse interval
266     foreach(data; dataset) {
267         //real intDE = intAToB_DE(data[0], data[1], 100, (real x) => exp(x));
268         real intDE = DEInt!real(data[1], data[0]).integrate((real x) => exp(x));
269         real truevalue = -(exp(data[1]) - exp(data[0]));
270 
271         assert(approxEqual(intDE, truevalue));
272     }
273 }
274 
275 // Complementary error function: erfc(a) = 2/sqrt(pi) * int_a^inf e^{-x^2} dx
276 unittest 
277 {
278     import std.mathspecial;
279 
280     real[] dataset = [
281         0.0,
282         0.01,
283         0.1,
284         1,
285         10,
286         100,
287     ];
288 
289     foreach(a; dataset) {
290         auto intDE = DEInt!real(a, real.infinity).integrate((real x) => exp(-x^^2));
291         intDE *= M_2_SQRTPI;
292 
293         auto truevalue = erfc(a);
294         assert(approxEqual(intDE, truevalue));
295     }
296 }
297 
298 // Complementary error function: erfc(a) = -2/sqrt(pi) * int_(-inf)^(-a) e^{-x^2} dx
299 unittest 
300 {
301     import std.mathspecial;
302 
303     real[] dataset = [
304         0.0,
305         0.01,
306         0.1,
307         1,
308         10,
309         100,
310     ];
311 
312     foreach(a; dataset) {
313         auto intDE = DEInt!real(-real.infinity, -a).integrate((real x) => exp(-x^^2));
314         intDE *= M_2_SQRTPI;
315 
316         auto truevalue = erfc(a);
317         assert(approxEqual(intDE, truevalue));
318     }
319 }
320 
321 // int_inf^inf 1/(1+x^2) dx = pi
322 unittest
323 {
324     auto intDE1 = DEInt!real(-real.infinity, real.infinity)
325                     .integrate((real x) => 1/(1 + x^^2));
326     assert(approxEqual(intDE1, PI));
327 }
328 
329 // int exp(-x^2) = sqrt(pi)
330 unittest
331 {
332     immutable val1 = DEInt!real(0, real.infinity, (real x) => exp(-x), Yes.isExpDecay, 100)
333         .integrate((real x) => 1.0/(2*sqrt(x)));
334 
335     immutable val2 = DEInt!real(real.infinity, 0, (real x) => exp(-x), Yes.isExpDecay, 100)
336         .integrate((real x) => -1.0/(2*sqrt(x)));
337 
338     immutable val3 = DEInt!real(-real.infinity, 0, (real x) => exp(x), Yes.isExpDecay, 100)
339         .integrate((real x) => 1.0/(2*sqrt(-x)));
340 
341     immutable val4 = DEInt!real(0, -real.infinity, (real x) => exp(x), Yes.isExpDecay, 100)
342         .integrate((real x) => -1.0/(2*sqrt(-x)));
343 
344     assert(approxEqual(val1, sqrt(PI)/2));
345     assert(approxEqual(val2, sqrt(PI)/2));
346     assert(approxEqual(val3, sqrt(PI)/2));
347     assert(approxEqual(val4, sqrt(PI)/2));
348 }
349 
350 // int exp(-x^2) * 1i = 1i * sqrt(pi)
351 unittest
352 {
353     import std.complex : Complex;
354 
355     immutable val1 = DEInt!(real, Complex!real)(0, real.infinity, ((real x) => exp(-x)*Complex!real(0, 1)), Yes.isExpDecay, 100)
356         .integrate((real x) => 1.0/(2*sqrt(x)));
357 
358     assert(val1.re.approxEqual(0));
359     assert(val1.im.approxEqual(sqrt(PI)/2));
360 }