The OpenD Programming Language

1 /++
2 This module contains algorithms for the $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution).
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 
6 Authors: John Michael Hall
7 
8 Copyright: 2023 Mir Stat Authors.
9 
10 +/
11 
12 module mir.stat.distribution.logistic;
13 
14 import mir.internal.utility: isFloatingPoint;
15 
16 /++
17 Computes the Logistic probability density function (PDF).
18 
19 Params:
20     x = value to evaluate PDF
21 
22 See_also:
23     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
24 +/
25 @safe pure nothrow @nogc
26 T logisticPDF(T)(const T x)
27     if (isFloatingPoint!T)
28 {
29     import mir.math.common: exp;
30 
31     const T exp_x = exp(-x);
32     return exp_x / ((1 + exp_x) * (1 + exp_x));
33 }
34 
35 /++
36 Ditto, with location and scale parameters (by standardizing `x`).
37 
38 Params:
39     x = value to evaluate PDF
40     location = location parameter
41     scale = scale parameter
42 +/
43 @safe pure nothrow @nogc
44 T logisticPDF(T)(const T x, const T location, const T scale)
45     if (isFloatingPoint!T)
46     in (scale > 0, "scale must be greater than zero")
47 {
48     return logisticPDF((x - location) / scale) / scale;
49 }
50 
51 ///
52 version(mir_stat_test)
53 @safe pure nothrow @nogc
54 unittest
55 {
56     import mir.test: shouldApprox;
57 
58     logisticPDF(-2.0).shouldApprox == 0.1049936;
59     logisticPDF(-1.0).shouldApprox == 0.1966119;
60     logisticPDF(-0.5).shouldApprox == 0.2350037;
61     logisticPDF(0.0).shouldApprox == 0.25;
62     logisticPDF(0.5).shouldApprox == 0.2350037;
63     logisticPDF(1.0).shouldApprox == 0.1966119;
64     logisticPDF(2.0).shouldApprox == 0.1049936;
65 
66     // Can also provide location/scale parameters
67     logisticPDF(-1.0, 2.0, 3.0).shouldApprox == 0.06553731;
68     logisticPDF(1.0, 2.0, 3.0).shouldApprox == 0.08106072;
69     logisticPDF(4.0, 2.0, 3.0).shouldApprox == 0.07471913;
70 }
71 
72 /++
73 Computes the Logistic cumulative distribution function (CDF).
74 
75 Params:
76     x = value to evaluate CDF
77 
78 See_also:
79     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
80 +/
81 @safe pure nothrow @nogc
82 T logisticCDF(T)(const T x)
83     if (isFloatingPoint!T)
84 {
85     import mir.math.common: exp;
86 
87     return 1 / (1 + exp(-x));
88 }
89 
90 /++
91 Ditto, with location and scale parameters (by standardizing `x`).
92 
93 Params:
94     x = value to evaluate CDF
95     location = location parameter
96     scale = scale parameter
97 +/
98 @safe pure nothrow @nogc
99 T logisticCDF(T)(const T x, const T location, const T scale)
100     if (isFloatingPoint!T)
101     in (scale > 0, "scale must be greater than zero")
102 {
103     return logisticCDF((x - location) / scale);
104 }
105 
106 ///
107 version(mir_stat_test)
108 @safe pure nothrow @nogc
109 unittest
110 {
111     import mir.test: shouldApprox;
112 
113     logisticCDF(-2.0).shouldApprox == 0.1192029;
114     logisticCDF(-1.0).shouldApprox == 0.2689414;
115     logisticCDF(-0.5).shouldApprox == 0.3775407;
116     logisticCDF(0.0).shouldApprox == 0.5;
117     logisticCDF(0.5).shouldApprox == 0.6224593;
118     logisticCDF(1.0).shouldApprox == 0.7310586;
119     logisticCDF(2.0).shouldApprox == 0.8807971;
120 
121     // Can also provide location/scale parameters
122     logisticCDF(-1.0, 2.0, 3.0).shouldApprox == 0.2689414;
123     logisticCDF(1.0, 2.0, 3.0).shouldApprox == 0.4174298;
124     logisticCDF(4.0, 2.0, 3.0).shouldApprox == 0.6607564;
125 }
126 
127 /++
128 Computes the Logistic complementary cumulative distribution function (CCDF).
129 
130 Params:
131     x = value to evaluate CCDF
132 
133 See_also:
134     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
135 +/
136 @safe pure nothrow @nogc
137 T logisticCCDF(T)(const T x)
138     if (isFloatingPoint!T)
139 {
140     import mir.math.common: exp;
141 
142     const T exp_x = exp(-x);
143     return exp_x / (1 + exp_x);
144 }
145 
146 /++
147 Ditto, with location and scale parameters (by standardizing `x`).
148 
149 Params:
150     x = value to evaluate CCDF
151     location = location parameter
152     scale = scale parameter
153 +/
154 @safe pure nothrow @nogc
155 T logisticCCDF(T)(const T x, const T location, const T scale)
156     if (isFloatingPoint!T)
157     in (scale > 0, "scale must be greater than zero")
158 {
159     return logisticCCDF((x - location) / scale);
160 }
161 
162 ///
163 version(mir_stat_test)
164 @safe pure nothrow @nogc
165 unittest
166 {
167     import mir.test: shouldApprox;
168 
169     logisticCCDF(-2.0).shouldApprox == 0.8807971;
170     logisticCCDF(-1.0).shouldApprox == 0.7310586;
171     logisticCCDF(-0.5).shouldApprox == 0.6224593;
172     logisticCCDF(0.0).shouldApprox == 0.5;
173     logisticCCDF(0.5).shouldApprox == 0.3775407;
174     logisticCCDF(1.0).shouldApprox == 0.2689414;
175     logisticCCDF(2.0).shouldApprox == 0.1192029;
176 
177     // Can also provide location/scale parameters
178     logisticCCDF(-1.0, 2.0, 3.0).shouldApprox == 0.7310586;
179     logisticCCDF(1.0, 2.0, 3.0).shouldApprox == 0.5825702;
180     logisticCCDF(4.0, 2.0, 3.0).shouldApprox == 0.3392436;
181 }
182 
183 /++
184 Computes the Logistic inverse cumulative distribution function (InvCDF).
185 
186 Params:
187     p = value to evaluate InvCDF
188 
189 See_also:
190     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
191 +/
192 @safe pure nothrow @nogc
193 T logisticInvCDF(T)(const T p)
194     if (isFloatingPoint!T)
195     in (p >= 0, "p must be greater than or equal to 0")
196     in (p <= 1, "p must be less than or equal to 1")
197 {
198     import mir.math.common: log;
199 
200     return log(p / (1 - p));
201 }
202 
203 /++
204 Ditto, with location and scale parameters (by standardizing `x`).
205 
206 Params:
207     p = value to evaluate InvCDF
208     location = location parameter
209     scale = scale parameter
210 
211 See_also:
212     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
213 +/
214 @safe pure nothrow @nogc
215 T logisticInvCDF(T)(const T p, const T location, const T scale)
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     in (scale > 0, "scale must be greater than zero")
220 {
221     return location + scale * logisticInvCDF(p);
222 }
223 
224 ///
225 version(mir_stat_test)
226 @safe pure nothrow @nogc
227 unittest {
228     import mir.test: shouldApprox;
229 
230     logisticInvCDF(0.0).shouldApprox == -double.infinity;
231     logisticInvCDF(0.25).shouldApprox == -1.098612;
232     logisticInvCDF(0.5).shouldApprox == 0.0;
233     logisticInvCDF(0.75).shouldApprox == 1.098612;
234     logisticInvCDF(1.0).shouldApprox == double.infinity;
235 
236     // Can also provide location/scale parameters
237     logisticInvCDF(0.2, 2, 3).shouldApprox == -2.158883;
238     logisticInvCDF(0.4, 2, 3).shouldApprox == 0.7836047;
239     logisticInvCDF(0.6, 2, 3).shouldApprox == 3.216395;
240     logisticInvCDF(0.8, 2, 3).shouldApprox == 6.158883;
241 }
242 
243 /++
244 Computes the Logistic log probability density function (LPDF).
245 
246 Params:
247     x = value to evaluate LPDF
248 
249 See_also:
250     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
251 +/
252 @safe pure nothrow @nogc
253 T logisticLPDF(T)(const T x)
254     if (isFloatingPoint!T)
255 {
256     import mir.math.common: exp, log;
257 
258     return -x - 2 * log(1 + exp(-x));
259 }
260 
261 /++
262 Ditto, with location and scale parameters (by standardizing `x`).
263 
264 Params:
265     x = value to evaluate LPDF
266     location = location parameter
267     scale = scale parameter
268 
269 See_also:
270     $(LINK2 https://en.wikipedia.org/wiki/Logistic_distribution, Logistic Distribution)
271 +/
272 @safe pure nothrow @nogc
273 T logisticLPDF(T)(const T x, const T location, const T scale)
274     if (isFloatingPoint!T)
275     in (scale > 0, "scale must be greater than zero")
276 {
277     import mir.math.common: log;
278 
279     return logisticLPDF((x - location) / scale) - log(scale);
280 }
281 
282 ///
283 version(mir_stat_test)
284 @safe pure nothrow @nogc
285 unittest
286 {
287     import mir.math.common: log;
288     import mir.test: shouldApprox;
289 
290     logisticLPDF(-2.0).shouldApprox == log(0.1049936);
291     logisticLPDF(-1.0).shouldApprox == log(0.1966119);
292     logisticLPDF(-0.5).shouldApprox == log(0.2350037);
293     logisticLPDF(0.0).shouldApprox == log(0.25);
294     logisticLPDF(0.5).shouldApprox == log(0.2350037);
295     logisticLPDF(1.0).shouldApprox == log(0.1966119);
296     logisticLPDF(2.0).shouldApprox == log(0.1049936);
297 
298     // Can also provide location/scale parameters
299     logisticLPDF(-1.0, 2.0, 3.0).shouldApprox == log(0.06553731);
300     logisticLPDF(1.0, 2.0, 3.0).shouldApprox == log(0.08106072);
301     logisticLPDF(4.0, 2.0, 3.0).shouldApprox == log(0.07471913);
302 }