Skip to content

feat: warn when inequality constraints are supplied to a solver that ignores them - #379

Draft
lachlangrose wants to merge 8 commits into
masterfrom
feat/warn-inequality-ignored
Draft

lachlangrose wants to merge 8 commits into
masterfrom
feat/warn-inequality-ignored

Conversation

@lachlangrose

Copy link
Copy Markdown
Member

Draft, stacked on #378 (non-convergence warning) → #377 (ADMM scaling fix) → #374 (the xfail tests). Its diff against master includes those commits until they merge; the change here is the last commit.

Only the admm solver (and the native solver) use inequality constraints. With cg, lsmr or a callable solver they were dropped without a word.

This is not just a builder-API corner. GeologicalFeatureBuilder turns the l / u columns of model data into inequality constraints, and GeologicalModel solves with cg by default, so inequality data in a model's data frame was silently ignored.

Change

solve_system logs a warning when the assembled inequality system has rows and the solver is not admm:

2 inequality constraint rows were supplied but solver 'cg' does not use them, so they are ignored. Use solver='admm' to enforce them.

It names the solver ('custom' for a callable) and the number of rows. The native solver returns earlier in solve_system and handles inequalities itself, so it is not affected. Nothing else changes: no result changes, no new failure.

Tests (test_inequality_enforcement.py)

  • The cg xfail from test: pin the recipe and inequality-constraint problems (strict xfail) #374 is now a passing test (it was my expectation, not a documented contract; the message says what to do).
  • Callable solver warns ('custom'); admm does not warn; cg with no inequality rows does not warn.
  • The model path: l / u columns added to the Claudius model data, default solver, model.update() warns about 2 rows.
  • With the source change reverted exactly the three tests that expect the warning fail. The remaining xfail is the default-penalty one.
  • packages/loop_interpolation/tests: 623 passed, 46 skipped, 3 xfailed; tests: 731 passed, 6 skipped, 3 xfailed (the 3 are the recipe xfails from test: pin the recipe and inequality-constraint problems (strict xfail) #374, which this branch does not fix). ruff: no new findings.

Not done

It stays a warning. Switching to admm automatically when inequality data is present would be a behaviour change (and admm's default penalty is still weak, see #377 / #378), so I left that to a decision.

🤖 Generated with Claude Code

lachlangrose and others added 8 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>
…ignores them

Only the admm solver (and the native solver) use inequality constraints. With cg, lsmr or a
callable solver they were dropped without a word, including the l / u columns of model data
that GeologicalModel turns into inequality constraints and then solves with cg by default.

solve_system now logs a warning, naming the solver and the number of ignored rows, when the
inequality system has rows and the solver is not admm. Nothing else changes.

The cg xfail from the inequality test file is now a passing test; the default-penalty xfail
stays.

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

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>
…or, not on every solve

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