001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.statistics.distribution; 018 019import org.apache.commons.numbers.gamma.Gamma; 020import org.apache.commons.numbers.gamma.GammaRatio; 021import org.apache.commons.numbers.gamma.LogGamma; 022import org.apache.commons.numbers.gamma.RegularizedGamma; 023import org.apache.commons.rng.UniformRandomProvider; 024import org.apache.commons.rng.sampling.distribution.AhrensDieterMarsagliaTsangGammaSampler; 025import org.apache.commons.rng.sampling.distribution.SharedStateContinuousSampler; 026 027/** 028 * Implementation of the Nakagami distribution. 029 * 030 * <p>The probability density function of \( X \) is: 031 * 032 * <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) \] 033 * 034 * <p>for \( \mu > 0 \) the shape, 035 * \( \Omega > 0 \) the scale, and 036 * \( x \in (0, \infty) \). 037 * 038 * @see <a href="https://en.wikipedia.org/wiki/Nakagami_distribution">Nakagami distribution (Wikipedia)</a> 039 */ 040public final class NakagamiDistribution extends AbstractContinuousDistribution { 041 /** Support lower bound. */ 042 private static final double SUPPORT_LO = 0; 043 /** Support upper bound. */ 044 private static final double SUPPORT_HI = Double.POSITIVE_INFINITY; 045 046 /** The shape parameter. */ 047 private final double mu; 048 /** The scale parameter. */ 049 private final double omega; 050 /** Density prefactor. */ 051 private final double densityPrefactor; 052 /** Log density prefactor. */ 053 private final double logDensityPrefactor; 054 /** Cached value for inverse probability function. */ 055 private final double mean; 056 /** Cached value for inverse probability function. */ 057 private final double variance; 058 059 /** 060 * @param mu Shape parameter (must be positive). 061 * @param omega Scale parameter (must be positive). Controls the spread of the distribution. 062 */ 063 private NakagamiDistribution(double mu, 064 double omega) { 065 this.mu = mu; 066 this.omega = omega; 067 densityPrefactor = 2.0 * Math.pow(mu, mu) / (Gamma.value(mu) * Math.pow(omega, mu)); 068 logDensityPrefactor = Constants.LN_TWO + Math.log(mu) * mu - LogGamma.value(mu) - Math.log(omega) * mu; 069 final double v = GammaRatio.delta(mu, 0.5); 070 mean = Math.sqrt(omega / mu) / v; 071 variance = omega - (omega / mu) / v / v; 072 } 073 074 /** 075 * Creates a Nakagami distribution. 076 * 077 * @param mu Shape parameter (must be positive). 078 * @param omega Scale parameter (must be positive). Controls the spread of the distribution. 079 * @return the distribution 080 * @throws IllegalArgumentException if {@code mu <= 0} or if 081 * {@code omega <= 0}. 082 */ 083 public static NakagamiDistribution of(double mu, 084 double omega) { 085 if (mu <= 0) { 086 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, mu); 087 } 088 if (omega <= 0) { 089 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, omega); 090 } 091 return new NakagamiDistribution(mu, omega); 092 } 093 094 /** 095 * Gets the shape parameter of this distribution. 096 * 097 * @return the shape parameter. 098 */ 099 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}