What
SNES_Stokes_SaddlePt.solve() executes
self.tolerance = tolerance # pyx:8811 (and 8799 on the Picard branch)
self.snes.atol = self.atol
self.petsc_options.setValue("snes_max_it", snes_max_it)
self.snes.setFromOptions()
on every solve, and the tolerance setter writes (pyx:6300-6301):
self.petsc_options["fieldsplit_pressure_ksp_rtol"] = self._tolerance * 0.1
self.petsc_options["fieldsplit_velocity_ksp_rtol"] = self._tolerance * 0.033
So a user's value for either option is overwritten between being set and being read by
setFromOptions(). Both options are documented (in the tolerance docstring itself)
and both are unreachable.
Reproducer
s.tolerance = 1e-8
s.petsc_options["fieldsplit_velocity_ksp_rtol"] = 1.0e-4 # ignored
s.petsc_options["fieldsplit_velocity_ksp_max_it"] = 7 # honoured
s.solve()
pc = s.snes.getKSP().getPC(); pc.setUp()
k = pc.getFieldSplitSubKSP()[0]
print(k.getTolerances()) # rtol 3.300e-10 (= 1e-8 * 0.033), max_it 7
Measured, res-8 box, both orderings:
set BEFORE first solve -> vel_rtol=3.300e-10 (asked 1.000e-04) vel_max_it=7 (asked 7)
set AFTER first solve -> vel_rtol=3.300e-10 (asked 1.000e-04) vel_max_it=7 (asked 7)
max_it is the control: same prefix (Solver_N_fieldsplit_velocity_), same dict, same
solve — it goes through, because the tolerance setter does not touch it. Only the two
keys the setter writes are affected. Re-asserting the option after the solve does not help
either; the next solve clobbers it again.
Why it matters more than a missing knob
The failure is invisible. A sweep over the velocity inner tolerance returns rows that
are identical to five significant figures, which reads as "this variable does not matter"
rather than "this variable was never set". It cost a 12-cell configuration sweep here
before the identical cells were followed up:
outer vel_rtol schur_mu | ok nl vel wall |F|/|F0|
gmres tight eta_eff | F 37 36633 779.5s 4.9134e-01
gmres 1e-4 eta_eff | F 37 36612 771.9s 4.9134e-01
Six decades apart, same trajectory.
It also means the Citcom inner-tolerance margins (0.033 velocity, 0.1 pressure) have
never been tunable. Their existence is principled — inexact inner solves must still be
bounded well below the tolerance asked of the outer solve — but their size is inherited
convention with no derivation on record, and nobody could have measured an alternative
even if they had wanted to. That is a real gap for hard nonlinear Stokes, where the
velocity block is applied once per Schur action and the inner tolerance is a direct
multiplier on total cost.
Suggested fix
Have the tolerance setter establish these as defaults rather than re-assert them:
track whether the user has written either key explicitly and leave it alone if so;
and stop calling the setter from inside solve() unless the tolerance has actually
changed. The snes_rtol/ksp_atol writes in the same setter are fine to keep — those are
the tolerance's own business — but the two sub-block rtols are separately meaningful
quantities that a user has a legitimate reason to set.
Underworld development team with AI support from Claude Code
What
SNES_Stokes_SaddlePt.solve()executeson every solve, and the
tolerancesetter writes (pyx:6300-6301):So a user's value for either option is overwritten between being set and being read by
setFromOptions(). Both options are documented (in thetolerancedocstring itself)and both are unreachable.
Reproducer
Measured, res-8 box, both orderings:
max_itis the control: same prefix (Solver_N_fieldsplit_velocity_), same dict, samesolve — it goes through, because the
tolerancesetter does not touch it. Only the twokeys the setter writes are affected. Re-asserting the option after the solve does not help
either; the next solve clobbers it again.
Why it matters more than a missing knob
The failure is invisible. A sweep over the velocity inner tolerance returns rows that
are identical to five significant figures, which reads as "this variable does not matter"
rather than "this variable was never set". It cost a 12-cell configuration sweep here
before the identical cells were followed up:
Six decades apart, same trajectory.
It also means the Citcom inner-tolerance margins (0.033 velocity, 0.1 pressure) have
never been tunable. Their existence is principled — inexact inner solves must still be
bounded well below the tolerance asked of the outer solve — but their size is inherited
convention with no derivation on record, and nobody could have measured an alternative
even if they had wanted to. That is a real gap for hard nonlinear Stokes, where the
velocity block is applied once per Schur action and the inner tolerance is a direct
multiplier on total cost.
Suggested fix
Have the
tolerancesetter establish these as defaults rather than re-assert them:track whether the user has written either key explicitly and leave it alone if so;
and stop calling the setter from inside
solve()unless the tolerance has actuallychanged. The
snes_rtol/ksp_atolwrites in the same setter are fine to keep — those arethe tolerance's own business — but the two sub-block rtols are separately meaningful
quantities that a user has a legitimate reason to set.
Underworld development team with AI support from Claude Code