The OpenD Programming Language

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