View Javadoc
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 &gt; 0 \) the shape,
35   * \( \Omega &gt; 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 }