cultmath: BoundedLeastSquares test drives the tolerance below the noise floor
With b along the smallest left singular vector, |A^T b| is tiny, so the KKT tolerance (1e-6 of it) sits below the gradient's rounding noise at cond(A) >= 1e6 and can never be met. The solver must stop on the progress rule, in at most six passes, never at the cap. This is the path the progress rule exists for; the earlier fixtures reached it only by accident. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
854f01e4c7
commit
6328f15345
1 changed files with 25 additions and 2 deletions
|
|
@ -722,7 +722,7 @@ public sealed class BoundedLeastSquaresTests
|
|||
|
||||
// A = U diag(s) V^T with s geometric from 1 down to 1/cond, so cond(A) is the requested value (float rounding of A
|
||||
// perturbs it slightly). m x n with m >= n; b is generic, so it has a real residual when m > n.
|
||||
private static (float[] a, float[] b) ConditionedProblem(int m, int n, double cond, uint seed, double aScale = 1.0)
|
||||
private static (float[] a, float[] b) ConditionedProblem(int m, int n, double cond, uint seed, double aScale = 1.0, bool bAlongSmallestSingularVector = false)
|
||||
{
|
||||
var rng = new CultMath.Random(seed * 2654435761u + 17u);
|
||||
double[][] Orthonormal(int dim, int count)
|
||||
|
|
@ -758,7 +758,7 @@ public sealed class BoundedLeastSquaresTests
|
|||
a[r * n + c] += (float)(u[k][r] * s * w[k][c]);
|
||||
}
|
||||
var b = new float[m];
|
||||
for (var i = 0; i < m; i++) b[i] = rng.NextFloat(-3f, 3f);
|
||||
for (var i = 0; i < m; i++) b[i] = bAlongSmallestSingularVector ? (float)u[n - 1][i] : rng.NextFloat(-3f, 3f);
|
||||
return (a, b);
|
||||
}
|
||||
|
||||
|
|
@ -808,6 +808,29 @@ public sealed class BoundedLeastSquaresTests
|
|||
Assert.InRange(confirmed, 1, 20);
|
||||
}
|
||||
|
||||
// b along the smallest left singular vector makes the optimum lie along the weakest direction: |A^T b| is tiny, so
|
||||
// the tolerance (1e-6 of it) sits below the rounding noise of the gradient (~1e-16 |A^T A| |x|) for cond(A) >= 1e6.
|
||||
// The tolerance is then unreachable, and the solver must stop on the progress rule, after the handful of Newton
|
||||
// passes that still lower the cost, never at the 100-iteration cap.
|
||||
[Theory]
|
||||
[InlineData(1e6)]
|
||||
[InlineData(1e7)]
|
||||
public void ToleranceBelowTheNoiseFloorStopsOnProgressNotOnTheCap(double cond)
|
||||
{
|
||||
const int m = 28, n = 24;
|
||||
var confirmed = 0;
|
||||
for (uint seed = 1; seed <= 30; seed++)
|
||||
{
|
||||
var (a, b) = ConditionedProblem(m, n, cond, seed, 1.0, bAlongSmallestSingularVector: true);
|
||||
var x = new float[n];
|
||||
var status = Solve(m, n, a, b, Fill(-1e9f, n), Fill(1e9f, n), x, out var iterations);
|
||||
Assert.True(status == BoundedLeastSquaresStatus.Converged, $"cond {cond} seed {seed}: {status} after {iterations}");
|
||||
Assert.True(iterations <= 6, $"cond {cond} seed {seed}: {iterations} iterations");
|
||||
if (iterations >= 1) confirmed++;
|
||||
}
|
||||
Assert.True(confirmed >= 15, $"cond {cond}: only {confirmed} of 30 reached the tolerance floor");
|
||||
}
|
||||
|
||||
// Scale-freeness was only claimed for scaling A, b and the bounds together; A alone at 1e6 with the bounds left
|
||||
// alone must also converge, and as fast.
|
||||
[Theory]
|
||||
|
|
|
|||
Loading…
Reference in a new issue