Skip to content

Fix gradient norm scaling in pounders convergence checks - #708

Merged
timmens merged 1 commit into
mainfrom
fix/657-pounders-gradient-norm
Sep 30, 2026
Merged

timmens merged 1 commit into
mainfrom
fix/657-pounders-gradient-norm

Conversation

@timmens

@timmens timmens commented Sep 30, 2026

Copy link
Copy Markdown
Member

Fixes #657.

Problem

POUNDERS fits its residual model in coordinates scaled by the trust-region radius, (x - center) / delta. The model's linear_terms therefore already equal delta * gradient. The convergence checks multiplied this by delta again, so convergence_gtol_abs, convergence_gtol_rel and convergence_gtol_scaled compared delta**2 * ||gradient|| against the tolerances.

Once the trust region shrank, runs reported "Norm of the gradient relative to the criterion value is less than relative_gradient_tolerance" at points far from the minimum. From the start vector [1e-6, 1e-6, 1e-6] the iteration path is chaotic: a relative perturbation of 1e-15 changes where it ends. Tiny floating-point differences between platforms then decided whether the run hit the minimum. That explains the scattered results in #657. None of the reported Linux results were minima (criterion 2455 to 3922, optimum 2384.48). In one traced run the true gradient was about 5e5 while the tested norm was 7e-4.

Fix

Use ||linear_terms|| / delta, the gradient in the original coordinates. This matches Stefan Wild's reference implementation (IBCDFO formquad.m returns G / delta, which pounders.m compares against g_tol), and the docs, which describe the criterion as the norm of the gradient. The double scaling was copied from PETSc TAO's pounders.c (gnorm *= mfqP->delta) in the original port.

Tests that previously failed now run

Test Before After
test_bntr (all 5 cases, including start_vec3-cg, which failed in #657) Skipped on Linux (all Python versions) and Windows. Ran on macOS only. Runs on Linux, Windows and macOS. No skips left.
test_bntr assertions Parameters only (decimal=3) Parameters, plus final criterion value 2384.477 (abs 1e-2)
test_bntr_no_false_convergence_under_start_value_jitter (new, slow) n/a. Fails on main: 4 of 10 jittered runs stop away from the minimum. Passes: 10 of 10 reach the minimum. Runs on Linux CI (tests-with-cov).

Locally (macOS): pounders tests 100 passed, 1 skipped, 1 xfailed. tests/optimagic/optimizers, test_many_algorithms and test_estimate_msm: 647 passed, 12 skipped, 1 xfailed. This PR's CI is the first run of test_bntr on Linux and Windows since the skips were added.

Behavior change

Stopping changes for all POUNDERS users. Robustness and cost with 1e-15 start value jitter, 20 runs per case:

Runs at the minimum Median iterations, start [0.15, 0.008, 0.01] Median iterations, start [1e-6]*3
Before 17 to 19 of 20 22 70
After 20 of 20 35 96

With the correct gradient norm, the default gradient tolerances (1e-8) rarely trigger. Most runs now stop because the model was identical in successive iterations. Whether the default tolerances should change is a separate question.

Suggested CHANGES.md entry:

Fix the gradient norm in the pounders convergence checks, which was scaled by the squared trust-region radius ({gh}657). The gtol criteria now use the model gradient in the original coordinates. Stopping may take more iterations (about 50% more with default tolerances in our tests), and reports of convergence at non-minima are much rarer.

The residual model is fitted in coordinates scaled by the trust-region
radius, so main_model.linear_terms already equals delta times the
gradient. The convergence checks multiplied this by delta again, so the
gtol_abs, gtol_rel and gtol_scaled tests compared delta**2 * ||grad||
against the tolerances. Once the trust region shrank, runs could report
gradient convergence at points far from a minimum.

Divide the model gradient by delta instead, so the checks use the
gradient in the original coordinates, as in Stefan Wild's reference
POUNDERs implementation (IBCDFO formquad.m returns G / delta).

Remove the Linux and Windows skips on test_bntr, assert the final
criterion value, and add a regression test with jittered start values.
@codecov

codecov Bot commented Sep 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/optimagic/optimizers/pounders.py 92.46% <100.00%> (-0.11%) ⬇️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timmens

timmens commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

CI confirms that the previously skipped test_bntr cases now run and pass on Linux and Windows. All checks are green.

Before (latest main run, run 30351008031), s = skipped:

ubuntu-latest py313:  tests/optimagic/optimizers/test_pounders_integration.py sssss.
windows-latest py313: tests\optimagic\optimizers\test_pounders_integration.py sssss.

After (this PR, run 36701300636):

ubuntu-latest py313:  tests/optimagic/optimizers/test_pounders_integration.py .......
windows-latest py313: tests\optimagic\optimizers\test_pounders_integration.py ......

The five sssss are the test_bntr cases, including start_vec3-cg from #657. On Linux, the seventh test is the new slow regression test test_bntr_no_false_convergence_under_start_value_jitter. Windows runs tests-fast, which deselects slow tests. The same holds for the py312 and py314 jobs.

@timmens
timmens merged commit 66738d9 into main Sep 30, 2026
22 checks passed
@timmens
timmens deleted the fix/657-pounders-gradient-norm branch September 30, 2026 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test_bntr[start_vec3-cg] fails on Linux with Python 3.10

1 participant