Skip to content

feat: warn when ADMM uses all its iterations without converging - #378

Draft
lachlangrose wants to merge 4 commits into
masterfrom
feat/admm-nonconvergence-warning
Draft

lachlangrose wants to merge 4 commits into
masterfrom
feat/admm-nonconvergence-warning

Conversation

@lachlangrose

Copy link
Copy Markdown
Member

Draft, stacked on #377 (the ADMM scaling fix), which is stacked on #374. Its diff against master includes those commits until they merge; the change here is the last commit.

admm_solve returned after nmajor iterations without saying whether it had converged. #377 found that with the default admm_weight=0.01 the sparse dome test is still unconverged after 200 iterations (primal residual 0.056 against a tolerance of 0.0025, 4 of 24 bounds violated) and the solver gave no sign of it.

Change

The major loop now records how it ended. If it used every iteration without meeting both the primal and dual tolerances it logs a warning:

ADMM used all 200 iterations without converging: primal residual 0.056 (tolerance 0.0025), dual residual ... (tolerance ...). The inequality constraints may still be violated. Try a larger admm_weight (the final value used was 0.01) or a larger nmajor.
  • Not reported: a solve that converged, and a solve that stopped because the model stopped changing (model_update_tol, which the caller asks for).
  • Nothing else changes: same return value, same solution, the default penalty is untouched. It is a log warning only; the ok flag returned by solve_system is still True. I chose not to change that flag because other code treats it as "the solve ran".

Tests (added to test_inequality_enforcement.py)

  • Sparse dome scenario: the default penalty warns once, naming the iteration count, both residuals with tolerances and admm_weight; admm_weight=1.0 converges, does not warn, and enforces the bounds.
  • A small exact problem (data pull two bounded unknowns above their bounds): converged in time, no warning; admm_weight=1e-4, nmajor=20 warns and the bounds really are violated, so the warning is true; model_update_tol stop, no warning; nmajor=3 (fewer than min_iterations=5, so it cannot be declared converged) warns.
  • With the source change reverted exactly the three tests that expect a warning fail.
  • packages/loop_interpolation/tests: 618 passed, 46 skipped, 4 xfailed (includes the golden tests). ruff: no new findings.

Notes

🤖 Generated with Claude Code

lachlangrose and others added 3 commits October 29, 2026 14:50
Recipe: to_recipe_json / from_recipe_json do not store a feature's build parameters
(nelements, interpolatortype, regularisation), so a model built with anything but the defaults
rebuilds with the defaults and gives a different field (differences of 12 to 60 on a field of
range about 560). The default-parameter case passes; the three others are strict xfails.

Inequality: ADMM enforces the bounds with the arguments the existing builder test uses, but
with its default arguments returns essentially the unconstrained solution (4 of 24 bounds
violated, worst 0.056), and with solver cg the inequality data are dropped without a warning.
A control (the unconstrained solution does violate bounds) and the working ADMM configuration
pass; the default-ADMM case and the cg-warning expectation are strict xfails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…olve

solve_system column-scales the equality system (apply_scaling_matrix, on by default) and
solves for y where c = S @ y, but passed the inequality matrix Q to ADMM unscaled. The bounds
were therefore applied to y instead of to the field Q @ c. With scaling on, even a large penalty
left bounds violated (admm_weight=10: 10 of 24). Pass Q @ S so Q @ y' = Q @ c.

With this fixed, ADMM behaves like ordinary ADMM: admm_weight 1 and 10 now satisfy all 24 bounds
(admm_weight=1 stops at iteration 90). The default penalty (0.01) is still too weak for the test
scenario (primal residual 0.056 after 200 iterations against a tolerance of 0.0025) and the solver
returns without a warning; that strict xfail stays, with its reason rewritten. The cg warning
xfail is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
admm_solve returned after nmajor iterations with no indication of whether it had converged, so an
under-penalised or under-iterated solve left inequality constraints violated silently (with the
default admm_weight=0.01 on the sparse dome, 4 of 24 bounds, worst 0.056).

The loop now records how it ended. If it used every iteration without meeting the primal and dual
tolerances it logs a warning with both residuals and their tolerances and suggests a larger
admm_weight or nmajor. Stopping because the model stopped changing (model_update_tol, which the
caller asks for) is not reported.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…residuals accurately

The constant-norm polish re-solves with ADMM every iteration, so the warning repeated N+1 times.
The first solve stays quiet when a polish follows and only the last polish iteration reports.
When only the dual residual missed its tolerance the message no longer says the inequality
constraints may be violated. warn_on_nonconvergence=False silences it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant