The OpenD Programming Language

1 /++
2 This module contains algorithms for the $(LINK2 https://en.wikipedia.org/wiki/Cauchy_distribution, Cauchy 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.cauchy;
13 
14 import mir.internal.utility: isFloatingPoint;
15 
16 /++
17 Computes the Cauchy probability density function (PDF).
18 
19 Params:
20     x = value to evaluate PDF
21 
22 See_also:
23     $(LINK2 https://en.wikipedia.org/wiki/Cauchy_distribution, Cauchy Distribution)
24 +/
25 @safe pure nothrow @nogc
26 T cauchyPDF(T)(const T x)
27     if (isFloatingPoint!T)
28 {
29     import mir.math.constant: M_1_PI;
30 
31     return T(M_1_PI) / (1 + x * 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 cauchyPDF(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 cauchyPDF((x - location) / scale) / scale;
48 }
49 
50 ///
51 version(mir_stat_test)
52 @safe pure nothrow @nogc
53 unittest {
54     import mir.test: shouldApprox;
55 
56     cauchyPDF(-3.0).shouldApprox == 0.03183099;
57     cauchyPDF(-2.0).shouldApprox == 0.06366198;
58     cauchyPDF(-1.0).shouldApprox == 0.1591549;
59     cauchyPDF(0.0).shouldApprox == 0.3183099;
60     cauchyPDF(1.0).shouldApprox == 0.1591549;
61     cauchyPDF(2.0).shouldApprox == 0.06366198;
62     cauchyPDF(3.0).shouldApprox == 0.03183099;
63 
64     // Can include location/scale
65     cauchyPDF(-3.0, 1, 2).shouldApprox == 0.03183099;
66     cauchyPDF(-2.0, 1, 2).shouldApprox == 0.04897075;
67     cauchyPDF(-1.0, 1, 2).shouldApprox == 0.07957747;
68     cauchyPDF(0.0, 1, 2).shouldApprox == 0.127324;
69     cauchyPDF(1.0, 1, 2).shouldApprox == 0.1591549;
70     cauchyPDF(2.0, 1, 2).shouldApprox == 0.127324;
71     cauchyPDF(3.0, 1, 2).shouldApprox == 0.07957747;
72 }
73 
74 /++
75 Computes the Cauchy cumulative distribution function (CDF).
76 
77 Params:
78     x = value to evaluate CDF
79 
80 See_also:
81     $(LINK2 https://en.wikipedia.org/wiki/Cauchy_distribution, Cauchy Distribution)
82 +/
83 @safe pure nothrow @nogc
84 T cauchyCDF(T)(const T x)
85     if (isFloatingPoint!T)
86 {
87     import mir.math.constant: M_1_PI;
88     import std.math.trigonometry: atan;
89 
90     return 0.5 + T(M_1_PI) * atan(x);
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 cauchyCDF(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 cauchyCDF((x - location) / scale);
107 }
108 
109 ///
110 version(mir_stat_test)
111 @safe pure nothrow @nogc
112 unittest {
113     import mir.test: shouldApprox;
114 
115     cauchyCDF(-3.0).shouldApprox == 0.1024164;
116     cauchyCDF(-2.0).shouldApprox == 0.1475836;
117     cauchyCDF(-1.0).shouldApprox == 0.25;
118     cauchyCDF(0.0).shouldApprox == 0.5;
119     cauchyCDF(1.0).shouldApprox == 0.75;
120     cauchyCDF(2.0).shouldApprox == 0.8524164;
121     cauchyCDF(3.0).shouldApprox == 0.8975836;
122 
123     // Can include location/scale
124     cauchyCDF(-3.0, 1, 2).shouldApprox == 0.1475836;
125     cauchyCDF(-2.0, 1, 2).shouldApprox == 0.187167;
126     cauchyCDF(-1.0, 1, 2).shouldApprox == 0.25;
127     cauchyCDF(0.0, 1, 2).shouldApprox == 0.3524164;
128     cauchyCDF(1.0, 1, 2).shouldApprox == 0.5;
129     cauchyCDF(2.0, 1, 2).shouldApprox == 0.6475836;
130     cauchyCDF(3.0, 1, 2).shouldApprox == 0.75;
131 }
132 
133 /++
134 Computes the Cauchy complementary cumulative distribution function (CCDF).
135 
136 Params:
137     x = value to evaluate CCDF
138 
139 See_also:
140     $(LINK2 https://en.wikipedia.org/wiki/Cauchy_distribution, Cauchy Distribution)
141 +/
142 @safe pure nothrow @nogc
143 T cauchyCCDF(T)(const T x)
144     if (isFloatingPoint!T)
145 {
146     return cauchyCDF(-x);
147 }
148 
149 /++
150 Ditto, with location and scale parameters (by standardizing `x`).
151 
152 Params:
153     x = value to evaluate CCDF
154     location = location parameter
155     scale = scale parameter
156 +/
157 @safe pure nothrow @nogc
158 T cauchyCCDF(T)(const T x, const T location, const T scale)
159     if (isFloatingPoint!T)
160     in (scale > 0, "scale must be greater than zero")
161 {
162     return cauchyCDF((location - x) / scale);
163 }
164 
165 ///
166 version(mir_stat_test)
167 @safe pure nothrow @nogc
168 unittest {
169     import mir.test: shouldApprox;
170 
171     cauchyCCDF(-3.0).shouldApprox == 0.8975836;
172     cauchyCCDF(-2.0).shouldApprox == 0.8524164;
173     cauchyCCDF(-1.0).shouldApprox == 0.75;
174     cauchyCCDF(0.0).shouldApprox == 0.5;
175     cauchyCCDF(1.0).shouldApprox == 0.25;
176     cauchyCCDF(2.0).shouldApprox == 0.1475836;
177     cauchyCCDF(3.0).shouldApprox == 0.1024164;
178 
179     // Can include location/scale
180     cauchyCCDF(-3.0, 1, 2).shouldApprox == 0.8524164;
181     cauchyCCDF(-2.0, 1, 2).shouldApprox == 0.812833;
182     cauchyCCDF(-1.0, 1, 2).shouldApprox == 0.75;
183     cauchyCCDF(0.0, 1, 2).shouldApprox == 0.6475836;
184     cauchyCCDF(1.0, 1, 2).shouldApprox == 0.5;
185     cauchyCCDF(2.0, 1, 2).shouldApprox == 0.3524164;
186     cauchyCCDF(3.0, 1, 2).shouldApprox == 0.25;
187 }
188 
189 /++
190 Computes the Cauchy inverse cumulative distribution function (InvCDF).
191 
192 Params:
193     p = value to evaluate InvCDF
194 
195 See_also:
196     $(LINK2 https://en.wikipedia.org/wiki/Cauchy_distribution, Cauchy Distribution)
197 +/
198 @safe pure nothrow @nogc
199 T cauchyInvCDF(T)(const T p)
200     if (isFloatingPoint!T)
201     in (p >= 0, "p must be greater than or equal to 0")
202     in (p <= 1, "p must be less than or equal to 1")
203 {
204     import mir.math.constant: PI;
205     import std.math.trigonometry: tan;
206 
207     if (p > 0 && p < 1) {
208         return tan(T(PI) * (p - 0.5));
209     } else if (p == 0) {
210         return -T.infinity;
211     } else if (p == 1) {
212         return T.infinity;
213     }
214     assert(0, "Should not be here");
215 }
216 
217 /++
218 Ditto, with location and scale parameters.
219 
220 Params:
221     p = value to evaluate InvCDF
222     location = location parameter
223     scale = scale parameter
224 +/
225 @safe pure nothrow @nogc
226 T cauchyInvCDF(T)(const T p, const T location, const T scale)
227     if (isFloatingPoint!T)
228     in (p >= 0, "p must be greater than or equal to 0")
229     in (p <= 1, "p must be less than or equal to 1")
230     in (scale > 0, "scale must be greater than zero")
231 {
232      return location + scale * cauchyInvCDF(p);
233 }
234 
235 ///
236 version(mir_stat_test)
237 @safe pure nothrow @nogc
238 unittest {
239     import mir.test: shouldApprox;
240 
241     cauchyInvCDF(0.0).shouldApprox == -double.infinity;
242     cauchyInvCDF(0.1).shouldApprox == -3.077684;
243     cauchyInvCDF(0.2).shouldApprox == -1.376382;
244     cauchyInvCDF(0.3).shouldApprox == -0.7265425;
245     cauchyInvCDF(0.4).shouldApprox == -0.3249197;
246     cauchyInvCDF(0.5).shouldApprox == 0.0;
247     cauchyInvCDF(0.6).shouldApprox == 0.3249197;
248     cauchyInvCDF(0.7).shouldApprox == 0.7265425;
249     cauchyInvCDF(0.8).shouldApprox == 1.376382;
250     cauchyInvCDF(0.9).shouldApprox == 3.077684;
251     cauchyInvCDF(1.0).shouldApprox == double.infinity;
252 
253     // Can include location/scale
254     cauchyInvCDF(0.2, 1, 2).shouldApprox == -1.752764;
255     cauchyInvCDF(0.4, 1, 2).shouldApprox == 0.3501606;
256     cauchyInvCDF(0.6, 1, 2).shouldApprox == 1.649839;
257     cauchyInvCDF(0.8, 1, 2).shouldApprox == 3.752764;
258 }
259 
260 /++
261 Computes the Cauchy log probability density function (LPDF).
262 
263 Params:
264     x = value to evaluate LPDF
265 
266 See_also:
267     $(LINK2 https://en.wikipedia.org/wiki/Cauchy_distribution, Cauchy Distribution)
268 +/
269 @safe pure nothrow @nogc
270 T cauchyLPDF(T)(const T x)
271     if (isFloatingPoint!T)
272 {
273     import mir.math.common: log;
274     import mir.stat.constant: LOGPI;
275 
276     return -T(LOGPI) - log(1 + x * x);
277 }
278 
279 /++
280 Ditto, with location and scale parameters (by standardizing `x`).
281 
282 Params:
283     x = value to evaluate LPDF
284     location = location parameter
285     scale = scale parameter
286 +/
287 @safe pure nothrow @nogc
288 T cauchyLPDF(T)(const T x, const T location, const T scale)
289     if (isFloatingPoint!T)
290     in (scale > 0, "scale must be greater than zero")
291 {
292     import mir.math.common: log;
293 
294     return cauchyLPDF((x - location) / scale) - log(scale);
295 }
296 
297 ///
298 version(mir_stat_test)
299 @safe pure nothrow @nogc
300 unittest {
301     import mir.math.common: log;
302     import mir.test: shouldApprox;
303 
304     cauchyLPDF(-3.0).shouldApprox == log(0.03183099);
305     cauchyLPDF(-2.0).shouldApprox == log(0.06366198);
306     cauchyLPDF(-1.0).shouldApprox == log(0.1591549);
307     cauchyLPDF(0.0).shouldApprox == log(0.3183099);
308     cauchyLPDF(1.0).shouldApprox == log(0.1591549);
309     cauchyLPDF(2.0).shouldApprox == log(0.06366198);
310     cauchyLPDF(3.0).shouldApprox == log(0.03183099);
311 
312     // Can include location/scale
313     cauchyLPDF(-3.0, 1, 2).shouldApprox == log(0.03183099);
314     cauchyLPDF(-2.0, 1, 2).shouldApprox == log(0.04897075);
315     cauchyLPDF(-1.0, 1, 2).shouldApprox == log(0.07957747);
316     cauchyLPDF(0.0, 1, 2).shouldApprox == log(0.127324);
317     cauchyLPDF(1.0, 1, 2).shouldApprox == log(0.1591549);
318     cauchyLPDF(2.0, 1, 2).shouldApprox == log(0.127324);
319     cauchyLPDF(3.0, 1, 2).shouldApprox == log(0.07957747);
320 }