diff --git a/src/Numerics/Distributions/Discrete/Bernoulli.cs b/src/Numerics/Distributions/Discrete/Bernoulli.cs index 43828368..d60addf1 100644 --- a/src/Numerics/Distributions/Discrete/Bernoulli.cs +++ b/src/Numerics/Distributions/Discrete/Bernoulli.cs @@ -306,7 +306,7 @@ namespace MathNet.Numerics.Distributions } /// - /// Samples an array of Bernoulli distributed random variables. + /// Samples a sequence of Bernoulli distributed random variables. /// /// The random number generator to use. /// The probability of generating a 1. diff --git a/src/Numerics/Distributions/Discrete/DiscreteUniform.cs b/src/Numerics/Distributions/Discrete/DiscreteUniform.cs new file mode 100644 index 00000000..36ad4eb7 --- /dev/null +++ b/src/Numerics/Distributions/Discrete/DiscreteUniform.cs @@ -0,0 +1,354 @@ +// +// 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; + + /// + /// The discrete uniform distribution is a distribution over integers. The distribution + /// is parameterized by a lower and upper bound (both inclusive). + /// + /// The distribution will use the by default. + /// Users can 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 DiscreteUniform : IDiscreteDistribution + { + /// + /// The distribution's lower bound. + /// + private int _lower; + + /// + /// The distribution's upper bound. + /// + private int _upper; + + /// + /// The distribution's random number generator. + /// + private Random _random; + + /// + /// Initializes a new instance of the DiscreteUniform class. + /// + /// Lower bound. + /// Upper bound; must be at least as large as . + public DiscreteUniform(int lower, int upper) + { + SetParameters(lower, upper); + RandomSource = new System.Random(); + } + + /// + /// A string representation of the distribution. + /// + public override string ToString() + { + return "DiscreteUniform(Lower = " + _lower + ", Upper = " + _upper + ")"; + } + + /// + /// Checks whether the parameters of the distribution are valid. + /// + /// Lower bound. + /// Upper bound; must be at least as large as . + /// True when the parameters are valid, false otherwise. + private static bool IsValidParameterSet(int lower, int upper) + { + if (lower <= upper) + { + return true; + } + + return false; + } + + /// + /// Sets the parameters of the distribution after checking their validity. + /// + /// Lower bound. + /// Upper bound; must be at least as large as . + /// When the parameters don't pass the function. + private void SetParameters(int lower, int upper) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(lower, upper)) + { + throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + } + + _lower = lower; + _upper = upper; + } + + /// + /// Gets or sets the lower bound of the probability distribution. + /// + public int LowerBound + { + get + { + return _lower; + } + + set + { + SetParameters(value, _upper); + } + } + + /// + /// Gets or sets the upper bound of the probability distribution. + /// + public int UpperBound + { + get + { + return _upper; + } + + set + { + SetParameters(_lower, value); + } + } + + #region IDistribution Members + + /// + /// 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 distribution. + /// + public double Mean + { + get { return (_lower + _upper) / 2.0; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return System.Math.Sqrt(((_upper - _lower + 1.0) * (_upper - _lower + 1.0) - 1.0) / 12.0); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return ((_upper - _lower + 1.0) * (_upper - _lower + 1.0) - 1.0) / 12.0; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return System.Math.Log(_upper - _lower + 1.0); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return 0.0; } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum { get { return _lower; } } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum { get { return _upper; } } + + /// + /// Computes the cumulative distribution function of the Bernoulli distribution. + /// + /// The location at which to compute the cumulative density. + /// the cumulative density at . + public double CumulativeDistribution(double x) + { + if (x < _lower) + { + return 0.0; + } + else if (x >= _upper) + { + return 1.0; + } + + return Math.Min(1.0, (Math.Floor(x) - _lower + 1) / (_upper - _lower + 1)); + } + + #endregion + + #region IDiscreteDistribution Members + + /// + /// The mode of the distribution; since every element in the domain has the same probability this method returns the middle one. + /// + public int Mode + { + get { return (int) Math.Floor((_lower + _upper) / 2.0); } + } + + /// + /// The median of the distribution. + /// + public int Median + { + get { return (int)Math.Floor((_lower + _upper) / 2.0); } + } + + /// + /// Computes the probability of a specific value. + /// + public double Probability(int val) + { + if (val >= _lower && val <= _upper) + { + return 1.0 / (_upper - _lower + 1); + } + + return 0.0; + } + + /// + /// Computes the probability of a specific value. + /// + public double ProbabilityLn(int val) + { + if (val >= _lower && val <= _upper) + { + return - Math.Log(_upper - _lower + 1); + } + + return Double.NegativeInfinity; + } + + /// + /// Samples a uniformly distributed random variable. + /// + public int Sample() + { + return DoSample(RandomSource, _lower, _upper); + } + + /// + /// Samples an array of uniformly distributed random variables. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + while (true) + { + yield return DoSample(RandomSource, _lower, _upper); + } + } + + #endregion + + /// + /// Samples a uniformly distributed random variable. + /// + /// The random number generator to use. + /// The lower bound of the uniform random variable. + /// The upper bound of the uniform random variable. + /// A sample from the discrete uniform distribution. + public static int Sample(System.Random rnd, int lower, int upper) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(lower, upper)) + { + throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + } + + return DoSample(rnd, lower, upper); + } + + /// + /// Samples a sequence of uniformly distributed random variables. + /// + /// The random number generator to use. + /// The lower bound of the uniform random variable. + /// The upper bound of the uniform random variable. + /// a sequence of samples from the discrete uniform distribution. + public static IEnumerable Samples(System.Random rnd, int lower, int upper) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(lower, upper)) + { + throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + } + + while (true) + { + yield return DoSample(rnd, lower, upper); + } + } + + /// + /// Generates one sample from the discrete uniform distribution. This method does not do any parameter checking. + /// + /// The random source to use. + /// The lower bound of the uniform random variable. + /// The upper bound of the uniform random variable. + /// A random sample from the discrete uniform distribution. + private static int DoSample(System.Random rnd, int lower, int upper) + { + return rnd.Next() % (upper - lower + 1) + lower; + } + } +} \ No newline at end of file diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 068ff5fd..45eefabf 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -57,6 +57,7 @@ + diff --git a/src/UnitTests/DistributionTests/Discrete/DiscreteUniformTests.cs b/src/UnitTests/DistributionTests/Discrete/DiscreteUniformTests.cs new file mode 100644 index 00000000..888e90c9 --- /dev/null +++ b/src/UnitTests/DistributionTests/Discrete/DiscreteUniformTests.cs @@ -0,0 +1,261 @@ +// +// 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.UnitTests.DistributionTests +{ + using System; + using System.Linq; + using MbUnit.Framework; + using MathNet.Numerics.Distributions; + + [TestFixture] + public class DiscreteUniformTests + { + [SetUp] + public void SetUp() + { + Control.CheckDistributionParameters = true; + } + + [Test] + [Row(-10, 10)] + [Row(0, 4)] + [Row(10, 20)] + [Row(20, 20)] + public void CanCreateDiscreteUniform(int l, int u) + { + var du = new DiscreteUniform(l, u); + AssertEx.AreEqual(l, du.LowerBound); + AssertEx.AreEqual(u, du.UpperBound); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Row(-1, -2)] + [Row(6, 5)] + public void DiscreteUniformCreateFailsWithBadParameters(int l, int u) + { + var du = new DiscreteUniform(l, u); + } + + [Test] + public void ValidateToString() + { + var b = new DiscreteUniform(0, 10); + AssertEx.AreEqual("DiscreteUniform(Lower = 0, Upper = 10)", b.ToString()); + } + + [Test] + [Row(0)] + [Row(3)] + [Row(10)] + public void CanSetLowerBound(int p) + { + var b = new DiscreteUniform(0, 10); + b.LowerBound = p; + } + + [Test] + [Row(0)] + [Row(3)] + [Row(10)] + public void CanSetUpperBound(int p) + { + var b = new DiscreteUniform(0, 10); + b.UpperBound = p; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Row(11)] + [Row(20.0)] + public void SetLowerBoundFails(int p) + { + var b = new DiscreteUniform(0, 10); + b.LowerBound = p; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Row(-11)] + [Row(-20)] + public void SetUpperBoundFails(int p) + { + var b = new DiscreteUniform(0, 10); + b.UpperBound = p; + } + + [Test] + [Row(-10, 10, 3.0445224377234229965005979803657054342845752874046093)] + [Row(0, 4, 1.6094379124341003746007593332261876395256013542685181)] + [Row(10, 20, 2.3978952727983705440619435779651292998217068539374197)] + [Row(20, 20, 0.0)] + public void ValidateEntropy(int l, int u, double e) + { + var du = new DiscreteUniform(l, u); + AssertHelpers.AlmostEqual(e, du.Entropy, 14); + } + + [Test] + [Row(-10, 10)] + [Row(0, 4)] + [Row(10, 20)] + [Row(20, 20)] + public void ValidateSkewness(int l, int u) + { + var du = new DiscreteUniform(l, u); + AssertEx.AreEqual(0.0, du.Skewness); + } + + [Test] + [Row(-10, 10, 0)] + [Row(0, 4, 2)] + [Row(10, 20, 15)] + [Row(20, 20, 20)] + public void ValidateMode(int l, int u, int m) + { + var du = new DiscreteUniform(l, u); + AssertEx.AreEqual(m, du.Mode); + } + + [Test] + [Row(-10, 10, 0)] + [Row(0, 4, 2)] + [Row(10, 20, 15)] + [Row(20, 20, 20)] + public void ValidateMedian(int l, int u, int m) + { + var du = new DiscreteUniform(l, u); + Assert.AreEqual(m, du.Median); + } + + [Test] + [Row(-10, 10, 0.0)] + [Row(0, 4, 2.0)] + [Row(10, 20, 15.0)] + [Row(20, 20, 20.0)] + public void ValidateMean(int l, int u, double m) + { + var du = new DiscreteUniform(l, u); + Assert.AreEqual(m, du.Mean); + } + + [Test] + public void ValidateMinimum() + { + var b = new DiscreteUniform(-10, 10); + AssertEx.AreEqual(-10, b.Minimum); + } + + [Test] + public void ValidateMaximum() + { + var b = new DiscreteUniform(-10, 10); + AssertEx.AreEqual(10, b.Maximum); + } + + [Test] + [Row(-10, 10, -5, 1/21.0)] + [Row(-10, 10, 1, 1 / 21.0)] + [Row(-10, 10, 10, 1 / 21.0)] + [Row(-10, -10, 0, 0.0)] + [Row(-10, -10, -10, 1.0)] + public void ValidateProbability(int l, int u, int x, double p) + { + var b = new DiscreteUniform(l, u); + AssertEx.AreEqual(p, b.Probability(x)); + } + + [Test] + [Row(-10, 10, -5, -3.0445224377234229965005979803657054342845752874046093)] + [Row(-10, 10, 1, -3.0445224377234229965005979803657054342845752874046093)] + [Row(-10, 10, 10, -3.0445224377234229965005979803657054342845752874046093)] + [Row(-10, -10, 0, Double.NegativeInfinity)] + [Row(-10, -10, -10, 0.0)] + public void ValidateProbabilityLn(int l, int u, int x, double dln) + { + var b = new DiscreteUniform(l, u); + AssertEx.AreEqual(dln, b.ProbabilityLn(x)); + } + + [Test] + public void CanSampleStatic() + { + var d = DiscreteUniform.Sample(new Random(), 0, 10); + } + + [Test] + public void CanSampleSequenceStatic() + { + var ied = DiscreteUniform.Samples(new Random(), 0, 10); + var arr = ied.Take(5).ToArray(); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void FailSampleStatic() + { + var d = DiscreteUniform.Sample(new Random(), 20, 10); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void FailSampleSequenceStatic() + { + var ied = DiscreteUniform.Samples(new Random(), 20, 10).First(); + } + + [Test] + public void CanSample() + { + var n = new DiscreteUniform(0, 10); + var d = n.Sample(); + } + + [Test] + public void CanSampleSequence() + { + var n = new DiscreteUniform(0, 10); + var ied = n.Samples(); + var e = ied.Take(5).ToArray(); + } + + [Test] + [Row(-10, 10, -5, 6.0 / 21.0)] + [Row(-10, 10, 1, 12.0 / 21.0)] + [Row(-10, 10, 10, 1.0)] + [Row(-10, -10, 0, 1.0)] + [Row(-10, -10, -10, 1.0)] + [Row(-10, -10, -11, 0.0)] + public void ValidateCumulativeDistribution(int l, int u, double x, double cdf) + { + var b = new DiscreteUniform(l, u); + AssertEx.AreEqual(cdf, b.CumulativeDistribution(x)); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index d93aff16..25bc2450 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -71,6 +71,7 @@ +