Measured off the live PETSc objects after two solves, tolerance = 1e-6, on development @ 04379583:
| option |
Stokes |
Stokes_Constrained |
ksp_rtol |
reachable |
NOT reachable (→ tolerance × 0.1) |
fieldsplit_velocity_ksp_rtol |
NOT reachable (→ tolerance × 0.033) |
reachable |
fieldsplit_pressure_ksp_rtol |
NOT reachable (→ tolerance × 0.1) |
reachable |
fieldsplit_{velocity,pressure}_ksp_max_it |
reachable |
reachable |
fieldsplit_{velocity,pressure}_ksp_type |
reachable |
reachable |
snes_max_it |
NOT reachable (→ 50) |
NOT reachable (→ 50) |
snes_rtol |
NOT reachable (→ tolerance) |
NOT reachable (→ tolerance) |
Both are saddle-point solvers. They disagree about the outer KSP tolerance and about the inner ones, in opposite directions, and nothing documents the difference. max_it and ksp_type on the same prefixes are the control: same dict, same solve, they go through — so this is specific to the keys a tolerance setter writes.
Scalar (Poisson) and vector (Vector_Projection) families are clean — every option tested is reachable. The whole class is localised to the saddle-point path.
Repro: ~/+Simulations/solver_reachability_audit/reachability_matrix.py -uw_family stokes|constrained|scalar|vector. It reads the live objects rather than the options database (a clobbered key leaves no trace there), solves twice (solve-time clobbers only bite on the second), and runs a defaults arm as the control so a "reachable" verdict cannot be an accident of the override matching the default.
Two findings, one surface
1. The disagreement itself. Whatever the intended ownership rule is, it should be the same for both solvers.
2. snes_rtol is owned but looks settable. The in-flight _reassert_outer_tolerances() (see below) deliberately re-writes snes_rtol and ksp_atol at every solve, by design — tolerance owns the outer tolerances. That is a defensible decision, but it leaves snes_rtol looking like a knob that silently does nothing. Either honour the user's value or say in the tolerance docstring that these keys are owned and will be overwritten. The current middle ground — accept it, then discard it — is the worst of the three.
Where the fix belongs
#475 / feature/regime-diagram. That branch already carries the fix for the snes_max_it and fieldsplit_*_ksp_rtol half of this (#477), so this is adjacent work on the same mechanism. Note those commits are not yet pushed — f1555b3a, 5ad68b2f, 954ff4f3 are local only, so the PR does not show them and this issue looks broader than it is until they land.
Found by the solver configuration audit, docs/reviews/2026-07/solver-configuration-reachability-audit.md (branch docs/solver-config-audit), findings F-3 and F-4.
Underworld development team with AI support from Claude Code
Measured off the live PETSc objects after two solves,
tolerance = 1e-6, ondevelopment@04379583:StokesStokes_Constrainedksp_rtoltolerance × 0.1)fieldsplit_velocity_ksp_rtoltolerance × 0.033)fieldsplit_pressure_ksp_rtoltolerance × 0.1)fieldsplit_{velocity,pressure}_ksp_max_itfieldsplit_{velocity,pressure}_ksp_typesnes_max_itsnes_rtoltolerance)tolerance)Both are saddle-point solvers. They disagree about the outer KSP tolerance and about the inner ones, in opposite directions, and nothing documents the difference.
max_itandksp_typeon the same prefixes are the control: same dict, same solve, they go through — so this is specific to the keys atolerancesetter writes.Scalar (
Poisson) and vector (Vector_Projection) families are clean — every option tested is reachable. The whole class is localised to the saddle-point path.Repro:
~/+Simulations/solver_reachability_audit/reachability_matrix.py -uw_family stokes|constrained|scalar|vector. It reads the live objects rather than the options database (a clobbered key leaves no trace there), solves twice (solve-time clobbers only bite on the second), and runs a defaults arm as the control so a "reachable" verdict cannot be an accident of the override matching the default.Two findings, one surface
1. The disagreement itself. Whatever the intended ownership rule is, it should be the same for both solvers.
2.
snes_rtolis owned but looks settable. The in-flight_reassert_outer_tolerances()(see below) deliberately re-writessnes_rtolandksp_atolat every solve, by design —toleranceowns the outer tolerances. That is a defensible decision, but it leavessnes_rtollooking like a knob that silently does nothing. Either honour the user's value or say in thetolerancedocstring that these keys are owned and will be overwritten. The current middle ground — accept it, then discard it — is the worst of the three.Where the fix belongs
#475 /
feature/regime-diagram. That branch already carries the fix for thesnes_max_itandfieldsplit_*_ksp_rtolhalf of this (#477), so this is adjacent work on the same mechanism. Note those commits are not yet pushed —f1555b3a,5ad68b2f,954ff4f3are local only, so the PR does not show them and this issue looks broader than it is until they land.Found by the solver configuration audit,
docs/reviews/2026-07/solver-configuration-reachability-audit.md(branchdocs/solver-config-audit), findings F-3 and F-4.Underworld development team with AI support from Claude Code