2020-10-31 22:27:18 +00:00
|
|
|
using System;
|
2023-07-07 12:41:01 +00:00
|
|
|
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
|
|
|
|
2023-07-06 08:26:12 +00:00
|
|
|
namespace FastRng;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// ShapeFitter is a rejection sampler, cf. https://en.wikipedia.org/wiki/Rejection_sampling
|
|
|
|
/// </summary>
|
2023-07-07 12:41:01 +00:00
|
|
|
public sealed class ShapeFitter<TNum> where TNum : IFloatingPointIeee754<TNum>, IDivisionOperators<TNum, TNum, TNum>
|
2020-10-31 22:27:18 +00:00
|
|
|
{
|
2023-07-07 12:41:01 +00:00
|
|
|
private readonly TNum[] probabilities;
|
|
|
|
private readonly IRandom<TNum> rng;
|
|
|
|
private readonly TNum max;
|
|
|
|
private readonly TNum sampleSize;
|
|
|
|
private readonly IDistribution<TNum> uniform;
|
2023-07-06 08:26:12 +00:00
|
|
|
|
2020-10-31 22:27:18 +00:00
|
|
|
/// <summary>
|
2023-07-06 08:26:12 +00:00
|
|
|
/// Creates a shape fitter instance.
|
2020-10-31 22:27:18 +00:00
|
|
|
/// </summary>
|
2023-07-06 08:26:12 +00:00
|
|
|
/// <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>
|
2023-07-07 12:41:01 +00:00
|
|
|
public ShapeFitter(Func<TNum, TNum> shapeFunction, IRandom<TNum> rng, ushort sampleSize = 50)
|
2020-10-31 22:27:18 +00:00
|
|
|
{
|
2023-07-06 08:26:12 +00:00
|
|
|
this.rng = rng;
|
2023-07-07 12:41:01 +00:00
|
|
|
this.uniform = new Uniform<TNum>(rng);
|
|
|
|
this.sampleSize = TNum.CreateChecked(sampleSize);
|
|
|
|
this.probabilities = new TNum[sampleSize];
|
2020-10-31 22:27:18 +00:00
|
|
|
|
2023-07-07 12:41:01 +00:00
|
|
|
var sampleStepSize = TNum.One / TNum.CreateChecked(sampleSize);
|
|
|
|
var nextStep = TNum.Zero + sampleStepSize;
|
|
|
|
var maxValue = TNum.Zero;
|
2023-07-06 08:26:12 +00:00
|
|
|
for (var n = 0; n < sampleSize; n++)
|
2020-10-31 22:27:18 +00:00
|
|
|
{
|
2023-07-06 08:26:12 +00:00
|
|
|
this.probabilities[n] = shapeFunction(nextStep);
|
|
|
|
if (this.probabilities[n] > maxValue)
|
|
|
|
maxValue = this.probabilities[n];
|
2020-10-31 22:27:18 +00:00
|
|
|
|
2023-07-06 08:26:12 +00:00
|
|
|
nextStep += sampleStepSize;
|
2020-10-31 22:27:18 +00:00
|
|
|
}
|
|
|
|
|
2023-07-06 08:26:12 +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>
|
2023-07-10 14:38:25 +00:00
|
|
|
public TNum NextNumber(CancellationToken token = default)
|
2023-07-06 08:26:12 +00:00
|
|
|
{
|
|
|
|
while (!token.IsCancellationRequested)
|
2020-10-31 22:27:18 +00:00
|
|
|
{
|
2023-07-10 14:38:25 +00:00
|
|
|
var x = this.rng.GetUniform(token);
|
2023-07-07 12:41:01 +00:00
|
|
|
if (TNum.IsNaN(x))
|
2023-07-06 08:26:12 +00:00
|
|
|
return x;
|
2020-10-31 22:27:18 +00:00
|
|
|
|
2023-07-07 12:41:01 +00:00
|
|
|
var nextBucket = int.CreateChecked(TNum.Floor(x * this.sampleSize));
|
2023-07-06 08:26:12 +00:00
|
|
|
if (nextBucket >= this.probabilities.Length)
|
|
|
|
nextBucket = this.probabilities.Length - 1;
|
2020-11-01 12:47:33 +00:00
|
|
|
|
2023-07-06 08:26:12 +00:00
|
|
|
var threshold = this.probabilities[nextBucket];
|
2023-07-10 14:38:25 +00:00
|
|
|
var y = this.uniform.NextNumber(TNum.Zero, this.max, token);
|
2023-07-07 12:41:01 +00:00
|
|
|
if (TNum.IsNaN(y))
|
2023-07-06 08:26:12 +00:00
|
|
|
return y;
|
2020-10-31 22:27:18 +00:00
|
|
|
|
2023-07-06 08:26:12 +00:00
|
|
|
if(y > threshold)
|
|
|
|
continue;
|
2020-10-31 22:27:18 +00:00
|
|
|
|
2023-07-06 08:26:12 +00:00
|
|
|
return x;
|
2020-10-31 22:27:18 +00:00
|
|
|
}
|
2023-07-06 08:26:12 +00:00
|
|
|
|
2023-07-07 12:41:01 +00:00
|
|
|
return TNum.NaN;
|
2020-10-31 22:27:18 +00:00
|
|
|
}
|
|
|
|
}
|