1 /++ 2 Comulative density functions 3 +/ 4 /** 5 Authors: [Ilya Yaroshenko](http://9il.github.io) 6 7 Copyright: © 2014-2015 [Ilya Yaroshenko](http://9il.github.io) 8 9 License: MIT 10 */ 11 module atmosphere.cdf; 12 13 import core.stdc.tgmath; 14 15 import std.algorithm; 16 import std.traits; 17 import std.range; 18 19 import atmosphere.moment; 20 import atmosphere.params; 21 import atmosphere.pdf; 22 import atmosphere.utilities; 23 24 import std.math : isNormal, isFinite, isNaN, approxEqual; 25 26 /++ 27 Normal PDF 28 +/ 29 struct NormalSCDF(T) 30 { 31 T mu, sigma; 32 33 /++ 34 Params: 35 mu = location 36 sigma2 = sigma^2 37 +/ 38 this(T mu, T sigma) 39 in { 40 assert(sigma > 0); 41 assert(mu.isFinite); 42 } 43 body { 44 this.mu = mu; 45 this.sigma = sigma; 46 } 47 48 /// 49 T opCall(T x) const 50 { 51 return normalDistribution((x - mu) / sigma); 52 } 53 } 54 55 56 /++ 57 Gamma CDF 58 +/ 59 struct GammaSCDF(T) 60 if(isFloatingPoint!T) 61 { 62 private T shape, scale; 63 64 /++ 65 Constructor 66 Params: 67 shape = gamma shape parameter 68 scale = gamma scale parameter 69 +/ 70 this(T shape, T scale) 71 in { 72 assert(shape.isNormal); 73 assert(shape > 0); 74 assert(scale.isNormal); 75 assert(scale > 0); 76 } 77 body { 78 this.shape = shape; 79 this.scale = scale; 80 } 81 82 /// 83 T opCall(T x) const 84 { 85 import std.mathspecial : gammaIncomplete; 86 return x <= 0 ? 0 : gammaIncomplete(shape, x / scale); 87 } 88 } 89 90 91 /// 92 unittest 93 { 94 auto cdf = GammaSCDF!double(3, 2); 95 auto x = cdf(0.1); 96 assert(isNormal(x)); 97 } 98 99 100 /++ 101 Inverse-gamma CDF 102 +/ 103 struct InverseGammaSCDF(T) 104 if(isFloatingPoint!T) 105 { 106 private T shape, scale; 107 108 /++ 109 Constructor 110 Params: 111 shape = gamma shape parameter 112 scale = gamma scale parameter 113 +/ 114 this(T shape, T scale) 115 in { 116 assert(shape.isNormal); 117 assert(shape > 0); 118 assert(scale.isNormal); 119 assert(scale > 0); 120 } 121 body { 122 this.shape = shape; 123 this.scale = scale; 124 } 125 126 /// 127 T opCall(T x) const 128 { 129 import std.mathspecial : gammaIncomplete; 130 return x <= 0 ? 0 : gammaIncomplete(shape, scale / x); 131 } 132 } 133 134 /// 135 unittest 136 { 137 auto cdf = InverseGammaSCDF!double(3, 2); 138 auto x = cdf(0.1); 139 assert(isNormal(x)); 140 } 141 142 143 /++ 144 Generalized gamma CDF 145 +/ 146 struct GeneralizedGammaSCDF(T) 147 if(isFloatingPoint!T) 148 { 149 private T shape, power, scale, gammaShape; 150 151 /++ 152 Constructor 153 Params: 154 shape = shape parameter 155 power = power parameter 156 scale = scale parameter 157 +/ 158 this(T shape, T power, T scale) 159 in { 160 assert(shape.isNormal); 161 assert(shape > 0); 162 assert(power.isFinite); 163 assert(scale.isNormal); 164 assert(scale > 0); 165 } 166 body { 167 this.shape = shape; 168 this.power = power; 169 this.scale = scale; 170 this.gammaShape = tgamma(shape); 171 assert(gammaShape.isNormal); 172 } 173 174 /// 175 T opCall(T x) const 176 { 177 import std.mathspecial : gammaIncomplete; 178 return x <= 0 ? 0 : gammaIncomplete(shape, pow(x / scale, power)) / gammaShape; 179 } 180 } 181 182 /// 183 unittest 184 { 185 auto cdf = GeneralizedGammaSCDF!double(3, 2, 0.5); 186 auto x = cdf(0.1); 187 assert(isNormal(x)); 188 } 189 190 191 /++ 192 Inverse Gaussian CDF 193 +/ 194 struct InverseGaussianSCDF(T) 195 if(isFloatingPoint!T) 196 { 197 private T omega, chi, psi; 198 199 ///Constructor 200 this(T chi, T psi) 201 in { 202 assert(chi.isNormal); 203 assert(chi > 0); 204 assert(psi.isNormal); 205 assert(psi > 0); 206 } 207 body { 208 this.chi = chi; 209 this.psi = psi; 210 this.omega = sqrt(chi * psi); 211 } 212 213 /// 214 T opCall(T x) const 215 { 216 import std.mathspecial : normalDistribution; 217 if(x <= 0) 218 return 0; 219 immutable a = sqrt(chi / x); 220 immutable b = sqrt(psi * x); 221 return normalDistribution(b - a) - exp(2*omega) * normalDistribution(-(a+b)); 222 } 223 } 224 225 /// 226 unittest 227 { 228 auto cdf = InverseGaussianSCDF!double(3, 2); 229 auto x = cdf(0.1); 230 assert(isNormal(x)); 231 } 232 233 234 /++ 235 Comulative density function interface 236 +/ 237 interface CDF(T) 238 { 239 /++ 240 Call operator 241 +/ 242 T opCall(T x); 243 } 244 245 /// 246 unittest 247 { 248 import std.traits, std.mathspecial; 249 250 class NormalCDF : CDF!real 251 { 252 real opCall(real x) 253 { 254 return normalDistribution(x); 255 } 256 } 257 258 auto cdf = new NormalCDF; 259 auto x = cdf(0.1); 260 assert(isNormal(x)); 261 } 262 263 264 /// 265 alias toCDF = convertTo!CDF; 266 267 /// 268 unittest 269 { 270 CDF!double cdf = GammaSCDF!double(1, 3).toCDF; 271 } 272 273 /++ 274 Class to compute cumulative density function as integral of it's probability density function. $(RED Unstable) algorithm. 275 +/ 276 abstract class NumericCDF(T) : CDF!T 277 { 278 import scid.calculus : Result, integrate; 279 280 private PDF!T pdf; 281 private T a, epsRel, epsAbs; 282 private T[] subdivisions; 283 private T[] partials; 284 285 /++ 286 Constructor 287 Params: 288 pdf = The PDF to _integrate. 289 subdivisions = TODO. 290 a = (optional) The lower limit of integration. 291 epsRel = (optional) The requested relative accuracy. 292 epsAbs = (optional) The requested absolute accuracy. 293 See_also: [struct Result](https://github.com/kyllingstad/scid/blob/a9f3916526e4bf9a4da35d14a969e1abfa17a496/source/scid/types.d) 294 +/ 295 this(PDF!T pdf, T[] subdivisions, T a = -T.infinity, T epsRel = 1e-6, T epsAbs = 0) 296 in { 297 assert(!subdivisions.empty); 298 assert(subdivisions.all!isFinite); 299 assert(subdivisions.all!(s => s > a)); 300 assert(subdivisions.isSorted); 301 assert(subdivisions.findAdjacent.empty); 302 } 303 body { 304 this.pdf = pdf; 305 this.a = a; 306 this.epsRel = epsRel; 307 this.epsAbs = epsAbs; 308 this.subdivisions = subdivisions; 309 this.partials = new T[subdivisions.length]; 310 partials.front = pdf.integrate(a, subdivisions.front, epsRel, epsAbs); 311 foreach(i, ref partial; partials[1..$]) 312 partial = pdf.integrate(subdivisions[i], subdivisions[i+1], epsRel, epsAbs); 313 } 314 315 /// 316 final T opCall(T x) 317 { 318 import std.algorithm : sum; 319 if(x == -T.infinity) 320 return 0; 321 if(x == T.infinity) 322 return 1; 323 if(x.isNaN) 324 return x; 325 immutable i = subdivisions.length - subdivisions.assumeSorted.trisect(x)[2].length; 326 return sum(partials[0..i]) 327 + pdf.integrate(i ? subdivisions[i-1] : a, x, epsRel, epsAbs); 328 } 329 } 330 331 /// Numeric cumulative density function of standard normal distribution 332 unittest 333 { 334 import std.traits, std.mathspecial; 335 import atmosphere.pdf; 336 337 class NormalPDF : PDF!real 338 { 339 real opCall(real x) 340 { 341 // 1/sqrt(2 PI) 342 enum c = 0.398942280401432677939946L; 343 return c * exp(-0.5f * x * x); 344 } 345 } 346 347 class NormalCDF : NumericCDF!real 348 { 349 this() 350 { 351 super(new NormalPDF, [-3, -1, 0, 1, 3]); 352 } 353 } 354 355 auto cdf = new NormalCDF; 356 357 assert(approxEqual(cdf(1.3), normalDistribution(1.3))); 358 } 359 360 361 /++ 362 Class to compute complementary cumulative density function as integral of it's probability density function. $(RED Unstable) algorithm. 363 +/ 364 abstract class NumericCCDF(T) : CDF!T 365 { 366 import scid.calculus : Result, integrate; 367 import atmosphere.pdf; 368 369 private PDF!T pdf; 370 private T b, epsRel, epsAbs; 371 private T[] subdivisions; 372 private T[] partials; 373 374 /++ 375 Constructor 376 Params: 377 pdf = The PDF to _integrate. 378 b = (optional) The upper limit of integration. 379 epsRel = (optional) The requested relative accuracy. 380 epsAbs = (optional) The requested absolute accuracy. 381 See_also: [struct Result](https://github.com/kyllingstad/scid/blob/a9f3916526e4bf9a4da35d14a969e1abfa17a496/source/scid/types.d) 382 +/ 383 this(PDF!T pdf, T[] subdivisions, T b = T.infinity, T epsRel = 1e-6, T epsAbs = 0) 384 in { 385 assert(!subdivisions.empty); 386 assert(subdivisions.all!isFinite); 387 assert(subdivisions.all!(s => s < b)); 388 assert(subdivisions.isSorted); 389 assert(subdivisions.findAdjacent.empty); 390 } 391 body { 392 this.pdf = pdf; 393 this.b = b; 394 this.epsRel = epsRel; 395 this.epsAbs = epsAbs; 396 this.subdivisions = subdivisions; 397 this.partials = new T[subdivisions.length]; 398 partials.back = pdf.integrate(subdivisions.back, b, epsRel, epsAbs); 399 foreach(i, ref partial; partials[0..$-1]) 400 partial = pdf.integrate(subdivisions[i], subdivisions[i+1], epsRel, epsAbs); 401 } 402 403 /// 404 final T opCall(T x) 405 { 406 import std.algorithm : sum; 407 if(x == -T.infinity) 408 return 0; 409 if(x == T.infinity) 410 return 1; 411 if(x.isNaN) 412 return x; 413 immutable i = subdivisions.length - subdivisions.assumeSorted.trisect(x)[0].length; 414 return sum(partials[$-i..$]) 415 + pdf.integrate(x, i ? subdivisions[$-i] : b, epsRel, epsAbs); 416 } 417 } 418 419 /// Numeric complementary cumulative density function of standard normal distribution 420 unittest 421 { 422 import std.traits, std.mathspecial; 423 import atmosphere.pdf; 424 425 class NormalPDF : PDF!real 426 { 427 real opCall(real x) 428 { 429 // 1/sqrt(2 PI) 430 enum c = 0.398942280401432677939946L; 431 return c * exp(-0.5f * x * x); 432 } 433 } 434 435 class NormalCCDF : NumericCCDF!real 436 { 437 this() 438 { 439 super(new NormalPDF, [-3, -1, 0, 1, 3]); 440 } 441 } 442 443 auto ccdf = new NormalCCDF; 444 445 assert(approxEqual(ccdf(1.3), 1-normalDistribution(1.3))); 446 } 447 448 449 /++ 450 Generalized hyperbolic (generalized inverse Gaussian mixture of normals) CDF. $(RED Unstable) algorithm. 451 452 See_Also: [distribution.params](distribution/params.html) 453 +/ 454 final class GeneralizedHyperbolicCDF(T): NumericCDF!T 455 { 456 /++ 457 Constructor 458 +/ 459 this(T lambda, T alpha, T beta, T delta, T mu) 460 { 461 immutable params = GHypAlphaDelta!T(alpha, beta, delta); 462 auto pdf = new GeneralizedHyperbolicPDF!T(lambda, alpha, beta, delta, mu); 463 immutable mean = mu + generalizedHyperbolicMean(lambda, beta, params.chi, params.psi); 464 super(pdf, [mean]); 465 } 466 } 467 468 /// 469 unittest 470 { 471 auto cdf = new GeneralizedHyperbolicCDF!double(3, 2, 1, 5, 6); 472 auto x = cdf(0.1); 473 assert(isNormal(x)); 474 } 475 476 477 /++ 478 Proper generalized inverse Gaussian CDF. $(RED Unstable) algorithm. 479 480 See_Also: [distribution.params](distribution/params.html) 481 +/ 482 final class ProperGeneralizedInverseGaussianCDF(T): NumericCDF!T 483 { 484 /++ 485 Constructor 486 +/ 487 this(T lambda, T eta, T omega) 488 { 489 auto pdf = ProperGeneralizedInverseGaussianSPDF!T(lambda, eta, omega); 490 immutable mean = properGeneralizedInverseGaussianMean(lambda, eta, omega); 491 super(pdf.toPDF, [mean], 0); 492 } 493 } 494 495 /// 496 unittest 497 { 498 auto cdf = new ProperGeneralizedInverseGaussianCDF!double(3, 2, 4); 499 auto x = cdf(0.1); 500 assert(isNormal(x)); 501 } 502 503 504 /++ 505 Variance-mean mixture of normals. $(RED Unstable) algorithm. 506 +/ 507 abstract class NormalVarianceMeanMixtureCDF(T) : CDF!T 508 if(isFloatingPoint!T) 509 { 510 private PDF!T pdf; 511 private T beta, mu; 512 private T epsRel, epsAbs; 513 private T[] subdivisions; 514 515 /++ 516 Constructor 517 Params: 518 pdf = The PDF to _integrate. 519 beta = NVMM scale 520 mu = NVMM location 521 subdivisions = TODO. 522 epsRel = (optional) The requested relative accuracy. 523 epsAbs = (optional) The requested absolute accuracy. 524 See_also: [struct Result](https://github.com/kyllingstad/scid/blob/a9f3916526e4bf9a4da35d14a969e1abfa17a496/source/scid/types.d) 525 +/ 526 this(PDF!T pdf, T beta, T mu, T[] subdivisions = null, T epsRel = 1e-6, T epsAbs = 0) 527 in { 528 assert(subdivisions.all!isFinite); 529 assert(subdivisions.all!(s => s > 0)); 530 assert(subdivisions.isSorted); 531 assert(subdivisions.findAdjacent.empty); 532 } 533 body { 534 this.pdf = pdf; 535 this.beta = beta; 536 this.mu = mu; 537 this.epsRel = epsRel; 538 this.epsAbs = epsAbs; 539 this.subdivisions = subdivisions; 540 } 541 542 543 T opCall(T x) 544 { 545 import std.mathspecial : normalDistribution; 546 import scid.calculus : integrate; 547 T f(T z) { 548 return normalDistribution((x - (mu + beta * z)) / sqrt(z)) * pdf(z); 549 } 550 T sum = 0; 551 T a = 0; 552 foreach(s; subdivisions) 553 { 554 sum += integrate(&f, a, s, epsRel, epsAbs); 555 a = s; 556 } 557 sum += integrate(&f, a, T.infinity); 558 return sum; 559 } 560 } 561 562 /// 563 unittest 564 { 565 import atmosphere; 566 567 class MyGeneralizedHyperbolicCDF(T) : NormalVarianceMeanMixtureCDF!T 568 { 569 this(T lambda, GHypEtaOmega!T params, T mu) 570 { 571 with(params) 572 { 573 auto pdf = ProperGeneralizedInverseGaussianSPDF!T(lambda, eta, omega); 574 auto mean = properGeneralizedInverseGaussianMean(lambda, eta, omega); 575 super(pdf.toPDF, params.beta, mu, [mean]); 576 } 577 } 578 } 579 580 immutable double lambda = 2; 581 immutable double mu = 0.3; 582 immutable params = GHypEtaOmega!double(2, 3, 4); 583 auto cghyp = new GeneralizedHyperbolicCDF!double(lambda, params.alpha, params.beta, params.delta, mu); 584 auto cnvmm = new MyGeneralizedHyperbolicCDF!double(lambda, params, mu); 585 foreach(i; [-100, 10, 0, 10, 100]) 586 assert(approxEqual(cghyp(i), cnvmm(i))); 587 } 588 589 590 /++ 591 Generalized variance-gamma (generalized gamma mixture of normals) CDF. $(RED Unstable) algorithm. 592 +/ 593 final class GeneralizedVarianceGammaCDF(T): NormalVarianceMeanMixtureCDF!T 594 { 595 /++ 596 Constructor 597 Params: 598 shape = shape parameter (generalized gamma) 599 power = power parameter (generalized gamma) 600 scale = scale parameter (generalized gamma) 601 beta = NVMM scale 602 mu = NVMM location 603 +/ 604 this(T shape, T power, T scale, T beta, T mu) 605 { 606 auto pdf = GeneralizedGammaSPDF!T(shape, power, scale); 607 immutable mean = generalizedGammaMean(shape, power, scale); 608 super(pdf.toPDF, beta, mu, [mean]); 609 } 610 } 611 612 /// 613 unittest 614 { 615 auto cdf = new GeneralizedVarianceGammaCDF!double(3, 2, 4, 5, 6); 616 auto x = cdf(0.1); 617 assert(isNormal(x)); 618 } 619 620 621 /// 622 unittest 623 { 624 final class MyGeneralizedVarianceGammaCDF(T): NumericCDF!T 625 { 626 this(T shape, T power, T scale, T beta, T mu) 627 { 628 auto pdf = new GeneralizedVarianceGammaPDF!T(shape, power, scale, beta, mu); 629 immutable mean = mu + generalizedVarianceGammaMean(shape, power, scale, beta); 630 super(pdf, [mean]); 631 } 632 } 633 immutable double shape = 2, power = 1.2, scale = 0.3, beta = 3, mu = -1; 634 auto p0 = new GeneralizedVarianceGammaCDF!double(shape, power, scale, beta, mu); 635 auto p1 = new MyGeneralizedVarianceGammaCDF!double(shape, power, scale, beta, mu); 636 foreach(i; 0..10) 637 assert(approxEqual(p0(i), p1(i))); 638 }