// // Math.NET Numerics, part of the Math.NET Project // http://mathnet.opensourcedotnet.info // // Copyright (c) 2009 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // namespace MathNet.Numerics.Distributions { using System; using System.Collections.Generic; using Properties; /// /// Implements the univariate Gamma distribution. For details about this distribution, see /// Wikipedia - Gamma distribution. /// /// /// The Gamma distribution is parametrized by a shape and inverse scale parameter. When we want /// to specify a Gamma distribution which is a point distribution we set the shape parameter to be the /// location of the point distribution and the inverse scale as positive infinity. The distribution /// with shape and inverse scale both zero is undefined. /// Random number generation for the Gamma distribution is based on the algorithm in: /// "A Simple Method for Generating Gamma Variables" - Marsaglia & Tsang /// ACM Transactions on Mathematical Software, Vol. 26, No. 3, September 2000, Pages 363–372. /// The distribution will use the by default. /// Users can get/set the random number generator by using the property. /// The statistics classes will check all the incoming parameters whether they are in the allowed /// range. This might involve heavy computation. Optionally, by setting Control.CheckDistributionParameters /// to false, all parameter checks can be turned off. public class Gamma : IContinuousDistribution { /// /// Gamma shape parameter. /// private double _shape; /// /// Gamma inverse scale parameter. /// private double _invScale; /// /// The distribution's random number generator. /// private Random _random; /// /// Initializes a new instance of the Gamma class. /// /// The shape of the Gamma distribution. /// The inverse scale of the Gamma distribution. public Gamma(double shape, double invScale) { SetParameters(shape, invScale); RandomSource = new Random(); } /// /// Constructs a Gamma distribution from a shape and scale parameter. The distribution will /// be initialized with the default random number generator. /// /// The shape of the Gamma distribution. /// The scale of the Gamma distribution. /// a normal distribution. public static Gamma WithShapeScale(double shape, double scale) { return new Gamma(shape, 1.0/scale); } /// /// Constructs a Gamma distribution from a shape and inverse scale parameter. The distribution will /// be initialized with the default random number generator. /// /// The shape of the Gamma distribution. /// The inverse scale of the Gamma distribution. /// a normal distribution. public static Gamma WithShapeInvScale(double shape, double invScale) { return new Gamma(shape, invScale); } /// /// A string representation of the distribution. /// /// a string representation of the distribution. public override string ToString() { return "Gamma(Shape = " + _shape + ", Inverse Scale = " + _invScale + ")"; } /// /// Checks whether the parameters of the distribution are valid. /// /// The shape of the Gamma distribution. /// The inverse scale of the Gamma distribution. /// True when the parameters are valid, false otherwise. private static bool IsValidParameterSet(double shape, double invScale) { if (shape < 0.0 || invScale < 0.0 || Double.IsNaN(shape) || Double.IsNaN(invScale)) { return false; } return true; } /// /// Sets the parameters of the distribution after checking their validity. /// /// The shape of the Gamma distribution. /// The inverse scale of the Gamma distribution. /// When the parameters don't pass the function. private void SetParameters(double shape, double invScale) { if (Control.CheckDistributionParameters && !IsValidParameterSet(shape, invScale)) { throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); } _shape = shape; _invScale = invScale; } /// /// Gets or sets the shape of the Gamma distribution. /// public double Shape { get { return _shape; } set { SetParameters(value, _invScale); } } /// /// Gets or sets the scale of the Gamma distribution. /// public double Scale { get { return 1.0 / _invScale; } set { double invScale = 1.0/value; if(Double.IsNegativeInfinity(invScale)) { invScale = - invScale; } SetParameters(_shape, invScale); } } /// /// Gets or sets the inverse scale of the Gamma distribution. /// public double InvScale { get { return _invScale; } set { SetParameters(_shape, value); } } #region IDistribution implementation /// /// Gets or sets the random number generator which is used to draw random samples. /// public Random RandomSource { get { return _random; } set { if (value == null) { throw new ArgumentNullException(); } _random = value; } } /// /// Gets the mean of the Gamma distribution. /// public double Mean { get { if (Double.IsPositiveInfinity(_invScale)) { return _shape; } else if(_invScale == 0.0 && _shape == 0.0) { return Double.NaN; } else { return _shape / _invScale; } } } /// /// Gets the variance of the Gamma distribution. /// public double Variance { get { if (Double.IsPositiveInfinity(_invScale)) { return 0.0; } else if (_invScale == 0.0 && _shape == 0.0) { return Double.NaN; } else { return _shape / (_invScale * _invScale); } } } /// /// Gets the standard deviation of the Gamma distribution. /// public double StdDev { get { if (Double.IsPositiveInfinity(_invScale)) { return 0.0; } else if (_invScale == 0.0 && _shape == 0.0) { return Double.NaN; } else { return Math.Sqrt(_shape / (_invScale * _invScale)); } } } /// /// Gets the entropy of the Gamma distribution. /// public double Entropy { get { if (Double.IsPositiveInfinity(_invScale)) { return 0.0; } else if (_invScale == 0.0 && _shape == 0.0) { return Double.NaN; } else { return _shape - Math.Log(_invScale) + SpecialFunctions.GammaLn(_shape) + ((1.0 - _shape) * SpecialFunctions.DiGamma(_shape)); } } } /// /// Gets the skewness of the Gamma distribution. /// public double Skewness { get { if (Double.IsPositiveInfinity(_invScale)) { return 0.0; } else if (_invScale == 0.0 && _shape == 0.0) { return Double.NaN; } else { return 2.0 / Math.Sqrt(_shape); } } } #endregion #region IContinuousDistribution implementation /// /// Gets the mode of the Gamma distribution. /// public double Mode { get { if (Double.IsPositiveInfinity(_invScale)) { return _shape; } else if (_invScale == 0.0 && _shape == 0.0) { return Double.NaN; } else { return (_shape - 1.0) / _invScale; } } } /// /// Gets the median of the Gamma distribution. /// public double Median { get { throw new NotSupportedException(); } } /// /// Gets the minimum of the Gamma distribution. /// public double Minimum { get { return 0.0; } } /// /// Gets the maximum of the Gamma distribution. /// public double Maximum { get { return Double.PositiveInfinity; } } /// /// Computes the density of the Gamma distribution. /// /// The location at which to compute the density. /// the density at . public double Density(double x) { if (Double.IsPositiveInfinity(_invScale)) { if (x == _shape) { return Double.PositiveInfinity; } else { return 0.0; } } else if (_shape == 0.0 && _invScale == 0.0) { return 0.0; } else if (_shape == 1.0) { return _invScale * Math.Exp(- _invScale * x); } else { return Math.Pow(_invScale, _shape) * Math.Pow(x, _shape - 1.0) * Math.Exp(-_invScale * x) / SpecialFunctions.Gamma(_shape); } } /// /// Computes the log density of the Gamma distribution. /// /// The location at which to compute the log density. /// the log density at . public double DensityLn(double x) { if (Double.IsPositiveInfinity(_invScale)) { if (x == _shape) { return Double.PositiveInfinity; } else { return Double.NegativeInfinity; } } else if(_shape == 0.0 && _invScale == 0.0) { return Double.NegativeInfinity; } else if(_shape == 1.0) { return Math.Log(_invScale) - (_invScale * x); } else { return (_shape * Math.Log(_invScale)) + ((_shape - 1.0) * Math.Log(x)) - (_invScale * x) - SpecialFunctions.GammaLn(_shape); } } /// /// Computes the cumulative distribution function of the Gamma distribution. /// /// The location at which to compute the cumulative density. /// the cumulative density at . public double CumulativeDistribution(double x) { if (Double.IsPositiveInfinity(_invScale)) { if (x >= _shape) { return 1.0; } else { return 0.0; } } else { return SpecialFunctions.IncompleteGamma(_shape, x * _invScale, true); } } /// /// Generates a sample from the Gamma distribution. /// /// a sample from the distribution. public double Sample() { return SampleGamma(RandomSource, _shape, _invScale); } /// /// Generates a sequence of samples from the Gamma distribution. /// /// a sequence of samples from the distribution. public IEnumerable Samples() { while (true) { yield return SampleGamma(RandomSource, _shape, _invScale); } } #endregion /// /// Generates a sample from the Gamma distribution. /// /// The random number generator to use. /// The shape of the Gamma distribution from which to generate samples. /// The inverse scale of the Gamma distribution from which to generate samples. /// a sample from the distribution. public static double Sample(Random rng, double shape, double invScale) { if (Control.CheckDistributionParameters && !IsValidParameterSet(shape, invScale)) { throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); } return SampleGamma(rng, shape, invScale); } /// /// Generates a sequence of samples from the Gamma distribution. /// /// The random number generator to use. /// The shape of the Gamma distribution from which to generate samples. /// The inverse scale of the Gamma distribution from which to generate samples. /// a sequence of samples from the distribution. public static IEnumerable Samples(Random rng, double shape, double invScale) { if (Control.CheckDistributionParameters && !IsValidParameterSet(shape, invScale)) { throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); } while (true) { yield return SampleGamma(rng, shape, invScale); } } /// /// Sampling implementation based on: /// "A Simple Method for Generating Gamma Variables" - Marsaglia & Tsang /// ACM Transactions on Mathematical Software, Vol. 26, No. 3, September 2000, Pages 363–372. /// /// The random number generator to use. /// The shape of the Gamma distribution. /// The inverse scale of the Gamma distribution. /// A sample from a Gamma distributed random variable. internal static double SampleGamma(System.Random rnd, double shape, double invScale) { if (Double.IsPositiveInfinity(invScale)) { return shape; } else { double a = shape; double alphafix = 1.0; // Fix when alpha is less than one. if (shape < 1.0) { a = shape + 1.0; alphafix = System.Math.Pow(rnd.NextDouble(), 1.0 / shape); } double d = a - (1.0 / 3.0); double c = 1.0 / System.Math.Sqrt(9.0 * d); while (true) { double x = Normal.Sample(rnd, 0.0, 1.0); double v = 1.0 + (c * x); while (v <= 0.0) { x = Normal.Sample(rnd, 0.0, 1.0); v = 1.0 + (c * x); } v = v * v * v; double u = rnd.NextDouble(); x = x * x; if (u < 1.0 - (0.0331 * x * x)) { return alphafix * d * v / invScale; } if (System.Math.Log(u) < (0.5 * x) + (d * (1.0 - v + System.Math.Log(v)))) { return alphafix * d * v / invScale; } } } } } }