Compare commits
1
Commits
d044cafad9
...
d360093be1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d360093be1 |
@@ -23,4 +23,41 @@ public static class Suitability
|
||||
var z = (value - optimum) / tolerance;
|
||||
return MathF.Exp(-0.5f * z * z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trapezoidal suitability in <c>[0, 1]</c>: <c>0</c> at or beyond the hard limits
|
||||
/// <paramref name="min"/>/<paramref name="max"/>, ramping linearly up to <c>1</c> at
|
||||
/// <paramref name="optimalLow"/>, flat <c>1</c> across the comfortable plateau to
|
||||
/// <paramref name="optimalHigh"/>, then ramping back down to <c>0</c> at <paramref name="max"/>.
|
||||
/// Models a hard tolerance band with a plateau (RimWorld-style plant growth vs. temperature:
|
||||
/// dormant below <paramref name="min"/> or above <paramref name="max"/>). A degenerate edge
|
||||
/// (<paramref name="optimalLow"/> ≤ <paramref name="min"/> or <paramref name="optimalHigh"/> ≥
|
||||
/// <paramref name="max"/>) becomes a hard step on that side. The four bounds are expected
|
||||
/// ordered (<c>min ≤ optimalLow ≤ optimalHigh ≤ max</c>); pass ordered values.
|
||||
/// </summary>
|
||||
public static float Trapezoid(
|
||||
float value,
|
||||
float min,
|
||||
float optimalLow,
|
||||
float optimalHigh,
|
||||
float max
|
||||
)
|
||||
{
|
||||
if (value <= min || value >= max)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (value < optimalLow)
|
||||
{
|
||||
return optimalLow > min ? (value - min) / (optimalLow - min) : 1f;
|
||||
}
|
||||
|
||||
if (value > optimalHigh)
|
||||
{
|
||||
return max > optimalHigh ? (max - value) / (max - optimalHigh) : 1f;
|
||||
}
|
||||
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,4 +48,45 @@ public class SuitabilityTests
|
||||
Assert.Equal(1f, Suitability.Gaussian(5f, 5f, 0f));
|
||||
Assert.Equal(0f, Suitability.Gaussian(6f, 5f, 0f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trapezoid_IsFlatAcrossThePlateau()
|
||||
{
|
||||
Assert.Equal(1f, Suitability.Trapezoid(10f, 0f, 10f, 42f, 58f), 5);
|
||||
Assert.Equal(1f, Suitability.Trapezoid(25f, 0f, 10f, 42f, 58f), 5);
|
||||
Assert.Equal(1f, Suitability.Trapezoid(42f, 0f, 10f, 42f, 58f), 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trapezoid_RampsLinearlyOnEachShoulder()
|
||||
{
|
||||
Assert.Equal(0.5f, Suitability.Trapezoid(5f, 0f, 10f, 42f, 58f), 5); // halfway up the cold ramp
|
||||
Assert.Equal(0.5f, Suitability.Trapezoid(50f, 0f, 10f, 42f, 58f), 5); // halfway down the heat ramp
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trapezoid_IsZeroAtAndBeyondTheHardLimits()
|
||||
{
|
||||
Assert.Equal(0f, Suitability.Trapezoid(0f, 0f, 10f, 42f, 58f));
|
||||
Assert.Equal(0f, Suitability.Trapezoid(-5f, 0f, 10f, 42f, 58f));
|
||||
Assert.Equal(0f, Suitability.Trapezoid(58f, 0f, 10f, 42f, 58f));
|
||||
Assert.Equal(0f, Suitability.Trapezoid(70f, 0f, 10f, 42f, 58f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trapezoid_DegenerateShoulder_IsAHardStep()
|
||||
{
|
||||
// optimalLow == min: full suitability immediately above the lower limit, zero at/below it.
|
||||
Assert.Equal(1f, Suitability.Trapezoid(5f, 0f, 0f, 10f, 20f));
|
||||
Assert.Equal(0f, Suitability.Trapezoid(0f, 0f, 0f, 10f, 20f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Trapezoid_StaysWithinUnitRange()
|
||||
{
|
||||
for (var v = -50f; v <= 80f; v += 1f)
|
||||
{
|
||||
Assert.InRange(Suitability.Trapezoid(v, 0f, 10f, 42f, 58f), 0f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user