The base _build is where a solver's preconditioner choice is resolved against the mesh hierarchy (_apply_preconditioner_options), before snes.setFromOptions() reads the option bundle. The semi-Lagrangian solvers (AdvDiffusion, and the other solvers.py solvers on the same pattern) run the three setup stages directly inside their own solve():
if not self.is_setup:
self._setup_pointwise_functions(verbose)
self._setup_discretisation(verbose)
self._setup_solver(verbose)
_setup_solver ends with self.is_setup = True, so the super().solve() that follows enters _build, hits if self.is_setup: return, and the resolution never runs. _pc_resolved stays False and nothing is recorded in pc_fallbacks.
Measured (feature/levelset-supg, amr-dev), a structured quad box with refinement=2 (three levels), AdvDiffusion with preconditioner = "fmg", one solve:
SLCN preconditioner='fmg' levels=3 pc=gamg pc_resolved=False fallbacks={}
The request is silently ignored: GAMG stays live, no warning, no record. preconditioner = "gamg" looks correct only because GAMG is what the scalar base pushes in __init__.
The Eulerian SUPG solver (#673) had the same pattern and the same symptom; it now builds through self._build(verbose) in place of the three calls, which is the one-line fix. The same change applies to each solve() in systems/solvers.py that pre-runs the stages (lines around 857, 1103, 1695 at 417f7b8). The pre-run exists so that update_pre_solve sees a built solver, but the history update needs the field, not the SNES, so the order can stay as it is.
Underworld development team with AI support from Claude Code
The base
_buildis where a solver'spreconditionerchoice is resolved against the mesh hierarchy (_apply_preconditioner_options), beforesnes.setFromOptions()reads the option bundle. The semi-Lagrangian solvers (AdvDiffusion, and the othersolvers.pysolvers on the same pattern) run the three setup stages directly inside their ownsolve():_setup_solverends withself.is_setup = True, so thesuper().solve()that follows enters_build, hitsif self.is_setup: return, and the resolution never runs._pc_resolvedstays False and nothing is recorded inpc_fallbacks.Measured (feature/levelset-supg, amr-dev), a structured quad box with
refinement=2(three levels),AdvDiffusionwithpreconditioner = "fmg", one solve:The request is silently ignored: GAMG stays live, no warning, no record.
preconditioner = "gamg"looks correct only because GAMG is what the scalar base pushes in__init__.The Eulerian SUPG solver (#673) had the same pattern and the same symptom; it now builds through
self._build(verbose)in place of the three calls, which is the one-line fix. The same change applies to eachsolve()insystems/solvers.pythat pre-runs the stages (lines around 857, 1103, 1695 at 417f7b8). The pre-run exists so thatupdate_pre_solvesees a built solver, but the history update needs the field, not the SNES, so the order can stay as it is.Underworld development team with AI support from Claude Code