diff --git a/Source/RandomTools.Core/Options/Delay/DelayOptions.cs b/Source/RandomTools.Core/Options/Delay/DelayOptions.cs
index ffd31f6..7b2826c 100644
--- a/Source/RandomTools.Core/Options/Delay/DelayOptions.cs
+++ b/Source/RandomTools.Core/Options/Delay/DelayOptions.cs
@@ -228,5 +228,158 @@ The interval is too narrow to produce meaningful randomness.
}
}
}
+
+ ///
+ /// Configuration options for a Bates distribution used to generate
+ /// bounded random delays.
+ ///
+ /// The Bates distribution is defined as the arithmetic mean of
+ /// N independent uniform samples. It produces a smooth,
+ /// bell-shaped distribution that remains strictly within the
+ /// configured and range.
+ ///
+ ///
+ /// When Samples = 1, the distribution degenerates to a uniform
+ /// distribution. Increasing Samples makes the distribution
+ /// progressively smoother and more concentrated toward the center,
+ /// approaching a bounded normal-like shape.
+ ///
+ ///
+ public sealed class Bates : DelayOptionsBase
+ {
+ ///
+ /// Gets or sets the number of uniform samples used to construct the
+ /// Bates distribution. Must be at least 1.
+ ///
+ internal int Samples;
+
+ ///
+ /// Sets the number of uniform samples used to compute the Bates mean.
+ /// Larger values produce smoother, more bell-shaped distributions.
+ ///
+ /// The number of samples (must be ≥ 1).
+ /// The current instance for fluent configuration.
+ public Bates WithSamples(int value)
+ {
+ Samples = value;
+ return this;
+ }
+
+ ///
+ /// Validates the configuration and throws an
+ /// if the settings are not suitable for generating a Bates distribution.
+ ///
+ public override void Validate()
+ {
+ base.Validate();
+
+ if ((Maximum - Minimum) <= double.Epsilon)
+ {
+ throw new OptionsValidationException(this,
+ $"Configured range [{Minimum}, {Maximum}] is too narrow. " +
+ $"A Bates distribution cannot reliably generate values within such a small interval.");
+ }
+
+ if (Samples < 1)
+ {
+ throw new OptionsValidationException(this,
+ $"Samples ({Samples}) must be at least 1 to produce meaningful Bates distribution sampling.");
+ }
+ }
+
+ ///
+ public override bool Equals(Bates? other)
+ {
+ return base.Equals(other) &&
+ other.Samples == Samples;
+ }
+
+ ///
+ public override int GetHashCode() =>
+ HashCode.Combine(Minimum, Maximum, TimeUnit, Samples);
+ }
+
+ ///
+ /// Configuration options for a Polynomial / Power delay distribution.
+ ///
+ /// The Polynomial distribution generates values in [Minimum, Maximum] with a density
+ /// proportional to (x - Minimum)^Power or (Maximum - x)^Power if reversed.
+ ///
+ ///
+ /// A Power of 0 produces a uniform distribution. Higher Power values produce
+ /// increasingly skewed distributions toward Maximum (or Minimum if Reverse=true).
+ ///
+ ///
+ public sealed class Polynomial : DelayOptionsBase
+ {
+ ///
+ /// Gets the power exponent for the polynomial distribution.
+ /// Must be >= 0.
+ ///
+ internal double Power { get; private set; } = 1.0;
+
+ ///
+ /// Indicates whether the distribution is reversed: density proportional to (Maximum - x)^Power.
+ ///
+ internal bool Reverse { get; private set; } = false;
+
+ ///
+ /// Sets the power exponent (Power ≥ 0) for the polynomial distribution.
+ ///
+ /// Exponent value.
+ /// The current instance for fluent configuration.
+ public Polynomial WithPower(double value)
+ {
+ Power = value;
+ return this;
+ }
+
+ ///
+ /// Sets whether the distribution is reversed (more values near Minimum).
+ ///
+ /// True to reverse, false for normal orientation.
+ /// The current instance for fluent configuration.
+ public Polynomial WithReverse(bool value)
+ {
+ Reverse = value;
+ return this;
+ }
+
+ ///
+ /// Validates the configuration, throwing an
+ /// if any option is invalid.
+ ///
+ public override void Validate()
+ {
+ base.Validate();
+
+ if ((Maximum - Minimum) <= double.Epsilon)
+ {
+ throw new OptionsValidationException(this,
+ $"Configured range [{Minimum}, {Maximum}] is too narrow. " +
+ "Polynomial distribution cannot reliably generate values in such a small interval.");
+ }
+
+ EnsureFinite(Power);
+
+ if (Power < 0.0)
+ {
+ throw new OptionsValidationException(this,
+ $"Power ({Power}) must be >= 0.");
+ }
+ }
+
+ ///
+ public override bool Equals(Polynomial? other)
+ {
+ return base.Equals(other) &&
+ other.Power == Power &&
+ other.Reverse == Reverse;
+ }
+
+ ///
+ public override int GetHashCode() =>
+ HashCode.Combine(Minimum, Maximum, TimeUnit, Power, Reverse);
+ }
}
}
\ No newline at end of file
diff --git a/Source/RandomTools.Core/Random/Delay/ArcsineDelay.cs b/Source/RandomTools.Core/Random/Delay/ArcsineDelay.cs
index 33ccd66..b1cf1cc 100644
--- a/Source/RandomTools.Core/Random/Delay/ArcsineDelay.cs
+++ b/Source/RandomTools.Core/Random/Delay/ArcsineDelay.cs
@@ -36,9 +36,6 @@ public ArcsineDelay(DelayOptions.Arcsine options) : base(options)
/// A representing the generated delay.
public override TimeSpan Next()
{
- double min = Options.Minimum;
- double max = Options.Maximum;
-
// Generate a uniform random value in [0,1)
double u = CoreTools.NextDouble();
@@ -47,7 +44,7 @@ public override TimeSpan Next()
double sinSq = Math.Sin(angle) * Math.Sin(angle);
// Scale the result to the configured range
- double value = min + sinSq * (max - min);
+ double value = ScaleToRange(sinSq);
// Convert the numeric value to a TimeSpan using the specified time unit
return CoreTools.ToTimeSpan(value, Options.TimeUnit);
diff --git a/Source/RandomTools.Core/Random/Delay/BatesDelay.cs b/Source/RandomTools.Core/Random/Delay/BatesDelay.cs
new file mode 100644
index 0000000..705284c
--- /dev/null
+++ b/Source/RandomTools.Core/Random/Delay/BatesDelay.cs
@@ -0,0 +1,53 @@
+using RandomTools.Core.Options.Delay;
+
+namespace RandomTools.Core.Random.Delay
+{
+ ///
+ /// Generates delays based on a distribution.
+ ///
+ /// The Bates distribution is defined as the arithmetic mean of
+ /// independent uniform random samples.
+ /// It produces a smooth, bell-shaped distribution strictly within the configured
+ /// and range.
+ ///
+ ///
+ /// When Samples = 1, this degenerates to a uniform distribution. Increasing
+ /// Samples results in a smoother, more centered distribution.
+ ///
+ ///
+ public sealed class BatesDelay : RandomDelay
+ {
+#pragma warning disable IDE0290 // Use primary constructor
+ ///
+ /// Initializes a new instance of the class
+ /// with the specified .
+ ///
+ /// Configuration options for the Bates distribution.
+ public BatesDelay(DelayOptions.Bates options) : base(options)
+#pragma warning restore IDE0290 // Use primary constructor
+ {
+ }
+
+ ///
+ /// Generates the next random delay based on the configured Bates distribution.
+ ///
+ /// A representing the generated delay.
+ public override TimeSpan Next()
+ {
+ // Number of uniform samples to average
+ int N = Options.Samples;
+ double mean = 0.0;
+
+ for (int i = 0; i < N; i++)
+ {
+ double next = CoreTools.NextDouble();
+ mean += (next - mean) / (i + 1);
+ }
+
+ // Map mean from [0,1] to the configured [Minimum, Maximum] range
+ double value = ScaleToRange(mean);
+
+ return CoreTools.ToTimeSpan(value, Options.TimeUnit);
+ }
+ }
+}
diff --git a/Source/RandomTools.Core/Random/Delay/PolynomialDelay.cs b/Source/RandomTools.Core/Random/Delay/PolynomialDelay.cs
new file mode 100644
index 0000000..db304ef
--- /dev/null
+++ b/Source/RandomTools.Core/Random/Delay/PolynomialDelay.cs
@@ -0,0 +1,53 @@
+using RandomTools.Core.Options.Delay;
+
+namespace RandomTools.Core.Random.Delay
+{
+ ///
+ /// Generates random delays based on a Polynomial / Power distribution.
+ ///
+ /// The distribution generates values in [Minimum, Maximum] with a density
+ /// proportional to (t - Minimum)^Power or (Maximum - t)^Power if is true.
+ ///
+ ///
+ /// A Power of 0 produces a uniform distribution. Higher Power values produce
+ /// increasingly skewed delays toward Maximum (or Minimum if reversed).
+ ///
+ ///
+ public sealed class PolynomialDelay : RandomDelay
+ {
+#pragma warning disable IDE0290 // Use primary constructor
+ ///
+ /// Initializes a new instance of with the specified options.
+ ///
+ /// Polynomial distribution configuration options.
+ public PolynomialDelay(DelayOptions.Polynomial options) : base(options)
+#pragma warning restore IDE0290 // Use primary constructor
+ {
+ }
+
+ ///
+ /// Generates the next random delay according to the configured Polynomial distribution.
+ ///
+ /// A representing the generated delay.
+ public override TimeSpan Next()
+ {
+ // Generate a uniform random value in [0,1)
+ double u = CoreTools.NextDouble();
+
+ // Apply inverse CDF of the Polynomial distribution to get a normalized fraction
+ // fraction ∈ [0,1], representing relative position in the [Minimum, Maximum] interval
+ double fraction = Math.Pow(u, 1.0 / (Options.Power + 1.0));
+
+ if (Options.Reverse)
+ {
+ // more values closer to Minimum
+ fraction = 1.0 - fraction;
+ }
+
+ // Scale the normalized fraction to the [Minimum, Maximum] range
+ double value = ScaleToRange(fraction);
+
+ return CoreTools.ToTimeSpan(value, Options.TimeUnit);
+ }
+ }
+}
diff --git a/Source/RandomTools.Core/Random/Delay/RandomDelay.cs b/Source/RandomTools.Core/Random/Delay/RandomDelay.cs
index 0255ca2..d62ff61 100644
--- a/Source/RandomTools.Core/Random/Delay/RandomDelay.cs
+++ b/Source/RandomTools.Core/Random/Delay/RandomDelay.cs
@@ -30,7 +30,9 @@ public abstract class RandomDelay : RandomBase
///
/// Configuration defining how delays are calculated, including range and distribution parameters.
///
+#pragma warning disable IDE0290 // Use primary constructor
protected RandomDelay(TOptions options) : base(options)
+#pragma warning restore IDE0290 // Use primary constructor
{
// Base class stores options and provides the Next() method for generating delays.
}
@@ -93,5 +95,28 @@ public async Task WaitAsync(CancellationToken cancellationToken = defa
return delay;
}
+
+ ///
+ /// Scales a fraction in the range [0,1] to the configured [Minimum, Maximum] range.
+ ///
+ ///
+ /// A value between 0 and 1, typically produced by the underlying random distribution.
+ ///
+ ///
+ /// The value scaled linearly to the range [Minimum, Maximum].
+ ///
+ ///
+ /// Thrown if is less than 0 or greater than 1.
+ ///
+ protected double ScaleToRange(double fraction)
+ {
+ // Validate that the fraction is within the normalized [0,1] range
+ ArgumentOutOfRangeException.ThrowIfNegative(fraction);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(fraction, 1.0);
+
+ // Linearly scale fraction to the configured range
+ double range = Options.Maximum - Options.Minimum;
+ return Math.FusedMultiplyAdd(range, fraction, Options.Minimum);
+ }
}
}
diff --git a/Source/RandomTools.Tests/TriangularDelayTests.cs b/Source/RandomTools.Tests/TriangularDelayTests.cs
index b486fdb..1df2f4c 100644
--- a/Source/RandomTools.Tests/TriangularDelayTests.cs
+++ b/Source/RandomTools.Tests/TriangularDelayTests.cs
@@ -1,4 +1,5 @@
-using RandomTools.Core.Options.Delay;
+using RandomTools.Core;
+using RandomTools.Core.Options.Delay;
using RandomTools.Core.Random.Delay;
using System;
using System.Collections.Generic;
@@ -12,6 +13,50 @@ namespace RandomTools.Tests
[TestFixture]
public class TriangularDelayTests
{
+ [Test]
+ public void TestThree()
+ {
+ var options = new DelayOptions.Bates()
+ .WithMinimum(100)
+ .WithMaximum(200)
+ .WithSamples(6);
+
+ var delay = new BatesDelay(options);
+ var data = new List();
+ for (int i = 0; i < 1_000_000; i++)
+ {
+ var next = delay.Next();
+ data.Add(next);
+ }
+
+ var min = data.Min(x => x);
+ var max = data.Max(x => x);
+ Debugger.Break();
+ }
+
+ [Test]
+ public void TestTwo()
+ {
+ var options = new DelayOptions.Arcsine()
+ .WithMinimum(100)
+ .WithMaximum(150)
+ .WithTimeUnit(TimeUnit.Second);
+
+ var delay = new ArcsineDelay(options);
+ var data = new List();
+
+ for (int i = 0; i < 1_000_000; i++)
+ {
+ var next = delay.Next();
+ data.Add(next);
+ }
+
+ var min = data.Min(x => x);
+ var max = data.Max(x => x);
+
+ Debugger.Break();
+ }
+
[Test]
public void TestOne()
{