Skip to content

Geometric-MG bundle owns its outer-KSP pairing: fgmres, applied to the live KSP - #515

Merged
lmoresi merged 1 commit into
developmentfrom
bugfix/mg-krylov-smoother-flexible-outer
Aug 9, 2026
Merged

Geometric-MG bundle owns its outer-KSP pairing: fgmres, applied to the live KSP#515
lmoresi merged 1 commit into
developmentfrom
bugfix/mg-krylov-smoother-flexible-outer

Conversation

@lmoresi

@lmoresi lmoresi commented Aug 9, 2026

Copy link
Copy Markdown
Member

Fixes #514. First car of the landing train recorded in the planning-hub liaison ledger: this → #510#511.

The defect

Since #471 the geometric-MG bundle's default smoother is gmres/4. A Krylov smoother makes the preconditioner vary between applications, and a plain-gmres outer's recurrence assumes it fixed. Measured on a 3-D adapt child (PR #510's CI, reproduced locally): the KSP reported CONVERGED_RTOL at 3 iterations while the true residual stalled at 1.3e-6 — one hundred times the gate. The same failure was previously found and fixed locally, twice: the Stokes velocity sub-KSP (the bundle's own comment records the reason) and the free-surface solver ("false-converges … TRUE residual blew up", systems/solvers.py). Piecemeal fixes of one pairing rule are the drift #471 exists to end.

