NakagamiDistribution.java

  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. import org.apache.commons.numbers.gamma.Gamma;
  19. import org.apache.commons.numbers.gamma.GammaRatio;
  20. import org.apache.commons.numbers.gamma.LogGamma;
  21. import org.apache.commons.numbers.gamma.RegularizedGamma;
  22. import org.apache.commons.rng.UniformRandomProvider;
  23. import org.apache.commons.rng.sampling.distribution.AhrensDieterMarsagliaTsangGammaSampler;
  24. import org.apache.commons.rng.sampling.distribution.SharedStateContinuousSampler;

  25. /**
  26.  * Implementation of the Nakagami distribution.
  27.  *
  28.  * <p>The probability density function of \( X \) is:
  29.  *
  30.  * <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) \]
  31.  *
  32.  * <p>for \( \mu &gt; 0 \) the shape,
  33.  * \( \Omega &gt; 0 \) the scale, and
  34.  * \( x \in (0, \infty) \).
  35.  *
  36.  * @see <a href="https://en.wikipedia.org/wiki/Nakagami_distribution">Nakagami distribution (Wikipedia)</a>
  37.  */
  38. public final class NakagamiDistribution extends AbstractContinuousDistribution {
  39.     /** Support lower bound. */
  40.     private static final double SUPPORT_LO = 0;
  41.     /** Support upper bound. */
  42.     private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;

  43.     /** The shape parameter. */
  44.     private final double mu;
  45.     /** The scale parameter. */
  46.     private final double omega;
  47.     /** Density prefactor. */
  48.     private final double densityPrefactor;
  49.     /** Log density prefactor. */
  50.     private final double logDensityPrefactor;
  51.     /** Cached value for inverse probability function. */
  52.     private final double mean;
  53.     /** Cached value for inverse probability function. */
  54.     private final double variance;

  55.     /**
  56.      * @param mu Shape parameter (must be positive).
  57.      * @param omega Scale parameter (must be positive). Controls the spread of the distribution.
  58.      */
  59.     private NakagamiDistribution(double mu,
  60.                                  double omega) {
  61.         this.mu = mu;
  62.         this.omega = omega;
  63.         densityPrefactor = 2.0 * Math.pow(mu, mu) / (Gamma.value(mu) * Math.pow(omega, mu));
  64.         logDensityPrefactor = Constants.LN_TWO + Math.log(mu) * mu - LogGamma.value(mu) - Math.log(omega) * mu;
  65.         final double v = GammaRatio.delta(mu, 0.5);
  66.         mean = Math.sqrt(omega / mu) / v;
  67.         variance = omega - (omega / mu) / v / v;
  68.     }

  69.     /**
  70.      * Creates a Nakagami distribution.
  71.      *
  72.      * @param mu Shape parameter (must be positive).
  73.      * @param omega Scale parameter (must be positive). Controls the spread of the distribution.
  74.      * @return the distribution
  75.      * @throws IllegalArgumentException  if {@code mu <= 0} or if
  76.      * {@code omega <= 0}.
  77.      */
  78.     public static NakagamiDistribution of(double mu,
  79.                                           double omega) {
  80.         if (mu <= 0) {
  81.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, mu);
  82.         }
  83.         if (omega <= 0) {
  84.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, omega);
  85.         }
  86.         return new NakagamiDistribution(mu, omega);
  87.     }

  88.     /**
  89.      * Gets the shape parameter of this distribution.
  90.      *
  91.      * @return the shape parameter.
  92.      */
  93.     public double getShape() {
  94.         return mu;
  95.     }

  96.     /**
  97.      * Gets the scale parameter of this distribution.
  98.      *
  99.      * @return the scale parameter.
  100.      */
  101.     public double getScale() {
  102.         return omega;
  103.     }

  104.     /** {@inheritDoc} */
  105.     @Override
  106.     public double density(double x) {
  107.         if (x <= SUPPORT_LO ||
  108.             x >= SUPPORT_HI) {
  109.             return 0;
  110.         }

  111.         return densityPrefactor * Math.pow(x, 2 * mu - 1) * Math.exp(-mu * x * x / omega);
  112.     }

  113.     /** {@inheritDoc} */
  114.     @Override
  115.     public double logDensity(double x) {
  116.         if (x <= SUPPORT_LO ||
  117.             x >= SUPPORT_HI) {
  118.             return Double.NEGATIVE_INFINITY;
  119.         }

  120.         return logDensityPrefactor + Math.log(x) * (2 * mu - 1) - (mu * x * x / omega);
  121.     }

  122.     /** {@inheritDoc} */
  123.     @Override
  124.     public double cumulativeProbability(double x) {
  125.         if (x <= SUPPORT_LO) {
  126.             return 0;
  127.         } else if (x >= SUPPORT_HI) {
  128.             return 1;
  129.         }

  130.         return RegularizedGamma.P.value(mu, mu * x * x / omega);
  131.     }

  132.     /** {@inheritDoc} */
  133.     @Override
  134.     public double survivalProbability(double x) {
  135.         if (x <= SUPPORT_LO) {
  136.             return 1;
  137.         } else if (x >= SUPPORT_HI) {
  138.             return 0;
  139.         }

  140.         return RegularizedGamma.Q.value(mu, mu * x * x / omega);
  141.     }

  142.     /**
  143.      * {@inheritDoc}
  144.      *
  145.      * <p>For shape parameter \( \mu \) and scale parameter \( \Omega \), the mean is:
  146.      *
  147.      * <p>\[ \frac{\Gamma(m+\frac{1}{2})}{\Gamma(m)}\left(\frac{\Omega}{m}\right)^{1/2} \]
  148.      */
  149.     @Override
  150.     public double getMean() {
  151.         return mean;
  152.     }

  153.     /**
  154.      * {@inheritDoc}
  155.      *
  156.      * <p>For shape parameter \( \mu \) and scale parameter \( \Omega \), the variance is:
  157.      *
  158.      * <p>\[ \Omega\left(1-\frac{1}{m}\left(\frac{\Gamma(m+\frac{1}{2})}{\Gamma(m)}\right)^2\right) \]
  159.      */
  160.     @Override
  161.     public double getVariance() {
  162.         return variance;
  163.     }

  164.     /**
  165.      * {@inheritDoc}
  166.      *
  167.      * <p>The lower bound of the support is always 0.
  168.      *
  169.      * @return 0.
  170.      */
  171.     @Override
  172.     public double getSupportLowerBound() {
  173.         return SUPPORT_LO;
  174.     }

  175.     /**
  176.      * {@inheritDoc}
  177.      *
  178.      * <p>The upper bound of the support is always positive infinity.
  179.      *
  180.      * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
  181.      */
  182.     @Override
  183.     public double getSupportUpperBound() {
  184.         return SUPPORT_HI;
  185.     }

  186.     @Override
  187.     public Sampler createSampler(UniformRandomProvider rng) {
  188.         // Generate using a related Gamma distribution
  189.         // See https://en.wikipedia.org/wiki/Nakagami_distribution#Generation
  190.         final double shape = mu;
  191.         final double scale = omega / mu;
  192.         final SharedStateContinuousSampler sampler =
  193.             AhrensDieterMarsagliaTsangGammaSampler.of(rng, shape, scale);
  194.         return () -> Math.sqrt(sampler.sample());
  195.     }
  196. }