diff --git a/FastRng/Double/Distributions/ChiSquare.cs b/FastRng/Double/Distributions/ChiSquare.cs deleted file mode 100644 index 3bc46bb..0000000 --- a/FastRng/Double/Distributions/ChiSquare.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace FastRng.Double.Distributions -{ - public sealed class ChiSquare : IDistribution - { - private double degreesOfFreedom = 1.0; - - public IRandom Random { get; set; } - - public double DegreesOfFreedom - { - get => this.degreesOfFreedom; - set - { - if(value <= 0.0) - throw new ArgumentOutOfRangeException(message: "DegreesOfFreedom must be greater than 0", null); - - this.degreesOfFreedom = value; - } - } - - public async ValueTask GetDistributedValue(CancellationToken token = default) - { - if (this.Random == null) - return double.NaN; - - return await this.Random.NextNumber(new Gamma{ Shape = 0.5 * this.DegreesOfFreedom, Scale = 2.0 }, token); - } - } -} \ No newline at end of file diff --git a/FastRng/Double/Distributions/ChiSquareK1.cs b/FastRng/Double/Distributions/ChiSquareK1.cs new file mode 100644 index 0000000..4cae567 --- /dev/null +++ b/FastRng/Double/Distributions/ChiSquareK1.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class ChiSquareK1 : IDistribution + { + private const double K = 1.0; + private const double K_HALF = K * 0.5d; + private const double K_HALF_MINUS_ONE = K_HALF - 1.0d; + private const double CONSTANT = 0.252; + + private static readonly double DIVISOR; + + private ShapeFitter fitter; + private IRandom random; + + static ChiSquareK1() + { + var twoToTheKHalf = Math.Pow(2, K_HALF); + var gammaKHalf = MathTools.Gamma(K_HALF); + DIVISOR = twoToTheKHalf * gammaKHalf; + } + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(ChiSquareK1.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * ((Math.Pow(x, K_HALF_MINUS_ONE) * Math.Exp(-x * 0.5d)) / DIVISOR); + + public async ValueTask GetDistributedValue(CancellationToken token = default) + { + if (this.Random == null) + return double.NaN; + + return await this.fitter.NextNumber(token); + } + } +} \ No newline at end of file diff --git a/FastRng/Double/Distributions/ChiSquareK10.cs b/FastRng/Double/Distributions/ChiSquareK10.cs new file mode 100644 index 0000000..532da97 --- /dev/null +++ b/FastRng/Double/Distributions/ChiSquareK10.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class ChiSquareK10 : IDistribution + { + private const double K = 10.0; + private const double K_HALF = K * 0.5d; + private const double K_HALF_MINUS_ONE = K_HALF - 1.0d; + private const double CONSTANT = 0.252; + + private static readonly double DIVISOR; + + private ShapeFitter fitter; + private IRandom random; + + static ChiSquareK10() + { + var twoToTheKHalf = Math.Pow(2, K_HALF); + var gammaKHalf = MathTools.Gamma(K_HALF); + DIVISOR = twoToTheKHalf * gammaKHalf; + } + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(ChiSquareK10.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * ((Math.Pow(x, K_HALF_MINUS_ONE) * Math.Exp(-x * 0.5d)) / DIVISOR); + + public async ValueTask GetDistributedValue(CancellationToken token = default) + { + if (this.Random == null) + return double.NaN; + + return await this.fitter.NextNumber(token); + } + } +} \ No newline at end of file diff --git a/FastRng/Double/Distributions/ChiSquareK4.cs b/FastRng/Double/Distributions/ChiSquareK4.cs new file mode 100644 index 0000000..59178f2 --- /dev/null +++ b/FastRng/Double/Distributions/ChiSquareK4.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class ChiSquareK4 : IDistribution + { + private const double K = 4.0; + private const double K_HALF = K * 0.5d; + private const double K_HALF_MINUS_ONE = K_HALF - 1.0d; + private const double CONSTANT = 0.252; + + private static readonly double DIVISOR; + + private ShapeFitter fitter; + private IRandom random; + + static ChiSquareK4() + { + var twoToTheKHalf = Math.Pow(2, K_HALF); + var gammaKHalf = MathTools.Gamma(K_HALF); + DIVISOR = twoToTheKHalf * gammaKHalf; + } + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(ChiSquareK4.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * ((Math.Pow(x, K_HALF_MINUS_ONE) * Math.Exp(-x * 0.5d)) / DIVISOR); + + public async ValueTask GetDistributedValue(CancellationToken token = default) + { + if (this.Random == null) + return double.NaN; + + return await this.fitter.NextNumber(token); + } + } +} \ No newline at end of file diff --git a/FastRngTests/Double/Distributions/ChiSquareK1.cs b/FastRngTests/Double/Distributions/ChiSquareK1.cs new file mode 100644 index 0000000..0fd7adf --- /dev/null +++ b/FastRngTests/Double/Distributions/ChiSquareK1.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using FastRng.Double; +using NUnit.Framework; + +namespace FastRngTests.Double.Distributions +{ + [ExcludeFromCodeCoverage] + public class ChiSquareK1 + { + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareDistribution01() + { + var dist = new FastRng.Double.Distributions.ChiSquareK1(); + var fqa = new FrequencyAnalysis(); + var rng = new MultiThreadedRng(); + + for (var n = 0; n < 100_000; n++) + { + var value = await rng.NextNumber(dist); + fqa.CountThis(value); + } + + rng.StopProducer(); + + var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(1.00032041964207).Within(0.004)); + Assert.That(result[1], Is.EqualTo(0.70380551227703).Within(0.02)); + Assert.That(result[2], Is.EqualTo(0.571788691668126).Within(0.05)); + + Assert.That(result[21], Is.EqualTo(0.192011337664754).Within(0.07)); + Assert.That(result[22], Is.EqualTo(0.186854182385981).Within(0.07)); + Assert.That(result[23], Is.EqualTo(0.182007652359976).Within(0.07)); + + Assert.That(result[50], Is.EqualTo(0.109088865614875).Within(0.02)); + + Assert.That(result[75], Is.EqualTo(0.07886274821701).Within(0.01)); + Assert.That(result[85], Is.EqualTo(0.070520397849883).Within(0.01)); + Assert.That(result[90], Is.EqualTo(0.066863009640287).Within(0.01)); + + Assert.That(result[97], Is.EqualTo(0.062214737436948).Within(0.01)); + Assert.That(result[98], Is.EqualTo(0.061590997922187).Within(0.01)); + Assert.That(result[99], Is.EqualTo(0.060976622578824).Within(0.01)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareGeneratorWithRange01() + { + var dist = new FastRng.Double.Distributions.ChiSquareK1(); + var rng = new MultiThreadedRng(); + var samples = new double[1_000]; + for (var n = 0; n < samples.Length; n++) + samples[n] = await rng.NextNumber(-1.0, 1.0, dist); + + rng.StopProducer(); + Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0), "Min out of range"); + Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max out of range"); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareGeneratorWithRange02() + { + var dist = new FastRng.Double.Distributions.ChiSquareK1(); + var rng = new MultiThreadedRng(); + var samples = new double[1_000]; + for (var n = 0; n < samples.Length; n++) + samples[n] = await rng.NextNumber(0.0, 1.0, dist); + + rng.StopProducer(); + Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0), "Min is out of range"); + Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max is out of range"); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareGeneratorWithRange03() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.ChiSquareK1 { Random = rng }; // Test default parameters + + var samples = new double[1_000]; + for (var n = 0; n < samples.Length; n++) + samples[n] = await dist.GetDistributedValue(); + + rng.StopProducer(); + Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0), "Min is out of range"); + Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max is out of range"); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task NoRandomNumberGenerator01() + { + var dist = new FastRng.Double.Distributions.ChiSquareK1(); + Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); + Assert.That(await dist.GetDistributedValue(), Is.NaN); + } + } +} \ No newline at end of file diff --git a/FastRngTests/Double/Distributions/ChiSquareK10.cs b/FastRngTests/Double/Distributions/ChiSquareK10.cs new file mode 100644 index 0000000..1476e7c --- /dev/null +++ b/FastRngTests/Double/Distributions/ChiSquareK10.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using FastRng.Double; +using NUnit.Framework; + +namespace FastRngTests.Double.Distributions +{ + [ExcludeFromCodeCoverage] + public class ChiSquareK10 + { + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareDistribution01() + { + var dist = new FastRng.Double.Distributions.ChiSquareK10(); + var fqa = new FrequencyAnalysis(); + var rng = new MultiThreadedRng(); + + for (var n = 0; n < 100_000; n++) + { + var value = await rng.NextNumber(dist); + fqa.CountThis(value); + } + + rng.StopProducer(); + + var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(0.0000000164021588).Within(0.00000002)); + Assert.That(result[1], Is.EqualTo(0.0000002611256437).Within(0.0000003)); + Assert.That(result[2], Is.EqualTo(0.0000013153553250).Within(0.000002)); + + Assert.That(result[21], Is.EqualTo(0.003459320622874).Within(0.005)); + Assert.That(result[22], Is.EqualTo(0.004111875573379).Within(0.005)); + Assert.That(result[23], Is.EqualTo(0.004850674298859).Within(0.005)); + + Assert.That(result[50], Is.EqualTo(0.086418773275056).Within(0.05)); + + Assert.That(result[75], Is.EqualTo(0.376092741436046).Within(0.08)); + Assert.That(result[85], Is.EqualTo(0.586569751611096).Within(0.08)); + Assert.That(result[90], Is.EqualTo(0.717189736168766).Within(0.08)); + + Assert.That(result[97], Is.EqualTo(0.931477764640217).Within(0.08)); + Assert.That(result[98], Is.EqualTo(0.965244855212136).Within(0.08)); + Assert.That(result[99], Is.EqualTo(0.999827884370044).Within(0.08)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareGeneratorWithRange01() + { + var dist = new FastRng.Double.Distributions.ChiSquareK10(); + var rng = new MultiThreadedRng(); + var samples = new double[1_000]; + for (var n = 0; n < samples.Length; n++) + samples[n] = await rng.NextNumber(-1.0, 1.0, dist); + + rng.StopProducer(); + Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0), "Min out of range"); + Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max out of range"); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareGeneratorWithRange02() + { + var dist = new FastRng.Double.Distributions.ChiSquareK10(); + var rng = new MultiThreadedRng(); + var samples = new double[1_000]; + for (var n = 0; n < samples.Length; n++) + samples[n] = await rng.NextNumber(0.0, 1.0, dist); + + rng.StopProducer(); + Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0), "Min is out of range"); + Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max is out of range"); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestChiSquareGeneratorWithRange03() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.ChiSquareK10 { Random = rng }; // Test default parameters + + var samples = new double[1_000]; + for (var n = 0; n < samples.Length; n++) + samples[n] = await dist.GetDistributedValue(); + + rng.StopProducer(); + Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0), "Min is out of range"); + Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max is out of range"); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task NoRandomNumberGenerator01() + { + var dist = new FastRng.Double.Distributions.ChiSquareK10(); + Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); + Assert.That(await dist.GetDistributedValue(), Is.NaN); + } + } +} \ No newline at end of file diff --git a/FastRngTests/Double/Distributions/ChiSquare.cs b/FastRngTests/Double/Distributions/ChiSquareK4.cs similarity index 64% rename from FastRngTests/Double/Distributions/ChiSquare.cs rename to FastRngTests/Double/Distributions/ChiSquareK4.cs index 5e44bdd..e5a0dd0 100644 --- a/FastRngTests/Double/Distributions/ChiSquare.cs +++ b/FastRngTests/Double/Distributions/ChiSquareK4.cs @@ -8,30 +8,44 @@ using NUnit.Framework; namespace FastRngTests.Double.Distributions { [ExcludeFromCodeCoverage] - public class ChiSquare + public class ChiSquareK4 { [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task TestChiSquareDistribution01() { - const double DOF = 7.0; - const double MEAN = DOF; - const double VARIANCE = 2 * DOF; - - var dist = new FastRng.Double.Distributions.ChiSquare{ DegreesOfFreedom = DOF }; - var stats = new RunningStatistics(); + var dist = new FastRng.Double.Distributions.ChiSquareK4(); + var fqa = new FrequencyAnalysis(); var rng = new MultiThreadedRng(); for (var n = 0; n < 100_000; n++) - stats.Push(await rng.NextNumber(dist)); - + { + var value = await rng.NextNumber(dist); + fqa.CountThis(value); + } + rng.StopProducer(); - TestContext.WriteLine($"mean={MEAN} vs. {stats.Mean}"); - TestContext.WriteLine($"variance={VARIANCE} vs {stats.Variance}"); - Assert.That(stats.Mean, Is.EqualTo(MEAN).Within(0.4), "Mean is out of range"); - Assert.That(stats.Variance, Is.EqualTo(VARIANCE).Within(0.4), "Variance is out of range"); + var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(0.016417705906679).Within(0.02)); + Assert.That(result[1], Is.EqualTo(0.032671644513723).Within(0.02)); + Assert.That(result[2], Is.EqualTo(0.048763041010352).Within(0.02)); + + Assert.That(result[21], Is.EqualTo(0.32518779111264).Within(0.05)); + Assert.That(result[22], Is.EqualTo(0.338273451612642).Within(0.05)); + Assert.That(result[23], Is.EqualTo(0.351220492939994).Within(0.05)); + + Assert.That(result[50], Is.EqualTo(0.65209223303425).Within(0.05)); + + Assert.That(result[75], Is.EqualTo(0.857562207152294).Within(0.08)); + Assert.That(result[85], Is.EqualTo(0.923072405412387).Within(0.08)); + Assert.That(result[90], Is.EqualTo(0.952623623874265).Within(0.08)); + + Assert.That(result[97], Is.EqualTo(0.990616879396201).Within(0.08)); + Assert.That(result[98], Is.EqualTo(0.995734077068522).Within(0.08)); + Assert.That(result[99], Is.EqualTo(1.00077558852585).Within(0.1)); } [Test] @@ -39,10 +53,11 @@ namespace FastRngTests.Double.Distributions [Category(TestCategories.NORMAL)] public async Task TestChiSquareGeneratorWithRange01() { + var dist = new FastRng.Double.Distributions.ChiSquareK4(); var rng = new MultiThreadedRng(); var samples = new double[1_000]; for (var n = 0; n < samples.Length; n++) - samples[n] = await rng.NextNumber(-1.0, 1.0, new FastRng.Double.Distributions.ChiSquare()); + samples[n] = await rng.NextNumber(-1.0, 1.0, dist); rng.StopProducer(); Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0), "Min out of range"); @@ -54,10 +69,11 @@ namespace FastRngTests.Double.Distributions [Category(TestCategories.NORMAL)] public async Task TestChiSquareGeneratorWithRange02() { + var dist = new FastRng.Double.Distributions.ChiSquareK4(); var rng = new MultiThreadedRng(); var samples = new double[1_000]; for (var n = 0; n < samples.Length; n++) - samples[n] = await rng.NextNumber(0.0, 1.0, new FastRng.Double.Distributions.ChiSquare()); + samples[n] = await rng.NextNumber(0.0, 1.0, dist); rng.StopProducer(); Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0), "Min is out of range"); @@ -70,7 +86,7 @@ namespace FastRngTests.Double.Distributions public async Task TestChiSquareGeneratorWithRange03() { var rng = new MultiThreadedRng(); - var dist = new FastRng.Double.Distributions.ChiSquare { Random = rng }; // Test default parameters + var dist = new FastRng.Double.Distributions.ChiSquareK4 { Random = rng }; // Test default parameters var samples = new double[1_000]; for (var n = 0; n < samples.Length; n++) @@ -81,25 +97,12 @@ namespace FastRngTests.Double.Distributions Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0), "Max is out of range"); } - [Test] - [Category(TestCategories.COVER)] - [Category(TestCategories.NORMAL)] - public void ParameterTest01() - { - var dist = new FastRng.Double.Distributions.ChiSquare(); - - Assert.Throws(() => dist.DegreesOfFreedom = 0); - Assert.Throws(() => dist.DegreesOfFreedom = -78); - Assert.DoesNotThrow(() => dist.DegreesOfFreedom = 0.0001); - Assert.DoesNotThrow(() => dist.DegreesOfFreedom = 4); - } - [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task NoRandomNumberGenerator01() { - var dist = new FastRng.Double.Distributions.ChiSquare(); + var dist = new FastRng.Double.Distributions.ChiSquareK4(); Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); Assert.That(await dist.GetDistributedValue(), Is.NaN); }