1 /++ 2 This module contains algorithms for the $(LINK2 https://en.wikipedia.org/wiki/Geometric_distribution, Geometric Distribution). 3 4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 5 6 Authors: John Michael Hall 7 8 Copyright: 2022-3 Mir Stat Authors. 9 10 +/ 11 12 module mir.stat.distribution.geometric; 13 14 import mir.internal.utility: isFloatingPoint; 15 16 /++ 17 Computes the geometric probability density function (PMF). 18 19 Params: 20 k = value to evaluate PMF 21 p = `true` probability 22 23 See_also: $(LINK2 https://en.wikipedia.org/wiki/Geometric_distribution, Geometric Distribution) 24 +/ 25 @safe pure @nogc nothrow 26 T geometricPMF(T)(const size_t k, const T p) 27 if (isFloatingPoint!T) 28 in (p >= 0, "p must be greater than or equal to 0") 29 in (p <= 1, "p must be less than or equal to 1") 30 { 31 import mir.math.common: pow; 32 return pow(1 - p, k) * p; 33 } 34 35 /// 36 @safe pure @nogc nothrow 37 version(mir_stat_test) 38 unittest 39 { 40 import mir.test: shouldApprox; 41 42 0.geometricPMF(0.5).shouldApprox == 0.5; 43 1.geometricPMF(0.5).shouldApprox == 0.25; 44 2.geometricPMF(0.25).shouldApprox == 0.140625; 45 } 46 47 /++ 48 Computes the geometric cumulative density function (CDF). 49 50 Params: 51 k = value to evaluate CDF 52 p = `true` probability 53 54 See_also: $(LINK2 https://en.wikipedia.org/wiki/Geometric_distribution, Geometric Distribution) 55 +/ 56 @safe pure @nogc nothrow 57 T geometricCDF(T)(const size_t k, const T p) 58 if (isFloatingPoint!T) 59 in (p >= 0, "p must be greater than or equal to 0") 60 in (p <= 1, "p must be less than or equal to 1") 61 { 62 import mir.math.common: pow; 63 return 1 - pow(1 - p, k + 1); 64 } 65 66 /// ditto 67 @safe pure @nogc nothrow 68 T geometricCDF(T)(const T x, const T p) 69 if (isFloatingPoint!T) 70 in (x >= -1, "x must be larger than or equal to -1") 71 in (p >= 0, "p must be greater than or equal to 0") 72 in (p <= 1, "p must be less than or equal to 1") 73 { 74 if (x < 0) 75 return 0; 76 return geometricCDF!T(cast(const size_t) x, p); 77 } 78 79 /// 80 @safe pure @nogc nothrow 81 version(mir_stat_test) 82 unittest 83 { 84 import mir.test: shouldApprox; 85 86 geometricCDF(-1.0, 0.5).shouldApprox == 0; // UFCS chaining deduces this as size_t instead of a floating point type 87 0.geometricCDF(0.5).shouldApprox == 0.5; 88 1.geometricCDF(0.5).shouldApprox == 0.75; 89 2.geometricCDF(0.5).shouldApprox == 0.875; 90 91 geometricCDF(-1.0, 0.25).shouldApprox == 0; // UFCS chaining deduces this as size_t instead of a floating point type 92 0.geometricCDF(0.25).shouldApprox == 0.25; 93 1.geometricCDF(0.25).shouldApprox == 0.4375; 94 2.geometricCDF(0.25).shouldApprox == 0.578125; 95 2.5.geometricCDF(0.25).shouldApprox == 0.578125; 96 } 97 98 /++ 99 Computes the geometric complementary cumulative density function (CCDF). 100 101 Params: 102 k = value to evaluate CCDF 103 p = `true` probability 104 105 See_also: $(LINK2 https://en.wikipedia.org/wiki/Geometric_distribution, Geometric Distribution) 106 +/ 107 @safe pure @nogc nothrow 108 T geometricCCDF(T)(const size_t k, const T p) 109 if (isFloatingPoint!T) 110 in (p >= 0, "p must be greater than or equal to 0") 111 in (p <= 1, "p must be less than or equal to 1") 112 { 113 import mir.math.common: pow; 114 return pow(1 - p, k + 1); 115 } 116 117 /// ditto 118 @safe pure @nogc nothrow 119 T geometricCCDF(T)(const T x, const T p) 120 if (isFloatingPoint!T) 121 in (x >= -1, "x must be larger than or equal to -1") 122 in (p >= 0, "p must be greater than or equal to 0") 123 in (p <= 1, "p must be less than or equal to 1") 124 { 125 if (x < 0) 126 return 1; 127 return geometricCCDF!T(cast(const size_t) x, p); 128 } 129 130 /// 131 @safe pure @nogc nothrow 132 version(mir_stat_test) 133 unittest 134 { 135 import mir.test: shouldApprox; 136 137 geometricCCDF(-1.0, 0.5).shouldApprox == 1.0; // UFCS chaining deduces this as size_t instead of a floating point type 138 0.geometricCCDF(0.5).shouldApprox == 0.5; 139 1.geometricCCDF(0.5).shouldApprox == 0.25; 140 2.geometricCCDF(0.5).shouldApprox == 0.125; 141 142 geometricCCDF(-1.0, 0.25).shouldApprox == 1.0; // UFCS chaining deduces this as size_t instead of a floating point type 143 0.geometricCCDF(0.25).shouldApprox == 0.75; 144 1.geometricCCDF(0.25).shouldApprox == 0.5625; 145 2.geometricCCDF(0.25).shouldApprox == 0.421875; 146 2.5.geometricCCDF(0.25).shouldApprox == 0.421875; 147 } 148 149 /++ 150 Computes the geometric inverse cumulative distribution function (InvCDF). 151 152 Params: 153 q = value to evaluate InvCDF 154 p = `true` probability 155 156 See_also: $(LINK2 https://en.wikipedia.org/wiki/Geometric_distribution, Geometric Distribution) 157 +/ 158 @safe pure @nogc nothrow 159 T geometricInvCDF(T)(const T q, const T p) 160 if (isFloatingPoint!T) 161 in (q >= 0, "q must be greater than or equal to 0") 162 in (q <= 1, "q must be less than or equal to 1") 163 in (p >= 0, "p must be greater than or equal to 0") 164 in (p <= 1, "p must be less than or equal to 1") 165 { 166 import mir.math.common: approxEqual, ceil, nearbyint, log; 167 if (q == 1) { 168 return T.infinity; 169 } 170 if (p == 1) { 171 return 0; 172 } 173 T guess = log(1 - q) / log(1 - p); 174 T guessNearby = nearbyint(guess); 175 if (approxEqual(guess, guessNearby, T.epsilon * 2)) { 176 return guessNearby - 1; 177 } else { 178 return ceil(guess) - 1; 179 } 180 } 181 182 /// 183 @safe pure @nogc nothrow 184 version(mir_stat_test) 185 unittest 186 { 187 import mir.test: should; 188 189 0.geometricInvCDF(0.5).should == -1; 190 0.5.geometricInvCDF(0.5).should == 0; 191 0.75.geometricInvCDF(0.5).should == 1; 192 0.875.geometricInvCDF(0.5).should == 2; 193 0.95.geometricInvCDF(0.5).should == 4; 194 195 0.geometricInvCDF(0.25).should == -1; 196 0.25.geometricInvCDF(0.25).should == 0; 197 0.4375.geometricInvCDF(0.25).should == 1; 198 0.578125.geometricInvCDF(0.25).should == 2; 199 0.95.geometricInvCDF(0.25).should == 10; 200 201 0.5.geometricInvCDF(1).should == 0; 202 1.geometricInvCDF(0.5).should == double.infinity; 203 } 204 205 /++ 206 Computes the geometric log probability density function (LPMF). 207 208 Params: 209 k = value to evaluate LPMF 210 p = `true` probability 211 212 See_also: $(LINK2 https://en.wikipedia.org/wiki/Geometric_distribution, Geometric Distribution) 213 +/ 214 @safe pure @nogc nothrow 215 T geometricLPMF(T)(const size_t k, const T p) 216 if (isFloatingPoint!T) 217 in (p >= 0, "p must be greater than or equal to 0") 218 in (p <= 1, "p must be less than or equal to 1") 219 { 220 import mir.math.common: log; 221 import mir.math.internal.xlogy: xlog1py; 222 return xlog1py(k, -p) + log(p); 223 } 224 225 /// 226 @safe pure @nogc nothrow 227 version(mir_stat_test) 228 unittest 229 { 230 import mir.math.common: exp; 231 import mir.test: shouldApprox; 232 233 0.geometricLPMF(0.5).exp.shouldApprox == 0.geometricPMF(0.5); 234 1.geometricLPMF(0.5).exp.shouldApprox == 1.geometricPMF(0.5); 235 2.geometricLPMF(0.25).exp.shouldApprox == 2.geometricPMF(0.25); 236 }