1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.statistics.distribution;
18
19 import org.apache.commons.numbers.gamma.Gamma;
20 import org.apache.commons.numbers.gamma.GammaRatio;
21 import org.apache.commons.numbers.gamma.LogGamma;
22 import org.apache.commons.numbers.gamma.RegularizedGamma;
23 import org.apache.commons.rng.UniformRandomProvider;
24 import org.apache.commons.rng.sampling.distribution.AhrensDieterMarsagliaTsangGammaSampler;
25 import org.apache.commons.rng.sampling.distribution.SharedStateContinuousSampler;
26
27 /**
28 * Implementation of the Nakagami distribution.
29 *
30 * <p>The probability density function of \( X \) is:
31 *
32 * <p>\[ f(x; \mu, \Omega) = \frac{2\mu^\mu}{\Gamma(\mu)\Omega^\mu}x^{2\mu-1}\exp\left(-\frac{\mu}{\Omega}x^2\right) \]
33 *
34 * <p>for \( \mu > 0 \) the shape,
35 * \( \Omega > 0 \) the scale, and
36 * \( x \in (0, \infty) \).
37 *
38 * @see <a href="https://en.wikipedia.org/wiki/Nakagami_distribution">Nakagami distribution (Wikipedia)</a>
39 */
40 public final class NakagamiDistribution extends AbstractContinuousDistribution {
41 /** Support lower bound. */
42 private static final double SUPPORT_LO = 0;
43 /** Support upper bound. */
44 private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;
45
46 /** The shape parameter. */
47 private final double mu;
48 /** The scale parameter. */
49 private final double omega;
50 /** Density prefactor. */
51 private final double densityPrefactor;
52 /** Log density prefactor. */
53 private final double logDensityPrefactor;
54 /** Cached value for inverse probability function. */
55 private final double mean;
56 /** Cached value for inverse probability function. */
57 private final double variance;
58
59 /**
60 * @param mu Shape parameter (must be positive).
61 * @param omega Scale parameter (must be positive). Controls the spread of the distribution.
62 */
63 private NakagamiDistribution(double mu,
64 double omega) {
65 this.mu = mu;
66 this.omega = omega;
67 densityPrefactor = 2.0 * Math.pow(mu, mu) / (Gamma.value(mu) * Math.pow(omega, mu));
68 logDensityPrefactor = Constants.LN_TWO + Math.log(mu) * mu - LogGamma.value(mu) - Math.log(omega) * mu;
69 final double v = GammaRatio.delta(mu, 0.5);
70 mean = Math.sqrt(omega / mu) / v;
71 variance = omega - (omega / mu) / v / v;
72 }
73
74 /**
75 * Creates a Nakagami distribution.
76 *
77 * @param mu Shape parameter (must be positive).
78 * @param omega Scale parameter (must be positive). Controls the spread of the distribution.
79 * @return the distribution
80 * @throws IllegalArgumentException if {@code mu <= 0} or if
81 * {@code omega <= 0}.
82 */
83 public static NakagamiDistribution of(double mu,
84 double omega) {
85 if (mu <= 0) {
86 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, mu);
87 }
88 if (omega <= 0) {
89 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, omega);
90 }
91 return new NakagamiDistribution(mu, omega);
92 }
93
94 /**
95 * Gets the shape parameter of this distribution.
96 *
97 * @return the shape parameter.
98 */
99 public double getShape() {
100 return mu;
101 }
102
103 /**
104 * Gets the scale parameter of this distribution.
105 *
106 * @return the scale parameter.
107 */
108 public double getScale() {
109 return omega;
110 }
111
112 /** {@inheritDoc} */
113 @Override
114 public double density(double x) {
115 if (x <= SUPPORT_LO ||
116 x >= SUPPORT_HI) {
117 return 0;
118 }
119
120 return densityPrefactor * Math.pow(x, 2 * mu - 1) * Math.exp(-mu * x * x / omega);
121 }
122
123 /** {@inheritDoc} */
124 @Override
125 public double logDensity(double x) {
126 if (x <= SUPPORT_LO ||
127 x >= SUPPORT_HI) {
128 return Double.NEGATIVE_INFINITY;
129 }
130
131 return logDensityPrefactor + Math.log(x) * (2 * mu - 1) - (mu * x * x / omega);
132 }
133
134 /** {@inheritDoc} */
135 @Override
136 public double cumulativeProbability(double x) {
137 if (x <= SUPPORT_LO) {
138 return 0;
139 } else if (x >= SUPPORT_HI) {
140 return 1;
141 }
142
143 return RegularizedGamma.P.value(mu, mu * x * x / omega);
144 }
145
146 /** {@inheritDoc} */
147 @Override
148 public double survivalProbability(double x) {
149 if (x <= SUPPORT_LO) {
150 return 1;
151 } else if (x >= SUPPORT_HI) {
152 return 0;
153 }
154
155 return RegularizedGamma.Q.value(mu, mu * x * x / omega);
156 }
157
158 /**
159 * {@inheritDoc}
160 *
161 * <p>For shape parameter \( \mu \) and scale parameter \( \Omega \), the mean is:
162 *
163 * <p>\[ \frac{\Gamma(m+\frac{1}{2})}{\Gamma(m)}\left(\frac{\Omega}{m}\right)^{1/2} \]
164 */
165 @Override
166 public double getMean() {
167 return mean;
168 }
169
170 /**
171 * {@inheritDoc}
172 *
173 * <p>For shape parameter \( \mu \) and scale parameter \( \Omega \), the variance is:
174 *
175 * <p>\[ \Omega\left(1-\frac{1}{m}\left(\frac{\Gamma(m+\frac{1}{2})}{\Gamma(m)}\right)^2\right) \]
176 */
177 @Override
178 public double getVariance() {
179 return variance;
180 }
181
182 /**
183 * {@inheritDoc}
184 *
185 * <p>The lower bound of the support is always 0.
186 *
187 * @return 0.
188 */
189 @Override
190 public double getSupportLowerBound() {
191 return SUPPORT_LO;
192 }
193
194 /**
195 * {@inheritDoc}
196 *
197 * <p>The upper bound of the support is always positive infinity.
198 *
199 * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
200 */
201 @Override
202 public double getSupportUpperBound() {
203 return SUPPORT_HI;
204 }
205
206 @Override
207 public Sampler createSampler(UniformRandomProvider rng) {
208 // Generate using a related Gamma distribution
209 // See https://en.wikipedia.org/wiki/Nakagami_distribution#Generation
210 final double shape = mu;
211 final double scale = omega / mu;
212 final SharedStateContinuousSampler sampler =
213 AhrensDieterMarsagliaTsangGammaSampler.of(rng, shape, scale);
214 return () -> Math.sqrt(sampler.sample());
215 }
216 }