The OpenD Programming Language

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