From e2f292a6ffecbb74175f6d4097c4c6dcb562e8cf Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 31 Oct 2020 16:20:38 +0100 Subject: [PATCH] Migrated laplace distribution to shape fitter --- FastRng/Double/Distributions/Laplace.cs | 28 ----- FastRng/Double/Distributions/LaplaceB01M0.cs | 43 +++++++ FastRng/Double/Distributions/LaplaceB01M05.cs | 43 +++++++ .../{Laplace.cs => LaplaceB01M0.cs} | 60 +++++----- .../Double/Distributions/LaplaceB01M05.cs | 106 ++++++++++++++++++ 5 files changed, 220 insertions(+), 60 deletions(-) delete mode 100644 FastRng/Double/Distributions/Laplace.cs create mode 100644 FastRng/Double/Distributions/LaplaceB01M0.cs create mode 100644 FastRng/Double/Distributions/LaplaceB01M05.cs rename FastRngTests/Double/Distributions/{Laplace.cs => LaplaceB01M0.cs} (59%) create mode 100644 FastRngTests/Double/Distributions/LaplaceB01M05.cs diff --git a/FastRng/Double/Distributions/Laplace.cs b/FastRng/Double/Distributions/Laplace.cs deleted file mode 100644 index 26eb270..0000000 --- a/FastRng/Double/Distributions/Laplace.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace FastRng.Double.Distributions -{ - public sealed class Laplace : IDistribution - { - public IRandom Random { get; set; } - - public double Mean { get; set; } = 0.0; - - public double Scale { get; set; } = 1.0; - - public async ValueTask GetDistributedValue(CancellationToken token = default) - { - if (this.Random == null) - return double.NaN; - - var value = await this.Random.GetUniform(token); - - if (value < 0.5) - return this.Mean + this.Scale * Math.Log(2.0 * value); - else - return this.Mean - this.Scale * Math.Log(2.0 * (1.0 - value)); - } - } -} \ No newline at end of file diff --git a/FastRng/Double/Distributions/LaplaceB01M0.cs b/FastRng/Double/Distributions/LaplaceB01M0.cs new file mode 100644 index 0000000..66127c0 --- /dev/null +++ b/FastRng/Double/Distributions/LaplaceB01M0.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class LaplaceB01M0 : IDistribution + { + private const double B = 0.1; + private const double MU = 0.0; + private const double CONSTANT = 0.221034183615129; + + private static readonly double FACTOR_LEFT; + + private ShapeFitter fitter; + private IRandom random; + + static LaplaceB01M0() + { + FACTOR_LEFT = CONSTANT / (2.0d * B); + } + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(LaplaceB01M0.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => FACTOR_LEFT * Math.Exp(-Math.Abs(x - MU) / B); + + 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/LaplaceB01M05.cs b/FastRng/Double/Distributions/LaplaceB01M05.cs new file mode 100644 index 0000000..2197507 --- /dev/null +++ b/FastRng/Double/Distributions/LaplaceB01M05.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class LaplaceB01M05 : IDistribution + { + private const double B = 0.1; + private const double MU = 0.5; + private const double CONSTANT = 0.2; + + private static readonly double FACTOR_LEFT; + + private ShapeFitter fitter; + private IRandom random; + + static LaplaceB01M05() + { + FACTOR_LEFT = CONSTANT / (2.0d * B); + } + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(LaplaceB01M05.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => FACTOR_LEFT * Math.Exp(-Math.Abs(x - MU) / B); + + 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/Laplace.cs b/FastRngTests/Double/Distributions/LaplaceB01M0.cs similarity index 59% rename from FastRngTests/Double/Distributions/Laplace.cs rename to FastRngTests/Double/Distributions/LaplaceB01M0.cs index bdd5270..12e29d5 100644 --- a/FastRngTests/Double/Distributions/Laplace.cs +++ b/FastRngTests/Double/Distributions/LaplaceB01M0.cs @@ -8,30 +8,40 @@ using NUnit.Framework; namespace FastRngTests.Double.Distributions { [ExcludeFromCodeCoverage] - public class Laplace + public class LaplaceB01M0 { [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task TestLaplaceDistribution01() { - const double MEAN = 0.2; - const double SCALE = 4.8; - const double VARIANCE = 2 * SCALE * SCALE; - - var dist = new FastRng.Double.Distributions.Laplace{ Mean = MEAN, Scale = SCALE }; - var stats = new RunningStatistics(); + var dist = new FastRng.Double.Distributions.LaplaceB01M0(); + var fra = new FrequencyAnalysis(); var rng = new MultiThreadedRng(); for (var n = 0; n < 100_000; n++) - stats.Push(await rng.NextNumber(dist)); + fra.CountThis(await rng.NextNumber(dist)); rng.StopProducer(); - TestContext.WriteLine($"mean={MEAN} vs. {stats.Mean}"); - TestContext.WriteLine($"variance={VARIANCE} vs {stats.Variance}"); + var result = fra.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(1.0000000000000000).Within(0.05)); + Assert.That(result[1], Is.EqualTo(0.9048374180359590).Within(0.05)); + Assert.That(result[2], Is.EqualTo(0.8187307530779810).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.5), "Variance is out of range"); + Assert.That(result[21], Is.EqualTo(0.1224564282529820).Within(0.05)); + Assert.That(result[22], Is.EqualTo(0.1108031583623340).Within(0.05)); + Assert.That(result[23], Is.EqualTo(0.1002588437228040).Within(0.05)); + + Assert.That(result[50], Is.EqualTo(0.0067379469990855).Within(0.002)); + + Assert.That(result[75], Is.EqualTo(0.0005530843701478).Within(0.0004)); + Assert.That(result[85], Is.EqualTo(0.0002034683690106).Within(0.0003)); + Assert.That(result[90], Is.EqualTo(0.0001234098040867).Within(0.0003)); + + Assert.That(result[97], Is.EqualTo(0.0000612834950532).Within(0.0002)); + Assert.That(result[98], Is.EqualTo(0.0000554515994322).Within(0.0002)); + Assert.That(result[99], Is.EqualTo(0.0000501746820562).Within(0.0002)); } [Test] @@ -40,9 +50,10 @@ namespace FastRngTests.Double.Distributions public async Task TestLaplaceGeneratorWithRange01() { var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.LaplaceB01M0(); 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.Laplace()); + samples[n] = await rng.NextNumber(-1.0, 1.0, dist); rng.StopProducer(); Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0), "Min out of range"); @@ -55,9 +66,10 @@ namespace FastRngTests.Double.Distributions public async Task TestLaplaceGeneratorWithRange02() { var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.LaplaceB01M0(); 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.Laplace()); + 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 +82,7 @@ namespace FastRngTests.Double.Distributions public async Task TestLaplaceGeneratorWithRange03() { var rng = new MultiThreadedRng(); - var dist = new FastRng.Double.Distributions.Laplace { Random = rng }; // Test default parameters + var dist = new FastRng.Double.Distributions.LaplaceB01M0 { Random = rng }; // Test default parameters var samples = new double[1_000]; for (var n = 0; n < samples.Length; n++) @@ -81,28 +93,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.Laplace(); - - Assert.DoesNotThrow(() => dist.Mean = -45); - Assert.DoesNotThrow(() => dist.Mean = 15); - Assert.DoesNotThrow(() => dist.Mean = 0); - - Assert.DoesNotThrow(() => dist.Scale = -45); - Assert.DoesNotThrow(() => dist.Scale = 15); - Assert.DoesNotThrow(() => dist.Scale = 0); - } - [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task NoRandomNumberGenerator01() { - var dist = new FastRng.Double.Distributions.Laplace(); + var dist = new FastRng.Double.Distributions.LaplaceB01M0(); Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); Assert.That(await dist.GetDistributedValue(), Is.NaN); } diff --git a/FastRngTests/Double/Distributions/LaplaceB01M05.cs b/FastRngTests/Double/Distributions/LaplaceB01M05.cs new file mode 100644 index 0000000..e478fa0 --- /dev/null +++ b/FastRngTests/Double/Distributions/LaplaceB01M05.cs @@ -0,0 +1,106 @@ +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 LaplaceB01M05 + { + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestLaplaceDistribution01() + { + var dist = new FastRng.Double.Distributions.LaplaceB01M05(); + var fra = new FrequencyAnalysis(); + var rng = new MultiThreadedRng(); + + for (var n = 0; n < 100_000; n++) + fra.CountThis(await rng.NextNumber(dist)); + + rng.StopProducer(); + var result = fra.NormalizeAndPlotEvents(TestContext.WriteLine); + + Assert.That(result[0], Is.EqualTo(0.0074465830709244).Within(0.003)); + Assert.That(result[1], Is.EqualTo(0.0082297470490200).Within(0.003)); + Assert.That(result[2], Is.EqualTo(0.0090952771016958).Within(0.003)); + + Assert.That(result[21], Is.EqualTo(0.0608100626252180).Within(0.01)); + Assert.That(result[22], Is.EqualTo(0.0672055127397498).Within(0.01)); + Assert.That(result[23], Is.EqualTo(0.0742735782143340).Within(0.01)); + + Assert.That(result[50], Is.EqualTo(1.0000000000000000).Within(0.2)); + + Assert.That(result[75], Is.EqualTo(0.0742735782143335).Within(0.008)); + Assert.That(result[85], Is.EqualTo(0.0273237224472924).Within(0.005)); + Assert.That(result[90], Is.EqualTo(0.0165726754017612).Within(0.003)); + + Assert.That(result[97], Is.EqualTo(0.0082297470490200).Within(0.0025)); + Assert.That(result[98], Is.EqualTo(0.0074465830709243).Within(0.0025)); + Assert.That(result[99], Is.EqualTo(0.0067379469990854).Within(0.0025)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestLaplaceGeneratorWithRange01() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.LaplaceB01M05(); + 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 TestLaplaceGeneratorWithRange02() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.LaplaceB01M05(); + 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 TestLaplaceGeneratorWithRange03() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.LaplaceB01M05 { 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.LaplaceB01M05(); + Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); + Assert.That(await dist.GetDistributedValue(), Is.NaN); + } + } +} \ No newline at end of file