FastRng/FastRng/ShapeFitter.cs

77 lines
2.6 KiB
C#
Raw Permalink Normal View History

2020-10-31 22:27:18 +00:00
using System;
using System.Numerics;
2020-10-31 22:27:18 +00:00
using System.Threading;
2023-07-06 08:08:46 +00:00
using FastRng.Distributions;
2020-10-31 22:27:18 +00:00
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>
2020-10-31 22:27:18 +00:00
{
private readonly TNum[] probabilities;
private readonly IRandom<TNum> rng;
private readonly TNum max;
private readonly TNum sampleSize;
private readonly IDistribution<TNum> uniform;
2020-10-31 22:27:18 +00:00
/// <summary>
/// Creates a shape fitter instance.
2020-10-31 22:27:18 +00:00
/// </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)
2020-10-31 22:27:18 +00:00
{
this.rng = rng;
this.uniform = new Uniform<TNum>(rng);
this.sampleSize = TNum.CreateChecked(sampleSize);
this.probabilities = new TNum[sampleSize];
2020-10-31 22:27:18 +00:00
var sampleStepSize = TNum.One / TNum.CreateChecked(sampleSize);
var nextStep = TNum.Zero + sampleStepSize;
var maxValue = TNum.Zero;
for (var n = 0; n < sampleSize; n++)
2020-10-31 22:27:18 +00:00
{
this.probabilities[n] = shapeFunction(nextStep);
if (this.probabilities[n] > maxValue)
maxValue = this.probabilities[n];
2020-10-31 22:27:18 +00:00
nextStep += sampleStepSize;
2020-10-31 22:27:18 +00:00
}
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)
2020-10-31 22:27:18 +00:00
{
var x = this.rng.GetUniform(token);
if (TNum.IsNaN(x))
return x;
2020-10-31 22:27:18 +00:00
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;
2020-10-31 22:27:18 +00:00
if(y > threshold)
continue;
2020-10-31 22:27:18 +00:00
return x;
2020-10-31 22:27:18 +00:00
}
return TNum.NaN;
2020-10-31 22:27:18 +00:00
}
}