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