diff --git a/src/Numerics/Distributions/TruncatedNormal.cs b/src/Numerics/Distributions/TruncatedNormal.cs new file mode 100644 index 00000000..72c8d271 --- /dev/null +++ b/src/Numerics/Distributions/TruncatedNormal.cs @@ -0,0 +1,246 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2013 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. +// + +using System; +using System.Collections.Generic; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +namespace MathNet.Numerics.Distributions { + + /// + /// Truncated Normal Distribution. + /// For more details about this distribution, see + /// Wikipedia - Truncated normal distribution + /// + public class TruncatedNormal : IContinuousDistribution { + + System.Random _random; + + readonly double _mean; + readonly double _stdDev; + readonly double _lowerBound; + readonly double _upperBound; + readonly Normal _uncorrectedNormal; + /// + /// The total density of the uncorrected normal distribution which is within the lower and upper bounds. + /// + readonly double _cumulativeDensityWithinBounds; + + /// + /// Initializes a new instance of the TruncatedNormal class with a particular mean, standard deviation, lower bound, and upper bound. The distribution will + /// be initialized with the default random number generator. The mean and standard deviation are that of the untruncated + /// normal distribution. + /// + /// The mean (μ) of the untruncated distribution. + /// The standard deviation (σ) of the untruncated distribution. Range: σ ≥ 0. + /// The inclusive lower bound of the truncated distribution. Default is double.NegativeInfinity. + /// The inclusive upper bound of the truncated distribution. Must be larger than . + /// Default is double.PositiveInfinity. + public TruncatedNormal(double mean, double stddev, double lowerBound = double.NegativeInfinity, double upperBound = double.PositiveInfinity) + :this(mean, stddev, SystemRandomSource.Default, lowerBound, upperBound) + { + + } + + /// + /// Initializes a new instance of the Normal class with a particular mean and standard deviation. The distribution will + /// be initialized with the default random number generator. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// The random number generator which is used to draw random samples. + public TruncatedNormal(double mean, double stddev, System.Random randomSource, double lowerBound = double.NegativeInfinity, double upperBound = double.PositiveInfinity) + { + if (!IsValidParameterSet(mean, stddev, lowerBound, upperBound)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _mean = mean; + _stdDev = stddev; + _lowerBound = lowerBound; + _upperBound = upperBound; + _uncorrectedNormal = Normal.WithMeanStdDev(_mean, _stdDev); + _cumulativeDensityWithinBounds = _uncorrectedNormal.CumulativeDistribution(_upperBound) - _uncorrectedNormal.CumulativeDistribution(_lowerBound); + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + public static bool IsValidParameterSet(double mean, double stddev, double lowerBound, double upperBound) + { + bool normalRequirements = Normal.IsValidParameterSet(mean, stddev); + bool boundsAreOrdered = lowerBound < upperBound; + return normalRequirements && boundsAreOrdered; + } + + public override string ToString() { + return "TruncatedNormal(μ = " + _mean + ", σ = " + _stdDev +", LowerBound = " + _lowerBound + ", UpperBound = " + _upperBound + ")"; + } + + /// + /// Gets the mode of the normal distribution. + /// + public double Mode + { + get + { + if (_mean < _lowerBound) + return _lowerBound; + if (_mean > _upperBound) + return _upperBound; + return _mean; + } + } + + /// + /// Gets the minimum of the truncated normal distribution. + /// + public double Minimum + { + get { return _lowerBound; } + } + + /// + /// Gets the maximum of the truncated normal distribution. + /// + public double Maximum + { + get { return _upperBound; } + } + + public double Mean + { + get + { + var pdfDifference = _uncorrectedNormal.Density(_lowerBound) - _uncorrectedNormal.Density(_upperBound); + var diffFromUncorrected = pdfDifference * _stdDev / _cumulativeDensityWithinBounds; + return _mean + diffFromUncorrected; + } + } + + public double Variance { + get { + throw new NotImplementedException(); + } + } + + public double StdDev { + get { + throw new NotImplementedException(); + } + } + + public double Entropy { + get { + throw new NotImplementedException(); + } + } + + public double Skewness { + get { + throw new NotImplementedException(); + } + } + + public double Median { + get { + throw new NotImplementedException(); + } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + if (x < _lowerBound || _upperBound < x) + return 0d; + + return _uncorrectedNormal.Density(x) / (_stdDev * _cumulativeDensityWithinBounds); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return Math.Log(Density(x)); + } + + public double Sample() { + throw new NotImplementedException(); + } + + public void Samples(double[] values) { + throw new NotImplementedException(); + } + + public IEnumerable Samples() { + throw new NotImplementedException(); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + if (x < _lowerBound) + return 0d; + if (x > _upperBound) + return 1d; + + double cumulative = _uncorrectedNormal.CumulativeDistribution(x) - _uncorrectedNormal.CumulativeDistribution(_lowerBound); + return cumulative / _cumulativeDensityWithinBounds; + } + } +} diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 9bfd671b..564c25ed 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -90,6 +90,7 @@ +