1 /++
2 Quantile 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.quantile;
12 
13 import core.stdc.tgmath;
14 
15 import std.traits;
16 import std.math : isNormal, isNaN, isFinite, approxEqual;
17 
18 import atmosphere.utilities;
19 
20 /++
21 Quantile function of the gamma distribution
22 +/
23 struct GammaSQuantile(T)
24 	if(isFloatingPoint!T)
25 {
26 	private T shape, scale;
27 
28 	///Constructor
29 	this(T shape, T scale)
30 	in {
31 		assert(shape.isNormal);
32 		assert(shape > 0);
33 		assert(scale.isNormal);
34 		assert(scale > 0);
35 	}
36 	body {
37 		this.shape = shape;
38 		this.scale = scale;
39 	}
40 
41 	///
42 	T opCall(T x) const
43 	in {
44 		assert(x >= 0);
45 		assert(x <= 1);		
46 	}
47 	body {
48 		import std.mathspecial : gammaIncompleteComplInverse;
49 		return scale * T(gammaIncompleteComplInverse(shape, 1-x));
50 	}
51 }
52 
53 ///
54 unittest 
55 {
56 	auto qf = GammaSQuantile!double(3, 2);
57 	auto x = qf(0.1);
58 	assert(isNormal(x));
59 }
60 
61 
62 /++
63 Quantile function of the inverse-gamma distribution
64 +/
65 struct InverseGammaSQuantile(T)
66 	if(isFloatingPoint!T)
67 {
68 	private T shape, scale;
69 
70 	///Constructor
71 	this(T shape, T scale)
72 	in {
73 		assert(shape.isNormal);
74 		assert(shape > 0);
75 		assert(scale.isNormal);
76 		assert(scale > 0);
77 	}
78 	body {
79 		this.shape = shape;
80 		this.scale = scale;
81 	}
82 
83 	///
84 	T opCall(T x) const
85 	in {
86 		assert(x >= 0);
87 		assert(x <= 1);		
88 	}
89 	body {
90 		import std.mathspecial : gammaIncompleteComplInverse;
91 		return scale / gammaIncompleteComplInverse(shape, 1-x);
92 	}
93 }
94 
95 ///
96 unittest 
97 {
98 	auto qf = InverseGammaSQuantile!double(3, 2);
99 	auto x = qf(0.1);
100 	assert(isNormal(x));
101 }
102 
103 
104 /++
105 Quantile function of the generalized gamma distribution
106 +/
107 struct GeneralizedGammaSQuantile(T)
108 	if(isFloatingPoint!T)
109 {
110 	private T shape, power, scale;
111 
112 	///Constructor
113 	this(T shape, T power, T scale)
114 	in {
115 		assert(shape.isNormal);
116 		assert(shape > 0);
117 		assert(power.isFinite);
118 		assert(scale.isNormal);
119 		assert(scale > 0);
120 	}
121 	body {
122 		this.shape = shape;
123 		this.power = power;
124 		this.scale = scale;
125 	}
126 
127 	///
128 	T opCall(T x) const
129 	in {
130 		assert(x >= 0);
131 		assert(x <= 1);		
132 	}
133 	body {
134 		import std.mathspecial : gammaIncompleteComplInverse;
135 		return scale * T(gammaIncompleteComplInverse(shape, 1-x)).pow(1/power);
136 	}
137 }
138 
139 ///
140 unittest 
141 {
142 	auto qf = GeneralizedGammaSQuantile!double(3, 2, 1);
143 	auto x = qf(0.1);
144 	assert(isNormal(x));
145 }
146 
147 
148 /++
149 Quantile function interface
150 +/
151 interface Quantile(T)
152 {
153 	/++
154 	Call operator
155 	+/
156 	abstract T opCall(T x);
157 }
158 
159 ///
160 unittest
161 {
162 	import std.traits, std.mathspecial;
163 
164 	class NormalQuantile : Quantile!real
165 	{
166 		real opCall(real x)
167 		in {
168 			assert(x >= 0);
169 			assert(x <= 1);
170 		}
171 		body {
172 			return normalDistributionInverse(x);
173 		}
174 	}
175 
176 	auto qf = new NormalQuantile;
177 	auto x = qf(0.1);
178 	assert(isNormal(x));
179 }
180 
181 
182 ///
183 alias toQuantile = convertTo!Quantile;
184 
185 ///
186 unittest
187 {
188 	Quantile!double qf = GammaSQuantile!double(1, 3).toQuantile;
189 }
190 
191 
192 /++
193 Class to compute quantile function as root of it's cumulative density function. $(RED Unstable) algorithm.
194 +/
195 abstract class NumericQuantile(T) : Quantile!T
196 {
197 	import atmosphere.cdf;
198 
199 	private CDF!T cdf;
200 	private T a, b;
201 	private scope bool delegate(T lo, T hi) tolerance;
202 
203 	/++
204 	Constructor
205     Params:
206 		cdf	= The CDF to to inverse.
207 		a	= (optional) The lower bound.
208 		b	= (optional) The upper bound.
209 	+/
210 	this(CDF!T cdf, T a = -T.max, T b = T.max)
211 	{
212 		this.cdf = cdf;
213 		this.a = a;
214 		this.b = b;
215 		this.tolerance = tolerance;
216 	}
217 
218 	/++
219 	Call operator
220 	+/
221 	final T opCall(T x)
222 	in {
223 		assert(x >= 0);
224 		assert(x <= 1);
225 	}
226 	out(result) {
227 		assert(!result.isNaN);
228 	}
229 	body {
230 		import std.numeric : findRoot;
231 		T f(T y)
232 			in     { assert(y.isFinite); }
233 			out(r) { assert(!r.isNaN);   }
234 			body   { return cdf(y) - x;  }
235 		//return tolerance ? findRoot(&f, a, b) : findRoot(&f, a, b, tolerance);
236 		return findRoot(&f, a, b);
237 	}
238 }
239 
240 /// Numeric quantile function of standard normal distribution
241 unittest
242 {
243 	import std.traits, std.mathspecial;
244 	import atmosphere.pdf;
245 	import atmosphere.cdf;
246 
247 	class NormalPDF : PDF!real
248 	{
249 		real opCall(real x)
250 		{
251 			// 1/sqrt(2 PI)
252 			enum c = 0.398942280401432677939946L;
253 			return c * exp(-0.5f * x * x);
254 		}
255 	}
256 
257 	class NormalCDF : NumericCDF!real ///$(RED Unstable) algorithm.
258 	{
259 		this()
260 		{
261 			super(new NormalPDF, [-3, -1, 0, 1, 3]);
262 		}
263 	}
264 
265 	class NormalQuantile : NumericQuantile!real ///$(RED Unstable) algorithm.
266 	{
267 		this()
268 		{
269 			super(new NormalCDF);
270 		}
271 	}
272 
273 	auto qf = new NormalQuantile;
274 
275 	assert(approxEqual(qf(0.3), normalDistributionInverse(0.3)));
276 }
277 
278 /// Numeric quantile function of Generalized Hyperbolic distribution
279 unittest
280 {
281 	import atmosphere.pdf;
282 	import atmosphere.cdf;
283 	import atmosphere.params;
284 	import atmosphere.moment;
285 
286 	class GHypCDF: NumericCDF!real ///$(RED Unstable) algorithm.
287 	{
288 		this(real lambda, GHypChiPsi!real params)
289 		{
290 			immutable mu = 0;
291 			auto pdf = new GeneralizedHyperbolicPDF!real(lambda, params.alpha, params.beta, params.delta, mu);
292 			immutable mean = generalizedHyperbolicMean!real(lambda, params.beta, params.chi, params.psi);
293 			super(pdf, [mean]);	
294 		}
295 	}
296 
297 	class GHypQuantile : NumericQuantile!real ///$(RED Unstable) algorithm.
298 	{
299 		this(real lambda, GHypChiPsi!real params)
300 		{
301 			super(new GHypCDF(lambda, params), -1000, 1000);	
302 		}
303 	}
304 
305 	auto qf = new GHypQuantile(0.5, GHypChiPsi!real(5, 0.7, 0.6));
306 	assert(approxEqual(qf(0.95), 40.9263));
307 	assert(approxEqual(qf(0.99), 64.977));
308 }