From e01e89f3f865ba4a7ddcd4d0f1bde25b42e15f94 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 30 Oct 2020 22:52:44 +0100 Subject: [PATCH] Added lorentz variation --- .../Double/Distributions/CauchyLorentzX1.cs | 36 ++++++ .../Double/Distributions/CauchyLorentzX1.cs | 109 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 FastRng/Double/Distributions/CauchyLorentzX1.cs create mode 100644 FastRngTests/Double/Distributions/CauchyLorentzX1.cs diff --git a/FastRng/Double/Distributions/CauchyLorentzX1.cs b/FastRng/Double/Distributions/CauchyLorentzX1.cs new file mode 100644 index 0000000..eea6e5b --- /dev/null +++ b/FastRng/Double/Distributions/CauchyLorentzX1.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class CauchyLorentzX1 : IDistribution + { + private const double CONSTANT = 0.31; + private const double SCALE = 0.1; + private const double MEDIAN = 1.0; + + private ShapeFitter fitter; + private IRandom random; + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(CauchyLorentzX1.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * (1.0 / (Math.PI * SCALE)) * ((SCALE * SCALE) / (Math.Pow(x - MEDIAN, 2) + (SCALE * SCALE))); + + 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/CauchyLorentzX1.cs b/FastRngTests/Double/Distributions/CauchyLorentzX1.cs new file mode 100644 index 0000000..b617890 --- /dev/null +++ b/FastRngTests/Double/Distributions/CauchyLorentzX1.cs @@ -0,0 +1,109 @@ +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 CauchyLorentzX1 + { + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestCauchyDistribution01() + { + // The properties of the cauchy distribution cannot be tested by mean, media or variance, + // cf. https://en.wikipedia.org/wiki/Cauchy_distribution#Explanation_of_undefined_moments + + var dist = new FastRng.Double.Distributions.CauchyLorentzX1(); + 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(0.009966272570142).Within(0.003)); + Assert.That(result[1], Is.EqualTo(0.010168596941156).Within(0.003)); + Assert.That(result[2], Is.EqualTo(0.010377123221893).Within(0.005)); + + Assert.That(result[21], Is.EqualTo(0.015956672819692).Within(0.005)); + Assert.That(result[22], Is.EqualTo(0.016366904083094).Within(0.005)); + Assert.That(result[23], Is.EqualTo(0.016793067514802).Within(0.005)); + + Assert.That(result[50], Is.EqualTo(0.039454644029179).Within(0.015)); + + Assert.That(result[75], Is.EqualTo(0.145970509936354).Within(0.03)); + Assert.That(result[85], Is.EqualTo(0.333365083503296).Within(0.1)); + Assert.That(result[90], Is.EqualTo(0.545171628270584).Within(0.1)); + + Assert.That(result[97], Is.EqualTo(0.948808314586302).Within(0.06)); + Assert.That(result[98], Is.EqualTo(0.976990739772032).Within(0.03)); + Assert.That(result[99], Is.EqualTo(0.986760647169751).Within(0.02)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + public async Task TestCauchyGeneratorWithRange01() + { + var dist = new FastRng.Double.Distributions.CauchyLorentzX0(); + 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 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 TestCauchyGeneratorWithRange02() + { + var dist = new FastRng.Double.Distributions.CauchyLorentzX0(); + 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 TestCauchyGeneratorWithRange03() + { + var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.CauchyLorentzX0 { 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.CauchyLorentzX0(); + Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); + Assert.That(await dist.GetDistributedValue(), Is.NaN); + } + } +} \ No newline at end of file