diff --git a/src/FSharp/Distributions.fs b/src/FSharp/Distributions.fs index d6f6a8a9b..266ceb10c 100644 --- a/src/FSharp/Distributions.fs +++ b/src/FSharp/Distributions.fs @@ -118,7 +118,7 @@ module Sample = let logNormal mu sigma (rng:System.Random) = LogNormal.Sample(rng, mu, sigma) let logNormalSeq mu sigma (rng:System.Random) = LogNormal.Samples(rng, mu, sigma) - /// Negative-Binomial with number of failures (r) until the experiment stopped and probability (p) of a trial resulting in success. + /// Negative-Binomial with number of successes (r) until the experiment stopped and probability (p) of a trial resulting in success. let negativeBinomial r p (rng:System.Random) = NegativeBinomial.Sample(rng, r, p) let negativeBinomialSeq r p (rng:System.Random) = NegativeBinomial.Samples(rng, r, p) diff --git a/src/Numerics.Tests/DistributionTests/Discrete/NegativeBinomialTests.cs b/src/Numerics.Tests/DistributionTests/Discrete/NegativeBinomialTests.cs index 547aaee65..6eca7de38 100644 --- a/src/Numerics.Tests/DistributionTests/Discrete/NegativeBinomialTests.cs +++ b/src/Numerics.Tests/DistributionTests/Discrete/NegativeBinomialTests.cs @@ -45,7 +45,7 @@ public class NegativeBinomialTests /// /// Number of trials. /// Probability of success. - [TestCase(0.0, 0.0)] + [TestCase(0.0, 0.1)] [TestCase(0.1, 0.3)] [TestCase(1.0, 1.0)] public void CanCreateNegativeBinomial(double r, double p) @@ -60,6 +60,7 @@ public void CanCreateNegativeBinomial(double r, double p) /// /// Number of trials. /// Probability of success. + [TestCase(0.0, 0.0)] [TestCase(0.0, double.NaN)] [TestCase(0.0, -1.0)] [TestCase(0.0, 2.0)] @@ -99,7 +100,7 @@ public void ValidateEntropyThrowsNotSupportedException() /// /// Number of trials. /// Probability of success. - [TestCase(0.0, 0.0)] + [TestCase(0.0, 0.1)] [TestCase(0.1, 0.3)] [TestCase(1.0, 1.0)] public void ValidateSkewness(double r, double p) @@ -113,8 +114,8 @@ public void ValidateSkewness(double r, double p) /// /// Number of trials. /// Probability of success. - [TestCase(0.0, 0.0)] - [TestCase(0.3, 0.0)] + [TestCase(0.0, 0.1)] + [TestCase(0.3, 0.1)] [TestCase(1.0, 1.0)] public void ValidateMode(double r, double p) { @@ -165,7 +166,7 @@ public void ValidateMaximum() /// Number of trials. /// Probability of success. /// Input X value. - [TestCase(0.0, 0.0, 0)] + [TestCase(0.0, 0.1, 0)] [TestCase(0.1, 0.3, 1)] [TestCase(1.0, 1.0, 2)] [TestCase(1.0, 1.0, 3)] @@ -173,7 +174,10 @@ public void ValidateMaximum() public void ValidateProbability(double r, double p, int x) { var d = new NegativeBinomial(r, p); - Assert.AreEqual(Math.Exp(SpecialFunctions.GammaLn(r + x) - SpecialFunctions.GammaLn(r) - SpecialFunctions.GammaLn(x + 1.0) + (r * Math.Log(p)) + (x * Math.Log(1.0 - p))), d.Probability(x)); + if (p == 1.0 || r == 0.0) + Assert.AreEqual(x == 0 ? 1.0 : 0.0, d.Probability(x)); + else + Assert.AreEqual(Math.Exp(SpecialFunctions.GammaLn(r + x) - SpecialFunctions.GammaLn(r) - SpecialFunctions.GammaLn(x + 1.0) + (r * Math.Log(p)) + (x * Math.Log(1.0 - p))), d.Probability(x)); } /// @@ -182,7 +186,7 @@ public void ValidateProbability(double r, double p, int x) /// Number of trials. /// Probability of success. /// Input X value. - [TestCase(0.0, 0.0, 0)] + [TestCase(0.0, 0.1, 0)] [TestCase(0.1, 0.3, 1)] [TestCase(1.0, 1.0, 2)] [TestCase(1.0, 1.0, 3)] @@ -190,7 +194,10 @@ public void ValidateProbability(double r, double p, int x) public void ValidateProbabilityLn(double r, double p, int x) { var d = new NegativeBinomial(r, p); - Assert.AreEqual(SpecialFunctions.GammaLn(r + x) - SpecialFunctions.GammaLn(r) - SpecialFunctions.GammaLn(x + 1.0) + (r * Math.Log(p)) + (x * Math.Log(1.0 - p)), d.ProbabilityLn(x)); + if (p == 1.0 || r == 0.0) + Assert.AreEqual(x == 0 ? 0.0 : Double.NegativeInfinity, d.ProbabilityLn(x)); + else + Assert.AreEqual(SpecialFunctions.GammaLn(r + x) - SpecialFunctions.GammaLn(r) - SpecialFunctions.GammaLn(x + 1.0) + (r * Math.Log(p)) + (x * Math.Log(1.0 - p)), d.ProbabilityLn(x)); } /// @@ -199,7 +206,7 @@ public void ValidateProbabilityLn(double r, double p, int x) /// Number of trials. /// Probability of success. /// Input X value. - [TestCase(0.0, 0.0, 0)] + [TestCase(0.0, 0.1, 0)] [TestCase(0.1, 0.3, 1)] [TestCase(1.0, 1.0, 2)] [TestCase(1.0, 1.0, 3)] diff --git a/src/Numerics/Distributions/NegativeBinomial.cs b/src/Numerics/Distributions/NegativeBinomial.cs index f6d177693..3f36de68f 100644 --- a/src/Numerics/Distributions/NegativeBinomial.cs +++ b/src/Numerics/Distributions/NegativeBinomial.cs @@ -51,7 +51,7 @@ public class NegativeBinomial : IDiscreteDistribution /// Initializes a new instance of the class. /// /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public NegativeBinomial(double r, double p) { if (!IsValidParameterSet(r, p)) @@ -68,7 +68,7 @@ public NegativeBinomial(double r, double p) /// Initializes a new instance of the class. /// /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. /// The random number generator which is used to draw random samples. public NegativeBinomial(double r, double p, System.Random randomSource) { @@ -97,10 +97,10 @@ public override string ToString() /// Tests whether the provided values are valid parameters for this distribution. /// /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static bool IsValidParameterSet(double r, double p) { - return r >= 0.0 && p >= 0.0 && p <= 1.0; + return r >= 0.0 && p > 0.0 && p <= 1.0; } /// @@ -109,7 +109,7 @@ public static bool IsValidParameterSet(double r, double p) public double R => _r; /// - /// Gets the probability of success. Range: 0 ≤ p ≤ 1. + /// Gets the probability of success. Range: 0 < p ≤ 1. /// public double P => _p; @@ -202,10 +202,12 @@ public double CumulativeDistribution(double x) /// /// The location in the domain where we want to evaluate the probability mass function. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. /// the probability mass at location . public static double PMF(double r, double p, int k) { + if (p == 1.0 || r == 0.0) return k == 0 ? 1.0 : 0.0; + return Math.Exp(PMFLn(r, p, k)); } @@ -214,15 +216,17 @@ public static double PMF(double r, double p, int k) /// /// The location in the domain where we want to evaluate the log probability mass function. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. /// the log probability mass at location . public static double PMFLn(double r, double p, int k) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } + if (p == 1.0 || r == 0.0) return k == 0 ? 0.0 : Double.NegativeInfinity; + return SpecialFunctions.GammaLn(r + k) - SpecialFunctions.GammaLn(r) - SpecialFunctions.GammaLn(k + 1.0) @@ -235,12 +239,12 @@ public static double PMFLn(double r, double p, int k) /// /// The location at which to compute the cumulative distribution function. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. /// the cumulative distribution at location . /// public static double CDF(double r, double p, double x) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } @@ -253,21 +257,14 @@ public static double CDF(double r, double p, double x) /// /// The random number generator to use. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. /// a sample from the distribution. static int SampleUnchecked(System.Random rnd, double r, double p) { - var lambda = Gamma.SampleUnchecked(rnd, r, p); - var c = Math.Exp(-lambda); - var p1 = 1.0; - var k = 0; - do - { - k = k + 1; - p1 = p1*rnd.NextDouble(); - } - while (p1 >= c); - return k - 1; + if (p == 1.0) return 0; + + var lambda = Gamma.SampleUnchecked(rnd, r, p/(1-p)); + return Poisson.SampleUnchecked(rnd, lambda); } static void SamplesUnchecked(System.Random rnd, int[] values, double r, double p) @@ -317,10 +314,10 @@ public IEnumerable Samples() /// /// The random number generator to use. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static int Sample(System.Random rnd, double r, double p) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } @@ -333,10 +330,10 @@ public static int Sample(System.Random rnd, double r, double p) /// /// The random number generator to use. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static IEnumerable Samples(System.Random rnd, double r, double p) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } @@ -350,10 +347,10 @@ public static IEnumerable Samples(System.Random rnd, double r, double p) /// The random number generator to use. /// The array to fill with the samples. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static void Samples(System.Random rnd, int[] values, double r, double p) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } @@ -365,10 +362,10 @@ public static void Samples(System.Random rnd, int[] values, double r, double p) /// Samples a random variable. /// /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static int Sample(double r, double p) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } @@ -380,10 +377,10 @@ public static int Sample(double r, double p) /// Samples a sequence of this random variable. /// /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static IEnumerable Samples(double r, double p) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } @@ -396,10 +393,10 @@ public static IEnumerable Samples(double r, double p) /// /// The array to fill with the samples. /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. - /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The probability (p) of a trial resulting in success. Range: 0 < p ≤ 1. public static void Samples(int[] values, double r, double p) { - if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + if (!(r >= 0.0 && p > 0.0 && p <= 1.0)) { throw new ArgumentException("Invalid parametrization for the distribution."); } diff --git a/src/Numerics/Distributions/Poisson.cs b/src/Numerics/Distributions/Poisson.cs index 16e0def9e..0c8291244 100644 --- a/src/Numerics/Distributions/Poisson.cs +++ b/src/Numerics/Distributions/Poisson.cs @@ -246,12 +246,12 @@ public static double CDF(double lambda, double x) /// The random source to use. /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. /// A random sample from the Poisson distribution. - static int SampleUnchecked(System.Random rnd, double lambda) + internal static int SampleUnchecked(System.Random rnd, double lambda) { return (lambda < 30.0) ? DoSampleShort(rnd, lambda) : DoSampleLarge(rnd, lambda); } - static void SamplesUnchecked(System.Random rnd, int[] values, double lambda) + internal static void SamplesUnchecked(System.Random rnd, int[] values, double lambda) { if (lambda < 30.0) { @@ -300,7 +300,7 @@ static void SamplesUnchecked(System.Random rnd, int[] values, double lambda) } } - static IEnumerable SamplesUnchecked(System.Random rnd, double lambda) + internal static IEnumerable SamplesUnchecked(System.Random rnd, double lambda) { if (lambda < 30.0) {