diff --git a/FastRng/Double/Distributions/Exponential.cs b/FastRng/Double/Distributions/Exponential.cs deleted file mode 100644 index f75f0cf..0000000 --- a/FastRng/Double/Distributions/Exponential.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace FastRng.Double.Distributions -{ - public sealed class Exponential : IDistribution - { - private double mean = 1.0; - - public IRandom Random { get; set; } - - public double Mean - { - get => this.mean; - set - { - if(value <= 0.0) - throw new ArgumentOutOfRangeException(message: "Mean must be greater than 0", null); - - this.mean = value; - } - } - - public async ValueTask GetDistributedValue(CancellationToken token = default) - { - if (this.Random == null) - return double.NaN; - - if(this.Mean == 1.0) - return -Math.Log(await this.Random.GetUniform(token)); - else - return this.Mean * -Math.Log(await this.Random.GetUniform(token)); - } - } -} \ No newline at end of file diff --git a/FastRng/Double/Distributions/ExponentialLa10.cs b/FastRng/Double/Distributions/ExponentialLa10.cs new file mode 100644 index 0000000..ee6a1e6 --- /dev/null +++ b/FastRng/Double/Distributions/ExponentialLa10.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class ExponentialLa10 : IDistribution + { + private const double LAMBDA = 10.0; + private const double CONSTANT = 0.1106; + + private ShapeFitter fitter; + private IRandom random; + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(ExponentialLa10.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * LAMBDA * Math.Exp(-LAMBDA * x); + + 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/ExponentialLa5.cs b/FastRng/Double/Distributions/ExponentialLa5.cs new file mode 100644 index 0000000..e3fe405 --- /dev/null +++ b/FastRng/Double/Distributions/ExponentialLa5.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class ExponentialLa5 : IDistribution + { + private const double LAMBDA = 5.0; + private const double CONSTANT = 0.2103; + + private ShapeFitter fitter; + private IRandom random; + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(ExponentialLa5.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * LAMBDA * Math.Exp(-LAMBDA * x); + + 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/ExponentialLa10.cs b/FastRngTests/Double/Distributions/ExponentialLa10.cs new file mode 100644 index 0000000..23f038e --- /dev/null +++ b/FastRngTests/Double/Distributions/ExponentialLa10.cs @@ -0,0 +1,104 @@ +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 ExponentialLa10 + { + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestExponentialDistribution01() + { + var dist = new FastRng.Double.Distributions.ExponentialLa10(); + var fqa = new FrequencyAnalysis(); + var rng = new MultiThreadedRng(); + + for (var n = 0; n < 100_000; n++) + fqa.CountThis(await rng.NextNumber(dist)); + + rng.StopProducer(); + var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(1.00075018434777).Within(0.05)); + Assert.That(result[1], Is.EqualTo(0.905516212904248).Within(0.05)); + Assert.That(result[2], Is.EqualTo(0.81934495207398).Within(0.05)); + + Assert.That(result[21], Is.EqualTo(0.122548293148741).Within(0.01)); + Assert.That(result[22], Is.EqualTo(0.110886281157421).Within(0.01)); + Assert.That(result[23], Is.EqualTo(0.10033405633809).Within(0.01)); + + Assert.That(result[50], Is.EqualTo(0.00674300170146).Within(0.005)); + + Assert.That(result[75], Is.EqualTo(0.000553499285385).Within(0.001)); + Assert.That(result[85], Is.EqualTo(0.000203621007796).Within(0.001)); + Assert.That(result[90], Is.EqualTo(0.00012350238419).Within(0.001)); + + Assert.That(result[97], Is.EqualTo(0.0000613294689720).Within(0.0008)); + Assert.That(result[98], Is.EqualTo(0.0000554931983541).Within(0.0008)); + Assert.That(result[99], Is.EqualTo(0.0000502123223173).Within(0.0008)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestExponentialGeneratorWithRange01() + { + var dist = new FastRng.Double.Distributions.ExponentialLa10(); + 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 TestExponentialGeneratorWithRange02() + { + var dist = new FastRng.Double.Distributions.ExponentialLa10(); + 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 TestExponentialGeneratorWithRange03() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.ExponentialLa10 { 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.ExponentialLa10(); + 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/Exponential.cs b/FastRngTests/Double/Distributions/ExponentialLa5.cs similarity index 65% rename from FastRngTests/Double/Distributions/Exponential.cs rename to FastRngTests/Double/Distributions/ExponentialLa5.cs index 2ac94ef..f32fc39 100644 --- a/FastRngTests/Double/Distributions/Exponential.cs +++ b/FastRngTests/Double/Distributions/ExponentialLa5.cs @@ -8,29 +8,40 @@ using NUnit.Framework; namespace FastRngTests.Double.Distributions { [ExcludeFromCodeCoverage] - public class Exponential + public class ExponentialLa5 { [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task TestExponentialDistribution01() { - const double MEAN = 1.5; - const double VARIANCE = MEAN * MEAN; - - var dist = new FastRng.Double.Distributions.Exponential{ Mean = MEAN }; - var stats = new RunningStatistics(); + var dist = new FastRng.Double.Distributions.ExponentialLa5(); + var fqa = new FrequencyAnalysis(); var rng = new MultiThreadedRng(); for (var n = 0; n < 100_000; n++) - stats.Push(await rng.NextNumber(dist)); + fqa.CountThis(await rng.NextNumber(dist)); rng.StopProducer(); - TestContext.WriteLine($"mean={MEAN} vs. {stats.Mean}"); - TestContext.WriteLine($"variance={VARIANCE} vs {stats.Variance}"); + var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(1.0002177398625).Within(0.05)); + Assert.That(result[1], Is.EqualTo(0.951436545064811).Within(0.05)); + Assert.That(result[2], Is.EqualTo(0.905034437210948).Within(0.05)); - Assert.That(stats.Mean, Is.EqualTo(MEAN).Within(0.1), "Mean is out of range"); - Assert.That(stats.Variance, Is.EqualTo(VARIANCE).Within(0.1), "Variance is out of range"); + Assert.That(result[21], Is.EqualTo(0.35001394450853).Within(0.05)); + Assert.That(result[22], Is.EqualTo(0.332943563002074).Within(0.05)); + Assert.That(result[23], Is.EqualTo(0.31670571382568).Within(0.05)); + + Assert.That(result[50], Is.EqualTo(0.082102871800213).Within(0.01)); + + Assert.That(result[75], Is.EqualTo(0.023522866606758).Within(0.01)); + Assert.That(result[85], Is.EqualTo(0.014267339801329).Within(0.01)); + Assert.That(result[90], Is.EqualTo(0.011111415409621).Within(0.01)); + + Assert.That(result[97], Is.EqualTo(0.007830082099077).Within(0.008)); + Assert.That(result[98], Is.EqualTo(0.007448204488898).Within(0.008)); + Assert.That(result[99], Is.EqualTo(0.007084951269538).Within(0.008)); } [Test] @@ -38,10 +49,11 @@ namespace FastRngTests.Double.Distributions [Category(TestCategories.NORMAL)] public async Task TestExponentialGeneratorWithRange01() { + var dist = new FastRng.Double.Distributions.ExponentialLa5(); 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.Exponential()); + samples[n] = await rng.NextNumber(-1.0, 1.0, dist); rng.StopProducer(); Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0), "Min out of range"); @@ -53,10 +65,11 @@ namespace FastRngTests.Double.Distributions [Category(TestCategories.NORMAL)] public async Task TestExponentialGeneratorWithRange02() { + var dist = new FastRng.Double.Distributions.ExponentialLa5(); 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.Exponential()); + 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"); @@ -67,7 +80,7 @@ namespace FastRngTests.Double.Distributions public async Task TestExponentialGeneratorWithRange03() { var rng = new MultiThreadedRng(); - var dist = new FastRng.Double.Distributions.Exponential { Random = rng }; // Test default parameters + var dist = new FastRng.Double.Distributions.ExponentialLa5 { Random = rng }; // Test default parameters var samples = new double[1_000]; for (var n = 0; n < samples.Length; n++) @@ -78,25 +91,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.Exponential(); - - Assert.Throws(() => dist.Mean = 0); - Assert.Throws(() => dist.Mean = -78); - Assert.DoesNotThrow(() => dist.Mean = 0.0001); - Assert.DoesNotThrow(() => dist.Mean = 4); - } - [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task NoRandomNumberGenerator01() { - var dist = new FastRng.Double.Distributions.Exponential(); + var dist = new FastRng.Double.Distributions.ExponentialLa5(); Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); Assert.That(await dist.GetDistributedValue(), Is.NaN); }