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 }