cultmath: one owner of the snoise read's absolute terms (SNOISE_READ_VALUE, SNOISE_READ_GRADIENT)
span_slack and af_snoise each spelled 2^-14 (value) and 2^-12 (gradient) inline, in C# and HLSL. Both now read the constants beside SNOISE_HESSIAN (math.Affine.cs, CultMath.Affine.hlsl, Unity copies), so a device-judged widening moves every reader. Bits unchanged. The test oracles in AffineTests and GlslMirrorTests read the constants; a new test pins af_snoise's allowance to span_slack's terms. span_slack's comment no longer claims it is the only allowance. Does not build green yet: CultMath.glsl is not regenerated in this commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4de26d8655
commit
58a2d50a76
8 changed files with 61 additions and 14 deletions
|
|
@ -12,6 +12,12 @@
|
|||
// math.Affine.cs (SNOISE_HESSIAN).
|
||||
static const float CULTMATH_SNOISE_HESSIAN = 55.642143;
|
||||
|
||||
// The absolute rounding of one float32 read of cultmath_snoise_grad, value and gradient; the owner is
|
||||
// math.Affine.cs (SNOISE_READ_VALUE, SNOISE_READ_GRADIENT), read here by cultmath_af_snoise and in
|
||||
// CultMath.hlsl by cultmath_span_slack.
|
||||
static const float CULTMATH_SNOISE_READ_VALUE = 6.103515625e-5;
|
||||
static const float CULTMATH_SNOISE_READ_GRADIENT = 2.44140625e-4;
|
||||
|
||||
// An upper bound on the operator norm of the third-derivative tensor of cultmath_snoise(float3), the
|
||||
// next order after CULTMATH_SNOISE_HESSIAN; provenance in math.Span.cs (SNOISE_THIRD).
|
||||
static const float CULTMATH_SNOISE_THIRD = 535.8009;
|
||||
|
|
@ -87,7 +93,7 @@ float3 cultmath_af_snoise(float3 centre, float3 axis, float radius)
|
|||
float lipschitz = CULTMATH_SNOISE_LIPSCHITZ * reach;
|
||||
float a = dot(gradient, axis);
|
||||
float remainder = length(gradient) * radius + 0.5 * CULTMATH_SNOISE_HESSIAN * reach * reach;
|
||||
float allowance = 6.103515625e-5 + 2.44140625e-4 * reach
|
||||
float allowance = CULTMATH_SNOISE_READ_VALUE + CULTMATH_SNOISE_READ_GRADIENT * reach
|
||||
+ CULTMATH_SNOISE_LIPSCHITZ * 9.5367431640625e-7 * (abs(centre.x) + abs(centre.y) + abs(centre.z) + reach);
|
||||
bool centred = abs(a) + remainder < lipschitz;
|
||||
float slope = centred ? a : 0.0;
|
||||
|
|
|
|||
|
|
@ -645,8 +645,8 @@ float cultmath_span_rounding(float scale, float3 mq, SpanStage stage, float3 oct
|
|||
|
||||
float3 cultmath_span_slack(float rounding)
|
||||
{
|
||||
return float3(6.103515625e-5 + CULTMATH_SNOISE_LIPSCHITZ * rounding,
|
||||
2.44140625e-4 + CULTMATH_SNOISE_HESSIAN * rounding,
|
||||
return float3(CULTMATH_SNOISE_READ_VALUE + CULTMATH_SNOISE_LIPSCHITZ * rounding,
|
||||
CULTMATH_SNOISE_READ_GRADIENT + CULTMATH_SNOISE_HESSIAN * rounding,
|
||||
9.765625e-4 + CULTMATH_SNOISE_THIRD * rounding);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ public static partial class math
|
|||
// kernel must re-run MeasureHessian and re-pin it. SNOISE_THIRD (math.Span.cs) is the next-order pin.
|
||||
public const float SNOISE_HESSIAN = 55.642143f;
|
||||
|
||||
// The absolute rounding of one float32 read of the snoise kernel, apart from the coordinate's own
|
||||
// rounding (af_snoise's comment derives both): 2^-14 on the value and 2^-12 on the gradient. The single
|
||||
// owner of the two, read by af_snoise and by span_slack (math.Span.cs); the shader twins are
|
||||
// CULTMATH_SNOISE_READ_VALUE and CULTMATH_SNOISE_READ_GRADIENT.
|
||||
internal const float SNOISE_READ_VALUE = 6.103515625e-5f;
|
||||
internal const float SNOISE_READ_GRADIENT = 2.44140625e-4f;
|
||||
|
||||
/// <summary>The point form (x, 0, 0): the value x at every eps. Exact.</summary>
|
||||
public static float3 af_point(float value) => new(value, 0.0f, 0.0f);
|
||||
|
||||
|
|
@ -165,7 +172,7 @@ public static partial class math
|
|||
var lipschitz = SNOISE_LIPSCHITZ * reach;
|
||||
var a = dot(gradient, axis);
|
||||
var remainder = length(gradient) * radius + 0.5f * SNOISE_HESSIAN * reach * reach;
|
||||
var allowance = 6.103515625e-5f + 2.44140625e-4f * reach
|
||||
var allowance = SNOISE_READ_VALUE + SNOISE_READ_GRADIENT * reach
|
||||
+ SNOISE_LIPSCHITZ * 9.5367431640625e-7f * (abs(centre.x) + abs(centre.y) + abs(centre.z) + reach);
|
||||
var centred = abs(a) + remainder < lipschitz;
|
||||
var slope = centred ? a : 0.0f;
|
||||
|
|
|
|||
|
|
@ -159,10 +159,12 @@ public static partial class math
|
|||
|
||||
// The allowances (e_n, e_g, e_h) of the value, the gradient's length and the Hessian's Frobenius norm at
|
||||
// a rounding of 2^-20 S_o: the absolute term of each plus its derivative's pin times the rounding. The
|
||||
// single owner of the three; nothing else computes an allowance.
|
||||
// single owner of the three allowances of a span; af_snoise (math.Affine.cs) forms its own allowance
|
||||
// from the same SNOISE_READ_VALUE and SNOISE_READ_GRADIENT, the owners of the first two absolute terms,
|
||||
// so widening either moves every reader of the kernel at once.
|
||||
internal static float3 span_slack(float rounding) => new(
|
||||
6.103515625e-5f + SNOISE_LIPSCHITZ * rounding,
|
||||
2.44140625e-4f + SNOISE_HESSIAN * rounding,
|
||||
SNOISE_READ_VALUE + SNOISE_LIPSCHITZ * rounding,
|
||||
SNOISE_READ_GRADIENT + SNOISE_HESSIAN * rounding,
|
||||
9.765625e-4f + SNOISE_THIRD * rounding);
|
||||
|
||||
// The span is SpanPath: q(t), |t| <= halfLength, through midpoint with velocity at t = 0, |q'| <= speed
|
||||
|
|
@ -199,8 +201,8 @@ public static partial class math
|
|||
// the span's reach sqrt(3) nu halfLength (the coordinate of a path point); span_rounding is its owner. The
|
||||
// allowances are the value's e_n = 2^-14 + L 2^-20 S_o, the gradient length's e_g = 2^-12 + H 2^-20 S_o
|
||||
// (H is the gradient's own derivative's pin) and the Hessian's Frobenius norm's e_h = 2^-10 + T 2^-20 S_o
|
||||
// (T is the Hessian's derivative's pin): af_snoise's absolute terms for the first two, and 2^-10 for the
|
||||
// Hessian's; span_slack is their owner. They are observed where they are decided: the coordinate-band
|
||||
// (T is the Hessian's derivative's pin): SNOISE_READ_VALUE and SNOISE_READ_GRADIENT (math.Affine.cs) are the
|
||||
// absolute terms of the first two, 2^-10 the Hessian's; span_slack is the owner of the three. They are observed where they are decided: the coordinate-band
|
||||
// snoise_grad and snoise_hessian families of glsl-parity.json and SpanAllowanceCoversReadsAlongTheSpan
|
||||
// check every read against them. Those observations are samples, not bounds: a new seed moves them. What
|
||||
// they say is that the allowances enclose the error with a margin of about 15.
|
||||
|
|
|
|||
|
|
@ -295,9 +295,35 @@ public sealed class AffineTests
|
|||
|
||||
// af_snoise's float32 allowance, as its doc comment states it, for the width comparison below.
|
||||
private static double Allowance(float3 centre, double reach) =>
|
||||
Math.ScaleB(1.0, -14) + Math.ScaleB(reach, -12)
|
||||
SNOISE_READ_VALUE + SNOISE_READ_GRADIENT * reach
|
||||
+ SNOISE_LIPSCHITZ * Math.ScaleB(Math.Abs(centre.x) + Math.Abs(centre.y) + Math.Abs(centre.z) + reach, -20);
|
||||
|
||||
/// <summary>
|
||||
/// af_snoise reads the same absolute rounding terms of the kernel as span_slack: at the origin with no
|
||||
/// axis, the form's error is span_slack's value allowance at zero rounding plus the Lipschitz form's
|
||||
/// L R, and each unit of reach adds span_slack's gradient term at zero rounding plus L 2^-20 (the
|
||||
/// coordinate's own rounding). Widening the kernel's absolute read in one reader and not the other,
|
||||
/// the device judge's possible outcome, fails here. The expected bits are formed from span_slack, not
|
||||
/// from a literal.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SnoiseFormAndSpanSlackShareTheKernelsAbsoluteTerms()
|
||||
{
|
||||
var zero = new float3(0.0f, 0.0f, 0.0f);
|
||||
var slack = span_slack(0.0f);
|
||||
var value = snoise_grad(zero).w;
|
||||
foreach (var reach in new[] { 0.0f, 1.0f, 3.0f })
|
||||
{
|
||||
var lipschitz = SNOISE_LIPSCHITZ * reach;
|
||||
var allowance = slack.x + slack.y * reach + SNOISE_LIPSCHITZ * 9.5367431640625e-7f * reach;
|
||||
var e = lipschitz + allowance;
|
||||
var expected = e + (abs(value) + 0.0f + e) * 9.5367431640625e-7f;
|
||||
var form = af_snoise(zero, zero, reach);
|
||||
Assert.True(BitConverter.SingleToInt32Bits(form.z) == BitConverter.SingleToInt32Bits(expected),
|
||||
$"af_snoise(0, 0, {reach:R}).z is {form.z:R}; the allowances span_slack reads give {expected:R}");
|
||||
}
|
||||
}
|
||||
|
||||
private static float3 UnitVector(System.Random random)
|
||||
{
|
||||
float3 v;
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z >
|
|||
|
||||
// The float32 allowance of one octave of af_snoise: 2^-14 + 2^-12 R + L 2^-20 (|c|_1 + R).
|
||||
private static double SnoiseAllowance(double centre1, double reach) =>
|
||||
Math.ScaleB(1.0, -14) + Math.ScaleB(reach, -12) + math.SNOISE_LIPSCHITZ * Math.ScaleB(centre1 + reach, -20);
|
||||
math.SNOISE_READ_VALUE + math.SNOISE_READ_GRADIENT * reach + math.SNOISE_LIPSCHITZ * Math.ScaleB(centre1 + reach, -20);
|
||||
|
||||
// The Lipschitz form's width bound for one octave of the sampled-form check.
|
||||
private static double SnoiseWidthBound(double centre1, double reach) => math.SNOISE_LIPSCHITZ * reach + SnoiseAllowance(centre1, reach);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
// math.Affine.cs (SNOISE_HESSIAN).
|
||||
static const float CULTMATH_SNOISE_HESSIAN = 55.642143;
|
||||
|
||||
// The absolute rounding of one float32 read of cultmath_snoise_grad, value and gradient; the owner is
|
||||
// math.Affine.cs (SNOISE_READ_VALUE, SNOISE_READ_GRADIENT), read here by cultmath_af_snoise and in
|
||||
// CultMath.hlsl by cultmath_span_slack.
|
||||
static const float CULTMATH_SNOISE_READ_VALUE = 6.103515625e-5;
|
||||
static const float CULTMATH_SNOISE_READ_GRADIENT = 2.44140625e-4;
|
||||
|
||||
// An upper bound on the operator norm of the third-derivative tensor of cultmath_snoise(float3), the
|
||||
// next order after CULTMATH_SNOISE_HESSIAN; provenance in math.Span.cs (SNOISE_THIRD).
|
||||
static const float CULTMATH_SNOISE_THIRD = 535.8009;
|
||||
|
|
@ -87,7 +93,7 @@ float3 cultmath_af_snoise(float3 centre, float3 axis, float radius)
|
|||
float lipschitz = CULTMATH_SNOISE_LIPSCHITZ * reach;
|
||||
float a = dot(gradient, axis);
|
||||
float remainder = length(gradient) * radius + 0.5 * CULTMATH_SNOISE_HESSIAN * reach * reach;
|
||||
float allowance = 6.103515625e-5 + 2.44140625e-4 * reach
|
||||
float allowance = CULTMATH_SNOISE_READ_VALUE + CULTMATH_SNOISE_READ_GRADIENT * reach
|
||||
+ CULTMATH_SNOISE_LIPSCHITZ * 9.5367431640625e-7 * (abs(centre.x) + abs(centre.y) + abs(centre.z) + reach);
|
||||
bool centred = abs(a) + remainder < lipschitz;
|
||||
float slope = centred ? a : 0.0;
|
||||
|
|
|
|||
|
|
@ -645,8 +645,8 @@ float cultmath_span_rounding(float scale, float3 mq, SpanStage stage, float3 oct
|
|||
|
||||
float3 cultmath_span_slack(float rounding)
|
||||
{
|
||||
return float3(6.103515625e-5 + CULTMATH_SNOISE_LIPSCHITZ * rounding,
|
||||
2.44140625e-4 + CULTMATH_SNOISE_HESSIAN * rounding,
|
||||
return float3(CULTMATH_SNOISE_READ_VALUE + CULTMATH_SNOISE_LIPSCHITZ * rounding,
|
||||
CULTMATH_SNOISE_READ_GRADIENT + CULTMATH_SNOISE_HESSIAN * rounding,
|
||||
9.765625e-4 + CULTMATH_SNOISE_THIRD * rounding);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue