Compare commits
22 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 |
@ -12,5 +12,6 @@ public sealed class BetaA2B2<TNum> : Distribution<TNum> where TNum : IFloatingPo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
||||||
}
|
}
|
@ -12,5 +12,6 @@ public sealed class BetaA2B5<TNum> : Distribution<TNum> where TNum : IFloatingPo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
||||||
}
|
}
|
@ -12,5 +12,6 @@ public sealed class BetaA5B2<TNum> : Distribution<TNum> where TNum : IFloatingPo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow(x, ALPHA - TNum.One) * TNum.Pow(TNum.One - x, BETA - TNum.One);
|
||||||
}
|
}
|
@ -13,5 +13,6 @@ public sealed class CauchyLorentzX0<TNum> : Distribution<TNum> where TNum : IFlo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * ( TNum.One / (TNum.Pi * SCALE)) * ((SCALE * SCALE) / (TNum.Pow(x - MEDIAN, TWO) + (SCALE * SCALE)));
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ( TNum.One / (TNum.Pi * SCALE)) * ((SCALE * SCALE) / (TNum.Pow(x - MEDIAN, TWO) + (SCALE * SCALE)));
|
||||||
}
|
}
|
@ -13,5 +13,6 @@ public sealed class CauchyLorentzX1<TNum> : Distribution<TNum> where TNum : IFlo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * (TNum.One / (TNum.Pi * SCALE)) * ((SCALE * SCALE) / (TNum.Pow(x - MEDIAN, TWO) + (SCALE * SCALE)));
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * (TNum.One / (TNum.Pi * SCALE)) * ((SCALE * SCALE) / (TNum.Pow(x - MEDIAN, TWO) + (SCALE * SCALE)));
|
||||||
}
|
}
|
@ -24,5 +24,6 @@ public sealed class ChiSquareK1<TNum> : Distribution<TNum> where TNum : IFloatin
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
||||||
}
|
}
|
@ -24,5 +24,6 @@ public sealed class ChiSquareK10<TNum> : Distribution<TNum> where TNum : IFloati
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
||||||
}
|
}
|
@ -24,5 +24,6 @@ public sealed class ChiSquareK4<TNum> : Distribution<TNum> where TNum : IFloatin
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((TNum.Pow(x, K_HALF_MINUS_ONE) * TNum.Exp(-x * HALF)) / DIVISOR);
|
||||||
}
|
}
|
@ -16,7 +16,7 @@ public abstract class Distribution<TNum> : IDistribution<TNum> where TNum : IFlo
|
|||||||
this.fitter = new ShapeFitter<TNum>(this.ShapeFunction, rng, 100);
|
this.fitter = new ShapeFitter<TNum>(this.ShapeFunction, rng, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected abstract TNum ShapeFunction(TNum x);
|
public abstract TNum ShapeFunction(TNum x);
|
||||||
|
|
||||||
public TNum GetDistributedValue(CancellationToken token = default) => this.fitter.NextNumber(token);
|
public TNum GetDistributedValue(CancellationToken token = default) => this.fitter.NextNumber(token);
|
||||||
|
|
||||||
|
@ -11,5 +11,6 @@ public sealed class ExponentialLa10<TNum> : Distribution<TNum> where TNum : IFlo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(-LAMBDA * x);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(-LAMBDA * x);
|
||||||
}
|
}
|
@ -11,5 +11,6 @@ public sealed class ExponentialLa5<TNum> : Distribution<TNum> where TNum : IFloa
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(-LAMBDA * x);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(-LAMBDA * x);
|
||||||
}
|
}
|
@ -21,5 +21,6 @@ public sealed class GammaA5B15<TNum> : Distribution<TNum> where TNum : IFloating
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * ((BETA_TO_THE_ALPHA * TNum.Pow(x, ALPHA - TNum.One) * TNum.Exp(-BETA * x)) / GAMMA_ALPHA);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * ((BETA_TO_THE_ALPHA * TNum.Pow(x, ALPHA - TNum.One) * TNum.Exp(-BETA * x)) / GAMMA_ALPHA);
|
||||||
}
|
}
|
@ -11,5 +11,6 @@ public sealed class InverseExponentialLa10<TNum> : Distribution<TNum> where TNum
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(LAMBDA * x);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(LAMBDA * x);
|
||||||
}
|
}
|
@ -11,5 +11,6 @@ public sealed class InverseExponentialLa5<TNum> : Distribution<TNum> where TNum
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(LAMBDA * x);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => CONSTANT * LAMBDA * TNum.Exp(LAMBDA * x);
|
||||||
}
|
}
|
@ -22,5 +22,6 @@ public sealed class InverseGammaA3B05<TNum> : Distribution<TNum> where TNum : IF
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Pow(x, -ALPHA - TNum.One) * TNum.Exp(-BETA / x);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Pow(x, -ALPHA - TNum.One) * TNum.Exp(-BETA / x);
|
||||||
}
|
}
|
@ -20,5 +20,6 @@ public sealed class LaplaceB01M0<TNum> : Distribution<TNum> where TNum : IFloati
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Exp(-TNum.Abs(x - MU) / B);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Exp(-TNum.Abs(x - MU) / B);
|
||||||
}
|
}
|
@ -20,5 +20,6 @@ public sealed class LaplaceB01M05<TNum> : Distribution<TNum> where TNum : IFloat
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Exp(-TNum.Abs(x - MU) / B);
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => FACTOR_LEFT * TNum.Exp(-TNum.Abs(x - MU) / B);
|
||||||
}
|
}
|
@ -20,5 +20,6 @@ public sealed class LogNormalS1M0<TNum> : Distribution<TNum> where TNum : IFloat
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => (CONSTANT / (x * FACTOR)) * TNum.Exp( -(TNum.Pow(TNum.Log(x) - MU, TWO) / (TWO * TNum.Pow(SIGMA, TWO))));
|
/// <inheritdoc />
|
||||||
|
public override TNum ShapeFunction(TNum x) => (CONSTANT / (x * FACTOR)) * TNum.Exp( -(TNum.Pow(TNum.Log(x) - MU, TWO) / (TWO * TNum.Pow(SIGMA, TWO))));
|
||||||
}
|
}
|
@ -14,5 +14,6 @@ public sealed class NormalS02M05<TNum> : Distribution<TNum> where TNum : IFloati
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => TNum.One / (STD_DEV * SQRT_2_PI) * TNum.Exp(NEGATIVE_HALF * TNum.Pow((x - MEAN) / STD_DEV, TWO));
|
/// <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));
|
||||||
}
|
}
|
@ -26,5 +26,6 @@ public sealed class StudentTNu1<TNum> : Distribution<TNum> where TNum : IFloatin
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * TNum.Pow((DIVIDEND / DIVISOR) * TNum.Pow( TNum.One + TNum.Pow(START + x * COMPRESS, TWO) / NU, EXPONENT), COMPRESS);
|
/// <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);
|
||||||
}
|
}
|
@ -12,5 +12,6 @@ public sealed class WeibullK05La1<TNum> : Distribution<TNum> where TNum : IFloat
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private protected override TNum ShapeFunction(TNum x) => CONSTANT * ( (K / LAMBDA) * TNum.Pow(x / LAMBDA, K - TNum.One) * TNum.Exp(-TNum.Pow(x/LAMBDA, K)));
|
/// <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,16 +1,17 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
<AssemblyVersion>1.2.0</AssemblyVersion>
|
||||||
<FileVersion>1.1.0</FileVersion>
|
<FileVersion>1.2.0</FileVersion>
|
||||||
<PackageVersion>1.1.0</PackageVersion>
|
<PackageVersion>1.2.0</PackageVersion>
|
||||||
<Authors>Thorsten Sommer</Authors>
|
<Authors>Thorsten Sommer</Authors>
|
||||||
<PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/FastRng</PackageProjectUrl>
|
<PackageProjectUrl>https://devops.tsommer.org/open-source/dotnet/FastRng</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/FastRng</RepositoryUrl>
|
<RepositoryUrl>https://devops.tsommer.org/open-source/dotnet/FastRng</RepositoryUrl>
|
||||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
@ -21,5 +22,7 @@
|
|||||||
<DocumentationFile>bin\Release\FastRng.xml</DocumentationFile>
|
<DocumentationFile>bin\Release\FastRng.xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="\"/>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -18,4 +18,29 @@ public interface IRandom<TNum> : IDisposable where TNum : IFloatingPointIeee754<
|
|||||||
/// by using multiple threads at the same time.
|
/// by using multiple threads at the same time.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public TNum GetUniform(CancellationToken cancel = default);
|
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);
|
||||||
}
|
}
|
@ -4,6 +4,8 @@ using System.Numerics;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
// ReSharper disable RedundantExtendsListEntry
|
||||||
|
|
||||||
namespace FastRng;
|
namespace FastRng;
|
||||||
|
|
||||||
public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
||||||
@ -36,13 +38,12 @@ public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNu
|
|||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a multi-threaded random number generator.
|
/// Creates a multithreaded random number generator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This constructor uses the user's current local time
|
/// This constructor uses the user's current local time
|
||||||
/// to derive necessary parameters for the generator.
|
/// to derive the necessary parameters for the generator.
|
||||||
/// Thus, the results are depending on the time, where
|
/// Thus, the results depend on the time when the generator was created.
|
||||||
/// the generator was created.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public MultiChannelRng()
|
public MultiChannelRng()
|
||||||
{
|
{
|
||||||
@ -58,7 +59,7 @@ public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a multi-threaded random number generator.
|
/// Creates a multithreaded random number generator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A multi-threaded random number generator created by this constructor is
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
@ -150,18 +151,34 @@ public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNu
|
|||||||
|
|
||||||
private void StopProducer() => this.producerTokenSource.Cancel();
|
private void StopProducer() => this.producerTokenSource.Cancel();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose() => this.StopProducer();
|
public void Dispose() => this.StopProducer();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Implementation of IRandom<TNum>
|
#region Implementation of IRandom<TNum>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public TNum GetUniform(CancellationToken cancel = default)
|
public TNum GetUniform(CancellationToken cancel = default)
|
||||||
{
|
{
|
||||||
var valueTask = this.channelFloats.Reader.ReadAsync(cancel);
|
var valueTask = this.channelFloats.Reader.ReadAsync(cancel);
|
||||||
return valueTask.AsTask().Result;
|
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
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -5,6 +5,8 @@ using System.Numerics;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
// ReSharper disable RedundantExtendsListEntry
|
||||||
|
|
||||||
namespace FastRng;
|
namespace FastRng;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -12,18 +14,15 @@ namespace FastRng;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster.
|
/// 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. Consumers can use a token to cancel e.g. timeout an operation.<br/><br/>
|
/// 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
|
/// 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
|
/// 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/>
|
/// 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.
|
/// 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).
|
/// Cook's (johndcook.com) implementation (https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation).
|
||||||
/// Thanks John for the inspiration.<br/><br/>
|
/// Thanks, John, for the inspiration.<br/><br/>
|
||||||
///
|
|
||||||
/// Please notice: When using the debug environment, MultiThreadedRng might uses a smaller buffer size. Please ensure,
|
|
||||||
/// that the production environment uses a release build, though.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
||||||
{
|
{
|
||||||
@ -33,14 +32,17 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
private const int BUFFER_SIZE = 1_000_000;
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// The queue size means, how many buffer we store in a queue at the same time:
|
// The queue size means, how many buffers we store in a queue at the same time:
|
||||||
private const int QUEUE_SIZE_FLOAT = 2;
|
private const int QUEUE_SIZE_FLOAT = 2;
|
||||||
|
|
||||||
// The queue size means, how many buffer we store in a queue at the same time:
|
// 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 const int QUEUE_SIZE_INT = QUEUE_SIZE_FLOAT * 2;
|
||||||
|
|
||||||
private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f);
|
private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f);
|
||||||
|
|
||||||
|
// ReSharper disable StaticMemberInGenericType
|
||||||
private static readonly object LOCKER = new();
|
private static readonly object LOCKER = new();
|
||||||
|
// ReSharper restore StaticMemberInGenericType
|
||||||
|
|
||||||
// Gets used to stop the producer threads:
|
// Gets used to stop the producer threads:
|
||||||
private readonly CancellationTokenSource producerTokenSource = new();
|
private readonly CancellationTokenSource producerTokenSource = new();
|
||||||
@ -60,13 +62,15 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
// The uniform float producer thread:
|
// The uniform float producer thread:
|
||||||
private Thread producerRandomUniformDistributedFloat;
|
private Thread producerRandomUniformDistributedFloat;
|
||||||
|
|
||||||
|
private readonly UIntChannelProducer independentUIntProducer = new();
|
||||||
|
|
||||||
// Variable w and z for the uint generator. Both get used
|
// Variable w and z for the uint generator. Both get used
|
||||||
// as seeding variable as well (cf. constructors)
|
// as seeding variable as well (cf. constructors)
|
||||||
private uint mW;
|
private uint mW;
|
||||||
private uint mZ;
|
private uint mZ;
|
||||||
|
|
||||||
// This is the current buffer for the consumer side i.e. the public interfaces:
|
// This is the current buffer for the consumer side, i.e., the public interfaces:
|
||||||
private TNum[] currentBuffer = Array.Empty<TNum>();
|
private TNum[] currentBuffer = [];
|
||||||
|
|
||||||
// The current pointer to the next current buffer's address to read from:
|
// The current pointer to the next current buffer's address to read from:
|
||||||
private int currentBufferPointer = BUFFER_SIZE;
|
private int currentBufferPointer = BUFFER_SIZE;
|
||||||
@ -77,10 +81,8 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
/// Creates a multi-threaded random number generator.
|
/// Creates a multi-threaded random number generator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This constructor uses the user's current local time
|
/// This constructor uses the user's current local time to derive the necessary parameters for the generator.
|
||||||
/// to derive necessary parameters for the generator.
|
/// Thus, the results depend on the time when the generator was created.
|
||||||
/// Thus, the results are depending on the time, where
|
|
||||||
/// the generator was created.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public MultiThreadedRng()
|
public MultiThreadedRng()
|
||||||
{
|
{
|
||||||
@ -171,7 +173,7 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Ensure, that we do not produce more buffers, as configured:
|
// Ensure that we do not produce more buffers, as configured:
|
||||||
if (this.queueIntegers.Count < QUEUE_SIZE_INT)
|
if (this.queueIntegers.Count < QUEUE_SIZE_INT)
|
||||||
{
|
{
|
||||||
this.queueIntegers.Enqueue(nextBuffer);
|
this.queueIntegers.Enqueue(nextBuffer);
|
||||||
@ -202,7 +204,7 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
// A local source buffer of uints:
|
// A local source buffer of uints:
|
||||||
uint[] bufferSource = null;
|
uint[] bufferSource;
|
||||||
|
|
||||||
// Try to get the next source buffer:
|
// Try to get the next source buffer:
|
||||||
while (!this.queueIntegers.TryDequeue(out bufferSource) && !cancellationToken.IsCancellationRequested)
|
while (!this.queueIntegers.TryDequeue(out bufferSource) && !cancellationToken.IsCancellationRequested)
|
||||||
@ -224,7 +226,7 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Ensure, that the queue contains only the configured number of buffers:
|
// Ensure that the queue contains only the configured number of buffers:
|
||||||
if (this.queueFloats.Count < QUEUE_SIZE_FLOAT)
|
if (this.queueFloats.Count < QUEUE_SIZE_FLOAT)
|
||||||
{
|
{
|
||||||
this.queueFloats.Enqueue(nextBuffer);
|
this.queueFloats.Enqueue(nextBuffer);
|
||||||
@ -273,7 +275,7 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
// can get the next buffer:
|
// can get the next buffer:
|
||||||
lock (LOCKER)
|
lock (LOCKER)
|
||||||
{
|
{
|
||||||
// We are might not the first thread, which has to get the next buffer.
|
// 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
|
// When some other thread has already got the next buffer, the pointer
|
||||||
// was already reset to zero. In this case, we start over again:
|
// was already reset to zero. In this case, we start over again:
|
||||||
if(this.currentBufferPointer < BUFFER_SIZE)
|
if(this.currentBufferPointer < BUFFER_SIZE)
|
||||||
@ -295,6 +297,11 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
// Made a local copy of the current pointer:
|
// Made a local copy of the current pointer:
|
||||||
var myPointer = this.currentBufferPointer;
|
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:
|
// Increment the pointer for the next thread or call:
|
||||||
var nextPointer = myPointer + 1;
|
var nextPointer = myPointer + 1;
|
||||||
|
|
||||||
@ -303,11 +310,25 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
goto Start;
|
goto Start;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Case: Success. We updated the pointer and, thus, can use the read number.
|
// Case: Success. We updated the pointer and, thus, can use it to read the number.
|
||||||
//
|
//
|
||||||
return this.currentBuffer[myPointer];
|
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();
|
private void StopProducer() => this.producerTokenSource.Cancel();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -315,7 +336,11 @@ public sealed class MultiThreadedRng<TNum> : IRandom<TNum>, IDisposable where TN
|
|||||||
/// when it is no longer needed. Otherwise, the background threads
|
/// when it is no longer needed. Otherwise, the background threads
|
||||||
/// are still running.
|
/// are still running.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose() => this.StopProducer();
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.independentUIntProducer.Dispose();
|
||||||
|
this.StopProducer();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
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
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
327
FastRngTests/GetIntTests.cs
Normal file
327
FastRngTests/GetIntTests.cs
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FastRng;
|
||||||
|
using FastRng.Distributions;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace FastRngTests;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public class GetIntTests
|
||||||
|
{
|
||||||
|
#region Channel-Based
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void ChannelGetMax()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMax = random.Next();
|
||||||
|
|
||||||
|
using var rng = new MultiChannelRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMax);
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(0));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void ChannelGetMaxDistributionCheck()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMax = random.Next(3, 33);
|
||||||
|
var freqCheck = new IntFrequencyAnalysis(randomMax);
|
||||||
|
using var rng = new MultiChannelRng<float>();
|
||||||
|
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMax);
|
||||||
|
freqCheck.CountThis(value);
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(0));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
var max = distribution.Max();
|
||||||
|
var min = distribution.Min();
|
||||||
|
Assert.That(max - min, Is.LessThanOrEqualTo(0.27f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void ChannelGetMinMax()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMin = random.Next();
|
||||||
|
int randomMax;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
randomMax = random.Next();
|
||||||
|
} while (randomMax < randomMin);
|
||||||
|
|
||||||
|
using var rng = new MultiChannelRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMin, randomMax);
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(randomMin));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void ChannelGetMinMaxDistributionCheck()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMin = random.Next(0, 16);
|
||||||
|
var randomMax = random.Next(randomMin + 10, 33);
|
||||||
|
|
||||||
|
var freqCheck = new IntFrequencyAnalysis(randomMax - randomMin);
|
||||||
|
using var rng = new MultiChannelRng<float>();
|
||||||
|
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMin, randomMax);
|
||||||
|
freqCheck.CountThis(value - randomMin);
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(randomMin));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
var max = distribution.Max();
|
||||||
|
var min = distribution.Min();
|
||||||
|
Assert.That(max - min, Is.LessThanOrEqualTo(0.27f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void ChannelCheckMinMax01()
|
||||||
|
{
|
||||||
|
var expectedMax = 3;
|
||||||
|
|
||||||
|
var max = int.MinValue;
|
||||||
|
var min = int.MaxValue;
|
||||||
|
using var rng = new MultiChannelRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(expectedMax);
|
||||||
|
if (value < min)
|
||||||
|
min = value;
|
||||||
|
|
||||||
|
if (value > max)
|
||||||
|
max = value;
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(0));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(expectedMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(min, Is.EqualTo(0));
|
||||||
|
Assert.That(max, Is.EqualTo(expectedMax - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void ChannelCheckMinMax02()
|
||||||
|
{
|
||||||
|
var expectedMin = 2;
|
||||||
|
var expectedMax = 6;
|
||||||
|
|
||||||
|
var max = int.MinValue;
|
||||||
|
var min = int.MaxValue;
|
||||||
|
using var rng = new MultiChannelRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(expectedMin, expectedMax);
|
||||||
|
if (value < min)
|
||||||
|
min = value;
|
||||||
|
|
||||||
|
if (value > max)
|
||||||
|
max = value;
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(expectedMin));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(expectedMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(min, Is.EqualTo(expectedMin));
|
||||||
|
Assert.That(max, Is.EqualTo(expectedMax - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Multithreaded
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void MultithreadedGetMax()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMax = random.Next();
|
||||||
|
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMax);
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(0));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void MultithreadedGetMaxDistributionCheck()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMax = random.Next(3, 33);
|
||||||
|
var freqCheck = new IntFrequencyAnalysis(randomMax);
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMax);
|
||||||
|
freqCheck.CountThis(value);
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(0));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
var max = distribution.Max();
|
||||||
|
var min = distribution.Min();
|
||||||
|
Assert.That(max - min, Is.LessThanOrEqualTo(0.27f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void MultithreadedGetMinMax()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMin = random.Next();
|
||||||
|
int randomMax;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
randomMax = random.Next();
|
||||||
|
} while (randomMax < randomMin);
|
||||||
|
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMin, randomMax);
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(randomMin));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void MultithreadedGetMinMaxDistributionCheck()
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var randomMin = random.Next(0, 16);
|
||||||
|
var randomMax = random.Next(randomMin + 10, 33);
|
||||||
|
|
||||||
|
var freqCheck = new IntFrequencyAnalysis(randomMax - randomMin);
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(randomMin, randomMax);
|
||||||
|
freqCheck.CountThis(value - randomMin);
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(randomMin));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(randomMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine);
|
||||||
|
var max = distribution.Max();
|
||||||
|
var min = distribution.Min();
|
||||||
|
Assert.That(max - min, Is.LessThanOrEqualTo(0.27f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void MultithreadedCheckMinMax01()
|
||||||
|
{
|
||||||
|
var expectedMax = 3;
|
||||||
|
|
||||||
|
var max = int.MinValue;
|
||||||
|
var min = int.MaxValue;
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(expectedMax);
|
||||||
|
if (value < min)
|
||||||
|
min = value;
|
||||||
|
|
||||||
|
if (value > max)
|
||||||
|
max = value;
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(0));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(expectedMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(min, Is.EqualTo(0));
|
||||||
|
Assert.That(max, Is.EqualTo(expectedMax - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.COVER)]
|
||||||
|
[Category(TestCategories.NORMAL)]
|
||||||
|
[Category(TestCategories.INT)]
|
||||||
|
public void MultithreadedCheckMinMax02()
|
||||||
|
{
|
||||||
|
var expectedMin = 2;
|
||||||
|
var expectedMax = 6;
|
||||||
|
|
||||||
|
var max = int.MinValue;
|
||||||
|
var min = int.MaxValue;
|
||||||
|
using var rng = new MultiThreadedRng<float>();
|
||||||
|
for(var n = 0; n < 10_000; n++)
|
||||||
|
{
|
||||||
|
var value = rng.GetUniformInt(expectedMin, expectedMax);
|
||||||
|
if (value < min)
|
||||||
|
min = value;
|
||||||
|
|
||||||
|
if (value > max)
|
||||||
|
max = value;
|
||||||
|
|
||||||
|
Assert.That(value, Is.GreaterThanOrEqualTo(expectedMin));
|
||||||
|
Assert.That(value, Is.LessThanOrEqualTo(expectedMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(min, Is.EqualTo(expectedMin));
|
||||||
|
Assert.That(max, Is.EqualTo(expectedMax - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
82
FastRngTests/IntFrequencyAnalysis.cs
Normal file
82
FastRngTests/IntFrequencyAnalysis.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FastRngTests;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public sealed class IntFrequencyAnalysis
|
||||||
|
{
|
||||||
|
private readonly uint[] data;
|
||||||
|
|
||||||
|
public IntFrequencyAnalysis(int maxValue = 100)
|
||||||
|
{
|
||||||
|
this.data = new uint[maxValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CountThis(int value)
|
||||||
|
{
|
||||||
|
this.data[value]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] GetNormalizedEvents()
|
||||||
|
{
|
||||||
|
var max = (float)this.data.Max();
|
||||||
|
var result = new float[this.data.Length];
|
||||||
|
for (var n = 0; n < this.data.Length; n++)
|
||||||
|
{
|
||||||
|
result[n] = this.data[n] / max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] Normalize()
|
||||||
|
{
|
||||||
|
var max = (float)this.data.Max();
|
||||||
|
var result = new float[this.data.Length];
|
||||||
|
for (var n = 0; n < this.data.Length; n++)
|
||||||
|
result[n] = this.data[n] / max;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] NormalizeAndPlotEvents(Action<string> writer)
|
||||||
|
{
|
||||||
|
var result = this.Normalize();
|
||||||
|
Plot(result, writer, "Event Distribution");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlotOccurence(Action<string> writer)
|
||||||
|
{
|
||||||
|
var data = this.data.Select(n => n > 0f ? 1.0f : 0.0f).ToArray();
|
||||||
|
Plot(data, writer, "Occurrence Distribution");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Plot(float[] data, Action<string> writer, string name)
|
||||||
|
{
|
||||||
|
const int HEIGHT = 16;
|
||||||
|
|
||||||
|
var values = new float[data.Length];
|
||||||
|
for (var n = 0; n < data.Length; n++)
|
||||||
|
{
|
||||||
|
values[n] = data[n] * HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (var line = HEIGHT; line > 0; line--)
|
||||||
|
{
|
||||||
|
for (var column = 0; column < data.Length; column++)
|
||||||
|
sb.Append(values[column] >= line ? '█' : '░');
|
||||||
|
|
||||||
|
writer.Invoke(sb.ToString());
|
||||||
|
sb.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Invoke(name);
|
||||||
|
writer.Invoke(string.Empty);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using FastRng;
|
using FastRng;
|
||||||
using FastRng.Distributions;
|
using FastRng.Distributions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -223,6 +224,32 @@ public class MultiThreadedRngTests
|
|||||||
Assert.That(lorentzContains1, Is.True, "Lorentz distribution does not contained 1");
|
Assert.That(lorentzContains1, Is.True, "Lorentz distribution does not contained 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Category(TestCategories.LONG_RUNNING)]
|
||||||
|
public void TestMultiThreadedConsumer()
|
||||||
|
{
|
||||||
|
var job1 = Task.Factory.StartNew(Run, TaskCreationOptions.LongRunning);
|
||||||
|
var job2 = Task.Factory.StartNew(Run, TaskCreationOptions.LongRunning);
|
||||||
|
var job3 = Task.Factory.StartNew(Run, TaskCreationOptions.LongRunning);
|
||||||
|
|
||||||
|
Assert.DoesNotThrowAsync(async () => await job1);
|
||||||
|
Assert.DoesNotThrowAsync(async () => await job2);
|
||||||
|
Assert.DoesNotThrowAsync(async () => await job3);
|
||||||
|
return;
|
||||||
|
|
||||||
|
float Run()
|
||||||
|
{
|
||||||
|
var sum = 0f;
|
||||||
|
var distLorentz = new CauchyLorentzX1<float>(this.rng);
|
||||||
|
for (int i = 0; i < 100_000_000; i++)
|
||||||
|
{
|
||||||
|
sum += distLorentz.NextNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Category(TestCategories.COVER)]
|
[Category(TestCategories.COVER)]
|
||||||
[Category(TestCategories.NORMAL)]
|
[Category(TestCategories.NORMAL)]
|
||||||
|
@ -5,6 +5,7 @@ public static class TestCategories
|
|||||||
public const string COVER = "cover";
|
public const string COVER = "cover";
|
||||||
public const string PERFORMANCE = "performance";
|
public const string PERFORMANCE = "performance";
|
||||||
public const string NORMAL = "normal";
|
public const string NORMAL = "normal";
|
||||||
|
public const string INT = "int";
|
||||||
public const string EXAMPLE = "example";
|
public const string EXAMPLE = "example";
|
||||||
public const string LONG_RUNNING = "long running";
|
public const string LONG_RUNNING = "long running";
|
||||||
}
|
}
|
12
README.md
12
README.md
@ -1,14 +1,12 @@
|
|||||||
# FastRng
|
# FastRng
|
||||||
|
|
||||||
FastRng is a multi-threaded pseudo-random number generator. Besides the generation of uniformly distributed random numbers, there are several other distributions to choose from. For performance reasons the parameters of the distributions are not user-definable. For some distributions, therefore, different parameter variations are available. If a different combination is desired, a separate class can be created.
|
FastRng is a multithreaded pseudo-random number generator. Besides the generation of uniformly distributed random numbers, there are several other distributions to choose from. For performance reasons, the parameters of the distributions are not user-definable. For some distributions, therefore, different parameter variations are available. If a different combination is desired, a separate class can be created.
|
||||||
|
|
||||||
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. Consumers can use a token to cancel e.g. timeout an operation.
|
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.
|
||||||
|
|
||||||
FastRng (class `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.
|
FastRng (class `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.
|
||||||
|
|
||||||
The class `MultiThreadedRng` 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.
|
The class `MultiThreadedRng` 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.
|
||||||
|
|
||||||
Please notice: When using the debug environment, MultiThreadedRng might uses a smaller buffer size. Please ensure, that the production environment uses a release build, though.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Example code:
|
Example code:
|
||||||
@ -37,7 +35,7 @@ Notes:
|
|||||||
|
|
||||||
- `MultiThreadedRng` fills some buffers after creation. Thus, create and reuse it as long as needed. Avoid useless re-creation.
|
- `MultiThreadedRng` fills some buffers after creation. Thus, create and reuse it as long as needed. Avoid useless re-creation.
|
||||||
|
|
||||||
- Distributions need some time at creation to calculate probabilities. Thus, just create a distribution once and use reuse it. Avoid useless re-creation.
|
- Distributions need some time in creation to calculate probabilities. Thus, create a distribution once and use reuse it. Avoid useless re-creation.
|
||||||
|
|
||||||
## Available Distributions
|
## Available Distributions
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user