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 }