Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
639425c67d | |||
|
262dd6cf1f | ||
|
b2d8e2b5ed | ||
|
f9ea837f68 | ||
|
8ade2f5a5d | ||
|
817895015f | ||
|
a8121a306e | ||
|
4602bb14af | ||
|
0d1d48db3b | ||
d8f024a5c5 | |||
|
f1fe1de090 | ||
|
a838dacf2a | ||
|
faf6c99c19 | ||
9024752b53 | |||
|
e932df93cf | ||
2e9a5e336f | |||
|
e98378f246 | ||
fc8e666680 | |||
|
3b62152957 | ||
7d56951040 | |||
2f836892d1 | |||
146d524da8 | |||
1ec7ba7bba | |||
88b4a19345 | |||
df003facfd | |||
36c94be6d0 | |||
f39064dc05 | |||
|
7a14b60795 | ||
|
d0eca012ce | ||
|
1c28a57c06 | ||
|
6b656b90e3 | ||
|
e8cf1284d7 | ||
|
38764d5d97 | ||
|
7bc829482e | ||
|
d1d89fc7f6 | ||
|
944bdf36c1 | ||
|
9bc0fdf5a1 | ||
488025ad6c | |||
6b8a27b565 | |||
37e846233d | |||
afcd2f3dc8 | |||
23ee9d4684 |
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ContentModelUserStore">
|
<component name="UserContentModel">
|
||||||
<attachedFolders />
|
<attachedFolders />
|
||||||
<explicitIncludes />
|
<explicitIncludes />
|
||||||
<explicitExcludes />
|
<explicitExcludes />
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.FastRng/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.FastRng/riderModule.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="RIDER_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$USER_HOME$/.nuget/packages/microsoft.net.test.sdk/16.5.0/build/netcoreapp2.1" />
|
|
||||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.16.1/build/netcoreapp2.1/NUnit3.TestAdapter.dll" />
|
|
||||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.16.1/build/netcoreapp2.1/NUnit3.TestAdapter.pdb" />
|
|
||||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.16.1/build/netcoreapp2.1/nunit.engine.api.dll" />
|
|
||||||
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.16.1/build/netcoreapp2.1/nunit.engine.dll" />
|
|
||||||
<content url="file://$MODULE_DIR$/../.." />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
17
FastRng/Distributions/BetaA2B2.cs
Normal file
17
FastRng/Distributions/BetaA2B2.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class BetaA2B2<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum ALPHA = TNum.One + TNum.One;
|
||||||
|
private static readonly TNum BETA = TNum.One + TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(4);
|
||||||
|
|
||||||
|
public BetaA2B2(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
||||||
|
}
|
17
FastRng/Distributions/BetaA2B5.cs
Normal file
17
FastRng/Distributions/BetaA2B5.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class BetaA2B5<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum ALPHA = TNum.One + TNum.One;
|
||||||
|
private static readonly TNum BETA = TNum.CreateChecked(5);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(12.2f);
|
||||||
|
|
||||||
|
public BetaA2B5(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
||||||
|
}
|
17
FastRng/Distributions/BetaA5B2.cs
Normal file
17
FastRng/Distributions/BetaA5B2.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class BetaA5B2<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum ALPHA = TNum.CreateChecked(5f);
|
||||||
|
private static readonly TNum BETA = TNum.One + TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(12.2f);
|
||||||
|
|
||||||
|
public BetaA5B2(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
||||||
|
}
|
18
FastRng/Distributions/CauchyLorentzX0.cs
Normal file
18
FastRng/Distributions/CauchyLorentzX0.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class CauchyLorentzX0<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.31f);
|
||||||
|
private static readonly TNum SCALE = TNum.CreateChecked(0.1f);
|
||||||
|
private static readonly TNum MEDIAN = TNum.Zero;
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
|
||||||
|
public CauchyLorentzX0(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ( TNum.One / (TNum.Pi * SCALE)) * ((SCALE * SCALE) / (TNum.Pow(x - MEDIAN, TWO) + (SCALE * SCALE)));
|
||||||
|
}
|
18
FastRng/Distributions/CauchyLorentzX1.cs
Normal file
18
FastRng/Distributions/CauchyLorentzX1.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class CauchyLorentzX1<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.31f);
|
||||||
|
private static readonly TNum SCALE = TNum.CreateChecked(0.1f);
|
||||||
|
private static readonly TNum MEDIAN = TNum.One;
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
|
||||||
|
public CauchyLorentzX1(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * (TNum.One / (TNum.Pi * SCALE)) * ((SCALE * SCALE) / (TNum.Pow(x - MEDIAN, TWO) + (SCALE * SCALE)));
|
||||||
|
}
|
29
FastRng/Distributions/ChiSquareK1.cs
Normal file
29
FastRng/Distributions/ChiSquareK1.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class ChiSquareK1<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum HALF = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
private static readonly TNum K = TNum.One;
|
||||||
|
private static readonly TNum K_HALF = K * HALF;
|
||||||
|
private static readonly TNum K_HALF_MINUS_ONE = K_HALF - TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.252f);
|
||||||
|
|
||||||
|
private static readonly TNum DIVISOR;
|
||||||
|
|
||||||
|
static ChiSquareK1()
|
||||||
|
{
|
||||||
|
var twoToTheKHalf = TNum.Pow(TWO, K_HALF);
|
||||||
|
var gammaKHalf = MathToolsFloatingPoint<TNum>.Gamma(K_HALF);
|
||||||
|
DIVISOR = twoToTheKHalf * gammaKHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChiSquareK1(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
||||||
|
}
|
29
FastRng/Distributions/ChiSquareK10.cs
Normal file
29
FastRng/Distributions/ChiSquareK10.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class ChiSquareK10<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum HALF = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
private static readonly TNum K = TNum.CreateChecked(10.0f);
|
||||||
|
private static readonly TNum K_HALF = K * HALF;
|
||||||
|
private static readonly TNum K_HALF_MINUS_ONE = K_HALF - TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.252f);
|
||||||
|
|
||||||
|
private static readonly TNum DIVISOR;
|
||||||
|
|
||||||
|
static ChiSquareK10()
|
||||||
|
{
|
||||||
|
var twoToTheKHalf = TNum.Pow(TWO, K_HALF);
|
||||||
|
var gammaKHalf = MathToolsFloatingPoint<TNum>.Gamma(K_HALF);
|
||||||
|
DIVISOR = twoToTheKHalf * gammaKHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChiSquareK10(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
||||||
|
}
|
29
FastRng/Distributions/ChiSquareK4.cs
Normal file
29
FastRng/Distributions/ChiSquareK4.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class ChiSquareK4<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum HALF = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
private static readonly TNum K = TNum.CreateChecked(4f);
|
||||||
|
private static readonly TNum K_HALF = K * HALF;
|
||||||
|
private static readonly TNum K_HALF_MINUS_ONE = K_HALF - TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.252f);
|
||||||
|
|
||||||
|
private static readonly TNum DIVISOR;
|
||||||
|
|
||||||
|
static ChiSquareK4()
|
||||||
|
{
|
||||||
|
var twoToTheKHalf = TNum.Pow(TWO, K_HALF);
|
||||||
|
var gammaKHalf = MathToolsFloatingPoint<TNum>.Gamma(K_HALF);
|
||||||
|
DIVISOR = twoToTheKHalf * gammaKHalf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChiSquareK4(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
||||||
|
}
|
63
FastRng/Distributions/Distribution.cs
Normal file
63
FastRng/Distributions/Distribution.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public abstract class Distribution<TNum> : IDistribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private readonly ShapeFitter<TNum> fitter;
|
||||||
|
|
||||||
|
protected Distribution(IRandom<TNum> rng)
|
||||||
|
{
|
||||||
|
if (rng == null)
|
||||||
|
throw new ArgumentNullException(nameof(rng), "An IRandom<TNum> implementation is needed.");
|
||||||
|
|
||||||
|
this.fitter = new ShapeFitter<TNum>(this.ShapeFunction, rng, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract TNum ShapeFunction(TNum x);
|
||||||
|
|
||||||
|
public TNum GetDistributedValue(CancellationToken token = default) => this.fitter.NextNumber(token);
|
||||||
|
|
||||||
|
public uint NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
// Swap the values if the range start is greater than the range end:
|
||||||
|
if (rangeStart > rangeEnd)
|
||||||
|
(rangeStart, rangeEnd) = (rangeEnd, rangeStart);
|
||||||
|
|
||||||
|
var range = rangeEnd - rangeStart;
|
||||||
|
var distributedValue = this.GetDistributedValue(cancel);
|
||||||
|
return (uint) ((float.CreateChecked(distributedValue) * range) + rangeStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
// Swap the values if the range start is greater than the range end:
|
||||||
|
if (rangeStart > rangeEnd)
|
||||||
|
(rangeStart, rangeEnd) = (rangeEnd, rangeStart);
|
||||||
|
|
||||||
|
var range = rangeEnd - rangeStart;
|
||||||
|
var distributedValue = this.GetDistributedValue(cancel);
|
||||||
|
return (ulong) ((double.CreateChecked(distributedValue) * range) + rangeStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TNum NextNumber(TNum rangeStart, TNum rangeEnd, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
// Swap the values if the range start is greater than the range end:
|
||||||
|
if (rangeStart > rangeEnd)
|
||||||
|
(rangeStart, rangeEnd) = (rangeEnd, rangeStart);
|
||||||
|
|
||||||
|
var range = rangeEnd - rangeStart;
|
||||||
|
var distributedValue = this.GetDistributedValue(cancel);
|
||||||
|
return (distributedValue * range) + rangeStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TNum NextNumber(CancellationToken cancel = default) => this.NextNumber(TNum.Zero, TNum.One, cancel);
|
||||||
|
|
||||||
|
public bool HasDecisionBeenMade(TNum above, TNum below, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var number = this.NextNumber(cancel);
|
||||||
|
return number > above && number < below;
|
||||||
|
}
|
||||||
|
}
|
16
FastRng/Distributions/ExponentialLa10.cs
Normal file
16
FastRng/Distributions/ExponentialLa10.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class ExponentialLa10<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum LAMBDA = TNum.CreateChecked(10.0f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.1106f);
|
||||||
|
|
||||||
|
public ExponentialLa10(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(-LAMBDA * x);
|
||||||
|
}
|
16
FastRng/Distributions/ExponentialLa5.cs
Normal file
16
FastRng/Distributions/ExponentialLa5.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class ExponentialLa5<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum LAMBDA = TNum.CreateChecked(5.0f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.2103f);
|
||||||
|
|
||||||
|
public ExponentialLa5(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(-LAMBDA * x);
|
||||||
|
}
|
26
FastRng/Distributions/GammaA5B15.cs
Normal file
26
FastRng/Distributions/GammaA5B15.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class GammaA5B15<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum ALPHA = TNum.CreateChecked(5.0f);
|
||||||
|
private static readonly TNum BETA = TNum.CreateChecked(15.0f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.341344210715475f);
|
||||||
|
|
||||||
|
private static readonly TNum GAMMA_ALPHA;
|
||||||
|
private static readonly TNum BETA_TO_THE_ALPHA;
|
||||||
|
|
||||||
|
static GammaA5B15()
|
||||||
|
{
|
||||||
|
GAMMA_ALPHA = MathToolsFloatingPoint<TNum>.Gamma(ALPHA);
|
||||||
|
BETA_TO_THE_ALPHA = TNum.Pow(BETA, ALPHA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GammaA5B15(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((BETA_TO_THE_ALPHA * TNum.Pow(x, ALPHA - TNum.One) * TNum.Exp(-BETA * x)) / GAMMA_ALPHA);
|
||||||
|
}
|
19
FastRng/Distributions/IDistribution.cs
Normal file
19
FastRng/Distributions/IDistribution.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public interface IDistribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
public TNum GetDistributedValue(CancellationToken token = default);
|
||||||
|
|
||||||
|
public uint NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default);
|
||||||
|
|
||||||
|
public ulong NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default);
|
||||||
|
|
||||||
|
public TNum NextNumber(TNum rangeStart, TNum rangeEnd, CancellationToken cancel = default);
|
||||||
|
|
||||||
|
public TNum NextNumber(CancellationToken cancel = default);
|
||||||
|
|
||||||
|
public bool HasDecisionBeenMade(TNum above, TNum below, CancellationToken cancel = default);
|
||||||
|
}
|
16
FastRng/Distributions/InverseExponentialLa10.cs
Normal file
16
FastRng/Distributions/InverseExponentialLa10.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class InverseExponentialLa10<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum LAMBDA = TNum.CreateChecked(10.0f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(4.539992976248453e-06f);
|
||||||
|
|
||||||
|
public InverseExponentialLa10(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(LAMBDA * x);
|
||||||
|
}
|
16
FastRng/Distributions/InverseExponentialLa5.cs
Normal file
16
FastRng/Distributions/InverseExponentialLa5.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class InverseExponentialLa5<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum LAMBDA = TNum.CreateChecked(5.0f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.001347589399817f);
|
||||||
|
|
||||||
|
public InverseExponentialLa5(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(LAMBDA * x);
|
||||||
|
}
|
27
FastRng/Distributions/InverseGammaA3B05.cs
Normal file
27
FastRng/Distributions/InverseGammaA3B05.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class InverseGammaA3B05<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum ALPHA = TNum.CreateChecked(3.0f);
|
||||||
|
private static readonly TNum BETA = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.213922656884911f);
|
||||||
|
|
||||||
|
private static readonly TNum FACTOR_LEFT;
|
||||||
|
|
||||||
|
static InverseGammaA3B05()
|
||||||
|
{
|
||||||
|
var gammaAlpha = MathToolsFloatingPoint<TNum>.Gamma(ALPHA);
|
||||||
|
var betaToTheAlpha = TNum.Pow(BETA, ALPHA);
|
||||||
|
|
||||||
|
FACTOR_LEFT = CONSTANT * (betaToTheAlpha / gammaAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InverseGammaA3B05(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Pow(x, -ALPHA - TNum.One) * TNum.Exp(-BETA / x);
|
||||||
|
}
|
25
FastRng/Distributions/LaplaceB01M0.cs
Normal file
25
FastRng/Distributions/LaplaceB01M0.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class LaplaceB01M0<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2.0f);
|
||||||
|
private static readonly TNum B = TNum.CreateChecked(0.1f);
|
||||||
|
private static readonly TNum MU = TNum.Zero;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.221034183615129f);
|
||||||
|
|
||||||
|
private static readonly TNum FACTOR_LEFT;
|
||||||
|
|
||||||
|
static LaplaceB01M0()
|
||||||
|
{
|
||||||
|
FACTOR_LEFT = CONSTANT / (TWO * B);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LaplaceB01M0(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Exp(-TNum.Abs(x - MU) / B);
|
||||||
|
}
|
25
FastRng/Distributions/LaplaceB01M05.cs
Normal file
25
FastRng/Distributions/LaplaceB01M05.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class LaplaceB01M05<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2.0f);
|
||||||
|
private static readonly TNum B = TNum.CreateChecked(0.1f);
|
||||||
|
private static readonly TNum MU = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.2f);
|
||||||
|
|
||||||
|
private static readonly TNum FACTOR_LEFT;
|
||||||
|
|
||||||
|
static LaplaceB01M05()
|
||||||
|
{
|
||||||
|
FACTOR_LEFT = CONSTANT / (TWO * B);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LaplaceB01M05(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Exp(-TNum.Abs(x - MU) / B);
|
||||||
|
}
|
25
FastRng/Distributions/LogNormalS1M0.cs
Normal file
25
FastRng/Distributions/LogNormalS1M0.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class LogNormalS1M0<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
private static readonly TNum SIGMA = TNum.One;
|
||||||
|
private static readonly TNum MU = TNum.Zero;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(1.51998658387455f);
|
||||||
|
|
||||||
|
private static readonly TNum FACTOR;
|
||||||
|
|
||||||
|
static LogNormalS1M0()
|
||||||
|
{
|
||||||
|
FACTOR = SIGMA * TNum.Sqrt(TWO * TNum.Pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogNormalS1M0(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => (CONSTANT / (x * FACTOR)) * TNum.Exp( -(TNum.Pow(TNum.Log(x) - MU, TWO) / (TWO * TNum.Pow(SIGMA, TWO))));
|
||||||
|
}
|
19
FastRng/Distributions/NormalS02M05.cs
Normal file
19
FastRng/Distributions/NormalS02M05.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class NormalS02M05<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
private static readonly TNum NEGATIVE_HALF = TNum.CreateChecked(-0.5f);
|
||||||
|
private static readonly TNum SQRT_2_PI = TNum.CreateChecked(2.506628275f);
|
||||||
|
private static readonly TNum STD_DEV = TNum.CreateChecked(0.2f);
|
||||||
|
private static readonly TNum MEAN = TNum.CreateChecked(0.5f);
|
||||||
|
|
||||||
|
public NormalS02M05(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => TNum.One / (STD_DEV * SQRT_2_PI) * TNum.Exp(NEGATIVE_HALF * TNum.Pow((x - MEAN) / STD_DEV, TWO));
|
||||||
|
}
|
31
FastRng/Distributions/StudentTNu1.cs
Normal file
31
FastRng/Distributions/StudentTNu1.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class StudentTNu1<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum HALF = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum TWO = TNum.CreateChecked(2f);
|
||||||
|
private static readonly TNum NU = TNum.One;
|
||||||
|
private static readonly TNum START = TNum.Zero;
|
||||||
|
private static readonly TNum COMPRESS = TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(3.14190548592729f);
|
||||||
|
|
||||||
|
private static readonly TNum DIVIDEND;
|
||||||
|
private static readonly TNum DIVISOR;
|
||||||
|
private static readonly TNum EXPONENT;
|
||||||
|
|
||||||
|
static StudentTNu1()
|
||||||
|
{
|
||||||
|
DIVIDEND = MathToolsFloatingPoint<TNum>.Gamma((NU + TNum.One) * HALF);
|
||||||
|
DIVISOR = TNum.Sqrt(NU * TNum.Pi) * MathToolsFloatingPoint<TNum>.Gamma(NU * HALF);
|
||||||
|
EXPONENT = -((NU + TNum.One) * HALF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StudentTNu1(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow((DIVIDEND / DIVISOR) * TNum.Pow( TNum.One + TNum.Pow(START + x * COMPRESS, TWO) / NU, EXPONENT), COMPRESS);
|
||||||
|
}
|
58
FastRng/Distributions/Uniform.cs
Normal file
58
FastRng/Distributions/Uniform.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class Uniform<TNum> : IDistribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private readonly IRandom<TNum> rng;
|
||||||
|
|
||||||
|
public Uniform(IRandom<TNum> rng)
|
||||||
|
{
|
||||||
|
this.rng = rng ?? throw new ArgumentNullException(nameof(rng), "An IRandom<TNum> implementation is needed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TNum GetDistributedValue(CancellationToken token = default) => this.rng.GetUniform(token);
|
||||||
|
|
||||||
|
public uint NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
// Swap the values if the range start is greater than the range end:
|
||||||
|
if (rangeStart > rangeEnd)
|
||||||
|
(rangeStart, rangeEnd) = (rangeEnd, rangeStart);
|
||||||
|
|
||||||
|
var range = rangeEnd - rangeStart;
|
||||||
|
var distributedValue = this.GetDistributedValue(cancel);
|
||||||
|
return (uint) ((float.CreateChecked(distributedValue) * range) + rangeStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
// Swap the values if the range start is greater than the range end:
|
||||||
|
if (rangeStart > rangeEnd)
|
||||||
|
(rangeStart, rangeEnd) = (rangeEnd, rangeStart);
|
||||||
|
|
||||||
|
var range = rangeEnd - rangeStart;
|
||||||
|
var distributedValue = this.GetDistributedValue(cancel);
|
||||||
|
return (ulong) ((double.CreateChecked(distributedValue) * range) + rangeStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TNum NextNumber(TNum rangeStart, TNum rangeEnd, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
// Swap the values if the range start is greater than the range end:
|
||||||
|
if (rangeStart > rangeEnd)
|
||||||
|
(rangeStart, rangeEnd) = (rangeEnd, rangeStart);
|
||||||
|
|
||||||
|
var range = rangeEnd - rangeStart;
|
||||||
|
var distributedValue = this.GetDistributedValue(cancel);
|
||||||
|
return (distributedValue * range) + rangeStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TNum NextNumber(CancellationToken cancel = default) => this.NextNumber(TNum.Zero, TNum.One, cancel);
|
||||||
|
|
||||||
|
public bool HasDecisionBeenMade(TNum above, TNum below, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var number = this.NextNumber(cancel);
|
||||||
|
return number > above && number < below;
|
||||||
|
}
|
||||||
|
}
|
17
FastRng/Distributions/WeibullK05La1.cs
Normal file
17
FastRng/Distributions/WeibullK05La1.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng.Distributions;
|
||||||
|
|
||||||
|
public sealed class WeibullK05La1<TNum> : Distribution<TNum> where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum K = TNum.CreateChecked(0.5f);
|
||||||
|
private static readonly TNum LAMBDA = TNum.One;
|
||||||
|
private static readonly TNum CONSTANT = TNum.CreateChecked(0.221034183615129f);
|
||||||
|
|
||||||
|
public WeibullK05La1(IRandom<TNum> rng) : base(rng)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ( (K / LAMBDA) * TNum.Pow(x / LAMBDA, K - TNum.One) * TNum.Exp(-TNum.Pow(x/LAMBDA, K)));
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class BetaA2B2 : Distribution
|
|
||||||
{
|
|
||||||
private const double ALPHA = 2;
|
|
||||||
private const double BETA = 2;
|
|
||||||
private const double CONSTANT = 4;
|
|
||||||
|
|
||||||
public BetaA2B2(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * Math.Pow(x, ALPHA - 1) * Math.Pow(1 - x, BETA - 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class BetaA2B5 : Distribution
|
|
||||||
{
|
|
||||||
private const double ALPHA = 2;
|
|
||||||
private const double BETA = 5;
|
|
||||||
private const double CONSTANT = 12.2;
|
|
||||||
|
|
||||||
public BetaA2B5(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * Math.Pow(x, ALPHA - 1) * Math.Pow(1 - x, BETA - 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class BetaA5B2 : Distribution
|
|
||||||
{
|
|
||||||
private const double ALPHA = 5;
|
|
||||||
private const double BETA = 2;
|
|
||||||
private const double CONSTANT = 12.2;
|
|
||||||
|
|
||||||
public BetaA5B2(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * Math.Pow(x, ALPHA - 1) * Math.Pow(1 - x, BETA - 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class CauchyLorentzX0 : Distribution
|
|
||||||
{
|
|
||||||
private const double CONSTANT = 0.31;
|
|
||||||
private const double SCALE = 0.1;
|
|
||||||
private const double MEDIAN = 0.0;
|
|
||||||
|
|
||||||
public CauchyLorentzX0(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * (1.0 / (Math.PI * SCALE)) * ((SCALE * SCALE) / (Math.Pow(x - MEDIAN, 2) + (SCALE * SCALE)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class CauchyLorentzX1 : Distribution
|
|
||||||
{
|
|
||||||
private const double CONSTANT = 0.31;
|
|
||||||
private const double SCALE = 0.1;
|
|
||||||
private const double MEDIAN = 1.0;
|
|
||||||
|
|
||||||
public CauchyLorentzX1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * (1.0 / (Math.PI * SCALE)) * ((SCALE * SCALE) / (Math.Pow(x - MEDIAN, 2) + (SCALE * SCALE)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ChiSquareK1 : Distribution
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
static ChiSquareK1()
|
|
||||||
{
|
|
||||||
var twoToTheKHalf = Math.Pow(2, K_HALF);
|
|
||||||
var gammaKHalf = MathTools.Gamma(K_HALF);
|
|
||||||
DIVISOR = twoToTheKHalf * gammaKHalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChiSquareK1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * ((Math.Pow(x, K_HALF_MINUS_ONE) * Math.Exp(-x * 0.5d)) / DIVISOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ChiSquareK10 : Distribution
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
static ChiSquareK10()
|
|
||||||
{
|
|
||||||
var twoToTheKHalf = Math.Pow(2, K_HALF);
|
|
||||||
var gammaKHalf = MathTools.Gamma(K_HALF);
|
|
||||||
DIVISOR = twoToTheKHalf * gammaKHalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChiSquareK10(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * ((Math.Pow(x, K_HALF_MINUS_ONE) * Math.Exp(-x * 0.5d)) / DIVISOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ChiSquareK4 : Distribution
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
static ChiSquareK4()
|
|
||||||
{
|
|
||||||
var twoToTheKHalf = Math.Pow(2, K_HALF);
|
|
||||||
var gammaKHalf = MathTools.Gamma(K_HALF);
|
|
||||||
DIVISOR = twoToTheKHalf * gammaKHalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChiSquareK4(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * ((Math.Pow(x, K_HALF_MINUS_ONE) * Math.Exp(-x * 0.5d)) / DIVISOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public abstract class Distribution : IDistribution
|
|
||||||
{
|
|
||||||
private readonly ShapeFitter fitter;
|
|
||||||
private readonly IRandom random;
|
|
||||||
|
|
||||||
protected Distribution(IRandom rng)
|
|
||||||
{
|
|
||||||
if (rng == null)
|
|
||||||
throw new ArgumentNullException(nameof(rng), "An IRandom implementation is needed.");
|
|
||||||
|
|
||||||
this.random = rng;
|
|
||||||
this.fitter = new ShapeFitter(this.ShapeFunction, this.random, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract double ShapeFunction(double x);
|
|
||||||
|
|
||||||
public async ValueTask<double> GetDistributedValue(CancellationToken token = default) => await this.fitter.NextNumber(token);
|
|
||||||
|
|
||||||
public async ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (uint) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (ulong) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<double> NextNumber(double rangeStart, double rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (distributedValue * range) + rangeStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<double> NextNumber(CancellationToken cancel = default) => await this.NextNumber(0.0, 1.0, cancel);
|
|
||||||
|
|
||||||
public async ValueTask<bool> HasDecisionBeenMade(double above, double below = 1, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
var number = await this.NextNumber(cancel);
|
|
||||||
return number > above && number < below;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ExponentialLa10 : Distribution
|
|
||||||
{
|
|
||||||
private const double LAMBDA = 10.0;
|
|
||||||
private const double CONSTANT = 0.1106;
|
|
||||||
|
|
||||||
public ExponentialLa10(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * LAMBDA * Math.Exp(-LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ExponentialLa5 : Distribution
|
|
||||||
{
|
|
||||||
private const double LAMBDA = 5.0;
|
|
||||||
private const double CONSTANT = 0.2103;
|
|
||||||
|
|
||||||
public ExponentialLa5(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * LAMBDA * Math.Exp(-LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class GammaA5B15 : Distribution
|
|
||||||
{
|
|
||||||
private const double ALPHA = 5.0;
|
|
||||||
private const double BETA = 15.0;
|
|
||||||
private const double CONSTANT = 0.341344210715475;
|
|
||||||
|
|
||||||
private static readonly double GAMMA_ALPHA;
|
|
||||||
private static readonly double BETA_TO_THE_ALPHA;
|
|
||||||
|
|
||||||
static GammaA5B15()
|
|
||||||
{
|
|
||||||
GAMMA_ALPHA = MathTools.Gamma(ALPHA);
|
|
||||||
BETA_TO_THE_ALPHA = Math.Pow(BETA, ALPHA);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GammaA5B15(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * ((BETA_TO_THE_ALPHA * Math.Pow(x, ALPHA - 1.0d) * Math.Exp(-BETA * x)) / GAMMA_ALPHA);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public interface IDistribution
|
|
||||||
{
|
|
||||||
public ValueTask<double> GetDistributedValue(CancellationToken token);
|
|
||||||
|
|
||||||
public ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<double> NextNumber(double rangeStart, double rangeEnd, CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<double> NextNumber(CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<bool> HasDecisionBeenMade(double above, double below = 1.0, CancellationToken cancel = default);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class InverseExponentialLa10 : Distribution
|
|
||||||
{
|
|
||||||
private const double LAMBDA = 10.0;
|
|
||||||
private const double CONSTANT = 4.539992976248453e-06;
|
|
||||||
|
|
||||||
public InverseExponentialLa10(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * LAMBDA * Math.Exp(LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class InverseExponentialLa5 : Distribution
|
|
||||||
{
|
|
||||||
private const double LAMBDA = 5.0;
|
|
||||||
private const double CONSTANT = 0.001347589399817;
|
|
||||||
|
|
||||||
public InverseExponentialLa5(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * LAMBDA * Math.Exp(LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class InverseGammaA3B05 : Distribution
|
|
||||||
{
|
|
||||||
private const double ALPHA = 3.0;
|
|
||||||
private const double BETA = 0.5;
|
|
||||||
private const double CONSTANT = 0.213922656884911;
|
|
||||||
|
|
||||||
private static readonly double FACTOR_LEFT;
|
|
||||||
|
|
||||||
static InverseGammaA3B05()
|
|
||||||
{
|
|
||||||
var gammaAlpha = MathTools.Gamma(ALPHA);
|
|
||||||
var betaToTheAlpha = Math.Pow(BETA, ALPHA);
|
|
||||||
|
|
||||||
FACTOR_LEFT = CONSTANT * (betaToTheAlpha / gammaAlpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InverseGammaA3B05(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => FACTOR_LEFT * Math.Pow(x, -ALPHA - 1.0d) * Math.Exp(-BETA / x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class LaplaceB01M0 : Distribution
|
|
||||||
{
|
|
||||||
private const double B = 0.1;
|
|
||||||
private const double MU = 0.0;
|
|
||||||
private const double CONSTANT = 0.221034183615129;
|
|
||||||
|
|
||||||
private static readonly double FACTOR_LEFT;
|
|
||||||
|
|
||||||
static LaplaceB01M0()
|
|
||||||
{
|
|
||||||
FACTOR_LEFT = CONSTANT / (2.0d * B);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LaplaceB01M0(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => FACTOR_LEFT * Math.Exp(-Math.Abs(x - MU) / B);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class LaplaceB01M05 : Distribution
|
|
||||||
{
|
|
||||||
private const double B = 0.1;
|
|
||||||
private const double MU = 0.5;
|
|
||||||
private const double CONSTANT = 0.2;
|
|
||||||
|
|
||||||
private static readonly double FACTOR_LEFT;
|
|
||||||
|
|
||||||
static LaplaceB01M05()
|
|
||||||
{
|
|
||||||
FACTOR_LEFT = CONSTANT / (2.0d * B);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LaplaceB01M05(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => FACTOR_LEFT * Math.Exp(-Math.Abs(x - MU) / B);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class LogNormalS1M0 : Distribution
|
|
||||||
{
|
|
||||||
private const double SIGMA = 1.0;
|
|
||||||
private const double MU = 0.0;
|
|
||||||
private const double CONSTANT = 1.51998658387455;
|
|
||||||
|
|
||||||
private static readonly double FACTOR;
|
|
||||||
|
|
||||||
static LogNormalS1M0()
|
|
||||||
{
|
|
||||||
FACTOR = SIGMA * Math.Sqrt(2 * Math.PI);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogNormalS1M0(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => (CONSTANT / (x * FACTOR)) * Math.Exp( -(Math.Pow(Math.Log(x) - MU, 2) / (2 * Math.Pow(SIGMA, 2))));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class NormalS02M05 : Distribution
|
|
||||||
{
|
|
||||||
private const double SQRT_2_PI = 2.506628275;
|
|
||||||
private const double STDDEV = 0.2;
|
|
||||||
private const double MEAN = 0.5;
|
|
||||||
|
|
||||||
public NormalS02M05(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => 1.0 / (STDDEV * SQRT_2_PI) * Math.Exp(-0.5 * Math.Pow((x - MEAN) / STDDEV, 2.0));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class StudentTNu1 : Distribution
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
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 StudentTNu1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * Math.Pow((DIVIDEND / DIVISOR) * Math.Pow(1.0d + Math.Pow(START + x * COMPRESS, 2) / NU, EXPONENT), COMPRESS);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class Uniform : IDistribution
|
|
||||||
{
|
|
||||||
private readonly IRandom rng;
|
|
||||||
|
|
||||||
public Uniform(IRandom rng)
|
|
||||||
{
|
|
||||||
if (rng == null)
|
|
||||||
throw new ArgumentNullException(nameof(rng), "An IRandom implementation is needed.");
|
|
||||||
|
|
||||||
this.rng = rng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<double> GetDistributedValue(CancellationToken token = default) => await this.rng.GetUniform(token);
|
|
||||||
|
|
||||||
public async ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (uint) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (ulong) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<double> NextNumber(double rangeStart, double rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (distributedValue * range) + rangeStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<double> NextNumber(CancellationToken cancel = default) => await this.NextNumber(0.0, 1.0, cancel);
|
|
||||||
|
|
||||||
public async ValueTask<bool> HasDecisionBeenMade(double above, double below = 1, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
var number = await this.NextNumber(cancel);
|
|
||||||
return number > above && number < below;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Double.Distributions
|
|
||||||
{
|
|
||||||
public sealed class WeibullK05La1 : Distribution
|
|
||||||
{
|
|
||||||
private const double K = 0.5;
|
|
||||||
private const double LAMBDA = 1.0;
|
|
||||||
private const double CONSTANT = 0.221034183615129;
|
|
||||||
|
|
||||||
public WeibullK05La1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double ShapeFunction(double x) => CONSTANT * ( (K / LAMBDA) * Math.Pow(x / LAMBDA, K - 1.0d) * Math.Exp(-Math.Pow(x/LAMBDA, K)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FastRng.Double.Distributions;
|
|
||||||
|
|
||||||
namespace FastRng.Double
|
|
||||||
{
|
|
||||||
public interface IRandom : IDisposable
|
|
||||||
{
|
|
||||||
public ValueTask<double> GetUniform(CancellationToken cancel = default);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Double
|
|
||||||
{
|
|
||||||
public static class MathTools
|
|
||||||
{
|
|
||||||
private static readonly double SQRT_2 = Math.Sqrt(2.0);
|
|
||||||
private static readonly double SQRT_PI = Math.Sqrt(Math.PI);
|
|
||||||
|
|
||||||
public static double Gamma(double z)
|
|
||||||
{
|
|
||||||
// Source: http://rosettacode.org/wiki/Gamma_function#Go
|
|
||||||
|
|
||||||
const double F1 = 6.5;
|
|
||||||
const double A1 = .99999999999980993;
|
|
||||||
const double A2 = 676.5203681218851;
|
|
||||||
const double A3 = 1259.1392167224028;
|
|
||||||
const double A4 = 771.32342877765313;
|
|
||||||
const double A5 = 176.61502916214059;
|
|
||||||
const double A6 = 12.507343278686905;
|
|
||||||
const double A7 = .13857109526572012;
|
|
||||||
const double A8 = 9.9843695780195716e-6;
|
|
||||||
const double A9 = 1.5056327351493116e-7;
|
|
||||||
|
|
||||||
var t = z + F1;
|
|
||||||
var x = A1 +
|
|
||||||
A2 / z -
|
|
||||||
A3 / (z + 1) +
|
|
||||||
A4 / (z + 2) -
|
|
||||||
A5 / (z + 3) +
|
|
||||||
A6 / (z + 4) -
|
|
||||||
A7 / (z + 5) +
|
|
||||||
A8 / (z + 6) +
|
|
||||||
A9 / (z + 7);
|
|
||||||
|
|
||||||
return MathTools.SQRT_2 * MathTools.SQRT_PI * Math.Pow(t, z - 0.5) * Math.Exp(-t) * x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double Factorial(double x) => MathTools.Gamma(x + 1.0);
|
|
||||||
|
|
||||||
public static ulong Factorial(uint x)
|
|
||||||
{
|
|
||||||
if (x > 20)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(x), $"Cannot compute {x}!, since ulong.max is 18_446_744_073_709_551_615.");
|
|
||||||
|
|
||||||
ulong accumulator = 1;
|
|
||||||
for (uint factor = 1; factor <= x; factor++)
|
|
||||||
accumulator *= factor;
|
|
||||||
|
|
||||||
return accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ulong Factorial(int x)
|
|
||||||
{
|
|
||||||
if(x < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(x), "Given value must be greater as zero.");
|
|
||||||
|
|
||||||
return MathTools.Factorial((uint) x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,308 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FastRng.Double.Distributions;
|
|
||||||
|
|
||||||
namespace FastRng.Double
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A fast multi-threaded pseudo random number generator.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster.
|
|
||||||
/// Unlike Math.NET, MultiThreadedRng is multi-threaded and async. Consumers can await the next number without
|
|
||||||
/// blocking resources. Additionally, consumers can use a token to cancel e.g. timeout an operation as well.<br/><br/>
|
|
||||||
///
|
|
||||||
/// MultiThreadedRng using a shape fitter (a rejection sampler) to enforce arbitrary shapes of probabilities for
|
|
||||||
/// desired distributions. By using the shape fitter, it is even easy to define discontinuous, arbitrary functions
|
|
||||||
/// as shapes. Any consumer can define and use own distributions.<br/><br/>
|
|
||||||
///
|
|
||||||
/// This class uses the George Marsaglia's MWC algorithm. The algorithm's implementation based loosely on John D.
|
|
||||||
/// Cook's (johndcook.com) implementation (https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation).
|
|
||||||
/// Thanks John for the inspiration.<br/><br/>
|
|
||||||
///
|
|
||||||
/// Please notice: When using the debug environment, MultiThreadedRng uses a smaller buffer size. Please ensure,
|
|
||||||
/// that the production environment uses a release build, though.
|
|
||||||
/// </remarks>
|
|
||||||
public sealed class MultiThreadedRng : IRandom, IDisposable
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
private const int BUFFER_SIZE = 10_000;
|
|
||||||
#else
|
|
||||||
private const int BUFFER_SIZE = 1_000_000;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The queue size means, how many buffer we store in a queue at the same time:
|
|
||||||
private const int QUEUE_SIZE = 2;
|
|
||||||
|
|
||||||
// Gets used to stop the producer threads:
|
|
||||||
private readonly CancellationTokenSource producerTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
// The time a thread waits e.g. to check if the queue needs a new buffer:
|
|
||||||
private readonly TimeSpan waiter = TimeSpan.FromMilliseconds(10);
|
|
||||||
|
|
||||||
// The first queue, where to store buffers of random uint numbers:
|
|
||||||
private readonly ConcurrentQueue<uint[]> queueIntegers = new ConcurrentQueue<uint[]>();
|
|
||||||
|
|
||||||
// The second queue, where to store buffers of uniform random double numbers:
|
|
||||||
private readonly ConcurrentQueue<double[]> queueDoubles = new ConcurrentQueue<double[]>();
|
|
||||||
|
|
||||||
// The uint producer thread:
|
|
||||||
private Thread producerRandomUint;
|
|
||||||
|
|
||||||
// The uniform double producer thread:
|
|
||||||
private Thread producerRandomUniformDistributedDouble;
|
|
||||||
|
|
||||||
// Variable w and z for the uint generator. Both get used
|
|
||||||
// as seeding variable as well (cf. constructors)
|
|
||||||
private uint mW;
|
|
||||||
private uint mZ;
|
|
||||||
|
|
||||||
// This is the current buffer for the consumer side i.e. the public interfaces:
|
|
||||||
private double[] currentBuffer = Array.Empty<double>();
|
|
||||||
|
|
||||||
// The current pointer to the next current buffer's address to read from:
|
|
||||||
private int currentBufferPointer = BUFFER_SIZE;
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public MultiThreadedRng()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Initialize the mW and mZ by using
|
|
||||||
// the system's time.
|
|
||||||
//
|
|
||||||
var now = DateTime.Now;
|
|
||||||
var ticks = now.Ticks;
|
|
||||||
this.mW = (uint) (ticks >> 16);
|
|
||||||
this.mZ = (uint) (ticks % 4_294_967_296);
|
|
||||||
this.StartProducerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MultiThreadedRng(uint seedU)
|
|
||||||
{
|
|
||||||
this.mW = seedU;
|
|
||||||
this.mZ = 362_436_069;
|
|
||||||
this.StartProducerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MultiThreadedRng(uint seedU, uint seedV)
|
|
||||||
{
|
|
||||||
this.mW = seedU;
|
|
||||||
this.mZ = seedV;
|
|
||||||
this.StartProducerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartProducerThreads()
|
|
||||||
{
|
|
||||||
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
|
|
||||||
this.producerRandomUint.Start();
|
|
||||||
this.producerRandomUniformDistributedDouble = new Thread(() => this.RandomProducerUniformDistributedDouble(this.producerTokenSource.Token)) {IsBackground = true};
|
|
||||||
this.producerRandomUniformDistributedDouble.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Producers
|
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
|
||||||
private async void RandomProducerUint(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// A local next buffer, which gets filled next:
|
|
||||||
var nextBuffer = new uint[BUFFER_SIZE];
|
|
||||||
|
|
||||||
// Produce the necessary number of random uints:
|
|
||||||
for (var n = 0; n < nextBuffer.Length && !cancellationToken.IsCancellationRequested; n++)
|
|
||||||
{
|
|
||||||
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
|
|
||||||
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
|
|
||||||
nextBuffer[n] = (this.mZ << 16) + this.mW;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inside this loop, we try to enqueue the produced buffer:
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Ensure, that we do not produce more buffers, as configured:
|
|
||||||
if (this.queueIntegers.Count < QUEUE_SIZE)
|
|
||||||
{
|
|
||||||
this.queueIntegers.Enqueue(nextBuffer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The queue was full. Wait a moment and try it again:
|
|
||||||
await Task.Delay(this.waiter, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
// The producers should be stopped:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
|
||||||
private async void RandomProducerUniformDistributedDouble(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// A local source buffer of uints:
|
|
||||||
uint[] bufferSource = null;
|
|
||||||
|
|
||||||
// Try to get the next source buffer:
|
|
||||||
while (!this.queueIntegers.TryDequeue(out bufferSource) && !cancellationToken.IsCancellationRequested)
|
|
||||||
await Task.Delay(this.waiter, cancellationToken);
|
|
||||||
|
|
||||||
// Case: The producers should be stopped:
|
|
||||||
if(bufferSource == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// A local buffer to fill with uniform doubles:
|
|
||||||
var nextBuffer = new double[BUFFER_SIZE];
|
|
||||||
|
|
||||||
// Generate the necessary number of doubles:
|
|
||||||
for (var n = 0; n < nextBuffer.Length && !cancellationToken.IsCancellationRequested; n++)
|
|
||||||
nextBuffer[n] = (bufferSource[n] + 1.0) * 2.328306435454494e-10;
|
|
||||||
|
|
||||||
// Inside this loop, we try to enqueue the generated buffer:
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Ensure, that the queue contains only the configured number of buffers:
|
|
||||||
if (this.queueDoubles.Count < QUEUE_SIZE)
|
|
||||||
{
|
|
||||||
this.queueDoubles.Enqueue(nextBuffer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The queue was full. Wait a moment and try it again:
|
|
||||||
await Task.Delay(this.waiter, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementing interface
|
|
||||||
|
|
||||||
public async ValueTask<double> GetUniform(CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
while (!cancel.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// Check, if we need a new buffer to read from:
|
|
||||||
if (this.currentBufferPointer >= BUFFER_SIZE)
|
|
||||||
{
|
|
||||||
// Create a local copy of the current buffer's pointer:
|
|
||||||
var currentBufferReference = this.currentBuffer;
|
|
||||||
|
|
||||||
// Here, we store the next buffer until we implement it:
|
|
||||||
var nextBuffer = Array.Empty<double>();
|
|
||||||
|
|
||||||
// Try to get the next buffer from the queue:
|
|
||||||
while (this.currentBufferPointer >= BUFFER_SIZE && currentBufferReference == this.currentBuffer && !this.queueDoubles.TryDequeue(out nextBuffer))
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: There is no next buffer available.
|
|
||||||
// Must wait for producer(s) to provide next.
|
|
||||||
//
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(this.waiter, cancel);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: The consumer cancelled the request.
|
|
||||||
//
|
|
||||||
return double.NaN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Note: In general, it does not matter if the following compare-exchange is successful.
|
|
||||||
// 1st case: It was successful -- everything is fine. But we are responsible to re-set the currentBufferPointer.
|
|
||||||
// 2nd case: It was not successful. This means, that another thread was successful, though.
|
|
||||||
// That case is fine as well. But we would loose one buffer of work. Thus, we
|
|
||||||
// check for this case and preserve the buffer full of work.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Try to implement the dequeued buffer without locking other threads:
|
|
||||||
if (Interlocked.CompareExchange(ref this.currentBuffer, nextBuffer, currentBufferReference) != currentBufferReference)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: Another thread updated the buffer already.
|
|
||||||
// Thus, we enqueue our copy of the next buffer to preserve it.
|
|
||||||
//
|
|
||||||
this.queueDoubles.Enqueue(nextBuffer);
|
|
||||||
|
|
||||||
// Next? We can go ahead and yield a random number...
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: We updated the buffer.
|
|
||||||
//
|
|
||||||
this.currentBufferPointer = 0;
|
|
||||||
|
|
||||||
// Next? We can go ahead and yield a random number...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Made a local copy of the current pointer:
|
|
||||||
var myPointer = this.currentBufferPointer;
|
|
||||||
|
|
||||||
// Increment the pointer for the next thread or call:
|
|
||||||
var nextPointer = myPointer + 1;
|
|
||||||
|
|
||||||
// Try to update the pointer without locking other threads:
|
|
||||||
if (Interlocked.CompareExchange(ref this.currentBufferPointer, nextPointer, myPointer) == myPointer)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: Success. We updated the pointer and, thus, can use the pointer to read a number.
|
|
||||||
//
|
|
||||||
return this.currentBuffer[myPointer];
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Case: Another thread updated the pointer already. Must restart the process
|
|
||||||
// to get a random number.
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Case: The consumer cancelled the request.
|
|
||||||
//
|
|
||||||
return double.NaN;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopProducer() => this.producerTokenSource.Cancel();
|
|
||||||
|
|
||||||
public void Dispose() => this.StopProducer();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FastRng.Double.Distributions;
|
|
||||||
|
|
||||||
namespace FastRng.Double
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// ShapeFitter is a rejection sampler, cf. https://en.wikipedia.org/wiki/Rejection_sampling
|
|
||||||
/// </summary>
|
|
||||||
public sealed class ShapeFitter
|
|
||||||
{
|
|
||||||
private readonly double[] probabilities;
|
|
||||||
private readonly IRandom rng;
|
|
||||||
private readonly double max;
|
|
||||||
private readonly double sampleSize;
|
|
||||||
private readonly IDistribution uniform;
|
|
||||||
|
|
||||||
public ShapeFitter(Func<double, double> shapeFunction, IRandom rng, ushort sampleSize = 50)
|
|
||||||
{
|
|
||||||
this.rng = rng;
|
|
||||||
this.uniform = new Uniform(rng);
|
|
||||||
this.sampleSize = sampleSize;
|
|
||||||
this.probabilities = new double[sampleSize];
|
|
||||||
|
|
||||||
var sampleStepSize = 1.0d / sampleSize;
|
|
||||||
var nextStep = 0.0 + sampleStepSize;
|
|
||||||
var maxValue = 0.0d;
|
|
||||||
for (var n = 0; n < sampleSize; n++)
|
|
||||||
{
|
|
||||||
this.probabilities[n] = shapeFunction(nextStep);
|
|
||||||
if (this.probabilities[n] > maxValue)
|
|
||||||
maxValue = this.probabilities[n];
|
|
||||||
|
|
||||||
nextStep += sampleStepSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.max = maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<double> NextNumber(CancellationToken token = default)
|
|
||||||
{
|
|
||||||
while (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
var x = await this.rng.GetUniform(token);
|
|
||||||
if (double.IsNaN(x))
|
|
||||||
return x;
|
|
||||||
|
|
||||||
var nextBucket = (int)Math.Floor(x * this.sampleSize);
|
|
||||||
if (nextBucket >= this.probabilities.Length)
|
|
||||||
nextBucket = this.probabilities.Length - 1;
|
|
||||||
|
|
||||||
var threshold = this.probabilities[nextBucket];
|
|
||||||
var y = await this.uniform.NextNumber(0.0d, this.max, token);
|
|
||||||
if (double.IsNaN(y))
|
|
||||||
return y;
|
|
||||||
|
|
||||||
if(y > threshold)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
return double.NaN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,28 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<AssemblyVersion>1.2.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.2.0</FileVersion>
|
||||||
|
<PackageVersion>1.2.0</PackageVersion>
|
||||||
|
<Authors>Thorsten Sommer</Authors>
|
||||||
|
<PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/FastRng</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/FastRng</RepositoryUrl>
|
||||||
|
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<DocumentationFile>bin\Debug\FastRng.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DocumentationFile>bin\Release\FastRng.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="\"/>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class BetaA2B2 : Distribution
|
|
||||||
{
|
|
||||||
private const float ALPHA = 2f;
|
|
||||||
private const float BETA = 2f;
|
|
||||||
private const float CONSTANT = 4f;
|
|
||||||
|
|
||||||
public BetaA2B2(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * MathF.Pow(x, ALPHA - 1f) * MathF.Pow(1f - x, BETA - 1f);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class BetaA2B5 : Distribution
|
|
||||||
{
|
|
||||||
private const float ALPHA = 2f;
|
|
||||||
private const float BETA = 5f;
|
|
||||||
private const float CONSTANT = 12.2f;
|
|
||||||
|
|
||||||
public BetaA2B5(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * MathF.Pow(x, ALPHA - 1f) * MathF.Pow(1f - x, BETA - 1f);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class BetaA5B2 : Distribution
|
|
||||||
{
|
|
||||||
private const float ALPHA = 5f;
|
|
||||||
private const float BETA = 2f;
|
|
||||||
private const float CONSTANT = 12.2f;
|
|
||||||
|
|
||||||
public BetaA5B2(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * MathF.Pow(x, ALPHA - 1f) * MathF.Pow(1f - x, BETA - 1f);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class CauchyLorentzX0 : Distribution
|
|
||||||
{
|
|
||||||
private const float CONSTANT = 0.31f;
|
|
||||||
private const float SCALE = 0.1f;
|
|
||||||
private const float MEDIAN = 0.0f;
|
|
||||||
|
|
||||||
public CauchyLorentzX0(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * (1.0f / (MathF.PI * SCALE)) * ((SCALE * SCALE) / (MathF.Pow(x - MEDIAN, 2f) + (SCALE * SCALE)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class CauchyLorentzX1 : Distribution
|
|
||||||
{
|
|
||||||
private const float CONSTANT = 0.31f;
|
|
||||||
private const float SCALE = 0.1f;
|
|
||||||
private const float MEDIAN = 1.0f;
|
|
||||||
|
|
||||||
public CauchyLorentzX1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * (1.0f / (MathF.PI * SCALE)) * ((SCALE * SCALE) / (MathF.Pow(x - MEDIAN, 2f) + (SCALE * SCALE)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ChiSquareK1 : Distribution
|
|
||||||
{
|
|
||||||
private const float K = 1.0f;
|
|
||||||
private const float K_HALF = K * 0.5f;
|
|
||||||
private const float K_HALF_MINUS_ONE = K_HALF - 1.0f;
|
|
||||||
private const float CONSTANT = 0.252f;
|
|
||||||
|
|
||||||
private static readonly float DIVISOR;
|
|
||||||
|
|
||||||
static ChiSquareK1()
|
|
||||||
{
|
|
||||||
var twoToTheKHalf = MathF.Pow(2f, K_HALF);
|
|
||||||
var gammaKHalf = MathTools.Gamma(K_HALF);
|
|
||||||
DIVISOR = twoToTheKHalf * gammaKHalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChiSquareK1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * ((MathF.Pow(x, K_HALF_MINUS_ONE) * MathF.Exp(-x * 0.5f)) / DIVISOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ChiSquareK10 : Distribution
|
|
||||||
{
|
|
||||||
private const float K = 10.0f;
|
|
||||||
private const float K_HALF = K * 0.5f;
|
|
||||||
private const float K_HALF_MINUS_ONE = K_HALF - 1.0f;
|
|
||||||
private const float CONSTANT = 0.252f;
|
|
||||||
|
|
||||||
private static readonly float DIVISOR;
|
|
||||||
|
|
||||||
static ChiSquareK10()
|
|
||||||
{
|
|
||||||
var twoToTheKHalf = MathF.Pow(2f, K_HALF);
|
|
||||||
var gammaKHalf = MathTools.Gamma(K_HALF);
|
|
||||||
DIVISOR = twoToTheKHalf * gammaKHalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChiSquareK10(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * ((MathF.Pow(x, K_HALF_MINUS_ONE) * MathF.Exp(-x * 0.5f)) / DIVISOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ChiSquareK4 : Distribution
|
|
||||||
{
|
|
||||||
private const float K = 4.0f;
|
|
||||||
private const float K_HALF = K * 0.5f;
|
|
||||||
private const float K_HALF_MINUS_ONE = K_HALF - 1.0f;
|
|
||||||
private const float CONSTANT = 0.252f;
|
|
||||||
|
|
||||||
private static readonly float DIVISOR;
|
|
||||||
|
|
||||||
static ChiSquareK4()
|
|
||||||
{
|
|
||||||
var twoToTheKHalf = MathF.Pow(2, K_HALF);
|
|
||||||
var gammaKHalf = MathTools.Gamma(K_HALF);
|
|
||||||
DIVISOR = twoToTheKHalf * gammaKHalf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChiSquareK4(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * ((MathF.Pow(x, K_HALF_MINUS_ONE) * MathF.Exp(-x * 0.5f)) / DIVISOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public abstract class Distribution : IDistribution
|
|
||||||
{
|
|
||||||
private readonly ShapeFitter fitter;
|
|
||||||
private readonly IRandom random;
|
|
||||||
|
|
||||||
protected Distribution(IRandom rng)
|
|
||||||
{
|
|
||||||
if (rng == null)
|
|
||||||
throw new ArgumentNullException(nameof(rng), "An IRandom implementation is needed.");
|
|
||||||
|
|
||||||
this.random = rng;
|
|
||||||
this.fitter = new ShapeFitter(this.ShapeFunction, this.random, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract float ShapeFunction(float x);
|
|
||||||
|
|
||||||
public async ValueTask<float> GetDistributedValue(CancellationToken token = default) => await this.fitter.NextNumber(token);
|
|
||||||
|
|
||||||
public async ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (uint) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (ulong) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<float> NextNumber(float rangeStart, float rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (distributedValue * range) + rangeStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<float> NextNumber(CancellationToken cancel = default) => await this.NextNumber(0.0f, 1.0f, cancel);
|
|
||||||
|
|
||||||
public async ValueTask<bool> HasDecisionBeenMade(float above, float below = 1, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
var number = await this.NextNumber(cancel);
|
|
||||||
return number > above && number < below;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ExponentialLa10 : Distribution
|
|
||||||
{
|
|
||||||
private const float LAMBDA = 10.0f;
|
|
||||||
private const float CONSTANT = 0.1106f;
|
|
||||||
|
|
||||||
public ExponentialLa10(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * LAMBDA * MathF.Exp(-LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class ExponentialLa5 : Distribution
|
|
||||||
{
|
|
||||||
private const float LAMBDA = 5.0f;
|
|
||||||
private const float CONSTANT = 0.2103f;
|
|
||||||
|
|
||||||
public ExponentialLa5(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * LAMBDA * MathF.Exp(-LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class GammaA5B15 : Distribution
|
|
||||||
{
|
|
||||||
private const float ALPHA = 5.0f;
|
|
||||||
private const float BETA = 15.0f;
|
|
||||||
private const float CONSTANT = 0.341344210715475f;
|
|
||||||
|
|
||||||
private static readonly float GAMMA_ALPHA;
|
|
||||||
private static readonly float BETA_TO_THE_ALPHA;
|
|
||||||
|
|
||||||
static GammaA5B15()
|
|
||||||
{
|
|
||||||
GAMMA_ALPHA = MathTools.Gamma(ALPHA);
|
|
||||||
BETA_TO_THE_ALPHA = MathF.Pow(BETA, ALPHA);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GammaA5B15(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * ((BETA_TO_THE_ALPHA * MathF.Pow(x, ALPHA - 1.0f) * MathF.Exp(-BETA * x)) / GAMMA_ALPHA);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public interface IDistribution
|
|
||||||
{
|
|
||||||
public ValueTask<float> GetDistributedValue(CancellationToken token);
|
|
||||||
|
|
||||||
public ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<float> NextNumber(float rangeStart, float rangeEnd, CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<float> NextNumber(CancellationToken cancel = default);
|
|
||||||
|
|
||||||
public ValueTask<bool> HasDecisionBeenMade(float above, float below = 1.0f, CancellationToken cancel = default);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class InverseExponentialLa10 : Distribution
|
|
||||||
{
|
|
||||||
private const float LAMBDA = 10.0f;
|
|
||||||
private const float CONSTANT = 4.539992976248453e-06f;
|
|
||||||
|
|
||||||
public InverseExponentialLa10(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * LAMBDA * MathF.Exp(LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class InverseExponentialLa5 : Distribution
|
|
||||||
{
|
|
||||||
private const float LAMBDA = 5.0f;
|
|
||||||
private const float CONSTANT = 0.001347589399817f;
|
|
||||||
|
|
||||||
public InverseExponentialLa5(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * LAMBDA * MathF.Exp(LAMBDA * x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class InverseGammaA3B05 : Distribution
|
|
||||||
{
|
|
||||||
private const float ALPHA = 3.0f;
|
|
||||||
private const float BETA = 0.5f;
|
|
||||||
private const float CONSTANT = 0.213922656884911f;
|
|
||||||
|
|
||||||
private static readonly float FACTOR_LEFT;
|
|
||||||
|
|
||||||
static InverseGammaA3B05()
|
|
||||||
{
|
|
||||||
var gammaAlpha = MathTools.Gamma(ALPHA);
|
|
||||||
var betaToTheAlpha = MathF.Pow(BETA, ALPHA);
|
|
||||||
|
|
||||||
FACTOR_LEFT = CONSTANT * (betaToTheAlpha / gammaAlpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InverseGammaA3B05(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => FACTOR_LEFT * MathF.Pow(x, -ALPHA - 1.0f) * MathF.Exp(-BETA / x);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class LaplaceB01M0 : Distribution
|
|
||||||
{
|
|
||||||
private const float B = 0.1f;
|
|
||||||
private const float MU = 0.0f;
|
|
||||||
private const float CONSTANT = 0.221034183615129f;
|
|
||||||
|
|
||||||
private static readonly float FACTOR_LEFT;
|
|
||||||
|
|
||||||
static LaplaceB01M0()
|
|
||||||
{
|
|
||||||
FACTOR_LEFT = CONSTANT / (2.0f * B);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LaplaceB01M0(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => FACTOR_LEFT * MathF.Exp(-MathF.Abs(x - MU) / B);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class LaplaceB01M05 : Distribution
|
|
||||||
{
|
|
||||||
private const float B = 0.1f;
|
|
||||||
private const float MU = 0.5f;
|
|
||||||
private const float CONSTANT = 0.2f;
|
|
||||||
|
|
||||||
private static readonly float FACTOR_LEFT;
|
|
||||||
|
|
||||||
static LaplaceB01M05()
|
|
||||||
{
|
|
||||||
FACTOR_LEFT = CONSTANT / (2.0f * B);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LaplaceB01M05(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => FACTOR_LEFT * MathF.Exp(-MathF.Abs(x - MU) / B);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class LogNormalS1M0 : Distribution
|
|
||||||
{
|
|
||||||
private const float SIGMA = 1.0f;
|
|
||||||
private const float MU = 0.0f;
|
|
||||||
private const float CONSTANT = 1.51998658387455f;
|
|
||||||
|
|
||||||
private static readonly float FACTOR;
|
|
||||||
|
|
||||||
static LogNormalS1M0()
|
|
||||||
{
|
|
||||||
FACTOR = SIGMA * MathF.Sqrt(2f * MathF.PI);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogNormalS1M0(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => (CONSTANT / (x * FACTOR)) * MathF.Exp( -(MathF.Pow(MathF.Log(x) - MU, 2f) / (2f * MathF.Pow(SIGMA, 2f))));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class NormalS02M05 : Distribution
|
|
||||||
{
|
|
||||||
private const float SQRT_2_PI = 2.506628275f;
|
|
||||||
private const float STDDEV = 0.2f;
|
|
||||||
private const float MEAN = 0.5f;
|
|
||||||
|
|
||||||
public NormalS02M05(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => 1.0f / (STDDEV * SQRT_2_PI) * MathF.Exp(-0.5f * MathF.Pow((x - MEAN) / STDDEV, 2.0f));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class StudentTNu1 : Distribution
|
|
||||||
{
|
|
||||||
private const float NU = 1.0f;
|
|
||||||
private const float START = 0.0f;
|
|
||||||
private const float COMPRESS = 1.0f;
|
|
||||||
private const float CONSTANT = 3.14190548592729f;
|
|
||||||
|
|
||||||
private static readonly float DIVIDEND;
|
|
||||||
private static readonly float DIVISOR;
|
|
||||||
private static readonly float EXPONENT;
|
|
||||||
|
|
||||||
static StudentTNu1()
|
|
||||||
{
|
|
||||||
DIVIDEND = MathTools.Gamma((NU + 1.0f) * 0.5f);
|
|
||||||
DIVISOR = MathF.Sqrt(NU * MathF.PI) * MathTools.Gamma(NU * 0.5f);
|
|
||||||
EXPONENT = -((NU + 1.0f) * 0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StudentTNu1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * MathF.Pow((DIVIDEND / DIVISOR) * MathF.Pow(1.0f + MathF.Pow(START + x * COMPRESS, 2f) / NU, EXPONENT), COMPRESS);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class Uniform : IDistribution
|
|
||||||
{
|
|
||||||
private readonly IRandom rng;
|
|
||||||
|
|
||||||
public Uniform(IRandom rng)
|
|
||||||
{
|
|
||||||
if (rng == null)
|
|
||||||
throw new ArgumentNullException(nameof(rng), "An IRandom implementation is needed.");
|
|
||||||
|
|
||||||
this.rng = rng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<float> GetDistributedValue(CancellationToken token = default) => await this.rng.GetUniform(token);
|
|
||||||
|
|
||||||
public async ValueTask<uint> NextNumber(uint rangeStart, uint rangeEnd, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (uint) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<ulong> NextNumber(ulong rangeStart, ulong rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (ulong) ((distributedValue * range) + rangeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<float> NextNumber(float rangeStart, float rangeEnd, CancellationToken cancel = default(CancellationToken))
|
|
||||||
{
|
|
||||||
if (rangeStart > rangeEnd)
|
|
||||||
{
|
|
||||||
var tmp = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeEnd - rangeStart;
|
|
||||||
var distributedValue = await this.GetDistributedValue(cancel);
|
|
||||||
return (distributedValue * range) + rangeStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<float> NextNumber(CancellationToken cancel = default) => await this.NextNumber(0.0f, 1.0f, cancel);
|
|
||||||
|
|
||||||
public async ValueTask<bool> HasDecisionBeenMade(float above, float below = 1, CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
var number = await this.NextNumber(cancel);
|
|
||||||
return number > above && number < below;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float.Distributions
|
|
||||||
{
|
|
||||||
public sealed class WeibullK05La1 : Distribution
|
|
||||||
{
|
|
||||||
private const float K = 0.5f;
|
|
||||||
private const float LAMBDA = 1.0f;
|
|
||||||
private const float CONSTANT = 0.221034183615129f;
|
|
||||||
|
|
||||||
public WeibullK05La1(IRandom rng) : base(rng)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float ShapeFunction(float x) => CONSTANT * ( (K / LAMBDA) * MathF.Pow(x / LAMBDA, K - 1.0f) * MathF.Exp(-MathF.Pow(x/LAMBDA, K)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FastRng.Float.Distributions;
|
|
||||||
|
|
||||||
namespace FastRng.Float
|
|
||||||
{
|
|
||||||
public interface IRandom : IDisposable
|
|
||||||
{
|
|
||||||
public ValueTask<float> GetUniform(CancellationToken cancel = default);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace FastRng.Float
|
|
||||||
{
|
|
||||||
public static class MathTools
|
|
||||||
{
|
|
||||||
private static readonly float SQRT_2 = MathF.Sqrt(2.0f);
|
|
||||||
private static readonly float SQRT_PI = MathF.Sqrt(MathF.PI);
|
|
||||||
|
|
||||||
public static float Gamma(float z)
|
|
||||||
{
|
|
||||||
// Source: http://rosettacode.org/wiki/Gamma_function#Go
|
|
||||||
|
|
||||||
const float F1 = 6.5f;
|
|
||||||
const float A1 = .99999999999980993f;
|
|
||||||
const float A2 = 676.5203681218851f;
|
|
||||||
const float A3 = 1259.1392167224028f;
|
|
||||||
const float A4 = 771.32342877765313f;
|
|
||||||
const float A5 = 176.61502916214059f;
|
|
||||||
const float A6 = 12.507343278686905f;
|
|
||||||
const float A7 = .13857109526572012f;
|
|
||||||
const float A8 = 9.9843695780195716e-6f;
|
|
||||||
const float A9 = 1.5056327351493116e-7f;
|
|
||||||
|
|
||||||
var t = z + F1;
|
|
||||||
var x = A1 +
|
|
||||||
A2 / z -
|
|
||||||
A3 / (z + 1) +
|
|
||||||
A4 / (z + 2) -
|
|
||||||
A5 / (z + 3) +
|
|
||||||
A6 / (z + 4) -
|
|
||||||
A7 / (z + 5) +
|
|
||||||
A8 / (z + 6) +
|
|
||||||
A9 / (z + 7);
|
|
||||||
|
|
||||||
return MathTools.SQRT_2 * MathTools.SQRT_PI * MathF.Pow(t, z - 0.5f) * MathF.Exp(-t) * x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float Factorial(float x) => MathTools.Gamma(x + 1.0f);
|
|
||||||
|
|
||||||
public static ulong Factorial(uint x)
|
|
||||||
{
|
|
||||||
if (x > 20)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(x), $"Cannot compute {x}!, since ulong.max is 18_446_744_073_709_551_615.");
|
|
||||||
|
|
||||||
ulong accumulator = 1;
|
|
||||||
for (uint factor = 1; factor <= x; factor++)
|
|
||||||
accumulator *= factor;
|
|
||||||
|
|
||||||
return accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ulong Factorial(int x)
|
|
||||||
{
|
|
||||||
if(x < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(x), "Given value must be greater as zero.");
|
|
||||||
|
|
||||||
return MathTools.Factorial((uint) x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,308 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FastRng.Float.Distributions;
|
|
||||||
|
|
||||||
namespace FastRng.Float
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A fast multi-threaded pseudo random number generator.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster.
|
|
||||||
/// Unlike Math.NET, MultiThreadedRng is multi-threaded and async. Consumers can await the next number without
|
|
||||||
/// blocking resources. Additionally, consumers can use a token to cancel e.g. timeout an operation as well.<br/><br/>
|
|
||||||
///
|
|
||||||
/// MultiThreadedRng using a shape fitter (a rejection sampler) to enforce arbitrary shapes of probabilities for
|
|
||||||
/// desired distributions. By using the shape fitter, it is even easy to define discontinuous, arbitrary functions
|
|
||||||
/// as shapes. Any consumer can define and use own distributions.<br/><br/>
|
|
||||||
///
|
|
||||||
/// This class uses the George Marsaglia's MWC algorithm. The algorithm's implementation based loosely on John D.
|
|
||||||
/// Cook's (johndcook.com) implementation (https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation).
|
|
||||||
/// Thanks John for the inspiration.<br/><br/>
|
|
||||||
///
|
|
||||||
/// Please notice: When using the debug environment, MultiThreadedRng uses a smaller buffer size. Please ensure,
|
|
||||||
/// that the production environment uses a release build, though.
|
|
||||||
/// </remarks>
|
|
||||||
public sealed class MultiThreadedRng : IRandom, IDisposable
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
private const int BUFFER_SIZE = 10_000;
|
|
||||||
#else
|
|
||||||
private const int BUFFER_SIZE = 1_000_000;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The queue size means, how many buffer we store in a queue at the same time:
|
|
||||||
private const int QUEUE_SIZE = 2;
|
|
||||||
|
|
||||||
// Gets used to stop the producer threads:
|
|
||||||
private readonly CancellationTokenSource producerTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
// The time a thread waits e.g. to check if the queue needs a new buffer:
|
|
||||||
private readonly TimeSpan waiter = TimeSpan.FromMilliseconds(10);
|
|
||||||
|
|
||||||
// The first queue, where to store buffers of random uint numbers:
|
|
||||||
private readonly ConcurrentQueue<uint[]> queueIntegers = new ConcurrentQueue<uint[]>();
|
|
||||||
|
|
||||||
// The second queue, where to store buffers of uniform random floating point numbers:
|
|
||||||
private readonly ConcurrentQueue<float[]> queueFloats = new ConcurrentQueue<float[]>();
|
|
||||||
|
|
||||||
// The uint producer thread:
|
|
||||||
private Thread producerRandomUint;
|
|
||||||
|
|
||||||
// The uniform float producer thread:
|
|
||||||
private Thread producerRandomUniformDistributedFloat;
|
|
||||||
|
|
||||||
// Variable w and z for the uint generator. Both get used
|
|
||||||
// as seeding variable as well (cf. constructors)
|
|
||||||
private uint mW;
|
|
||||||
private uint mZ;
|
|
||||||
|
|
||||||
// This is the current buffer for the consumer side i.e. the public interfaces:
|
|
||||||
private float[] currentBuffer = Array.Empty<float>();
|
|
||||||
|
|
||||||
// The current pointer to the next current buffer's address to read from:
|
|
||||||
private int currentBufferPointer = BUFFER_SIZE;
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public MultiThreadedRng()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Initialize the mW and mZ by using
|
|
||||||
// the system's time.
|
|
||||||
//
|
|
||||||
var now = DateTime.Now;
|
|
||||||
var ticks = now.Ticks;
|
|
||||||
this.mW = (uint) (ticks >> 16);
|
|
||||||
this.mZ = (uint) (ticks % 4_294_967_296);
|
|
||||||
this.StartProducerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MultiThreadedRng(uint seedU)
|
|
||||||
{
|
|
||||||
this.mW = seedU;
|
|
||||||
this.mZ = 362_436_069;
|
|
||||||
this.StartProducerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MultiThreadedRng(uint seedU, uint seedV)
|
|
||||||
{
|
|
||||||
this.mW = seedU;
|
|
||||||
this.mZ = seedV;
|
|
||||||
this.StartProducerThreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartProducerThreads()
|
|
||||||
{
|
|
||||||
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
|
|
||||||
this.producerRandomUint.Start();
|
|
||||||
this.producerRandomUniformDistributedFloat = new Thread(() => this.RandomProducerUniformDistributedFloat(this.producerTokenSource.Token)) {IsBackground = true};
|
|
||||||
this.producerRandomUniformDistributedFloat.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Producers
|
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
|
||||||
private async void RandomProducerUint(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// A local next buffer, which gets filled next:
|
|
||||||
var nextBuffer = new uint[BUFFER_SIZE];
|
|
||||||
|
|
||||||
// Produce the necessary number of random uints:
|
|
||||||
for (var n = 0; n < nextBuffer.Length && !cancellationToken.IsCancellationRequested; n++)
|
|
||||||
{
|
|
||||||
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
|
|
||||||
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
|
|
||||||
nextBuffer[n] = (this.mZ << 16) + this.mW;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inside this loop, we try to enqueue the produced buffer:
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Ensure, that we do not produce more buffers, as configured:
|
|
||||||
if (this.queueIntegers.Count < QUEUE_SIZE)
|
|
||||||
{
|
|
||||||
this.queueIntegers.Enqueue(nextBuffer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The queue was full. Wait a moment and try it again:
|
|
||||||
await Task.Delay(this.waiter, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
// The producers should be stopped:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
|
||||||
private async void RandomProducerUniformDistributedFloat(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// A local source buffer of uints:
|
|
||||||
uint[] bufferSource = null;
|
|
||||||
|
|
||||||
// Try to get the next source buffer:
|
|
||||||
while (!this.queueIntegers.TryDequeue(out bufferSource) && !cancellationToken.IsCancellationRequested)
|
|
||||||
await Task.Delay(this.waiter, cancellationToken);
|
|
||||||
|
|
||||||
// Case: The producers should be stopped:
|
|
||||||
if(bufferSource == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// A local buffer to fill with uniform floats:
|
|
||||||
var nextBuffer = new float[BUFFER_SIZE];
|
|
||||||
|
|
||||||
// Generate the necessary number of floats:
|
|
||||||
for (var n = 0; n < nextBuffer.Length && !cancellationToken.IsCancellationRequested; n++)
|
|
||||||
nextBuffer[n] = (bufferSource[n] + 1.0f) * 2.328306435454494e-10f;
|
|
||||||
|
|
||||||
// Inside this loop, we try to enqueue the generated buffer:
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Ensure, that the queue contains only the configured number of buffers:
|
|
||||||
if (this.queueFloats.Count < QUEUE_SIZE)
|
|
||||||
{
|
|
||||||
this.queueFloats.Enqueue(nextBuffer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The queue was full. Wait a moment and try it again:
|
|
||||||
await Task.Delay(this.waiter, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementing interface
|
|
||||||
|
|
||||||
public async ValueTask<float> GetUniform(CancellationToken cancel = default)
|
|
||||||
{
|
|
||||||
while (!cancel.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// Check, if we need a new buffer to read from:
|
|
||||||
if (this.currentBufferPointer >= BUFFER_SIZE)
|
|
||||||
{
|
|
||||||
// Create a local copy of the current buffer's pointer:
|
|
||||||
var currentBufferReference = this.currentBuffer;
|
|
||||||
|
|
||||||
// Here, we store the next buffer until we implement it:
|
|
||||||
var nextBuffer = Array.Empty<float>();
|
|
||||||
|
|
||||||
// Try to get the next buffer from the queue:
|
|
||||||
while (this.currentBufferPointer >= BUFFER_SIZE && currentBufferReference == this.currentBuffer && !this.queueFloats.TryDequeue(out nextBuffer))
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: There is no next buffer available.
|
|
||||||
// Must wait for producer(s) to provide next.
|
|
||||||
//
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(this.waiter, cancel);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: The consumer cancelled the request.
|
|
||||||
//
|
|
||||||
return float.NaN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Note: In general, it does not matter if the following compare-exchange is successful.
|
|
||||||
// 1st case: It was successful -- everything is fine. But we are responsible to re-set the currentBufferPointer.
|
|
||||||
// 2nd case: It was not successful. This means, that another thread was successful, though.
|
|
||||||
// That case is fine as well. But we would loose one buffer of work. Thus, we
|
|
||||||
// check for this case and preserve the buffer full of work.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Try to implement the dequeued buffer without locking other threads:
|
|
||||||
if (Interlocked.CompareExchange(ref this.currentBuffer, nextBuffer, currentBufferReference) != currentBufferReference)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: Another thread updated the buffer already.
|
|
||||||
// Thus, we enqueue our copy of the next buffer to preserve it.
|
|
||||||
//
|
|
||||||
this.queueFloats.Enqueue(nextBuffer);
|
|
||||||
|
|
||||||
// Next? We can go ahead and yield a random number...
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: We updated the buffer.
|
|
||||||
//
|
|
||||||
this.currentBufferPointer = 0;
|
|
||||||
|
|
||||||
// Next? We can go ahead and yield a random number...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Made a local copy of the current pointer:
|
|
||||||
var myPointer = this.currentBufferPointer;
|
|
||||||
|
|
||||||
// Increment the pointer for the next thread or call:
|
|
||||||
var nextPointer = myPointer + 1;
|
|
||||||
|
|
||||||
// Try to update the pointer without locking other threads:
|
|
||||||
if (Interlocked.CompareExchange(ref this.currentBufferPointer, nextPointer, myPointer) == myPointer)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Case: Success. We updated the pointer and, thus, can use the pointer to read a number.
|
|
||||||
//
|
|
||||||
return this.currentBuffer[myPointer];
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Case: Another thread updated the pointer already. Must restart the process
|
|
||||||
// to get a random number.
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Case: The consumer cancelled the request.
|
|
||||||
//
|
|
||||||
return float.NaN;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopProducer() => this.producerTokenSource.Cancel();
|
|
||||||
|
|
||||||
public void Dispose() => this.StopProducer();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FastRng.Float.Distributions;
|
|
||||||
|
|
||||||
namespace FastRng.Float
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// ShapeFitter is a rejection sampler, cf. https://en.wikipedia.org/wiki/Rejection_sampling
|
|
||||||
/// </summary>
|
|
||||||
public sealed class ShapeFitter
|
|
||||||
{
|
|
||||||
private readonly float[] probabilities;
|
|
||||||
private readonly IRandom rng;
|
|
||||||
private readonly float max;
|
|
||||||
private readonly float sampleSize;
|
|
||||||
private readonly IDistribution uniform;
|
|
||||||
|
|
||||||
public ShapeFitter(Func<float, float> shapeFunction, IRandom rng, ushort sampleSize = 50)
|
|
||||||
{
|
|
||||||
this.rng = rng;
|
|
||||||
this.uniform = new Uniform(rng);
|
|
||||||
this.sampleSize = sampleSize;
|
|
||||||
this.probabilities = new float[sampleSize];
|
|
||||||
|
|
||||||
var sampleStepSize = 1.0f / sampleSize;
|
|
||||||
var nextStep = 0.0f + sampleStepSize;
|
|
||||||
var maxValue = 0.0f;
|
|
||||||
for (var n = 0; n < sampleSize; n++)
|
|
||||||
{
|
|
||||||
this.probabilities[n] = shapeFunction(nextStep);
|
|
||||||
if (this.probabilities[n] > maxValue)
|
|
||||||
maxValue = this.probabilities[n];
|
|
||||||
|
|
||||||
nextStep += sampleStepSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.max = maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<float> NextNumber(CancellationToken token = default)
|
|
||||||
{
|
|
||||||
while (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
var x = await this.rng.GetUniform(token);
|
|
||||||
if (float.IsNaN(x))
|
|
||||||
return x;
|
|
||||||
|
|
||||||
var nextBucket = (int)MathF.Floor(x * this.sampleSize);
|
|
||||||
if (nextBucket >= this.probabilities.Length)
|
|
||||||
nextBucket = this.probabilities.Length - 1;
|
|
||||||
|
|
||||||
var threshold = this.probabilities[nextBucket];
|
|
||||||
var y = await this.uniform.NextNumber(0.0f, this.max, token);
|
|
||||||
if (float.IsNaN(y))
|
|
||||||
return y;
|
|
||||||
|
|
||||||
if(y > threshold)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float.NaN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
46
FastRng/IRandom.cs
Normal file
46
FastRng/IRandom.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for random number generators.
|
||||||
|
/// </summary>
|
||||||
|
public interface IRandom<TNum> : IDisposable where TNum : IFloatingPointIeee754<TNum>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a uniform distributed pseudo-random number from the interval (0,1].
|
||||||
|
/// This means, the result 0 is impossible, whereas 1 is possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe. You can consume numbers from the same generator
|
||||||
|
/// by using multiple threads at the same time.
|
||||||
|
/// </remarks>
|
||||||
|
public TNum GetUniform(CancellationToken cancel = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a uniform distributed pseudo-random number from the interval [0, max).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe. You can consume numbers from the same generator
|
||||||
|
/// by using multiple threads at the same time.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="max">The maximum value (exclusive). The max value returned will be max - 1.</param>
|
||||||
|
/// <param name="cancel">The cancellation token.</param>
|
||||||
|
/// <returns>A pseudo-random number from the interval [0, max).</returns>
|
||||||
|
public int GetUniformInt(int max, CancellationToken cancel = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a uniform distributed pseudo-random number from the interval [min, max).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe. You can consume numbers from the same generator
|
||||||
|
/// by using multiple threads at the same time.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="min">The minimum value (inclusive).</param>
|
||||||
|
/// <param name="max">The maximum value (exclusive). The max value returned will be max - 1.</param>
|
||||||
|
/// <param name="cancel">The cancellation token.</param>
|
||||||
|
/// <returns>A pseudo-random number from the interval [min, max).</returns>
|
||||||
|
public int GetUniformInt(int min, int max, CancellationToken cancel = default);
|
||||||
|
}
|
62
FastRng/MathToolsFloatingPoint.cs
Normal file
62
FastRng/MathToolsFloatingPoint.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides some mathematical function, which are not available within in .NET itself.
|
||||||
|
/// </summary>
|
||||||
|
public static class MathToolsFloatingPoint<TNum> where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
||||||
|
{
|
||||||
|
private static readonly TNum SQRT_2 = TNum.Sqrt(TNum.One + TNum.One);
|
||||||
|
private static readonly TNum SQRT_PI = TNum.Sqrt(TNum.Pi);
|
||||||
|
|
||||||
|
// Source: http://rosettacode.org/wiki/Gamma_function#Go
|
||||||
|
private static readonly TNum F1 = TNum.CreateChecked(6.5f);
|
||||||
|
private static readonly TNum A1 = TNum.CreateChecked(.99999999999980993f);
|
||||||
|
private static readonly TNum A2 = TNum.CreateChecked(676.5203681218851f);
|
||||||
|
private static readonly TNum A3 = TNum.CreateChecked(1259.1392167224028f);
|
||||||
|
private static readonly TNum A4 = TNum.CreateChecked(771.32342877765313f);
|
||||||
|
private static readonly TNum A5 = TNum.CreateChecked(176.61502916214059f);
|
||||||
|
private static readonly TNum A6 = TNum.CreateChecked(12.507343278686905f);
|
||||||
|
private static readonly TNum A7 = TNum.CreateChecked(.13857109526572012f);
|
||||||
|
private static readonly TNum A8 = TNum.CreateChecked(9.9843695780195716e-6f);
|
||||||
|
private static readonly TNum A9 = TNum.CreateChecked(1.5056327351493116e-7f);
|
||||||
|
|
||||||
|
private static readonly TNum CONST1 = TNum.One;
|
||||||
|
private static readonly TNum CONST2 = CONST1 + TNum.One;
|
||||||
|
private static readonly TNum CONST3 = CONST2 + TNum.One;
|
||||||
|
private static readonly TNum CONST4 = CONST3 + TNum.One;
|
||||||
|
private static readonly TNum CONST5 = CONST4 + TNum.One;
|
||||||
|
private static readonly TNum CONST6 = CONST5 + TNum.One;
|
||||||
|
private static readonly TNum CONST7 = CONST6 + TNum.One;
|
||||||
|
|
||||||
|
private static readonly TNum CONST_HALF = TNum.CreateChecked(0.5f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mathematical gamma function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="z">The value for which you want calculate gamma.</param>
|
||||||
|
public static TNum Gamma(TNum z)
|
||||||
|
{
|
||||||
|
// Source: http://rosettacode.org/wiki/Gamma_function#Go
|
||||||
|
|
||||||
|
var t = z + F1;
|
||||||
|
var x = A1 +
|
||||||
|
A2 / z -
|
||||||
|
A3 / (z + CONST1) +
|
||||||
|
A4 / (z + CONST2) -
|
||||||
|
A5 / (z + CONST3) +
|
||||||
|
A6 / (z + CONST4) -
|
||||||
|
A7 / (z + CONST5) +
|
||||||
|
A8 / (z + CONST6) +
|
||||||
|
A9 / (z + CONST7);
|
||||||
|
|
||||||
|
return SQRT_2 * SQRT_PI * TNum.Pow(t, z - CONST_HALF) * TNum.Exp(-t) * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mathematical factorial function for floating-point numbers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The value, for which you want to know the factorial.</param>
|
||||||
|
public static TNum Factorial(TNum x) => Gamma(x + CONST1);
|
||||||
|
}
|
26
FastRng/MathToolsInteger.cs
Normal file
26
FastRng/MathToolsInteger.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides some mathematical function, which are not available within in .NET itself.
|
||||||
|
/// </summary>
|
||||||
|
public static class MathToolsInteger
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The mathematical factorial function for integer numbers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The value, for which you want to know the factorial.</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Throws, when x is greater than 20. Due to limitations of 64bit ulong type.</exception>
|
||||||
|
public static ulong Factorial(uint x)
|
||||||
|
{
|
||||||
|
if (x > 20)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(x), $"Cannot compute {x}!, since ulong.max is 18_446_744_073_709_551_615.");
|
||||||
|
|
||||||
|
ulong accumulator = 1;
|
||||||
|
for (uint factor = 1; factor <= x; factor++)
|
||||||
|
accumulator *= factor;
|
||||||
|
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
}
|
185
FastRng/MultiChannelRng.cs
Normal file
185
FastRng/MultiChannelRng.cs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
// ReSharper disable RedundantExtendsListEntry
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#else
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly Channel<uint> channelIntegers = Channel.CreateBounded<uint>(new BoundedChannelOptions(capacity: BUFFER_SIZE * 2) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = true });
|
||||||
|
private readonly Channel<TNum> channelFloats = Channel.CreateBounded<TNum>(new BoundedChannelOptions(capacity: BUFFER_SIZE) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = false });
|
||||||
|
|
||||||
|
private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f);
|
||||||
|
|
||||||
|
// Gets used to stop the producer threads:
|
||||||
|
private readonly CancellationTokenSource producerTokenSource = new();
|
||||||
|
|
||||||
|
// The uint producer thread:
|
||||||
|
private Thread producerRandomUint;
|
||||||
|
|
||||||
|
// The uniform float producer thread:
|
||||||
|
private Thread producerRandomUniformDistributedFloat;
|
||||||
|
|
||||||
|
// Variable w and z for the uint generator. Both get used
|
||||||
|
// as seeding variable as well (cf. constructors)
|
||||||
|
private uint mW;
|
||||||
|
private uint mZ;
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multithreaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This constructor uses the user's current local time
|
||||||
|
/// to derive the necessary parameters for the generator.
|
||||||
|
/// Thus, the results depend on the time when the generator was created.
|
||||||
|
/// </remarks>
|
||||||
|
public MultiChannelRng()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Initialize the mW and mZ by using
|
||||||
|
// the system's time.
|
||||||
|
//
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var ticks = now.Ticks;
|
||||||
|
this.mW = (uint) (ticks >> 16);
|
||||||
|
this.mZ = (uint) (ticks % 4_294_967_296);
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multithreaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
|
||||||
|
/// the behavior of the consuming application might be non-deterministic. This is possible if
|
||||||
|
/// the application with multiple threads consumes the numbers. The scheduling of the threads
|
||||||
|
/// is up to the operating system and might not be predictable.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">A seed value to generate a deterministic generator.</param>
|
||||||
|
public MultiChannelRng(uint seedU)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = 362_436_069;
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
|
||||||
|
/// the behavior of the consuming application might be non-deterministic. This is possible if
|
||||||
|
/// the application with multiple threads consumes the numbers. The scheduling of the threads
|
||||||
|
/// is up to the operating system and might not be predictable.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">The first seed value.</param>
|
||||||
|
/// <param name="seedV">The second seed value.</param>
|
||||||
|
public MultiChannelRng(uint seedU, uint seedV)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = seedV;
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartProducerThreads()
|
||||||
|
{
|
||||||
|
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUint.Start();
|
||||||
|
this.producerRandomUniformDistributedFloat = new Thread(() => this.RandomProducerUniformDistributedFloat(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUniformDistributedFloat.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Producers
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
private async void RandomProducerUint(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
|
||||||
|
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
|
||||||
|
await this.channelIntegers.Writer.WriteAsync((this.mZ << 16) + this.mW, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
private async void RandomProducerUniformDistributedFloat(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await this.channelFloats.Writer.WriteAsync((TNum.CreateChecked(await this.channelIntegers.Reader.ReadAsync(cancellationToken)) + TNum.One) * CONST_FLOAT_CONVERSION, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementing interfaces
|
||||||
|
|
||||||
|
#region Implementation of IDisposable
|
||||||
|
|
||||||
|
private void StopProducer() => this.producerTokenSource.Cancel();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose() => this.StopProducer();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IRandom<TNum>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TNum GetUniform(CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var valueTask = this.channelFloats.Reader.ReadAsync(cancel);
|
||||||
|
return valueTask.AsTask().Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int GetUniformInt(int max, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var valueTask = this.channelIntegers.Reader.ReadAsync(cancel);
|
||||||
|
return (int) (valueTask.AsTask().Result % (uint) max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int GetUniformInt(int min, int max, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var valueTask = this.channelIntegers.Reader.ReadAsync(cancel);
|
||||||
|
return (int) (valueTask.AsTask().Result % (uint) (max - min) + (uint) min);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
346
FastRng/MultiThreadedRng.cs
Normal file
346
FastRng/MultiThreadedRng.cs
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
// ReSharper disable RedundantExtendsListEntry
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A fast multi-threaded pseudo random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster.
|
||||||
|
/// Unlike Math.NET, MultiThreadedRng is multithreaded. Consumers can use a token to cancel an operation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// MultiThreadedRng using a shape fitter (a rejection sampler) to enforce arbitrary shapes of probabilities for
|
||||||
|
/// desired distributions. By using the shape fitter, it is even easier to define discontinuous, arbitrary functions
|
||||||
|
/// as shapes. Any consumer can define and use own distributions.<br/><br/>
|
||||||
|
///
|
||||||
|
/// This class uses the George Marsaglia's MWC algorithm. The algorithm's implementation is based loosely on John D.
|
||||||
|
/// Cook's (johndcook.com) implementation (https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation).
|
||||||
|
/// Thanks, John, for the inspiration.<br/><br/>
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#else
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The queue size means, how many buffers we store in a queue at the same time:
|
||||||
|
private const int QUEUE_SIZE_FLOAT = 2;
|
||||||
|
|
||||||
|
// The queue size means, how many buffers we store in a queue at the same time:
|
||||||
|
private const int QUEUE_SIZE_INT = QUEUE_SIZE_FLOAT * 2;
|
||||||
|
|
||||||
|
private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f);
|
||||||
|
|
||||||
|
// ReSharper disable StaticMemberInGenericType
|
||||||
|
private static readonly object LOCKER = new();
|
||||||
|
// ReSharper restore StaticMemberInGenericType
|
||||||
|
|
||||||
|
// Gets used to stop the producer threads:
|
||||||
|
private readonly CancellationTokenSource producerTokenSource = new();
|
||||||
|
|
||||||
|
// The time a thread waits e.g. to check if the queue needs a new buffer:
|
||||||
|
private readonly TimeSpan waiter = TimeSpan.FromMilliseconds(10);
|
||||||
|
|
||||||
|
// The first queue, where to store buffers of random uint numbers:
|
||||||
|
private readonly ConcurrentQueue<uint[]> queueIntegers = new();
|
||||||
|
|
||||||
|
// The second queue, where to store buffers of uniform random floating point numbers:
|
||||||
|
private readonly ConcurrentQueue<TNum[]> queueFloats = new();
|
||||||
|
|
||||||
|
// The uint producer thread:
|
||||||
|
private Thread producerRandomUint;
|
||||||
|
|
||||||
|
// The uniform float producer thread:
|
||||||
|
private Thread producerRandomUniformDistributedFloat;
|
||||||
|
|
||||||
|
private readonly UIntChannelProducer independentUIntProducer = new();
|
||||||
|
|
||||||
|
// Variable w and z for the uint generator. Both get used
|
||||||
|
// as seeding variable as well (cf. constructors)
|
||||||
|
private uint mW;
|
||||||
|
private uint mZ;
|
||||||
|
|
||||||
|
// This is the current buffer for the consumer side, i.e., the public interfaces:
|
||||||
|
private TNum[] currentBuffer = [];
|
||||||
|
|
||||||
|
// The current pointer to the next current buffer's address to read from:
|
||||||
|
private int currentBufferPointer = BUFFER_SIZE;
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This constructor uses the user's current local time to derive the necessary parameters for the generator.
|
||||||
|
/// Thus, the results depend on the time when the generator was created.
|
||||||
|
/// </remarks>
|
||||||
|
public MultiThreadedRng()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Initialize the mW and mZ by using
|
||||||
|
// the system's time.
|
||||||
|
//
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var ticks = now.Ticks;
|
||||||
|
this.mW = (uint) (ticks >> 16);
|
||||||
|
this.mZ = (uint) (ticks % 4_294_967_296);
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
|
||||||
|
/// the behavior of the consuming application might be non-deterministic. This is possible if
|
||||||
|
/// the application with multiple threads consumes the numbers. The scheduling of the threads
|
||||||
|
/// is up to the operating system and might not be predictable.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">A seed value to generate a deterministic generator.</param>
|
||||||
|
public MultiThreadedRng(uint seedU)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = 362_436_069;
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
|
||||||
|
/// the behavior of the consuming application might be non-deterministic. This is possible if
|
||||||
|
/// the application with multiple threads consumes the numbers. The scheduling of the threads
|
||||||
|
/// is up to the operating system and might not be predictable.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">The first seed value.</param>
|
||||||
|
/// <param name="seedV">The second seed value.</param>
|
||||||
|
public MultiThreadedRng(uint seedU, uint seedV)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = seedV;
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartProducerThreads()
|
||||||
|
{
|
||||||
|
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUint.Start();
|
||||||
|
this.producerRandomUniformDistributedFloat = new Thread(() => this.RandomProducerUniformDistributedFloat(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUniformDistributedFloat.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Producers
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
private async void RandomProducerUint(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// A local next buffer, which gets filled next:
|
||||||
|
var nextBuffer = new uint[BUFFER_SIZE];
|
||||||
|
|
||||||
|
// Produce the necessary number of random uints:
|
||||||
|
for (var n = 0; n < nextBuffer.Length && !cancellationToken.IsCancellationRequested; n++)
|
||||||
|
{
|
||||||
|
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
|
||||||
|
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
|
||||||
|
nextBuffer[n] = (this.mZ << 16) + this.mW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside this loop, we try to enqueue the produced buffer:
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure that we do not produce more buffers, as configured:
|
||||||
|
if (this.queueIntegers.Count < QUEUE_SIZE_INT)
|
||||||
|
{
|
||||||
|
this.queueIntegers.Enqueue(nextBuffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The queue was full. Wait a moment and try it again:
|
||||||
|
await Task.Delay(this.waiter, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// The producers should be stopped:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
private async void RandomProducerUniformDistributedFloat(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// A local source buffer of uints:
|
||||||
|
uint[] bufferSource;
|
||||||
|
|
||||||
|
// Try to get the next source buffer:
|
||||||
|
while (!this.queueIntegers.TryDequeue(out bufferSource) && !cancellationToken.IsCancellationRequested)
|
||||||
|
await Task.Delay(this.waiter, cancellationToken);
|
||||||
|
|
||||||
|
// Case: The producers should be stopped:
|
||||||
|
if(bufferSource == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// A local buffer to fill with uniform floats:
|
||||||
|
var nextBuffer = new TNum[BUFFER_SIZE];
|
||||||
|
|
||||||
|
// Generate the necessary number of floats:
|
||||||
|
for (var n = 0; n < nextBuffer.Length && !cancellationToken.IsCancellationRequested; n++)
|
||||||
|
nextBuffer[n] = (TNum.CreateChecked(bufferSource[n]) + TNum.One) * CONST_FLOAT_CONVERSION;
|
||||||
|
|
||||||
|
// Inside this loop, we try to enqueue the generated buffer:
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure that the queue contains only the configured number of buffers:
|
||||||
|
if (this.queueFloats.Count < QUEUE_SIZE_FLOAT)
|
||||||
|
{
|
||||||
|
this.queueFloats.Enqueue(nextBuffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The queue was full. Wait a moment and try it again:
|
||||||
|
await Task.Delay(this.waiter, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementing interface
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a uniform distributed pseudo-random number from the interval (0,1].
|
||||||
|
/// This means, the result 0 is impossible, whereas 1 is possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe. You can consume numbers from the same generator
|
||||||
|
/// by using multiple threads at the same time.
|
||||||
|
/// </remarks>
|
||||||
|
public TNum GetUniform(CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
if (cancel.IsCancellationRequested)
|
||||||
|
return TNum.NaN;
|
||||||
|
|
||||||
|
Start:
|
||||||
|
|
||||||
|
// Check if we have to load the next buffer:
|
||||||
|
if (this.currentBufferPointer >= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
// We have to get the next buffer from the queue. This is a critical
|
||||||
|
// section, because we have to ensure, that only one thread at a time
|
||||||
|
// can get the next buffer:
|
||||||
|
lock (LOCKER)
|
||||||
|
{
|
||||||
|
// We might not the first thread, which has to get the next buffer.
|
||||||
|
// When some other thread has already got the next buffer, the pointer
|
||||||
|
// was already reset to zero. In this case, we start over again:
|
||||||
|
if(this.currentBufferPointer < BUFFER_SIZE)
|
||||||
|
goto Start;
|
||||||
|
|
||||||
|
while (!this.queueFloats.TryDequeue(out this.currentBuffer))
|
||||||
|
{
|
||||||
|
if (cancel.IsCancellationRequested)
|
||||||
|
return TNum.NaN;
|
||||||
|
|
||||||
|
Thread.Sleep(TimeSpan.FromMilliseconds(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the pointer for the next thread or call:
|
||||||
|
this.currentBufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Made a local copy of the current pointer:
|
||||||
|
var myPointer = this.currentBufferPointer;
|
||||||
|
|
||||||
|
// Issue #8: This might happen when another thread interrupted the current thread, and
|
||||||
|
// the other thread has already updated the pointer. In this case, we start over again.
|
||||||
|
if (myPointer >= BUFFER_SIZE)
|
||||||
|
goto Start;
|
||||||
|
|
||||||
|
// Increment the pointer for the next thread or call:
|
||||||
|
var nextPointer = myPointer + 1;
|
||||||
|
|
||||||
|
// Try to update the pointer without locking other threads:
|
||||||
|
if (Interlocked.CompareExchange(ref this.currentBufferPointer, nextPointer, myPointer) != myPointer)
|
||||||
|
goto Start;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Case: Success. We updated the pointer and, thus, can use it to read the number.
|
||||||
|
//
|
||||||
|
return this.currentBuffer[myPointer];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int GetUniformInt(int max, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var valueTask = this.independentUIntProducer.GetNextAsync(cancel);
|
||||||
|
return (int) (valueTask.AsTask().Result % (uint) max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int GetUniformInt(int min, int max, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var valueTask = this.independentUIntProducer.GetNextAsync(cancel);
|
||||||
|
return (int) (valueTask.AsTask().Result % (uint) (max - min) + (uint) min);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopProducer() => this.producerTokenSource.Cancel();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes this generator. It is important to dispose a generator,
|
||||||
|
/// when it is no longer needed. Otherwise, the background threads
|
||||||
|
/// are still running.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.independentUIntProducer.Dispose();
|
||||||
|
this.StopProducer();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
77
FastRng/ShapeFitter.cs
Normal file
77
FastRng/ShapeFitter.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using FastRng.Distributions;
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ShapeFitter is a rejection sampler, cf. https://en.wikipedia.org/wiki/Rejection_sampling
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ShapeFitter<TNum> where TNum : IFloatingPointIeee754<TNum>, IDivisionOperators<TNum, TNum, TNum>
|
||||||
|
{
|
||||||
|
private readonly TNum[] probabilities;
|
||||||
|
private readonly IRandom<TNum> rng;
|
||||||
|
private readonly TNum max;
|
||||||
|
private readonly TNum sampleSize;
|
||||||
|
private readonly IDistribution<TNum> uniform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a shape fitter instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="shapeFunction">The function which describes the desired shape.</param>
|
||||||
|
/// <param name="rng">The random number generator instance to use.</param>
|
||||||
|
/// <param name="sampleSize">The number of sampling steps to sample the given function.</param>
|
||||||
|
public ShapeFitter(Func<TNum, TNum> shapeFunction, IRandom<TNum> rng, ushort sampleSize = 50)
|
||||||
|
{
|
||||||
|
this.rng = rng;
|
||||||
|
this.uniform = new Uniform<TNum>(rng);
|
||||||
|
this.sampleSize = TNum.CreateChecked(sampleSize);
|
||||||
|
this.probabilities = new TNum[sampleSize];
|
||||||
|
|
||||||
|
var sampleStepSize = TNum.One / TNum.CreateChecked(sampleSize);
|
||||||
|
var nextStep = TNum.Zero + sampleStepSize;
|
||||||
|
var maxValue = TNum.Zero;
|
||||||
|
for (var n = 0; n < sampleSize; n++)
|
||||||
|
{
|
||||||
|
this.probabilities[n] = shapeFunction(nextStep);
|
||||||
|
if (this.probabilities[n] > maxValue)
|
||||||
|
maxValue = this.probabilities[n];
|
||||||
|
|
||||||
|
nextStep += sampleStepSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.max = maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a random number regarding the given shape.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
|
/// <returns>The next value regarding the given shape.</returns>
|
||||||
|
public TNum NextNumber(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var x = this.rng.GetUniform(token);
|
||||||
|
if (TNum.IsNaN(x))
|
||||||
|
return x;
|
||||||
|
|
||||||
|
var nextBucket = int.CreateChecked(TNum.Floor(x * this.sampleSize));
|
||||||
|
if (nextBucket >= this.probabilities.Length)
|
||||||
|
nextBucket = this.probabilities.Length - 1;
|
||||||
|
|
||||||
|
var threshold = this.probabilities[nextBucket];
|
||||||
|
var y = this.uniform.NextNumber(TNum.Zero, this.max, token);
|
||||||
|
if (TNum.IsNaN(y))
|
||||||
|
return y;
|
||||||
|
|
||||||
|
if(y > threshold)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TNum.NaN;
|
||||||
|
}
|
||||||
|
}
|
121
FastRng/UIntChannelProducer.cs
Normal file
121
FastRng/UIntChannelProducer.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
internal sealed class UIntChannelProducer : IDisposable
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#else
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly Channel<uint> channelIntegers = Channel.CreateBounded<uint>(new BoundedChannelOptions(capacity: BUFFER_SIZE * 2) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = true });
|
||||||
|
|
||||||
|
// Gets used to stop the producer thread:
|
||||||
|
private readonly CancellationTokenSource producerTokenSource = new();
|
||||||
|
|
||||||
|
// The uint producer thread:
|
||||||
|
private Thread producerRandomUint;
|
||||||
|
|
||||||
|
// Variable w and z for the uint generator. Both get used
|
||||||
|
// as seeding variable as well (cf. constructors)
|
||||||
|
private uint mW;
|
||||||
|
private uint mZ;
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
public UIntChannelProducer()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Initialize the mW and mZ by using
|
||||||
|
// the system's time.
|
||||||
|
//
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var ticks = now.Ticks;
|
||||||
|
this.mW = (uint) (ticks >> 16);
|
||||||
|
this.mZ = (uint) (ticks % 4_294_967_296);
|
||||||
|
this.StartProducerThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multithreaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
|
||||||
|
/// the behavior of the consuming application might be non-deterministic. This is possible if
|
||||||
|
/// the application with multiple threads consumes the numbers. The scheduling of the threads
|
||||||
|
/// is up to the operating system and might not be predictable.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">A seed value to generate a deterministic generator.</param>
|
||||||
|
public UIntChannelProducer(uint seedU)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = 362_436_069;
|
||||||
|
this.StartProducerThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> Although the number generator and all distributions are deterministic,
|
||||||
|
/// the behavior of the consuming application might be non-deterministic. This is possible if
|
||||||
|
/// the application with multiple threads consumes the numbers. The scheduling of the threads
|
||||||
|
/// is up to the operating system and might not be predictable.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">The first seed value.</param>
|
||||||
|
/// <param name="seedV">The second seed value.</param>
|
||||||
|
public UIntChannelProducer(uint seedU, uint seedV)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = seedV;
|
||||||
|
this.StartProducerThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartProducerThread()
|
||||||
|
{
|
||||||
|
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUint.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
private async void RandomProducerUint(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16);
|
||||||
|
this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16);
|
||||||
|
await this.channelIntegers.Writer.WriteAsync((this.mZ << 16) + this.mW, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<uint> GetNextAsync(CancellationToken cancellationToken = default) => await this.channelIntegers.Reader.ReadAsync(cancellationToken);
|
||||||
|
|
||||||
|
#region Implementation of IDisposable
|
||||||
|
|
||||||
|
private void StopProducer() => this.producerTokenSource.Cancel();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose() => this.StopProducer();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
60
FastRngTests/DecisionTester.cs
Normal file
60
FastRngTests/DecisionTester.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FastRng;
|
||||||
|
using FastRng.Distributions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class DecisionTester
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public async Task DecisionUniform01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new Uniform<float>(rng);
|
||||||
|
|
||||||
|
var neededCoinTossesA = 0;
|
||||||
|
var neededCoinTossesB = 0;
|
||||||
|
var neededCoinTossesC = 0;
|
||||||
|
|
||||||
|
for(var n = 0; n < 100; n++) while (!dist.HasDecisionBeenMade(0.0f, 0.1f)) neededCoinTossesA++;
|
||||||
|
for(var n = 0; n < 100; n++) while (!dist.HasDecisionBeenMade(0.5f, 0.6f)) neededCoinTossesB++;
|
||||||
|
for(var n = 0; n < 100; n++) while (!dist.HasDecisionBeenMade(0.8f, 0.9f)) neededCoinTossesC++;
|
||||||
|
|
||||||
|
var values = new[] {neededCoinTossesA, neededCoinTossesB, neededCoinTossesC};
|
||||||
|
var max = values.Max();
|
||||||
|
var min = values.Min();
|
||||||
|
|
||||||
|
TestContext.WriteLine($"Coin tosses: a={neededCoinTossesA}, b={neededCoinTossesB}, c={neededCoinTossesC}");
|
||||||
|
Assert.That(max - min, Is.LessThanOrEqualTo(250));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public async Task DecisionWeibull01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new WeibullK05La1<float>(rng);
|
||||||
|
|
||||||
|
var neededCoinTossesA = 0;
|
||||||
|
var neededCoinTossesB = 0;
|
||||||
|
var neededCoinTossesC = 0;
|
||||||
|
|
||||||
|
for(var n = 0; n < 100; n++) while (!dist.HasDecisionBeenMade(0.0f, 0.1f)) neededCoinTossesA++;
|
||||||
|
for(var n = 0; n < 100; n++) while (!dist.HasDecisionBeenMade(0.5f, 0.6f)) neededCoinTossesB++;
|
||||||
|
for(var n = 0; n < 100; n++) while (!dist.HasDecisionBeenMade(0.8f, 0.9f)) neededCoinTossesC++;
|
||||||
|
|
||||||
|
var values = new[] {neededCoinTossesA, neededCoinTossesB, neededCoinTossesC};
|
||||||
|
var max = values.Max();
|
||||||
|
var min = values.Min();
|
||||||
|
|
||||||
|
TestContext.WriteLine($"Coin tosses: a={neededCoinTossesA}, b={neededCoinTossesB}, c={neededCoinTossesC}");
|
||||||
|
Assert.That(max - min, Is.LessThanOrEqualTo(2_800));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/BetaA2B2.cs
Normal file
82
FastRngTests/Distributions/BetaA2B2.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class BetaA2B2
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.BetaA2B2<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.0396f).Within(0.3f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.0784f).Within(0.3f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.1164f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.6864f).Within(0.3f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.7084f).Within(0.3f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.7296f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.9996f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.7296f).Within(0.3f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.4816f).Within(0.3f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.3276f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.0784f).Within(0.3f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.0396f).Within(0.3f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.0000f).Within(0.3f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var samples = new float[1_000];
|
||||||
|
var dist = new FastRng.Distributions.BetaA2B2<float>(rng);
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var samples = new float[1_000];
|
||||||
|
var dist = new FastRng.Distributions.BetaA2B2<float>(rng);
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.BetaA2B2<float>(null));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/BetaA2B5.cs
Normal file
82
FastRngTests/Distributions/BetaA2B5.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class BetaA2B5
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.BetaA2B5<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.11719271f).Within(0.3f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.22505783f).Within(0.3f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.32401717f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.99348410f).Within(0.3f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.98639433f).Within(0.3f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.97684451f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.35868592f).Within(0.3f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.03076227f).Within(0.03f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.00403061f).Within(0.03f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.00109800f).Within(0.01f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.00000191f).Within(0.000003f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.00000012f).Within(0.0000003f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.00000000f).Within(0.0000003f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var samples = new float[1_000];
|
||||||
|
var dist = new FastRng.Distributions.BetaA2B5<float>(rng);
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var samples = new float[1_000];
|
||||||
|
var dist = new FastRng.Distributions.BetaA2B5<float>(rng);
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.BetaA2B5<float>(null));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/BetaA5B2.cs
Normal file
82
FastRngTests/Distributions/BetaA5B2.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class BetaA5B2
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.BetaA5B2<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.0000001f).Within(0.0000003f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.0000019f).Within(0.00001f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.0000096f).Within(0.0004f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.0222918f).Within(0.03f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.0262883f).Within(0.03f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.0307623f).Within(0.03f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.4044237f).Within(0.2f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.9768445f).Within(0.15f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.9552714f).Within(0.15f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.8004420f).Within(0.35f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.2250578f).Within(0.03f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.1171927f).Within(0.03f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0f).Within(0.0004f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var samples = new float[1_000];
|
||||||
|
var dist = new FastRng.Distributions.BetaA5B2<float>(rng);
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestBetaGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var samples = new float[1_000];
|
||||||
|
var dist = new FastRng.Distributions.BetaA5B2<float>(rng);
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.BetaA5B2<float>(null));
|
||||||
|
}
|
||||||
|
}
|
85
FastRngTests/Distributions/CauchyLorentzX0.cs
Normal file
85
FastRngTests/Distributions/CauchyLorentzX0.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class CauchyLorentzX0
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void 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
|
||||||
|
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.CauchyLorentzX0<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.976990739772031f).Within(0.06f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.948808314586299f).Within(0.06f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.905284997403441f).Within(0.06f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.168965864241396f).Within(0.04f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.156877686354491f).Within(0.04f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.145970509936354f).Within(0.04f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.036533159835978f).Within(0.01f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.016793067514802f).Within(0.01f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.01316382933791f).Within(0.005f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.011773781734516f).Within(0.005f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.010168596941156f).Within(0.005f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.009966272570142f).Within(0.005f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.00976990739772f).Within(0.005f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestCauchyGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.CauchyLorentzX0<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestCauchyGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.CauchyLorentzX0<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.CauchyLorentzX0<float>(null));
|
||||||
|
}
|
||||||
|
}
|
85
FastRngTests/Distributions/CauchyLorentzX1.cs
Normal file
85
FastRngTests/Distributions/CauchyLorentzX1.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class CauchyLorentzX1
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void 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
|
||||||
|
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.CauchyLorentzX1<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.009966272570142f).Within(0.003f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.010168596941156f).Within(0.004f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.010377123221893f).Within(0.005f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.015956672819692f).Within(0.005f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.016366904083094f).Within(0.005f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.016793067514802f).Within(0.005f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.039454644029179f).Within(0.015f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.145970509936354f).Within(0.03f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.333365083503296f).Within(0.1f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.545171628270584f).Within(0.1f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.948808314586302f).Within(0.06f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.976990739772032f).Within(0.03f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.986760647169751f).Within(0.02f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestCauchyGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.CauchyLorentzX0<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestCauchyGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.CauchyLorentzX0<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.CauchyLorentzX1<float>(null));
|
||||||
|
}
|
||||||
|
}
|
85
FastRngTests/Distributions/ChiSquareK1.cs
Normal file
85
FastRngTests/Distributions/ChiSquareK1.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class ChiSquareK1
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK1<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
{
|
||||||
|
var value = dist.NextNumber();
|
||||||
|
fqa.CountThis(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(1.00032041964207f).Within(0.004f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.70380551227703f).Within(0.05f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.571788691668126f).Within(0.05f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.192011337664754f).Within(0.07f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.186854182385981f).Within(0.07f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.182007652359976f).Within(0.07f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.109088865614875f).Within(0.06f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.07886274821701f).Within(0.02f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.070520397849883f).Within(0.02f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.066863009640287f).Within(0.02f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.062214737436948f).Within(0.02f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.061590997922187f).Within(0.02f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.060976622578824f).Within(0.02f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK1<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK1<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.ChiSquareK1<float>(null));
|
||||||
|
}
|
||||||
|
}
|
85
FastRngTests/Distributions/ChiSquareK10.cs
Normal file
85
FastRngTests/Distributions/ChiSquareK10.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class ChiSquareK10
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK10<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
{
|
||||||
|
var value = dist.NextNumber();
|
||||||
|
fqa.CountThis(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.0000000164021588f).Within(0.0000002f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.0000002611256437f).Within(0.000003f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.0000013153553250f).Within(0.00002f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.003459320622874f).Within(0.005f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.004111875573379f).Within(0.005f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.004850674298859f).Within(0.005f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.086418773275056f).Within(0.05f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.376092741436046f).Within(0.08f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.586569751611096f).Within(0.08f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.717189736168766f).Within(0.08f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.931477764640217f).Within(0.08f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.965244855212136f).Within(0.08f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.999827884370044f).Within(0.08f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK10<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK10<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.ChiSquareK10<float>(null));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/ChiSquareK4.cs
Normal file
82
FastRngTests/Distributions/ChiSquareK4.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class ChiSquareK4
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK4<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.016417705906679f).Within(0.02f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.032671644513723f).Within(0.02f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.048763041010352f).Within(0.02f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.32518779111264f).Within(0.05f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.338273451612642f).Within(0.05f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.351220492939994f).Within(0.05f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.65209223303425f).Within(0.08f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.857562207152294f).Within(0.099f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.923072405412387f).Within(0.099f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.952623623874265f).Within(0.099f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.990616879396201f).Within(0.099f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.995734077068522f).Within(0.099f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(1.00077558852585f).Within(0.1f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK4<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestChiSquareGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ChiSquareK4<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.ChiSquareK4<float>(null));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/ExponentialLa10.cs
Normal file
82
FastRngTests/Distributions/ExponentialLa10.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class ExponentialLa10
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestExponentialDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ExponentialLa10<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(1.00075018434777f).Within(0.05f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.905516212904248f).Within(0.05f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.81934495207398f).Within(0.05f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.122548293148741f).Within(0.12f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.110886281157421f).Within(0.12f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.10033405633809f).Within(0.12f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.00674300170146f).Within(0.005f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.000553499285385f).Within(0.001f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.000203621007796f).Within(0.001f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.00012350238419f).Within(0.001f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.0000613294689720f).Within(0.0008f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.0000554931983541f).Within(0.0008f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.0000502123223173f).Within(0.0008f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestExponentialGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ExponentialLa10<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestExponentialGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ExponentialLa10<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.ExponentialLa10<float>(null));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/ExponentialLa5.cs
Normal file
82
FastRngTests/Distributions/ExponentialLa5.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class ExponentialLa5
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestExponentialDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ExponentialLa5<float>(rng);
|
||||||
|
var fqa = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fqa.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fqa.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(1.0002177398625f).Within(0.05f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.951436545064811f).Within(0.05f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.905034437210948f).Within(0.05f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.35001394450853f).Within(0.05f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.332943563002074f).Within(0.05f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.31670571382568f).Within(0.05f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.082102871800213f).Within(0.01f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.023522866606758f).Within(0.01f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.014267339801329f).Within(0.01f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.011111415409621f).Within(0.01f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.007830082099077f).Within(0.008f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.007448204488898f).Within(0.008f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.007084951269538f).Within(0.008f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestExponentialGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ExponentialLa5<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestExponentialGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.ExponentialLa5<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.ExponentialLa5<float>(null));
|
||||||
|
}
|
||||||
|
}
|
82
FastRngTests/Distributions/GammaA5B15.cs
Normal file
82
FastRngTests/Distributions/GammaA5B15.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using FastRng;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests.Distributions;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class GammaA5B15
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestGammaDistribution01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.GammaA5B15<float>(rng);
|
||||||
|
var fra = new FrequencyAnalysis();
|
||||||
|
|
||||||
|
for (var n = 0; n < 100_000; n++)
|
||||||
|
fra.CountThis(dist.NextNumber());
|
||||||
|
|
||||||
|
var result = fra.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
|
||||||
|
Assert.That(result[0], Is.EqualTo(0.0000929594237282f).Within(0.0008f));
|
||||||
|
Assert.That(result[1], Is.EqualTo(0.0012801746797876f).Within(0.002f));
|
||||||
|
Assert.That(result[2], Is.EqualTo(0.0055781488254349f).Within(0.004f));
|
||||||
|
|
||||||
|
Assert.That(result[21], Is.EqualTo(0.9331608887752720f).Within(0.09f));
|
||||||
|
Assert.That(result[22], Is.EqualTo(0.9594734828891280f).Within(0.09f));
|
||||||
|
Assert.That(result[23], Is.EqualTo(0.9790895765535350f).Within(0.09f));
|
||||||
|
|
||||||
|
Assert.That(result[50], Is.EqualTo(0.3478287795336570f).Within(0.06f));
|
||||||
|
|
||||||
|
Assert.That(result[75], Is.EqualTo(0.0403399049422936f).Within(0.009f));
|
||||||
|
Assert.That(result[85], Is.EqualTo(0.0163628388658126f).Within(0.009f));
|
||||||
|
Assert.That(result[90], Is.EqualTo(0.0097147611446660f).Within(0.005f));
|
||||||
|
|
||||||
|
Assert.That(result[97], Is.EqualTo(0.0041135143233153f).Within(0.008f));
|
||||||
|
Assert.That(result[98], Is.EqualTo(0.0036872732029996f).Within(0.008f));
|
||||||
|
Assert.That(result[99], Is.EqualTo(0.0033038503429554f).Within(0.008f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestGammaGeneratorWithRange01()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.GammaA5B15<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(-1.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void TestGammaGeneratorWithRange02()
|
||||||
|
{
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
var dist = new FastRng.Distributions.GammaA5B15<float>(rng);
|
||||||
|
var samples = new float[1_000];
|
||||||
|
for (var n = 0; n < samples.Length; n++)
|
||||||
|
samples[n] = dist.NextNumber(0.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.That(samples.Min(), Is.GreaterThanOrEqualTo(0.0f), "Min is out of range");
|
||||||
|
Assert.That(samples.Max(), Is.LessThanOrEqualTo(1.0f), "Max is out of range");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
public void NoRandomNumberGenerator01()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new FastRng.Distributions.GammaA5B15<float>(null));
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user