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 }