cultmath: integral_clamp_linear is finite for infinite clamp bounds (integral-infinite-bound-nan)
Each held stretch contributes only when its length is positive, so an infinite bound times a zero length never forms: relu (hi = +inf) and its mirrors give the finite integral instead of NaN, in C#, HLSL (Unity copy byte-identical) and the generated GLSL. Finite cases are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
38a2f50eb0
commit
b042bf19be
5 changed files with 41 additions and 5 deletions
|
|
@ -315,7 +315,9 @@ float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float
|
|||
float leave = clamp(max(atLo, atHi), start, stop);
|
||||
float before = b > 0.0 ? lo : hi;
|
||||
float after = b > 0.0 ? hi : lo;
|
||||
float total = before * (enter - start) + (a + b * (0.5 * (enter + leave))) * (leave - enter) + after * (stop - leave);
|
||||
float held = enter > start ? before * (enter - start) : 0.0;
|
||||
float kept = stop > leave ? after * (stop - leave) : 0.0;
|
||||
float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept;
|
||||
return direction * total;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -315,7 +315,9 @@ float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float
|
|||
float leave = clamp(max(atLo, atHi), start, stop);
|
||||
float before = b > 0.0 ? lo : hi;
|
||||
float after = b > 0.0 ? hi : lo;
|
||||
float total = before * (enter - start) + (a + b * (0.5 * (enter + leave))) * (leave - enter) + after * (stop - leave);
|
||||
float held = enter > start ? before * (enter - start) : 0.0;
|
||||
float kept = stop > leave ? after * (stop - leave) : 0.0;
|
||||
float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept;
|
||||
return direction * total;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ public static partial class math
|
|||
// line's value at the stretch's midpoint), and the stretch held at the far bound. b = 0 is the
|
||||
// constant clamp(a, lo, hi); t1 < t0 is the negative of the integral over [t1, t0]; lo above hi
|
||||
// clamps to hi everywhere, as clamp does. A crossing that overflows (b tiny) lies past the span and
|
||||
// cuts nothing.
|
||||
// cuts nothing. A held stretch contributes only when its length is positive, so an infinite bound
|
||||
// (relu has hi = +inf) times a zero length never forms: the integral of a ramp clamped on one side
|
||||
// only is finite.
|
||||
public static float integral_clamp_linear(float a, float b, float lo, float hi, float t0, float t1)
|
||||
{
|
||||
if (b == 0.0f)
|
||||
|
|
@ -120,7 +122,9 @@ public static partial class math
|
|||
var leave = clamp(max(atLo, atHi), start, stop);
|
||||
var before = b > 0.0f ? lo : hi;
|
||||
var after = b > 0.0f ? hi : lo;
|
||||
var total = before * (enter - start) + (a + b * (0.5f * (enter + leave))) * (leave - enter) + after * (stop - leave);
|
||||
var held = enter > start ? before * (enter - start) : 0.0f;
|
||||
var kept = stop > leave ? after * (stop - leave) : 0.0f;
|
||||
var total = held + (a + b * (0.5f * (enter + leave))) * (leave - enter) + kept;
|
||||
return direction * total;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,6 +319,32 @@ public sealed class SpanPrimitiveTests
|
|||
Assert.True(zeroSlopes > 10_000 && reversed > 40_000 && both > 2_000 && neither > 2_000, $"the sample meets a branch too rarely: {zeroSlopes} flat, {reversed} reversed, {both} both, {neither} neither");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An infinite bound never reached by the line contributes nothing, not infinity times zero: relu
|
||||
/// (hi = +inf) and its mirror (lo = -inf) give the finite integral, whether the line crosses the finite
|
||||
/// bound inside the span or not.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IntegralClampLinearIsFiniteForInfiniteBounds()
|
||||
{
|
||||
var inf = float.PositiveInfinity;
|
||||
// t over [0, 1] clamped below at 0: all ramp, 1/2.
|
||||
Assert.Equal(0.5f, integral_clamp_linear(0.0f, 1.0f, 0.0f, inf, 0.0f, 1.0f));
|
||||
// t - 1/2 over [0, 1] clamped below at 0: 0 until 1/2, then the ramp's 1/8.
|
||||
Assert.Equal(0.125f, integral_clamp_linear(-0.5f, 1.0f, 0.0f, inf, 0.0f, 1.0f));
|
||||
// The falling line 1/2 - t clamped below at 0 is the mirror: 1/8 over [0, 1].
|
||||
Assert.Equal(0.125f, integral_clamp_linear(0.5f, -1.0f, 0.0f, inf, 0.0f, 1.0f));
|
||||
// lo = -inf: clamp(t, -inf, 1/2) over [0, 1] is t up to 1/2 then 1/2 (1/8 + 1/4), and 1 - t clamped
|
||||
// above at 1/2 is 1/2 until t = 1/2 and then falls (1/4 + 1/8).
|
||||
Assert.Equal(0.375f, integral_clamp_linear(0.0f, 1.0f, -inf, 0.5f, 0.0f, 1.0f));
|
||||
Assert.Equal(0.375f, integral_clamp_linear(1.0f, -1.0f, -inf, 0.5f, 0.0f, 1.0f));
|
||||
// Both infinite: the line itself, the trapezoid, in either direction.
|
||||
Assert.Equal(7.5f, integral_clamp_linear(0.0f, 1.0f, -inf, inf, 1.0f, 4.0f));
|
||||
Assert.Equal(-7.5f, integral_clamp_linear(0.0f, 1.0f, -inf, inf, 4.0f, 1.0f));
|
||||
// A line starting on the finite bound and rising into the infinite one: all ramp, the integral of t over [2, 4].
|
||||
Assert.Equal(6.0f, integral_clamp_linear(0.0f, 1.0f, 2.0f, inf, 2.0f, 4.0f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntegralClampLinearHasExactValuesOnTheBinaryFloats()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -315,7 +315,9 @@ float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float
|
|||
float leave = clamp(max(atLo, atHi), start, stop);
|
||||
float before = b > 0.0 ? lo : hi;
|
||||
float after = b > 0.0 ? hi : lo;
|
||||
float total = before * (enter - start) + (a + b * (0.5 * (enter + leave))) * (leave - enter) + after * (stop - leave);
|
||||
float held = enter > start ? before * (enter - start) : 0.0;
|
||||
float kept = stop > leave ? after * (stop - leave) : 0.0;
|
||||
float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept;
|
||||
return direction * total;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue