diff --git a/FastRng/Double/Distributions/StudentT.cs b/FastRng/Double/Distributions/StudentT.cs deleted file mode 100644 index b605204..0000000 --- a/FastRng/Double/Distributions/StudentT.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace FastRng.Double.Distributions -{ - public sealed class StudentT : IDistribution - { - private static readonly IDistribution NORMAL_DISTRIBUTED = new Normal(); - 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; - - var normal = await this.Random.NextNumber(NORMAL_DISTRIBUTED, token); - var chiSquare = await this.Random.NextNumber(new ChiSquare {DegreesOfFreedom = this.DegreesOfFreedom}, token); - return normal / Math.Sqrt(chiSquare / this.DegreesOfFreedom); - } - } -} \ No newline at end of file diff --git a/FastRng/Double/Distributions/StudentTNu1.cs b/FastRng/Double/Distributions/StudentTNu1.cs new file mode 100644 index 0000000..91189e3 --- /dev/null +++ b/FastRng/Double/Distributions/StudentTNu1.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastRng.Double.Distributions +{ + public sealed class StudentTNu1 : IDistribution + { + private const double NU = 1.0; + private const double START = 0.0; + private const double COMPRESS = 1.0; + private const double CONSTANT = 3.14190548592729; + + private static readonly double DIVIDEND; + private static readonly double DIVISOR; + private static readonly double EXPONENT; + + private ShapeFitter fitter; + private IRandom random; + + static StudentTNu1() + { + DIVIDEND = MathTools.Gamma((NU + 1.0d) * 0.5d); + DIVISOR = Math.Sqrt(NU * Math.PI) * MathTools.Gamma(NU * 0.5d); + EXPONENT = -((NU + 1.0d) * 0.5d); + } + + public IRandom Random + { + get => this.random; + set + { + this.random = value; + this.fitter = new ShapeFitter(StudentTNu1.ShapeFunction, this.random, 100); + } + } + + private static double ShapeFunction(double x) => CONSTANT * Math.Pow((DIVIDEND / DIVISOR) * Math.Pow(1.0d + Math.Pow(START + x * COMPRESS, 2) / NU, EXPONENT), COMPRESS); + + 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/StudentT.cs b/FastRngTests/Double/Distributions/StudentTNu1.cs similarity index 66% rename from FastRngTests/Double/Distributions/StudentT.cs rename to FastRngTests/Double/Distributions/StudentTNu1.cs index ef7ad16..f56b9f1 100644 --- a/FastRngTests/Double/Distributions/StudentT.cs +++ b/FastRngTests/Double/Distributions/StudentTNu1.cs @@ -8,30 +8,40 @@ using NUnit.Framework; namespace FastRngTests.Double.Distributions { [ExcludeFromCodeCoverage] - public class StudentT + public class StudentTNu1 { [Test] [Category(TestCategories.COVER)] [Category(TestCategories.NORMAL)] public async Task TestStudentTDistribution01() { - const double DOF = 6; - const double MEAN = 0.0; - const double VARIANCE = DOF / (DOF - 2); - - var dist = new FastRng.Double.Distributions.StudentT{ DegreesOfFreedom = DOF }; - var stats = new RunningStatistics(); + var dist = new FastRng.Double.Distributions.StudentTNu1(); + 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.000000000).Within(0.2)); + Assert.That(result[1], Is.EqualTo(0.999700120).Within(0.2)); + Assert.That(result[2], Is.EqualTo(0.999200719).Within(0.2)); - 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.953929798).Within(0.2)); + Assert.That(result[22], Is.EqualTo(0.949852788).Within(0.2)); + Assert.That(result[23], Is.EqualTo(0.945631619).Within(0.2)); + + Assert.That(result[50], Is.EqualTo(0.793667169).Within(0.095)); + + Assert.That(result[75], Is.EqualTo(0.633937627).Within(0.09)); + Assert.That(result[85], Is.EqualTo(0.574902276).Within(0.09)); + Assert.That(result[90], Is.EqualTo(0.547070729).Within(0.09)); + + Assert.That(result[97], Is.EqualTo(0.510150990).Within(0.09)); + Assert.That(result[98], Is.EqualTo(0.505075501).Within(0.09)); + Assert.That(result[99], Is.EqualTo(0.500050000).Within(0.09)); } [Test] @@ -40,9 +50,10 @@ namespace FastRngTests.Double.Distributions public async Task TestStudentTGeneratorWithRange01() { var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.StudentTNu1(); 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.StudentT()); + 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 TestStudentTGeneratorWithRange02() { var rng = new MultiThreadedRng(); + var dist = new FastRng.Double.Distributions.StudentTNu1(); 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.StudentT()); + 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 TestStudentTGeneratorWithRange03() { var rng = new MultiThreadedRng(); - var dist = new FastRng.Double.Distributions.StudentT { Random = rng }; // Test default parameters + var dist = new FastRng.Double.Distributions.StudentTNu1 { Random = rng }; // Test default parameters var samples = new double[1_000]; for (var n = 0; n < samples.Length; n++) @@ -81,25 +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.StudentT(); - - 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.StudentT(); + var dist = new FastRng.Double.Distributions.StudentTNu1(); Assert.DoesNotThrowAsync(async () => await dist.GetDistributedValue()); Assert.That(await dist.GetDistributedValue(), Is.NaN); }