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 }