Skip to content

feat(solvers): default-on solve reporting + bounded/resumable difficulty solve#377

Open
lmoresi wants to merge 1 commit into
developmentfrom
feature/solver-report
Open

feat(solvers): default-on solve reporting + bounded/resumable difficulty solve#377
lmoresi wants to merge 1 commit into
developmentfrom
feature/solver-report

Conversation

@lmoresi

@lmoresi lmoresi commented Jul 22, 2026

Copy link
Copy Markdown
Member

What

Two general capabilities on SolverBaseClass, so every SNES-based solver gets them (Stokes, scalar, vector, and the rotated-free-slip KSP path):

1. Default-on difficulty reporting. Every solve() records a SolveReport (new pure-Python module systems/solve_report.py) with the converged reason, nonlinear/linear iteration counts, final and initial ||F||, residual reduction, contraction rho, function-evaluations, and the residual ladder. Read-only via solver.solve_report; a bounded solver.solve_history (deque, maxlen=32) keeps a short trail for continuation / time-stepping loops. Captured at the single solve chokepoint (_snes_solve_with_retries) so no solve() body needs editing; the rotated-free-slip path (which solves via ksp.solve, bypassing the chokepoint) gets an honest report from its own result rather than a stale one.

2. Bounded, resumable difficulty estimate. solver.estimate_difficulty(max_nl_its, warm=...) caps nonlinear iterations (predictable solver work, not wall-time), reports the effort spent, and continues losslessly from the partial iterate. A chunked start/stop/restart chain terminates at the same point an uninterrupted solve would: the chain anchors convergence to the original ||F0|| (absolute tol = tolerance*||F0||), which is looser than the restart-relative rtol*||F_restart|| and so fires first. Gated on an internal probe flag, so a plain solve() is byte-for-byte unchanged and cannot inherit a stale anchor.

Why

Groundwork for adaptive continuation / homotopy control: a solver should expose its difficulty and let a driver bound it to estimate difficulty cheaply, then resume without losing work. Reporting is default-on; the bounded/resumable knob is opt-in.

Safety

Behaviour of a normal solve is preserved -- the only additions on that path are a guarded setConvergenceHistory(reset=True) and a read-only report capture. estimate_difficulty restores tolerances after each call.

Tests

tests/test_1055_solve_report.py: default-on report on Stokes + scalar; bounded stop at the cap; lossless resume matching an uninterrupted reference; chunked chains terminate at the same ||F||.

Regression: level_1 and tier_a green -- 403 passed, 0 failed, 0 errors (1 pre-existing unrelated xfail).

Underworld development team with AI support from Claude Code

…lty solve

Two general capabilities on SolverBaseClass, so every SNES-based solver gets
them (Stokes, scalar, vector, and the rotated-free-slip KSP path):

1. Default-on difficulty reporting. Every solve() records a SolveReport
   (new pure-Python module systems/solve_report.py) exposing the converged
   reason, nonlinear/linear iteration counts, final and initial ||F||, the
   residual reduction, contraction rho, function-evaluations, and the residual
   ladder. Read-only via solver.solve_report; a bounded solver.solve_history
   (deque maxlen=32) keeps a short trail for continuation / time-stepping loops.
   Captured at the single solve chokepoint (_snes_solve_with_retries) so no
   solve() body needs editing; the rotated-free-slip path (which solves via
   ksp.solve, bypassing the chokepoint) gets an honest report from its own
   result rather than a stale one.

2. Bounded, resumable difficulty estimate. solver.estimate_difficulty(
   max_nl_its, warm=...) caps NONLINEAR iterations (predictable solver work,
   not wall-time), reports the effort spent, and continues losslessly from the
   partial iterate. A chunked start/stop/restart chain terminates at the SAME
   point an uninterrupted solve would: the chain anchors convergence to the
   original ||F0|| (absolute tol = tolerance*||F0||), which is looser than the
   restart-relative rtol*||F_restart|| and so fires first. Gated on an internal
   probe flag, so a plain solve() is byte-for-byte unchanged and cannot inherit
   a stale anchor.

Reporting is default-on; the bounded/resumable knob is opt-in. Behaviour of a
normal solve is preserved (only additions on that path are a guarded
setConvergenceHistory and a read-only report capture).

Tests: tests/test_1055_solve_report.py (default-on report on Stokes + scalar;
bounded stop at the cap; lossless resume matching an uninterrupted reference;
chunked chains terminate at the same ||F||). Regression: level_1/tier_a green
(403 passed, 0 failed, 0 errors).

Underworld development team with AI support from Claude Code
Copilot AI review requested due to automatic review settings July 22, 2026 00:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants