Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .claude/skills/plasticity-solvers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,13 @@ Override with `consistent_tangent=True` / `False` / `"continuation"`.
<sub>Footnote: before this work UW3 differentiated the flux with the viscosity still
wrapped, so `∂η/∂(grad v)` was dropped and viscoplastic solves silently ran the Picard
tangent — the origin of the "~20 iterations is intrinsic" folklore.</sub>

## SNESFAS — do not reach for it

Nonlinear multigrid (SNESFAS) looks tempting for hard viscoplastic solves but is
**not a viable option** at present (maintainer ruling 2026-07-17): there are no
good preconditioners for the nonlinear hierarchy, and it abandons the robust
linear-solver path (consistent tangent / continuation + fieldsplit + MG) that
this skill is built around. It stays options-only for experiments; treat it as a
future investigation. See `docs/developer/design/solver-strategies-catalogue.md`
and `MULTIGRID_MINIMAL_CONTROL_2026-07.md` (ruling 6).
183 changes: 183 additions & 0 deletions docs/developer/design/MULTIGRID_MINIMAL_CONTROL_2026-07.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Multigrid with minimal user control — design note

**Status**: RULED (2026-07-17) — the maintainer settled all seven questions;
rulings recorded below. No implementation in this PR; the rulings govern the
implementation PR that follows.

## Maintainer rulings (2026-07-17)

1. **Automatic routing**: GAMG remains the automatic default; custom-P
geometric FMG is **opt-in**, not auto-selected — it needs more production
mileage before it becomes a silent default (#276's fallback stands).
2. **Failure loudness — two-tier**: automatic/default MG decisions warn and
fall back to a working solve; an EXPLICIT user request (e.g. geometric FMG)
that cannot be honoured fails hard.
3. **Utility-solver exclusion**: an explicit `wants_mg = False` class
attribute — the solver class declares its MG policy readably; the options
latch is retired when this lands.
4. **User spelling**: `solver.multigrid = "auto" | "geometric" | "algebraic"
| "off"` — capability words per the purposeful-naming ruling.
`solver.preconditioner` remains the expert/PETSc-level knob.
5. **`set_custom_mg`**: removed next release cycle (one full deprecation
cycle, the Wave C pattern).
6. **SNESFAS**: options-only, and explicitly **not a viable option** at
present — "we don't have good preconditioners for this one and we seem to
lose the robust linear solver path if we adopt this strategy." Noted here
and in the solver skills as a future investigation only.
7. **Default hierarchies**: NO — meshes do not build a refinement hierarchy
by default. Hierarchy cost stays explicit and predictable; users who want
geometric MG ask for `refinement>=1`.

**Maintainer direction** (2026-07): *"the laplacian solvers all benefit
from MG; the real question is how much control is handed to / required
from the user? I would prefer that this is, by default, minimal."*

---

## 1. What exists today (post-#369)

Five distinct ways a UW3 solver ends up with (or without) multigrid:

### 1.1 `solver.preconditioner` — the one documented knob

`"auto"` (default) / `"fmg"` / `"gamg"` on `SolverBaseClass`
(`cython/petsc_generic_snes_solvers.pyx`). Resolution happens at
`_build` time in `_apply_preconditioner_options()`:

- `"auto"`: **native PETSc geometric FMG** when the mesh carries a
genuine refinement hierarchy (`len(mesh.dm_hierarchy) > 1`, i.e.
built with `refinement >= 1`), otherwise GAMG. Deliberately
conservative: it only ever *adds* geometric MG, never rewrites a PC
the user (or an internal utility solver) configured — an explicit
`pc_type` in the options DB latches `_pc_user_override` and wins
forever after.
- Native FMG is **locked out for single-field solvers** (#276): PETSc's
`DMCreateInjection` fails unpredictably (geometry × degree ×
refinement) on refined DMPlex for scalar/vector discretisations, so
`"auto"`/`"fmg"` on a scalar solver falls back to GAMG with a
warning. The Stokes **velocity block** (`fieldsplit_velocity_`) is
the validated native-FMG path.

### 1.2 Raw PETSc options — the expert escape hatch

`solver.petsc_options[...]` reaches everything (`pc_type=mg`, SNESFAS,
smoother choices, level counts). The `_pc_user_override` latch above is
what keeps 1.1 from fighting this. This is also how internal utility
solvers (projections, smoothers) carry their own tuned PC bundles.

### 1.3 `solver.set_custom_fmg(coarse_meshes, ...)` — custom-P geometric MG

The generalized custom-prolongation hierarchy
(`utilities/custom_mg.py`, `CustomMGHierarchy`): barycentric/RBF
node-level transfers, BC-per-level reduction, Galerkin (RAP) coarse
operators. Works for **any coarse/fine mesh pair** (nested or not),
**any single field** (`field_id=0` targets the Stokes velocity block),
and — since #369 — **in parallel** (co-partitioned rank-local fast path
plus a cross-partition fallback selected automatically on the
zero-column signature). Notably it does *not* need `DMCreateInjection`,
so it does not suffer #276's single-field lock-out.

### 1.4 Mesh-owned auto-injection on `adapt()` children (#369)

A refinement child from `mesh.adapt(...)` carries
`mesh._custom_mg_coarse_meshes` (the static coarse tail). The first
`solve()` of *any* solver on such a mesh lazily builds and installs a
custom-P hierarchy (`auto_inject_custom_mg`) — geometric MG on the
refined operator with **no user call at all**. It is opportunistic and
fail-safe: any build/validation failure warns and falls back to the
solver's default PC (a solve must never crash because an optional
preconditioner upgrade failed).

### 1.5 Legacy `solver.set_custom_mg(...)` — deprecated

Serial-only, single-field, finest-only reduction (no BC-per-level).
Deprecated in favour of `set_custom_fmg`; carries a
`TODO(deprecate)` lifespan marker (this PR, D-87).

**Adjacent fact**: SNESFAS (nonlinear multigrid) is reachable through
1.2 alone today (validated feasible via options; no UW3 surface).

## 2. The problem

The capability matrix is excellent but the *decision burden* sits with
the user: whether their Poisson solve gets MG depends on how the mesh
was built (`refinement=`), which solver class they used (velocity block
vs scalar), whether the mesh is an adapt child, and whether they know
`set_custom_fmg` exists. The #276 lock-out means the most common case —
**a scalar Laplacian on an ordinary refined mesh** — silently gets
GAMG even though a robust geometric path (1.3) exists one call away.

## 3. Proposal — minimal-control default

One principle: **solvers auto-configure the best available MG; the user
supplies nothing.** Expert control remains via exactly two escape
hatches: `solver.petsc_options` (always wins, latched) and
`set_custom_fmg` (explicit hierarchy).

Proposed `"auto"` resolution ladder, per solver, at `_build` time:

1. **User options present** (`_pc_user_override`) → hands off.
2. **Explicit `set_custom_fmg` hierarchy** → build + install it (loud
failure — the user asked for it).
3. **Adapt-child mesh-owned hierarchy** → auto-inject custom-P
(fail-safe, as today).
4. **Refinement hierarchy + multi-field velocity block** → native
PETSc FMG (as today).
5. **Refinement hierarchy + single-field solver** → *NEW*:
auto-build a custom-P FMG from `mesh.dm_hierarchy`'s coarse levels
(closing the #276 gap with the injection-free path) instead of
falling back to GAMG.
6. **No hierarchy** → GAMG (as today).

Steps 1–4 and 6 are current behaviour; only step 5 is new machinery
(and it reuses 1.3 wholesale — the coarse meshes already exist as
`mesh._coarse_level_meshes()`).

Under this ladder `solver.preconditioner` keeps its three values but
`"fmg"` on a single-field solver stops meaning "warn and use GAMG" and
starts meaning "custom-P geometric FMG".

## 4. Questions for the maintainer

1. **Adopt step 5?** Should `"auto"` route single-field solvers with a
refinement hierarchy to custom-P FMG (closing the #276 GAMG
fallback), or does custom-P need more production mileage before it
is a silent default? (An intermediate: `"fmg"` explicitly requests
it, `"auto"` keeps GAMG.)
2. **Failure loudness.** Auto-injection today warns-and-falls-back
(1.4). Is that the right contract for *every* automatic MG decision,
or should an explicit `preconditioner="fmg"` request fail hard when
the hierarchy cannot be built?
3. **Utility solvers.** Internal solvers (projections, mesh-smoothing
Poisson solves) are currently excluded via their own tuned options +
the override latch. Keep the latch as the exclusion mechanism, or
add an explicit `wants_mg = False` class attribute so exclusion is
visible in the solver class rather than in options state?
4. **Naming.** `solver.preconditioner` takes algorithm names
(`"fmg"`/`"gamg"`). Under the purposeful-naming ruling, should the
user surface become capability-shaped —
e.g. `solver.multigrid = "auto" | "geometric" | "algebraic" | "off"`
— with the current spellings kept as accepted aliases? Or is
`preconditioner` (an established PETSc term) exempt?
5. **Legacy `set_custom_mg` removal.** It is deprecated with a
lifespan marker. Remove in the next release cycle (with test_1015's
legacy cases), or keep one more cycle?
6. **SNESFAS scope.** Nonlinear MG stays options-only (out of the
minimal-control surface), or should a future `"auto"` consider it
for strongly nonlinear Laplacian-family solves?
7. **`mesh.dm_hierarchy` as the trigger.** The whole ladder keys off
`refinement >= 1` at mesh build. Is it acceptable that a mesh built
without `refinement=` never gets geometric MG silently (GAMG only),
or should UW3 build a default hierarchy (e.g. `refinement=1`) for
the common constructors so step 5 usually has something to work
with?

## 5. References

- `docs/developer/design/GENERALIZED_FMG_HIERARCHY_AND_ADAPT.md` —
the custom-P hierarchy design (BC-per-level invariant).
- `docs/advanced/multigrid-preconditioning.md` — user-facing guide.
- `utilities/custom_mg.py` — implementation (readability pass D-79..87
in the PR that carries this note).
- #276 (single-field native-FMG lock-out), #369 (parallel custom-MG +
auto-injection), #290 (custom prolongation).
10 changes: 10 additions & 0 deletions docs/developer/design/solver-strategies-catalogue.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,16 @@ degrades on poor cells (the GAMG anisotropy section above) —
mesh-quality monitoring is therefore not aesthetic, it directly
predicts solver robustness.

## SNESFAS (nonlinear multigrid) — NOT a viable option (ruled 2026-07-17)

SNESFAS remains reachable via PETSc options for experimentation, but it is
**not part of any recommended strategy**: we do not have good preconditioners
for the nonlinear hierarchy, and adopting it means losing the robust
linear-solver path (Newton/Picard + fieldsplit + the MG ladder above) that the
rest of this catalogue is built on. Treat it as a future investigation, not a
tool to reach for when a solve struggles — the failure-class map below never
routes to it. See `MULTIGRID_MINIMAL_CONTROL_2026-07.md`, ruling 6.

## Failure-class → strategy map (the picking guide)

```
Expand Down
1 change: 1 addition & 0 deletions docs/developer/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ design/ADAPTIVE_MESHING_DESIGN
design/mesh-adaptation-formulation
design/LAYER2_SBR_ADAPT_ON_TOP
design/NVB_GRADED_ADAPT
design/MULTIGRID_MINIMAL_CONTROL_2026-07
design/ARCHITECTURE_ANALYSIS
design/MATHEMATICAL_MIXIN_DESIGN
design/COORDINATE_MIGRATION_GUIDE
Expand Down
Loading
Loading