1 /++ 2 This module contains algorithms for the $(LINK2 https://en.wikipedia.org/wiki/Log-normal_distribution, Log-normal 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.log_normal; 13 14 import mir.internal.utility: isFloatingPoint; 15 16 /++ 17 Computes the Log-normal probability density function (PDF). 18 19 Params: 20 x = value to evaluate PDF 21 22 See_also: 23 $(LINK2 https://en.wikipedia.org/wiki/Log-normal_distribution, Log-normal Distribution) 24 +/ 25 @safe pure nothrow @nogc 26 T logNormalPDF(T)(const T x) 27 if (isFloatingPoint!T) 28 in (x >= 0, "x must be greater than or equal to zero") 29 { 30 import mir.math.common: log; 31 import mir.stat.distribution.normal: normalPDF; 32 if (x == 0) return 0; 33 return x.log.normalPDF / x; 34 } 35 36 /++ 37 Ditto, with location and scale parameters (by standardizing `x`). 38 39 Params: 40 x = value to evaluate PDF 41 mean = location parameter 42 stdDev = scale parameter 43 +/ 44 @safe pure nothrow @nogc 45 T logNormalPDF(T)(const T x, const T mean, const T stdDev) 46 if (isFloatingPoint!T) 47 in (x >= 0, "x must be greater than or equal to zero") 48 in (stdDev > 0, "stdDev must be greater than zero") 49 { 50 import mir.math.common: log; 51 import mir.stat.distribution.normal: normalPDF; 52 if (x == 0) return 0; 53 return x.log.normalPDF(mean, stdDev) / x; 54 } 55 56 /// 57 version(mir_stat_test) 58 @safe pure nothrow @nogc 59 unittest { 60 import mir.test: shouldApprox; 61 62 logNormalPDF(1.0).shouldApprox == 0.3989423; 63 logNormalPDF(2.0).shouldApprox == 0.156874; 64 logNormalPDF(3.0).shouldApprox == 0.07272826; 65 66 // Can include location/scale 67 logNormalPDF(1.0, 1, 2).shouldApprox == 0.1760327; 68 logNormalPDF(2.0, 1, 2).shouldApprox == 0.09856858; 69 logNormalPDF(3.0, 1, 2).shouldApprox == 0.06640961; 70 } 71 72 // check zero or near zero 73 version(mir_stat_test) 74 @safe pure nothrow @nogc 75 unittest { 76 import mir.test: shouldApprox; 77 78 logNormalPDF(0.0).shouldApprox == 0.0; 79 logNormalPDF(double.min_normal * double.epsilon).shouldApprox == 0.0; 80 logNormalPDF(double.min_normal).shouldApprox == 0.0; 81 82 logNormalPDF(0.0, 1, 2).shouldApprox == 0.0; 83 logNormalPDF(double.min_normal * double.epsilon, 1, 2).shouldApprox == 0.0; 84 logNormalPDF(double.min_normal, 1, 2).shouldApprox == 0.0; 85 } 86 87 /++ 88 Computes the Log-normal cumulative distribution function (CDF). 89 90 Params: 91 x = value to evaluate CDF 92 93 See_also: 94 $(LINK2 https://en.wikipedia.org/wiki/Log-normal_distribution, Log-normal Distribution) 95 +/ 96 @safe pure nothrow @nogc 97 T logNormalCDF(T)(const T x) 98 if (isFloatingPoint!T) 99 in (x >= 0, "x must be greater than or equal to zero") 100 { 101 import mir.math.common: log; 102 import mir.stat.distribution.normal: normalCDF; 103 return x.log.normalCDF; 104 } 105 106 /++ 107 Ditto, with location and scale parameters (by standardizing `x`). 108 109 Params: 110 x = value to evaluate CDF 111 mean = location parameter 112 stdDev = scale parameter 113 +/ 114 @safe pure nothrow @nogc 115 T logNormalCDF(T)(const T x, const T mean, const T stdDev) 116 if (isFloatingPoint!T) 117 in (x >= 0, "x must be greater than or equal to zero") 118 in (stdDev > 0, "stdDev must be greater than zero") 119 { 120 import mir.math.common: log; 121 import mir.stat.distribution.normal: normalCDF; 122 return x.log.normalCDF(mean, stdDev); 123 } 124 125 /// 126 version(mir_stat_test) 127 @safe pure nothrow @nogc 128 unittest { 129 import mir.test: shouldApprox; 130 131 logNormalCDF(0.0).shouldApprox == 0; 132 logNormalCDF(1.0).shouldApprox == 0.5; 133 logNormalCDF(2.0).shouldApprox == 0.7558914; 134 logNormalCDF(3.0).shouldApprox == 0.8640314; 135 136 // Can include location/scale 137 logNormalCDF(0.0, 1, 2).shouldApprox == 0; 138 logNormalCDF(1.0, 1, 2).shouldApprox == 0.3085375; 139 logNormalCDF(2.0, 1, 2).shouldApprox == 0.439031; 140 logNormalCDF(3.0, 1, 2).shouldApprox == 0.5196623; 141 } 142 143 /++ 144 Computes the Student's t complementary cumulative distribution function (CCDF). 145 146 Params: 147 x = value to evaluate CCDF 148 149 See_also: 150 $(LINK2 https://en.wikipedia.org/wiki/Log-normal_distribution, Log-normal Distribution) 151 +/ 152 @safe pure nothrow @nogc 153 T logNormalCCDF(T)(const T x) 154 if (isFloatingPoint!T) 155 in (x >= 0, "x must be greater than or equal to zero") 156 { 157 import mir.math.common: log; 158 import mir.stat.distribution.normal: normalCCDF; 159 return x.log.normalCCDF; 160 } 161 162 /++ 163 Ditto, with location and scale parameters (by standardizing `x`). 164 165 Params: 166 x = value to evaluate CCDF 167 mean = location parameter 168 stdDev = scale parameter 169 +/ 170 @safe pure nothrow @nogc 171 T logNormalCCDF(T)(const T x, const T mean, const T stdDev) 172 if (isFloatingPoint!T) 173 in (x >= 0, "x must be greater than or equal to zero") 174 in (stdDev > 0, "stdDev must be greater than zero") 175 { 176 import mir.math.common: log; 177 import mir.stat.distribution.normal: normalCCDF; 178 return x.log.normalCCDF(mean, stdDev); 179 } 180 181 /// 182 version(mir_stat_test) 183 @safe pure nothrow @nogc 184 unittest { 185 import mir.test: shouldApprox; 186 187 logNormalCCDF(0.0).shouldApprox == 1; 188 logNormalCCDF(1.0).shouldApprox == 0.5; 189 logNormalCCDF(2.0).shouldApprox == 0.2441086; 190 logNormalCCDF(3.0).shouldApprox == 0.1359686; 191 192 // Can include location/scale 193 logNormalCCDF(0.0, 1, 2).shouldApprox == 1; 194 logNormalCCDF(1.0, 1, 2).shouldApprox == 0.6914625; 195 logNormalCCDF(2.0, 1, 2).shouldApprox == 0.560969; 196 logNormalCCDF(3.0, 1, 2).shouldApprox == 0.4803377; 197 } 198 199 /++ 200 Computes the Log-normal inverse cumulative distribution function (InvCDF). 201 202 Params: 203 p = value to evaluate InvCDF 204 205 See_also: 206 $(LINK2 https://en.wikipedia.org/wiki/Log-normal_distribution, Log-normal Distribution) 207 +/ 208 @safe pure nothrow @nogc 209 T logNormalInvCDF(T)(const T p) 210 if (isFloatingPoint!T) 211 in (p >= 0, "p must be greater than or equal to 0") 212 in (p <= 1, "p must be less than or equal to 1") 213 out (r; r >= 0, "return must be greater than or equal to zero") 214 { 215 import mir.math.common: exp; 216 import mir.stat.distribution.normal: normalInvCDF; 217 return p.normalInvCDF.exp; 218 } 219 220 /++ 221 Ditto, with location and scale parameters (by standardizing `x`). 222 223 Params: 224 p = value to evaluate InvCDF 225 mean = location parameter 226 stdDev = scale parameter 227 +/ 228 @safe pure nothrow @nogc 229 T logNormalInvCDF(T)(const T p, const T mean, const T stdDev) 230 if (isFloatingPoint!T) 231 in (p >= 0, "p must be greater than or equal to 0") 232 in (p <= 1, "p must be less than or equal to 1") 233 in (stdDev > 0, "stdDev must be greater than zero") 234 out (r; r >= 0, "return must be greater than or equal to zero") 235 { 236 import mir.math.common: exp; 237 import mir.stat.distribution.normal: normalInvCDF; 238 return p.normalInvCDF(mean, stdDev).exp; 239 } 240 241 /// 242 version(mir_stat_test) 243 @safe pure nothrow @nogc 244 unittest { 245 import mir.test: shouldApprox; 246 247 logNormalInvCDF(0.00).shouldApprox == 0; 248 logNormalInvCDF(0.25).shouldApprox == 0.5094163; 249 logNormalInvCDF(0.50).shouldApprox == 1; 250 logNormalInvCDF(0.75).shouldApprox == 1.963031; 251 logNormalInvCDF(1.00).shouldApprox == double.infinity; 252 253 // Can include location/scale 254 logNormalInvCDF(0.00, 1, 2).shouldApprox == 0; 255 logNormalInvCDF(0.25, 1, 2).shouldApprox == 0.7054076; 256 logNormalInvCDF(0.50, 1, 2).shouldApprox == 2.718282; 257 logNormalInvCDF(0.75, 1, 2).shouldApprox == 10.47487; 258 logNormalInvCDF(1.00, 1, 2).shouldApprox == double.infinity; 259 } 260 261 /++ 262 Computes the Log-normal log probability density function (LPDF). 263 264 Params: 265 x = value to evaluate LPDF 266 267 See_also: 268 $(LINK2 https://en.wikipedia.org/wiki/Log-normal_distribution, Log-normal Distribution) 269 +/ 270 @safe pure nothrow @nogc 271 T logNormalLPDF(T)(const T x) 272 if (isFloatingPoint!T) 273 in (x >= 0, "x must be greater than or equal to zero") 274 { 275 import mir.math.common: log; 276 import mir.stat.distribution.normal: normalLPDF; 277 if (x == 0) return -T.infinity; 278 return x.log.normalLPDF - log(x); 279 } 280 281 /++ 282 Ditto, with location and scale parameters (by standardizing `x`). 283 284 Params: 285 x = value to evaluate LPDF 286 mean = location parameter 287 stdDev = scale parameter 288 +/ 289 @safe pure nothrow @nogc 290 T logNormalLPDF(T)(const T x, const T mean, const T stdDev) 291 if (isFloatingPoint!T) 292 in (x >= 0, "x must be greater than or equal to zero") 293 in (stdDev > 0, "stdDev must be greater than zero") 294 { 295 import mir.math.common: log; 296 import mir.stat.distribution.normal: normalLPDF; 297 if (x == 0) return -T.infinity; 298 return x.log.normalLPDF(mean, stdDev) - log(x); 299 } 300 301 /// 302 version(mir_stat_test) 303 @safe pure nothrow @nogc 304 unittest { 305 import mir.math.common: log; 306 import mir.test: shouldApprox; 307 308 logNormalLPDF(1.0).shouldApprox == log(0.3989423); 309 logNormalLPDF(2.0).shouldApprox == log(0.156874); 310 logNormalLPDF(3.0).shouldApprox == log(0.07272826); 311 312 // Can include location/scale 313 logNormalLPDF(1.0, 1, 2).shouldApprox == log(0.1760327); 314 logNormalLPDF(2.0, 1, 2).shouldApprox == log(0.09856858); 315 logNormalLPDF(3.0, 1, 2).shouldApprox == log(0.06640961); 316 } 317 318 319 // check zero 320 version(mir_stat_test) 321 @safe pure nothrow @nogc 322 unittest { 323 import mir.test: shouldApprox; 324 325 logNormalLPDF(0.0).shouldApprox == -double.infinity; 326 logNormalLPDF(0.0, 1, 2).shouldApprox == -double.infinity; 327 }