The fix, three parts

  1. ksp_type = fgmres joins the geometric bundle, both smoother variants — richardson does not need flexibility, but every variant must own the same key set (the stale-key derivation requires it), and fgmres judges convergence on the true residual, which is never wrong.
  2. The three solver-default writers of ksp_type now go through _push_managed_option, so the bundle may upgrade the default but never a user's explicit choice (petsc_options["ksp_type"] = ... still wins, per the One owner for the geometric-MG option bundle; rotated free-slip picks up a mesh-owned hierarchy (#468, #467) #471 ownership latch).
  3. The top-level install path applies the resolved type to the live KSP: the database write alone is provably inert there — the KSP consumed its options long before injection — and the new live test caught exactly that inert first version of this fix. The fieldsplit velocity sub-KSP needs no live application (its setFromOptions runs at the parent's PCSetUp, after the write); the rotated path already writes fgmres unconditionally on its per-solve prefix.

Verification

  • New unit test: the pairing invariant on every smoother variant, plus GAMG's stale list clearing the key.
  • New live test: adapt-child scalar solve comes out pc=mg + ksp=fgmres on the live objects.
  • On development + this fix: test_1021 (20), test_0842 (4), rotated parallel test_1064 at np=2 (9), Poisson/Stokes ND smoke (9) — all pass.
  • On the composed tree with PR Mesh surgery base: conforming surfaces, reconnection repair, fault zones (2-D) #510's hierarchies — the shape that exposes the drift: test_0842 passes at its 1e-8 gate (was 1.0e-6 broken; 2.7e-10 fixed), 40 tests green across 0842/1021/0753.
  • Honest scope note: on hierarchies where one FMG application converges the cycle, the two pairings are measurably indistinguishable (3.3e-8 either way on the 2-D fixture); test_0842 is the discriminating gate.

Underworld development team with AI support from Claude Code

…o the live KSP (#514)

Since #471 the bundle's default smoother is gmres/4. A Krylov smoother makes
the preconditioner vary between applications, and a plain-gmres outer's
recurrence assumes it fixed: measured on a 3-D adapt child (PR #510 CI, then
locally), the KSP reported CONVERGED_RTOL at 3 iterations with the true
residual stalled at 1.3e-6 - a hundred times the gate. The same failure was
found and fixed locally twice before, on the Stokes velocity sub-KSP and on
the free-surface solver, which is exactly the piecemeal drift #471 exists to
end; this puts the pairing in the one owner.

Three parts. ksp_type=fgmres joins the geometric bundle for BOTH smoother
variants - richardson does not need flexibility, but the stale-key derivation
requires every variant to own the same keys, and fgmres judges convergence on
the true residual, which is never wrong. The three solver-default writers of
ksp_type go through _push_managed_option, so the bundle may upgrade the
DEFAULT but never a user's explicit choice (the ownership latch). And the
top-level install path applies the resolved type to the LIVE KSP: the
database write alone is provably inert there, because the KSP consumed its
options long before injection - the new live test caught exactly that in its
first run. The fieldsplit velocity sub-KSP needs no live application (its
setFromOptions runs at the parent's PCSetUp, after the write) and the rotated
path already writes fgmres unconditionally on its per-solve prefix.

On hierarchies where one FMG application converges the cycle, the two
pairings are indistinguishable (measured: 3.3e-8 either way on the 2-D
fixture); the drift needs a hierarchy the smoother varies on, which is the
3-D one-level-per-doubling shape arriving with PR #510 - its test_0842 is the
discriminating gate, and passes on the composed tree.

Closes #514.

Underworld development team with AI support from Claude Code
Copilot AI lite review requested due to automatic review settings August 9, 2026 00:05

@lmoresi lmoresi left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Adversarial review (authoring session).

Where we attacked it:

  1. The inert-fix trap — caught by our own test's first run. The obvious fix (put ksp_type in the bundle) does nothing on the standard path: the top-level KSP consumes its options before injection, so the DB write never takes. The live-object application in _configure_pcmg(ksp=...) is the actual fix; the live test asserts on ksp.getType(), not the database, for exactly this reason.
  2. Ownership latch semantics. A user's petsc_options["ksp_type"] = "cg" survives: the latch sees an unrecorded key and the bundle backs off; the live application then applies the user's value (a no-op setType). The three converted default writers are internal writers of a now-bundle key, which #471's own doctrine says must be recorded — this was a pre-existing violation of that rule, not a new pattern.
  3. Negative control on the new live test. On the 2-D fixture the broken and fixed pairings are indistinguishable (3.3e-8 both, one FMG application — measured before setting the gate), so the error assert is a sanity bound only and says so; the discriminating gate is test_0842 on the 3-D subsampled hierarchy, which goes 1.0e-6 → 2.7e-10 with this fix and is part of PR #510's CI.
  4. Blast radius. fast/richardson variant: stationary smoother, fgmres merely judges on the true residual — sound, small extra memory. Fieldsplit velocity sub-KSP: bundle value equals current practice (fgmres, per the code's own comment). Rotated path: writes fgmres unconditionally already, untouched. GAMG fallback: ksp_type is in its stale list, so falling back clears our key and returns the default — verified by the bundle unit test.

Recommend merge; #510's re-run against post-merge development is the integration proof.

Underworld development team with AI support from Claude Code

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.

Pull request overview

This PR fixes a PETSc Krylov-method pairing defect in Underworld3’s geometric multigrid (FMG) option bundle: when the MG smoother itself is Krylov-based, the preconditioner becomes non-stationary between applications, so a non-flexible outer GMRES can report convergence while the true residual stalls. The change makes the geometric-MG bundle responsible for enforcing a flexible outer KSP type, and ensures that resolved defaults are applied to the live KSP object (not just written into the options DB).

Changes:

  • Add ksp_type=fgmres to the geometric multigrid option bundle (for both smoother variants) and ensure GAMG treats that key as stale to avoid prefix leakage.
  • Route solver-default ksp_type writes through _push_managed_option so bundle ownership can upgrade defaults without overriding user-owned settings.
  • Apply the resolved ksp_type to the live top-level KSP during custom-P FMG injection, and add tests that assert both bundle invariants and live-object behavior on an adapt-child scalar solve.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/test_1021_mg_option_bundle.py Adds regression tests for the geometric-MG “Krylov smoother ⇒ flexible outer” pairing and verifies the live KSP type on an adapt-child scalar solve.
src/underworld3/utilities/multigrid_options.py Extends the geometric MG settings bundle to include ksp_type=fgmres so the bundle owns the outer/inner pairing rule.
src/underworld3/utilities/custom_mg.py Threads the live top-level ksp into _configure_pcmg() and sets the resolved ksp_type directly on the live object after bundle application.
src/underworld3/cython/petsc_generic_snes_solvers.pyx Switches solver-default ksp_type initialization to _push_managed_option() (and similarly for other bundle-owned defaults) to preserve bundle/user ownership semantics.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@lmoresi
lmoresi merged commit 31f2ca1 into development Aug 9, 2026
3 checks passed
lmoresi added a commit that referenced this pull request Aug 9, 2026
…_Boundary removal (#503)

Two reconciliations beyond the textual merge:

- custom_mg.py: #515 puts ksp_type=fgmres in the geometric bundle and
  applies the resolved type to the live KSP from _configure_pcmg — the
  one-owner form of the same fix our _ensure_flexible_outer carried for
  the scalar custom-P route. #515's mechanism covers that route (the
  solver-default ksp_type=gmres is now a managed option the bundle may
  upgrade, and _install_transfers passes the live KSP), so our helper is
  removed outright; no residue is needed. The explicit-gamg opt-out gate
  in build_transfers survives unchanged.

- Null_Boundary fixtures: #503 stopped manufacturing the every-vertex
  sentinel label. test_0844's vertex-blanket negative control now builds
  its own blanket label explicitly (the hazard is any vertex-blanket
  label, not that one spelling — and the fixture is now self-contained);
  stale comments in test_0848, test_0842 and reconnect.py's
  _interface_edges docstring are updated to match, with the vertex
  exclusion kept load-bearing for caller enums and old checkpoints.

Underworld development team with AI support from Claude Code
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