From ff1d24433ec5d30d2359e4af60968149e355697e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 03:03:00 +0000 Subject: [PATCH 01/16] Fault multigrid: the rulings, what ships, and what is still on a branch, as a capability guide The three rulings (the base is never cut or rebalanced; one hierarchy carries the fault as the model has it; the placed ribbon serves both realisations), the fault-patch design as it ships on development with the iteration counts to expect and the configuration rules learned from failures, where parallel imbalance comes from, and the split-level hierarchy measured on feature/fault-split-multigrid but not merged. The two design documents it draws on gain status lines; the guide joins the capability-guide table and the toctree. Underworld development team with AI support from Claude Code --- .../fault-parallel-placement-2026-09.md | 4 + .../design/fault-patch-multigrid-2026-08.md | 6 + docs/developer/guides/fault-multigrid.md | 192 ++++++++++++++++++ docs/developer/index.md | 2 + 4 files changed, 204 insertions(+) create mode 100644 docs/developer/guides/fault-multigrid.md diff --git a/docs/developer/design/fault-parallel-placement-2026-09.md b/docs/developer/design/fault-parallel-placement-2026-09.md index e0ab9a10d..d0d671e89 100644 --- a/docs/developer/design/fault-parallel-placement-2026-09.md +++ b/docs/developer/design/fault-parallel-placement-2026-09.md @@ -1,5 +1,9 @@ # Fault networks in parallel: what moves, why, and the decision (September 2026) +**Status:** implemented on `development` (#669, #672). The rulings it records +(the base is never rebalanced; the fault's cell star is gathered onto one rank) +are restated for users in [`guides/fault-multigrid.md`](../guides/fault-multigrid.md). + This note records how the fault-network machinery behaves in parallel, the measurements that established it, the design decision taken with Louis on 2 September 2026, and the test that keeps the numbers honest. diff --git a/docs/developer/design/fault-patch-multigrid-2026-08.md b/docs/developer/design/fault-patch-multigrid-2026-08.md index c9d13d8ce..6ee36652a 100644 --- a/docs/developer/design/fault-patch-multigrid-2026-08.md +++ b/docs/developer/design/fault-patch-multigrid-2026-08.md @@ -1,5 +1,11 @@ # Fault-patch multigrid: the measured skeleton (2026-08, #629) +**Status:** implemented on `development` (#629: `set_custom_fmg(..., fac_zone=...)`, +`FaultNetwork.build(width=...)`, `place_thin_volume(mesher="ladder")`). The user-facing +rulings are in [`guides/fault-multigrid.md`](../guides/fault-multigrid.md); the +split-level hierarchy that extends this design is on `feature/fault-split-multigrid` +and recorded in the journal. + The solver architecture for fault-system Stokes problems, settled by measurement on a 2-D analysis rig and ready to carry into 3-D. One sentence: **ordinary balanced multigrid solves the smooth problem — diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md new file mode 100644 index 000000000..e33fb4e06 --- /dev/null +++ b/docs/developer/guides/fault-multigrid.md @@ -0,0 +1,192 @@ +--- +name: fault-multigrid +description: Which multigrid hierarchy a faulted Stokes solve gets in Underworld3 and why — the base mesh is never cut or rebalanced, the fault is carried on an ephemeral add-on, the finest level takes a strong patch keyed on the fault physics, and the coarse levels must carry the fault the way the fine operator does. The three rulings, what ships on development, the iteration counts to expect, where parallel imbalance comes from, and what is measured but still on a branch. +families: [Stokes, Stokes_Constrained] +kind: guide +--- + +# Multigrid for faulted meshes: the rulings + +A fault concentrates the difficulty of the Stokes operator into a thin +cross-cutting region: a split-node contact, a painted weak band, or a painted +transversely isotropic band. Ordinary multigrid handles everything smooth, +refined regions and layered viscosity jumps included, and fails on the band, +because a coarse space that cannot represent slip corrects nothing there. +This page says what the solver does about it on `development`, what is settled +by measurement, and what is measured but not yet merged. The construction of +the faulted mesh itself is on [Fault networks](../../advanced/fault-networks.md) +and [Split-node faults](../../advanced/split-node-faults.md); the general +multigrid controls are on +[Multigrid preconditioning](../../advanced/multigrid-preconditioning.md). + +## The three rulings + +These keep being re-derived in new sessions. They are decisions, taken with +Louis in August to October 2026, and they fix everything below. + +1. **The base mesh is never cut and never rebalanced.** The base and its + coarse levels are the persistent, distributed object that holds the fields. + Everything the fault needs is built on top of it: the add-on levels are + ephemeral, rebuilt whenever the fault moves, and may be partitioned any way, + because every transfer to them is partition-agnostic. A load fix is applied + to the add-on levels, never to the base. +2. **One hierarchy carries the fault as the model has it.** A split spine on + every level that holds the fault, or a weak transversely isotropic band at + each level's own resolution. The background stays welded either way. Not the + cut on some levels and a painted band on others: a smeared band on a coarse + level is a continuous shear that a split fine operator rejects, measured + worse than no fault on the coarse levels at every band strength. +3. **No bespoke meshes per case.** The placed ribbon band along the trace, + sub-sampled 2:1 so that every coarser spine vertex is a finer one, is the one + add-on structure that serves both realisations. The ribbon is part of the + multigrid design, not an alternative to it: it is the bridge between the + background mesh and the fault, the authored patch key, and the same mesh + discipline for split, weak and TI faults. + +## What ships on `development` + +The architecture is the fault-patch design (#629): **multigrid over everything, +strong patches where the physics is sharp.** The custom-prolongation Galerkin +ladder from the base mesh smooths every level; the finest level's smoother +gains one additive-Schwarz component whose subdomains are the fault zone, each +block solved directly once per smoother application. The patch is keyed on the +fault, never on the refinement structure: in a plate-scale mesh the refined +region is most of the node budget while the fault zone scales as the trace. + +With it, the velocity block converges in 4 to 9 iterations on the 2-D rig, +independent of viscosity contrast from 1 to $10^{6}$, of how many along-strike +blocks the trace is divided into, of a strong lid of either jump or smooth +profile, and of network topology. Split contact takes 6 to 7, a painted weak +band 9 falling to 7 as the contrast rises, a painted TI band 4. GAMG on the same +problems takes 40 to 178 and its iteration cap by $10^{6}$. In 3-D, pure +contact on a 20,000-cell ribbon takes 7 against GAMG's 56 with identical +physics. + +How to get it: + +- `FaultNetwork.build(width=w, realisation=...)` places the ribbon, adopts the + base's refinement hierarchy onto the final mesh (so build the base with + `refinement >= 1`), and sets the patch key itself: none for `"split"`, the + band's cells for `"ti"`. Nothing more is needed for the finest patch. +- A hand-built hierarchy registers with + `set_custom_fmg(solver, coarse_meshes, field_id=0, fac_zone=mask)`, where + `field_id=0` is the velocity block of a saddle-point solver and `fac_zone` + is a boolean cell mask or a list of per-segment masks; + `mesh.cells_labelled(name, value)` builds one from a placement's label. + `solver.preconditioner = "fmg"` is the *preference* form, which warns and + falls back to GAMG; `set_custom_fmg` is the *demand* form and raises. +- On an `adapt()` child the mesh-owned custom-prolongation tail is picked up + automatically, rotated free-slip included. On a plain refined annulus with + rotated free-slip the native hierarchy is not read, and a coarse-mesh tail + must be registered by hand (see + [Adapt-on-top faults](adapt-on-top-faults.md)). + +**Which key for which model.** The zone key is for volumetric fault +representations only: a weak, TI or damage zone whose width is physics. A +split-node fault runs the structural patch alone, which is installed +automatically from the split's coincident pairs. The ribbon is never the key; +it is resolution, not rheology. Measured: under pure contact the zone block is +redundant and inflates the finest block from the trace to most of the band. + +**What the width means** follows from the same choice. For a split fault +`width` is a resolution parameter, the band exists to give the cut its own +vertices, and nothing rheological may be keyed to it. For a weak or TI fault it +is the fault-zone thickness, a physical parameter that sets the slip rate and +wants two or three elements across it. Split nodes plus band-wide weakness with +the width chosen as a mesh number is neither model; it was built three times in +one campaign and is the error to recognise. + +## Configuration rules, each learned from a failure + +- The finest patch always contains the structural patch (the rows the coarse + level cannot represent). A zone away from the cut leaves those rows smoothed + nowhere and the velocity sub-solve caps on every application. The zone-block + builder unions them in; do not defeat it. +- Basic additive Schwarz, never restricted: discarding the halo part of the + subdomain correction stalls the outer Krylov at 80 to 375 iterations where + basic runs 6. +- Shifted factorisation is mandatory on split-contact patches + (`sub_pc_factor_shift_type nonzero`): the rotated Galerkin block carries + near-zero pivots from the constraint-zeroed transfer rows, and an unshifted + LU dies at zero iterations with `PC_FAILED`. +- SOR subdomain solves are the safe default; LU is what buys contrast + constancy on the near-singular split patch. +- The robust (GMRES) smoother under contrast: the fast Richardson variant + degrades already at $10^{2}$. +- Check the subdomain probe, not only the iteration count: a patch that + declines silently leaves a plausible whole-level solve, and two bugs in the + original campaign were caught only because the probe disagreed with the + narrative. +- The coarsest level must be small. The free-slip annulus leaves a rotation + null mode, so the coarse solve is a dense SVD on one rank: 900 unknowns are + instant, 14,000 took over thirty minutes. Add a level rather than tolerate a + large bottom; the general rule is on the multigrid page. +- The augmented-Lagrangian penalty at $\gamma = 1$ shortens the pressure Schur + loop once the velocity preconditioner is healthy (22 to 17 iterations, warm + solve ahead of GAMG outright); $\gamma = 10$ over-stiffens the velocity block + and reverses the gain. The recovered pressure is then the multiplier, and + peak slip drifts by about 1 % at rig resolution. + +## Parallel: where the imbalance is, and is not + +The base and its coarse levels are balanced at every rank count and nothing in +the fault machinery touches them. The imbalance is in the add-on levels, in two +parts, in this order of size: + +1. **The add-on child inherits the base's partition.** Every refined cell stays + on the rank that owned the cell it came from, so the ranks under the band + collect most of the new cells. On a 26,000-cell box at 16 ranks the base + puts about 930 cells on each rank and the child puts 6,800 on one. This + exists before any fault is split, and the base is not the fix. +2. **The fault's cell star is gathered onto one rank**, the contact design's + accepted trade: extra load on the owning rank for no communication inside + the contact solve. A network's faults go to different ranks region by + region, so the cost is a few stars per rank rather than all on one. + +With a band of area fraction $f$ refined $k$ halvings on $P$ ranks, for a fault +of length $L$ in a domain of width $D$, the busiest rank sits roughly +$f\,4^{k}\sqrt{P}\,D/L$ above the mean. The $4^{k}$ is the term to control: +keep the band at the base's resolution or one halving below it, and grade the +base toward the fault corridor at construction when more is needed, since the +base is partitioned once for what it holds. Measured on the toy, the solve +stops scaling at 8 ranks because every smoother sweep waits for the rank that +holds the band; a plate-scale model puts the cap higher, but it is a cap, and it +is set by the add-on's inherited partition. Partitioning the add-on levels for +themselves, star-weighted, is the planned fix; rebalancing the base has been +proposed twice and ruled out twice. + +## Measured, not yet merged: the split-level hierarchy + +On `feature/fault-split-multigrid` every add-on level is cut and split like the +finest (strategy C), with a side-aware transfer that locates each fine node +through its own cell's centroid, a per-level pair frame, and unreached coarse +unknowns dropped rather than repaired. The measurements that motivate it: + +- On the cut, iterations *fall* with refinement (6, 5, 4 at two, three and + four add-on levels) where the welded hierarchy's counts rise (13, 18, 23); a + Coulomb fault's Newton steps take 3 to 15 iterations against 15 to 50. +- A single placed ladder over the welded base is enough up to a ratio of about + 12 between the base's finest cell and the band spacing, for both + realisations. Past that, the TI realisation gains from nested welded mid + levels (8, 6, 5, 4 with zero to three mids) and the split realisation gains + nothing from them: it needs the chain of split ladders, or a base refined + under the band, which gives the same count. +- On a convecting annulus with a dipping trench (the Zhong and Gurnis 1995 + analogue), 10 velocity iterations against GAMG's 119, with one rank and two + agreeing in slip to four figures. + +None of this is on `development`: `fault_mg.py`, the ribbon replay onto the +add-on levels and the per-level frames are branch work, and `FaultNetwork.build` +still gives the finest band only. The record, with the dead ends (TI bands +under a split finest, node-cloud transfers to a split level, extrapolated +weights, nearest-node injection) is the journal topic +[Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md). + +## Sources + +- [Fault-patch multigrid: the measured skeleton](../design/fault-patch-multigrid-2026-08.md), + the architecture and the configuration rules (#629). +- [Fault networks in parallel](../design/fault-parallel-placement-2026-09.md), + the gathered star and the seam rules (#672). +- The journal topic page above, for the split-level work and the measurements + behind the rulings. diff --git a/docs/developer/index.md b/docs/developer/index.md index 9aaa0a868..aa256313d 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -63,6 +63,7 @@ pages, never copies. A change to a family is reviewed against its guides. | [Plasticity solvers](guides/plasticity-solvers.md) | viscoplastic and VEP Stokes | | [Adaptive meshing](guides/adaptive-meshing.md) | moving-mesh convection | | [Adapt-on-top faults](guides/adapt-on-top-faults.md) | fault models on adapted meshes | +| [Fault multigrid](guides/fault-multigrid.md) | the hierarchy and the strong patch for faulted Stokes | | [Free-surface convection](guides/free-surface-convection.md) | free-surface Stokes | | [Visualisation](guides/uw-visualisation.md), [CeTZ figures](guides/cetz-figures.md) | figures | | [Line diagrams](guides/uw-line-diagrams.md) | drawn figures | @@ -170,6 +171,7 @@ guides/nonlinear-solver guides/plasticity-solvers guides/adaptive-meshing guides/adapt-on-top-faults +guides/fault-multigrid guides/free-surface-convection guides/uw-visualisation guides/cetz-figures From 67e348463b7bddc60a9caf18d55e68fa7c6b6e5b Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Wed, 28 Oct 2026 07:46:36 +0000 Subject: [PATCH 02/16] Fault multigrid guide: what production runs do today is the placed ladder with a hand-built chain Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index e33fb4e06..e7ef52124 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -81,6 +81,17 @@ How to get it: must be registered by hand (see [Adapt-on-top faults](adapt-on-top-faults.md)). +**What production runs do today.** The campaign drivers (the zg95 annulus and slab +drivers, the box-fault driver) place the fault as a structured ladder band into a copy +of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", +split=True)`, build the split chain by hand (the same ladder at `2^k S` placed into +the base level `k` steps below the finest, so the rung sets nest), and hand every +level to `set_custom_fmg`. No bisection is involved. That chain is driver code because +the library replays a cut, not a placed band, onto the add-on levels; the ribbon +replay is the next library item. The adapt-and-cut route is the automated one and the +one the hierarchy strategies were compared on; it is not what production runs. +(Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`.) + **Which key for which model.** The zone key is for volumetric fault representations only: a weak, TI or damage zone whose width is physics. A split-node fault runs the structural patch alone, which is installed From afbffd4592db2fa1ceb818d1837714de5c61a454 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Thu, 29 Oct 2026 03:31:31 +0000 Subject: [PATCH 03/16] Fault multigrid guide: the production chain nests along the spine, not by vertices Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index e7ef52124..f4adbd4c7 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -85,8 +85,8 @@ How to get it: drivers, the box-fault driver) place the fault as a structured ladder band into a copy of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", split=True)`, build the split chain by hand (the same ladder at `2^k S` placed into -the base level `k` steps below the finest, so the rung sets nest), and hand every -level to `set_custom_fmg`. No bisection is involved. That chain is driver code because +the base level `k` steps below the finest, the band widening with the level, so the +levels nest along the spine), and hand every level to `set_custom_fmg`. No bisection is involved. That chain is driver code because the library replays a cut, not a placed band, onto the add-on levels; the ribbon replay is the next library item. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. From 431b412f493b6dec810139fb9c2bc2d82fffba46 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Thu, 29 Oct 2026 06:05:44 +0000 Subject: [PATCH 04/16] Fault multigrid guide: the chain's hosts as the drivers build them Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index f4adbd4c7..863cd1528 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -84,9 +84,10 @@ How to get it: **What production runs do today.** The campaign drivers (the zg95 annulus and slab drivers, the box-fault driver) place the fault as a structured ladder band into a copy of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", -split=True)`, build the split chain by hand (the same ladder at `2^k S` placed into -the base level `k` steps below the finest, the band widening with the level, so the -levels nest along the spine), and hand every level to `set_custom_fmg`. No bisection is involved. That chain is driver code because +split=True)`, build the split chain by hand (the same ladder at `2S` placed into the +base's finest level beside the `S` ladder and at `4S` into the level below, the band +widening with the level, so the chain replaces the base's top two levels and nests +along the spine), and hand every level to `set_custom_fmg`. No bisection is involved. That chain is driver code because the library replays a cut, not a placed band, onto the add-on levels; the ribbon replay is the next library item. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. From 65c1b4e3a75e3036a459a850493693170f69e73e Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Thu, 29 Oct 2026 11:10:06 +0000 Subject: [PATCH 05/16] Fault multigrid guide: the ladders live in copies of base levels; the base is never modified Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 863cd1528..673d35e0c 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -84,10 +84,12 @@ How to get it: **What production runs do today.** The campaign drivers (the zg95 annulus and slab drivers, the box-fault driver) place the fault as a structured ladder band into a copy of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", -split=True)`, build the split chain by hand (the same ladder at `2S` placed into the -base's finest level beside the `S` ladder and at `4S` into the level below, the band -widening with the level, so the chain replaces the base's top two levels and nests -along the spine), and hand every level to `set_custom_fmg`. No bisection is involved. That chain is driver code because +split=True)`, build the split chain by hand (the same ladder at `2S` placed into a +copy of the base's finest level, as the `S` ladder is, and at `4S` into a copy of the +level below, the band widening with the level so the chain nests along the spine), and +hand `set_custom_fmg` the base's coarser levels followed by those copies. The base and +its levels are never modified: every ladder lives in a copy, and the two base levels +whose copies carry ladders are left out of the solver's list, not changed. No bisection is involved. That chain is driver code because the library replays a cut, not a placed band, onto the add-on levels; the ribbon replay is the next library item. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. From 772dfa676cfc1e34a0e303ca8238524e71848382 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sat, 31 Oct 2026 23:01:59 +0000 Subject: [PATCH 06/16] Fault guides: the production tail is the welded base with the FAC LU patch on the split finest level (ruling of 2026-11-01) The fault-multigrid guide drops the chain as what production runs and states the ruled tail with its counts from the tail sweep on #912's build; its configuration rule on the blocks now says FAC blocks are LU and records that the rotated path ran them as ILU(0) from 2026-09-03 until #912 (#892). The split-level section says the library merged (#889, #890) and keeps the chain as the measured alternative. Mesh.multigrid_levels (#915) replaces the private accessor. The faults-in-convection guide's hierarchy section, code and table are rewritten to the welded tail, with the chain labelled as the alternative and the ILU-era table retired to the journal. The guide is added to the developer toctree it was missing from. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 82 +++++++++++----- docs/developer/guides/faults-in-convection.md | 95 +++++++++---------- docs/developer/index.md | 1 + 3 files changed, 105 insertions(+), 73 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 673d35e0c..0ea3223d4 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -1,6 +1,6 @@ --- name: fault-multigrid -description: Which multigrid hierarchy a faulted Stokes solve gets in Underworld3 and why — the base mesh is never cut or rebalanced, the fault is carried on an ephemeral add-on, the finest level takes a strong patch keyed on the fault physics, and the coarse levels must carry the fault the way the fine operator does. The three rulings, what ships on development, the iteration counts to expect, where parallel imbalance comes from, and what is measured but still on a branch. +description: Which multigrid hierarchy a faulted Stokes solve gets in Underworld3 and why — the base mesh is never cut or rebalanced, the fault is carried on an ephemeral add-on, the finest level takes a strong patch keyed on the fault physics, and the coarse levels must carry the fault the way the fine operator does. The three rulings, the production tail ruled on 2026-11-01 (the welded base, the split finest level, the FAC LU patch), what ships on development, the iteration counts to expect, where parallel imbalance comes from, and the split-level hierarchy as the measured alternative. families: [Stokes, Stokes_Constrained] kind: guide --- @@ -13,7 +13,7 @@ transversely isotropic band. Ordinary multigrid handles everything smooth, refined regions and layered viscosity jumps included, and fails on the band, because a coarse space that cannot represent slip corrects nothing there. This page says what the solver does about it on `development`, what is settled -by measurement, and what is measured but not yet merged. The construction of +by measurement and ruling, and what is kept as the measured alternative. The construction of the faulted mesh itself is on [Fault networks](../../advanced/fault-networks.md) and [Split-node faults](../../advanced/split-node-faults.md); the general multigrid controls are on @@ -51,7 +51,10 @@ ladder from the base mesh smooths every level; the finest level's smoother gains one additive-Schwarz component whose subdomains are the fault zone, each block solved directly once per smoother application. The patch is keyed on the fault, never on the refinement structure: in a plate-scale mesh the refined -region is most of the node budget while the fault zone scales as the trace. +region is most of the node budget while the fault zone scales as the trace. The +blocks are solved by LU with a nonzero shift; on the rotated free-slip path, +which every fault model uses, they ran as unshifted ILU(0) from 2026-09-03 until +#912 (2026-10-31), see the configuration rules below. With it, the velocity block converges in 4 to 9 iterations on the 2-D rig, independent of viscosity contrast from 1 to $10^{6}$, of how many along-strike @@ -68,6 +71,10 @@ How to get it: base's refinement hierarchy onto the final mesh (so build the base with `refinement >= 1`), and sets the patch key itself: none for `"split"`, the band's cells for `"ti"`. Nothing more is needed for the finest patch. +- `base.multigrid_levels` (#915) returns a mesh's levels coarsest first with the + mesh itself last, so a solver on a mesh placed on `base` takes + `set_custom_fmg(solver, base.multigrid_levels, field_id=0)` and nothing else; + a solver on `base` itself takes every level but the last. - A hand-built hierarchy registers with `set_custom_fmg(solver, coarse_meshes, field_id=0, fac_zone=mask)`, where `field_id=0` is the velocity block of a saddle-point solver and `fac_zone` @@ -81,19 +88,26 @@ How to get it: must be registered by hand (see [Adapt-on-top faults](adapt-on-top-faults.md)). -**What production runs do today.** The campaign drivers (the zg95 annulus and slab +**What production runs do.** The campaign drivers (the zg95 annulus and slab drivers, the box-fault driver) place the fault as a structured ladder band into a copy of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", -split=True)`, build the split chain by hand (the same ladder at `2S` placed into a -copy of the base's finest level, as the `S` ladder is, and at `4S` into a copy of the -level below, the band widening with the level so the chain nests along the spine), and -hand `set_custom_fmg` the base's coarser levels followed by those copies. The base and -its levels are never modified: every ladder lives in a copy, and the two base levels -whose copies carry ladders are left out of the solver's list, not changed. No bisection is involved. That chain is driver code because -the library replays a cut, not a placed band, onto the add-on levels; the ribbon -replay is the next library item. The adapt-and-cut route is the automated one and the +split=True)`, rung spacing $S$ and band $2S$; the base and its levels are never +modified, and no bisection is involved. The tail under it is ruled (Louis, +2026-11-01, on #892): **the welded base with the split $S$ level on top and the FAC +LU patch on that finest level**, `set_custom_fmg(stokes, base.multigrid_levels, +field_id=0)`. On UWTN 2026-022's fixtures with the LU blocks running it takes 66 +velocity iterations over a linear solve at band ratios 6, 12 and 24, and 7 per Newton +increment on the yielding-lid box at ratios 8, 16 and 25, with the Newton path and +the slip unchanged. The chain of split ladders (the same ladder at $2S$ placed into a +copy of the base's finest level and at $4S$ into a copy of the level below, each split +along its rungs, the band widening with the level) is the measured alternative and +not the production tail: its levels never carry the patch, and on the same states it +takes 83 to 166 and 28 to 76. It stays driver code (`uw_chain`) until the drivers' +default changes after kaiju's np 16 annulus run; whether split coarse levels remain a +library option is not decided. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. -(Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`.) +(Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`, the sweep +of 2026-10-31, and the decision and finding entries of 2026-11-01.) **Which key for which model.** The zone key is for volumetric fault representations only: a weak, TI or damage zone whose width is physics. A @@ -123,8 +137,14 @@ one campaign and is the error to recognise. (`sub_pc_factor_shift_type nonzero`): the rotated Galerkin block carries near-zero pivots from the constraint-zeroed transfer rows, and an unshifted LU dies at zero iterations with `PC_FAILED`. -- SOR subdomain solves are the safe default; LU is what buys contrast - constancy on the near-singular split patch. +- The FAC blocks are LU with the nonzero shift; ILU there is a regression. From + 2026-09-03 (891c9bb0, which made composite mode and LU the defaults in one + commit) until #912 (2026-10-31) the rotated path ran them as unshifted ILU(0): + the block options were written to the database, the rotated path deleted them + after forcing the smoother's set-up, and the composite's ASM child read the + database only at its first apply (#892). `test_1090_mg_live_configuration_parallel` + now reads back every level's live smoother against what was configured. Any + faulted rotated solve measured between those dates was measured on ILU blocks. - The robust (GMRES) smoother under contrast: the fast Richardson variant degrades already at $10^{2}$. - Check the subdomain probe, not only the iteration count: a patch that @@ -169,12 +189,18 @@ is set by the add-on's inherited partition. Partitioning the add-on levels for themselves, star-weighted, is the planned fix; rebalancing the base has been proposed twice and ruled out twice. -## Measured, not yet merged: the split-level hierarchy +## The split-level hierarchy: merged, and now the alternative -On `feature/fault-split-multigrid` every add-on level is cut and split like the -finest (strategy C), with a side-aware transfer that locates each fine node -through its own cell's centroid, a per-level pair frame, and unreached coarse -unknowns dropped rather than repaired. The measurements that motivate it: +`fault_mg.py`, `add_fault(split_levels=True)`, the side-aware transfer that +locates each fine node through its own cell's centroid, the per-level pair frame +(unreached coarse unknowns dropped rather than repaired) and the thin-sheet +solver merged to `development` on 2026-10-31 (#889, #890). On an adapt-and-cut +child every add-on level is cut and split like the finest (strategy C); the +ribbon replay of a placed band onto the add-on levels is not in the library, +and since the ruling above it is not a production need. The measurements that +motivated the split levels were all made before #912, on the rotated path with +the welded hierarchy's patch running ILU(0) blocks and the split levels carrying +no patch at all: - On the cut, iterations *fall* with refinement (6, 5, 4 at two, three and four add-on levels) where the welded hierarchy's counts rise (13, 18, 23); a @@ -189,9 +215,13 @@ unknowns dropped rather than repaired. The measurements that motivate it: analogue), 10 velocity iterations against GAMG's 119, with one rank and two agreeing in slip to four figures. -None of this is on `development`: `fault_mg.py`, the ribbon replay onto the -add-on levels and the per-level frames are branch work, and `FaultNetwork.build` -still gives the finest band only. The record, with the dead ends (TI bands +With the LU blocks the welded base with the split finest takes 3 velocity +iterations per linear solve at every band ratio from 6 to 25 on the yielding-lid +box, so the chain's case in 2-D is gone. It remains the measured alternative +where the LU patch is unavailable or its block too large (the block grows with +the band's rows; 3-D is not measured), and whether split coarse levels stay a +library option is not decided. `FaultNetwork.build` gives the finest band only, +which is now the production tail. The record, with the dead ends (TI bands under a split finest, node-cloud transfers to a split level, extrapolated weights, nearest-node injection) is the journal topic [Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md). @@ -203,4 +233,8 @@ weights, nearest-node injection) is the journal topic - [Fault networks in parallel](../design/fault-parallel-placement-2026-09.md), the gathered star and the seam rules (#672). - The journal topic page above, for the split-level work and the measurements - behind the rulings. + behind the rulings; its entries of 2026-10-31 (the tail sweep) and 2026-11-01 + (the tail ruling; the history of #892) for the production tail. +- Louis's ruling on the tail, on + [#892](https://github.com/underworldcode/underworld3/issues/892#issuecomment-6550186134), + and the fix, [#912](https://github.com/underworldcode/underworld3/pull/912). diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index ef3d5c1d1..24f194fd2 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -1,6 +1,6 @@ --- name: faults-in-convection -description: Recipe for a split-node fault in a TIME-DEPENDENT convection model (yielding lid, fault placed as the structured ladder by place_fault_ribbon_2d on a copy of the base's finest level, Stokes on the faulted mesh, temperature on the unfaulted base). Covers the multigrid hierarchy that keeps the faulted velocity solve bounded (the lean split-ladder chain, "C on the ladder"), the warm-start rules of the rotated nonlinear loop, rebuilding the faulted mesh for a moving trench, and the traps measured on the 4 x 1 box. Reach for THIS before writing or tuning a fault-in-convection driver; for the fault mechanics on an adapted mesh see `adapt-on-top-faults`, for the yield-law solver see `nonlinear-solver`. +description: Recipe for a split-node fault in a TIME-DEPENDENT convection model (yielding lid, fault placed as the structured ladder by place_fault_ribbon_2d on a copy of the base's finest level, Stokes on the faulted mesh, temperature on the unfaulted base). Covers the multigrid hierarchy that keeps the faulted velocity solve bounded (the welded base with the FAC LU patch on the split finest level, ruled 2026-11-01; the split-ladder chain is the measured alternative), the warm-start rules of the rotated nonlinear loop, rebuilding the faulted mesh for a moving trench, and the traps measured on the 4 x 1 box. Reach for THIS before writing or tuning a fault-in-convection driver; for the fault mechanics on an adapted mesh see `adapt-on-top-faults`, for the yield-law solver see `nonlinear-solver`. families: [Stokes, AdvDiffusion] kind: recipe --- @@ -8,8 +8,8 @@ kind: recipe # Faults in convection A split-node fault in a convecting, yielding lid. The pattern measured on a 4 x 1 box -(2026-10, branch `feature/box-faults`), with the fault-multigrid library of -`feature/fault-split-multigrid`: +(2026-10, on `feature/box-faults`), with the fault-multigrid library that has been on +`development` since 2026-10-31 (#889, #890, #912): - the base mesh carries the temperature and its transport and is never cut; - the faulted mesh is a copy of the base's finest level with the fault placed as the @@ -18,61 +18,58 @@ A split-node fault in a convecting, yielding lid. The pattern measured on a 4 x - Stokes is solved on the faulted mesh, and the velocity is evaluated back onto the base for the transport step. -## The multigrid hierarchy: split the coarse levels +## The multigrid hierarchy: the welded base, the split finest level, the LU patch -`set_custom_fmg(stokes, base_levels, field_id=0)` with the base's own levels gives the -WELDED hierarchy (A in `design/FAULT_MULTIGRID_HIERARCHIES.md`): only the finest level -carries the fault. Its coarse spaces cannot represent the slip, the weak bridge or the -ladder cells, and the velocity sub-solve caps at 200 iterations once Newton starts on a -mobilising lid. A placed ladder is not replayed onto the levels by `add_fault`, so the -split levels have to be built by hand as the mid levels: the same ladder at twice the -rung spacing placed on the finest base level (beside the faulted mesh's own ladder), -and at four times the rung spacing on the base level one step below, each split along -its own rungs. The coarser base levels stay welded. +The production tail (Louis, 2026-11-01, on #892) is the base's own levels under the +faulted mesh: `set_custom_fmg(stokes, base.multigrid_levels, field_id=0)`, where +`Mesh.multigrid_levels` returns the levels coarsest first with the base last, and the +faulted mesh, a placement on the base, is the finest level above them. Only that +finest level carries the fault. What makes the welded coarse spaces enough is the +patch on the finest level: an additive-Schwarz component over the split's coincident +pairs, installed without being asked for when the sole split level is the finest, its +blocks solved by LU with a nonzero shift (#912). ```python from underworld3.utilities.custom_mg import set_custom_fmg from underworld3.utilities.place_surface import place_fault_ribbon_2d -base_levels = base._coarse_level_meshes()[:-1] + [base] # coarsest .. finest - -def placed_ladder(host, coarsening): - # the spine and trace sampled at coarsening x the rung spacing S; the finest - # sample count is a multiple of 2**chain, so every coarser sampling is a subset - spine, trace = fault_trace(coarsening) - width = 2 * S * coarsening # two of the level's own rungs - mesh, info = place_fault_ribbon_2d(host, [("Fault", trace)], width, - spines=[("Fault", spine)], margin_rings=1, - clearance=0.7, split=True, mesher="ladder") - return mesh, info - -chain = 2 -mids = [placed_ladder(base_levels[len(base_levels) - k], 2 ** k)[0] - for k in range(chain, 0, -1)] -faulted, info = placed_ladder(base, 1) +spine, trace = fault_trace() +faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], 2 * S, + spines=[("Fault", spine)], margin_rings=1, + clearance=0.7, split=True, mesher="ladder") stokes = uw.systems.Stokes(faulted, ...) -set_custom_fmg(stokes, base_levels[:len(base_levels) - chain] + mids, field_id=0) +set_custom_fmg(stokes, base.multigrid_levels, field_id=0) # coarsest .. base; faulted on top ``` -Measured on one hard state (a mobilising lid, slip ~270, one solve from rest, 4 ranks; -velocity iterations as the rotated loop reports them per Newton increment): - -| Hierarchy | Levels (cells) | Newton increments | Velocity its, total | Velocity its per increment, Newton phase | Caps (200) | -|---|---|---|---|---|---| -| A, welded | 642, 2568, 10272, 11604 | 39 | 1849 | 71-200 | 2 | -| ladder chain all on the finest | 642, 2568, 10272, 10573, 10966, 11612 | 42 | 636 | 26-29 | 0 | -| **lean chain** (above) | 642, 2924, 10950, 11612 | 42 | 695 | 29-38 | 0 | - -A cycle of the chain placed entirely on the finest level visits three levels of ~11k -cells, about twice the work of a lean or welded cycle, so the lean chain is the cheapest -per solve. In the time-stepping run every velocity sub-solve capped under A once the lid -mobilised; under the lean chain none did, at 4-5 Newton increments per step and the same -slip. A band of constant width (two finest rungs) on every level gave the same counts as -the widened bands. The band patch smoother (`fac_zone=band`) did not help the welded -hierarchy: 114-150 velocity iterations per increment against 91-162 without. - -These counts were measured while the patch smoother's blocks ran ILU(0) rather than the -configured LU (#892). They are re-measured with LU blocks as step 5 of #892's plan. +Measured on UWTN 2026-022's fixtures in the tail sweep and reproduced on #912's build +(velocity iterations as the rotated loop reports them; the band ratio is the base's +finest cell size over the rung spacing $S$): + +| | band ratio | welded base + LU patch | lean split chain | +|---|---|---|---| +| linear fault, np 1, over all applications | 6, 12, 24 | 66, 65, 67 | 83, 118, 166 | +| box with a fault, np 4, per Newton increment | 8, 16, 25 | 7, 7, 7 | 28-40, 45-53, 67-76 | + +The Newton path and the peak slip are the same under both tails, and the welded tail's +counts do not move with the ratio. + +**The split chain, the alternative.** Until 2026-11-01 this guide recommended the lean +chain: the same ladder at $2S$ placed into a copy of the base's finest level and at +$4S$ into a copy of the level below, each split along its rungs, under the faulted +mesh, with the coarser base levels welded (`uw_chain = 2` in the drivers). That rested +on counts measured while the finest patch's blocks ran unshifted ILU(0) instead of the +configured LU (#892): under those the welded tail capped at 200 velocity iterations +once the lid mobilised and the chain did not. The chain's levels never carry the patch +(a pair touching a split coarse level skips FAC detection), so its counts did not +change with the fix, and with the LU blocks it costs 3 to 16 times the welded tail's +iterations on the same states. It stays in the drivers as the measured alternative, +for the case where the LU patch is unavailable or its block too large (the block grows +with the band's rows; 3-D is not measured), and the drivers' default changes once +kaiju's np 16 annulus run confirms the welded tail on `development`. Whether split +coarse levels stay a library option is not decided. The record, with the superseded +table, is the journal topic +[Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md) +(entries 2026-10-29, 2026-10-31 and 2026-11-01). ## The rotated nonlinear loop diff --git a/docs/developer/index.md b/docs/developer/index.md index da5f15ee7..0c155bf61 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -176,6 +176,7 @@ guides/plasticity-solvers guides/adaptive-meshing guides/adapt-on-top-faults guides/fault-multigrid +guides/faults-in-convection guides/free-surface-convection guides/uw-visualisation guides/cetz-figures From 73f8e11718793a31a84af9946c61c620152aa002 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Nov 2026 00:06:24 +0000 Subject: [PATCH 07/16] Fault guides: the np 16 annulus run confirms the welded tail; the finest patch sits on one rank Both guides cite kaiju's production annulus at np 16 beside the UWTN 2026-022 fixtures (5.9 against 7.7 velocity iterations per Newton increment, the same answers, both arms the same patch) and say the drivers' default moves to 0 with the campaign builds. The parallel section of the fault-multigrid guide records that the whole patch, 465 rows and 105 split pairs, is on one rank, so its LU solve is serial there (#895). Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 18 ++++++++++++++---- docs/developer/guides/faults-in-convection.md | 10 +++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 0ea3223d4..d8560d33e 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -102,9 +102,14 @@ the slip unchanged. The chain of split ladders (the same ladder at $2S$ placed i copy of the base's finest level and at $4S$ into a copy of the level below, each split along its rungs, the band widening with the level) is the measured alternative and not the production tail: its levels never carry the patch, and on the same states it -takes 83 to 166 and 28 to 76. It stays driver code (`uw_chain`) until the drivers' -default changes after kaiju's np 16 annulus run; whether split coarse levels remain a -library option is not decided. The adapt-and-cut route is the automated one and the +takes 83 to 166 and 28 to 76. The ruling's condition, confirmation on the production +annulus at np 16, is met (kaiju, `development` 25d62812, four steps from a TAf +checkpoint): the welded base takes 5.9 velocity iterations per Newton increment +against the chain's 7.7, with the same Newton counts, vrms to 2e-6 and slip to 1 in +6000; the margin is smaller than on the fixtures because both arms carry the same +finest patch and only the coarse levels differ. The chain stays driver code +(`uw_chain`), whose default moves to 0 as the campaign builds move to `development`; +whether split coarse levels remain a library option is not decided. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. (Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`, the sweep of 2026-10-31, and the decision and finding entries of 2026-11-01.) @@ -175,7 +180,12 @@ parts, in this order of size: 2. **The fault's cell star is gathered onto one rank**, the contact design's accepted trade: extra load on the owning rank for no communication inside the contact solve. A network's faults go to different ranks region by - region, so the cost is a few stars per rank rather than all on one. + region, so the cost is a few stars per rank rather than all on one. Measured + on the production annulus at np 16 (`development` 25d62812): the whole + finest-level patch, 465 rows and all 105 split pairs, sits on the one rank + whose partition holds the fault, so the patch's LU solve is serial there. At + that size it costs little; at higher rank counts, or with several faults in + one partition, it is the imbalance #895 tracks. With a band of area fraction $f$ refined $k$ halvings on $P$ ranks, for a fault of length $L$ in a domain of width $D$, the busiest rank sits roughly diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index 24f194fd2..fe9bbb6f2 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -64,9 +64,13 @@ once the lid mobilised and the chain did not. The chain's levels never carry the change with the fix, and with the LU blocks it costs 3 to 16 times the welded tail's iterations on the same states. It stays in the drivers as the measured alternative, for the case where the LU patch is unavailable or its block too large (the block grows -with the band's rows; 3-D is not measured), and the drivers' default changes once -kaiju's np 16 annulus run confirms the welded tail on `development`. Whether split -coarse levels stay a library option is not decided. The record, with the superseded +with the band's rows; 3-D is not measured). The ruling's condition is met: on the +production faulted annulus at np 16 (kaiju, `development` 25d62812, four steps from a +TAf checkpoint, both arms with the same 465-row finest patch) the welded base takes +5.9 velocity iterations per Newton increment against the chain's 7.7 and 448 s of +Stokes per step against 467 s, with the same Newton counts and the same answers; the +drivers' `uw_chain` default moves to 0 as the campaign builds move to `development`. +Whether split coarse levels stay a library option is not decided. The record, with the superseded table, is the journal topic [Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md) (entries 2026-10-29, 2026-10-31 and 2026-11-01). From c69fdd3ccf36d54c7f8d144af12ab7b0a8eaacd2 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Nov 2026 05:23:58 +0000 Subject: [PATCH 08/16] Fault guides: the np 16 annulus as corrected by kaiju's budget The two tails do the same velocity work on the production annulus (8.65 against 8.74 F-cycles per solve) in the same time within the run-to-run spread; the welded arm carries a 3584-row patch on one rank and the chain none, so the earlier reading of a smaller patch shared by both arms is withdrawn. The parallel section gives the patch's measured cost (21 to 28 s a step on one rank, 5 to 6 per cent of the solve) and says the structural patch is one block per rank with no row cap, the 2000-row cap being for zone blocks only. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 29 ++++++++++--------- docs/developer/guides/faults-in-convection.md | 15 +++++----- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index d8560d33e..79973f894 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -102,14 +102,14 @@ the slip unchanged. The chain of split ladders (the same ladder at $2S$ placed i copy of the base's finest level and at $4S$ into a copy of the level below, each split along its rungs, the band widening with the level) is the measured alternative and not the production tail: its levels never carry the patch, and on the same states it -takes 83 to 166 and 28 to 76. The ruling's condition, confirmation on the production -annulus at np 16, is met (kaiju, `development` 25d62812, four steps from a TAf -checkpoint): the welded base takes 5.9 velocity iterations per Newton increment -against the chain's 7.7, with the same Newton counts, vrms to 2e-6 and slip to 1 in -6000; the margin is smaller than on the fixtures because both arms carry the same -finest patch and only the coarse levels differ. The chain stays driver code -(`uw_chain`), whose default moves to 0 as the campaign builds move to `development`; -whether split coarse levels remain a library option is not decided. The adapt-and-cut route is the automated one and the +takes 83 to 166 and 28 to 76. On the production annulus at np 16 (kaiju, +`development` 25d62812, steps from a TAf checkpoint) the two tails do the same +velocity work, 8.65 against 8.74 F-cycles per solve, in the same time within the +run-to-run spread, with the same Newton counts and answers; the welded arm carries a +3584-row patch on one rank and the chain none. The welded tail is no worse there and +has one hierarchy fewer to build, so the drivers' `uw_chain` default moves to 0 as the +campaign builds move to `development`. Why the annulus shows none of the box's gain is +open. Whether split coarse levels remain a library option is not decided. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. (Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`, the sweep of 2026-10-31, and the decision and finding entries of 2026-11-01.) @@ -181,11 +181,14 @@ parts, in this order of size: accepted trade: extra load on the owning rank for no communication inside the contact solve. A network's faults go to different ranks region by region, so the cost is a few stars per rank rather than all on one. Measured - on the production annulus at np 16 (`development` 25d62812): the whole - finest-level patch, 465 rows and all 105 split pairs, sits on the one rank - whose partition holds the fault, so the patch's LU solve is serial there. At - that size it costs little; at higher rank counts, or with several faults in - one partition, it is the imbalance #895 tracks. + on the production annulus at np 16 (`development` 25d62812, kaiju's budget): + the whole finest-level patch, 3584 rows, sits on the one rank whose partition + holds the fault, and its LU solve runs there for 21 to 28 s a step while the + other 15 ranks wait, 5 to 6 % of the Stokes solve. The structural patch is one + block per rank with no row cap (the 2000-row cap, `fac_block_rows`, cuts only + the blocks built from `fac_zone` masks), which is the measured 2-D choice; + the share grows with the rank count or with several faults in one partition, + and is what #895 tracks. With a band of area fraction $f$ refined $k$ halvings on $P$ ranks, for a fault of length $L$ in a domain of width $D$, the busiest rank sits roughly diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index fe9bbb6f2..8b586ea34 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -64,13 +64,14 @@ once the lid mobilised and the chain did not. The chain's levels never carry the change with the fix, and with the LU blocks it costs 3 to 16 times the welded tail's iterations on the same states. It stays in the drivers as the measured alternative, for the case where the LU patch is unavailable or its block too large (the block grows -with the band's rows; 3-D is not measured). The ruling's condition is met: on the -production faulted annulus at np 16 (kaiju, `development` 25d62812, four steps from a -TAf checkpoint, both arms with the same 465-row finest patch) the welded base takes -5.9 velocity iterations per Newton increment against the chain's 7.7 and 448 s of -Stokes per step against 467 s, with the same Newton counts and the same answers; the -drivers' `uw_chain` default moves to 0 as the campaign builds move to `development`. -Whether split coarse levels stay a library option is not decided. The record, with the superseded +with the band's rows; 3-D is not measured). On the production faulted annulus at +np 16 (kaiju, `development` 25d62812, steps from a TAf checkpoint; the welded arm with +a 3584-row patch on one rank, the chain with none) the two tails do the same velocity +work, 8.65 against 8.74 F-cycles per solve, in the same Stokes time within the ±10 % +run-to-run spread, with the same Newton counts and answers; the box's gain does not +show there, and why is open. The welded tail is no worse and has one hierarchy fewer +to build, so the drivers' `uw_chain` default moves to 0 as the campaign builds move +to `development`. Whether split coarse levels stay a library option is not decided. The record, with the superseded table, is the journal topic [Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md) (entries 2026-10-29, 2026-10-31 and 2026-11-01). From 977d29612fd9c272f93a4bee45e1e7b6892e6d13 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Nov 2026 05:32:29 +0000 Subject: [PATCH 09/16] Fault guides: the annulus pair's band ratio of 3.9 explains the absent gain Both guides give the ratio where they said the reason was open: the production annulus runs at a band ratio of 3.9, below the smallest ratio in the sweep table, and the patch's gain appears from about 8 upward. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 6 ++++-- docs/developer/guides/faults-in-convection.md | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 79973f894..7e5903504 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -108,8 +108,10 @@ velocity work, 8.65 against 8.74 F-cycles per solve, in the same time within the run-to-run spread, with the same Newton counts and answers; the welded arm carries a 3584-row patch on one rank and the chain none. The welded tail is no worse there and has one hierarchy fewer to build, so the drivers' `uw_chain` default moves to 0 as the -campaign builds move to `development`. Why the annulus shows none of the box's gain is -open. Whether split coarse levels remain a library option is not decided. The adapt-and-cut route is the automated one and the +campaign builds move to `development`. The annulus shows none of the box's gain +because its band ratio is 3.9 (base finest cell 0.0156, rung spacing 0.004), below +the smallest ratio in the sweep table; the patch's gain appears from a ratio of about +8 upward. Whether split coarse levels remain a library option is not decided. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. (Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`, the sweep of 2026-10-31, and the decision and finding entries of 2026-11-01.) diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index 8b586ea34..f55e31906 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -69,7 +69,8 @@ np 16 (kaiju, `development` 25d62812, steps from a TAf checkpoint; the welded ar a 3584-row patch on one rank, the chain with none) the two tails do the same velocity work, 8.65 against 8.74 F-cycles per solve, in the same Stokes time within the ±10 % run-to-run spread, with the same Newton counts and answers; the box's gain does not -show there, and why is open. The welded tail is no worse and has one hierarchy fewer +show there because that model's band ratio is 3.9, below the smallest ratio in the +table. The welded tail is no worse and has one hierarchy fewer to build, so the drivers' `uw_chain` default moves to 0 as the campaign builds move to `development`. Whether split coarse levels stay a library option is not decided. The record, with the superseded table, is the journal topic From 155dce9f084282d4b7f76bd61c206c58a2ed525b Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Nov 2026 11:04:10 +0000 Subject: [PATCH 10/16] Fault multigrid guide: the patch is on one rank by construction, by the gather placement The parallel section states the two imbalance terms by route. An adapt-and-cut child inherits the base's partition; the placement route runs partition_addon first, which balances the add-on and gives the band's region to one rank, so the band, its split pairs and the FAC patch are on one rank at any rank count. The serial patch solve measured at np 16 is therefore the placement's cost, not the welded tail's; seams=conform would divide it and is unmeasured. The sentence naming star-weighted partitioning as the planned fix goes, since partition_addon is that fix. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 52 ++++++++++++------- docs/developer/guides/faults-in-convection.md | 3 +- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 7e5903504..3c775c222 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -174,23 +174,35 @@ The base and its coarse levels are balanced at every rank count and nothing in the fault machinery touches them. The imbalance is in the add-on levels, in two parts, in this order of size: -1. **The add-on child inherits the base's partition.** Every refined cell stays - on the rank that owned the cell it came from, so the ranks under the band - collect most of the new cells. On a 26,000-cell box at 16 ranks the base +1. **An adapt-and-cut child inherits the base's partition.** Every refined cell + stays on the rank that owned the cell it came from, so the ranks under the + band collect most of the new cells. On a 26,000-cell box at 16 ranks the base puts about 930 cells on each rank and the child puts 6,800 on one. This - exists before any fault is split, and the base is not the fix. -2. **The fault's cell star is gathered onto one rank**, the contact design's - accepted trade: extra load on the owning rank for no communication inside - the contact solve. A network's faults go to different ranks region by - region, so the cost is a few stars per rank rather than all on one. Measured - on the production annulus at np 16 (`development` 25d62812, kaiju's budget): - the whole finest-level patch, 3584 rows, sits on the one rank whose partition - holds the fault, and its LU solve runs there for 21 to 28 s a step while the - other 15 ranks wait, 5 to 6 % of the Stokes solve. The structural patch is one - block per rank with no row cap (the 2000-row cap, `fac_block_rows`, cuts only - the blocks built from `fac_zone` masks), which is the measured 2-D choice; - the share grows with the rank count or with several faults in one partition, - and is what #895 tracks. + exists before any fault is split, and the base is not the fix. The placement + route does not have this term: at np > 1 `place_fault_ribbon_2d` first runs + `addon_partition.partition_addon`, which moves the add-on once, before any + surgery, and balances the load to within 10 %. +2. **The band, its split pairs and the FAC patch are on one rank by + construction.** That is what the default placement, `seams="gather"`, does: + `partition_addon` gives the band's region to one rank and moves cells far + from the fault to balance the rest, and the band is then placed and split on + that rank, at any rank count. It is the contact design's accepted trade, + extra load on the owning rank for no communication inside the contact solve, + and a network's faults go to different ranks region by region, so the cost is + a few bands per rank rather than all on one. Measured on the production + annulus at np 16 (`development` 25d62812, kaiju's budget): the finest-level + patch, 3584 rows at fault depth 0.15 and 7010 rows at 0.45, each time all on + one rank of 16, and its LU solve runs there for 21 to 28 s a step while the + other 15 ranks wait, 5 to 6 % of the Stokes solve. That cost comes from the + gather placement, not from the welded tail. `seams="conform"` (#670) meshes + the band through the partition boundaries with nothing moved and would divide + the patch into one block per rank; it needs `mesher="network"` (the ladder + takes only `"gather"`), it has never run in these models, and whether the + iteration count holds with the patch divided among ranks is not measured. + The structural patch is one block per rank with no row cap (the 2000-row + cap, `fac_block_rows`, cuts only the blocks built from `fac_zone` masks), + the measured 2-D choice; the share grows with the rank count or with several + faults in one partition, and is what #895 tracks. With a band of area fraction $f$ refined $k$ halvings on $P$ ranks, for a fault of length $L$ in a domain of width $D$, the busiest rank sits roughly @@ -200,9 +212,11 @@ base toward the fault corridor at construction when more is needed, since the base is partitioned once for what it holds. Measured on the toy, the solve stops scaling at 8 ranks because every smoother sweep waits for the rank that holds the band; a plate-scale model puts the cap higher, but it is a cap, and it -is set by the add-on's inherited partition. Partitioning the add-on levels for -themselves, star-weighted, is the planned fix; rebalancing the base has been -proposed twice and ruled out twice. +is set by whichever rank holds the band: the inherited partition on the +adapt-and-cut route, the gather placement's choice on the placement route. +`partition_addon` is the add-on levels partitioned for themselves; what remains +is the serial band on its rank, and dividing it (`seams="conform"`) is +unmeasured. Rebalancing the base has been proposed twice and ruled out twice. ## The split-level hierarchy: merged, and now the alternative diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index f55e31906..07ac1ba56 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -66,7 +66,8 @@ iterations on the same states. It stays in the drivers as the measured alternati for the case where the LU patch is unavailable or its block too large (the block grows with the band's rows; 3-D is not measured). On the production faulted annulus at np 16 (kaiju, `development` 25d62812, steps from a TAf checkpoint; the welded arm with -a 3584-row patch on one rank, the chain with none) the two tails do the same velocity +a 3584-row patch on the one rank the default gather placement gives the band to, the +chain with none) the two tails do the same velocity work, 8.65 against 8.74 F-cycles per solve, in the same Stokes time within the ±10 % run-to-run spread, with the same Newton counts and answers; the box's gain does not show there because that model's band ratio is 3.9, below the smallest ratio in the From 14a57430ba46a28dbba6788db5b5c0142a1cd535 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Nov 2026 20:14:00 +0000 Subject: [PATCH 11/16] Fault multigrid guide: the placement route repartitions a copy of the base's finest grid; the base and its levels are read, never written Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 3c775c222..500a39b66 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -180,8 +180,13 @@ parts, in this order of size: puts about 930 cells on each rank and the child puts 6,800 on one. This exists before any fault is split, and the base is not the fix. The placement route does not have this term: at np > 1 `place_fault_ribbon_2d` first runs - `addon_partition.partition_addon`, which moves the add-on once, before any - surgery, and balances the load to within 10 %. + `addon_partition.partition_addon`, which redistributes a *copy* of the + base's finest grid (a cloned DM, distributed by a shell partition) once, + before any surgery, and balances its load to within 10 %. The base's DM and + its coarse levels are read, never written; the coarse levels under the + faulted mesh are the base's own level objects on the base's partition, and + the one pair that crosses partitions, the base's finest level against the + faulted mesh, is transferred through the provenance the move records. 2. **The band, its split pairs and the FAC patch are on one rank by construction.** That is what the default placement, `seams="gather"`, does: `partition_addon` gives the band's region to one rank and moves cells far From f7f37f6d643d55052c407687bbb057a00b086bb4 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Nov 2026 23:52:16 +0000 Subject: [PATCH 12/16] Fault guides: ruling 4, the add-on is the line and the fault carries its own resolution The fault-multigrid guide gains a fourth ruling from Louis's decision of 2026-11-02: the fault comes as a divided line or triangulated surface and its points are the ladder's rungs, no parameter sets the along-strike resolution, refinement around the fault is a separate question, and split coarse levels are not developed further. Ruling 3's 2:1 sub-sampling clause is marked as the chain's. The production paragraph and the split-level section say the chain stays as it is and the work goes to the patch's cost. The convection guide's recipe takes the trace's own points and a width, and its open sentence on split coarse levels is closed. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 46 +++++++++++++------ docs/developer/guides/faults-in-convection.md | 14 ++++-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 500a39b66..9959fa194 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -36,12 +36,23 @@ Louis in August to October 2026, and they fix everything below. cut on some levels and a painted band on others: a smeared band on a coarse level is a continuous shear that a split fine operator rejects, measured worse than no fault on the coarse levels at every band strength. -3. **No bespoke meshes per case.** The placed ribbon band along the trace, - sub-sampled 2:1 so that every coarser spine vertex is a finer one, is the one - add-on structure that serves both realisations. The ribbon is part of the - multigrid design, not an alternative to it: it is the bridge between the - background mesh and the fault, the authored patch key, and the same mesh - discipline for split, weak and TI faults. +3. **No bespoke meshes per case.** The placed ribbon band along the trace is + the one add-on structure that serves both realisations. The ribbon is part + of the multigrid design, not an alternative to it: it is the bridge between + the background mesh and the fault, the authored patch key, and the same mesh + discipline for split, weak and TI faults. (Its 2:1 sub-sampling, so that + every coarser spine vertex is a finer one, served the split chain, which + ruling 4 leaves as the alternative.) +4. **The add-on is the line, and the fault carries its own resolution** + (Louis, 2026-11-02). The fault comes as a divided line or a triangulated + surface, and its points are the ladder's rungs: no parameter $S$ sets the + along-strike resolution, and the drivers' trace resampling and $2S$ band + width are to go. The ladder exists to compute stresses and slip rates + accurately with the fewest added nodes; refinement around the fault, for + near-fault stress lobes, is a separate question. Split coarse levels are + not developed further and stay in the library as they are, for comparison. + The work that follows is on the solve (the patch's cost in 2-D and 3-D) and + on the parallel decomposition of the patch. ## What ships on `development` @@ -91,8 +102,10 @@ How to get it: **What production runs do.** The campaign drivers (the zg95 annulus and slab drivers, the box-fault driver) place the fault as a structured ladder band into a copy of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", -split=True)`, rung spacing $S$ and band $2S$; the base and its levels are never -modified, and no bisection is involved. The tail under it is ruled (Louis, +split=True)`, the trace's own points as the rungs; the base and its levels are never +modified, and no bisection is involved. (The drivers still resample the trace at a +spacing $S$ and take the band $2S$ wide; under ruling 4 that parameter goes, and the +band ratios below are the base's finest cell over the fault's own point spacing.) The tail under it is ruled (Louis, 2026-11-01, on #892): **the welded base with the split $S$ level on top and the FAC LU patch on that finest level**, `set_custom_fmg(stokes, base.multigrid_levels, field_id=0)`. On UWTN 2026-022's fixtures with the LU blocks running it takes 66 @@ -111,7 +124,8 @@ has one hierarchy fewer to build, so the drivers' `uw_chain` default moves to 0 campaign builds move to `development`. The annulus shows none of the box's gain because its band ratio is 3.9 (base finest cell 0.0156, rung spacing 0.004), below the smallest ratio in the sweep table; the patch's gain appears from a ratio of about -8 upward. Whether split coarse levels remain a library option is not decided. The adapt-and-cut route is the automated one and the +8 upward. Split coarse levels are not developed further (ruling 4); the chain stays +in the library as it is. The adapt-and-cut route is the automated one and the one the hierarchy strategies were compared on; it is not what production runs. (Journal: `2026-10-28-which-fault-mesh-construction-is-used-in-practice`, the sweep of 2026-10-31, and the decision and finding entries of 2026-11-01.) @@ -251,11 +265,15 @@ no patch at all: With the LU blocks the welded base with the split finest takes 3 velocity iterations per linear solve at every band ratio from 6 to 25 on the yielding-lid -box, so the chain's case in 2-D is gone. It remains the measured alternative -where the LU patch is unavailable or its block too large (the block grows with -the band's rows; 3-D is not measured), and whether split coarse levels stay a -library option is not decided. `FaultNetwork.build` gives the finest band only, -which is now the production tail. The record, with the dead ends (TI bands +box, so the chain's case in 2-D is gone. Its limit is structural: every coarse +level must hold a coarser copy of the fault, which in 3-D is surgery on every +level, and at a junction the $4S$ ladder cannot be placed at an $8S$ gap. Louis +ruled on 2026-11-02 that split coarse levels are not developed further; the +chain stays in the library as it is, for comparison with the measurements +already made, and no new work builds on it. The add-on's own weakness, the one +gathered LU block (the block grows with the band's rows; 3-D is not measured, +#895), is where the work goes next. `FaultNetwork.build` gives the finest band +only, which is the production tail. The record, with the dead ends (TI bands under a split finest, node-cloud transfers to a split level, extrapolated weights, nearest-node injection) is the journal topic [Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md). diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index 07ac1ba56..f219e652d 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -33,17 +33,20 @@ blocks solved by LU with a nonzero shift (#912). from underworld3.utilities.custom_mg import set_custom_fmg from underworld3.utilities.place_surface import place_fault_ribbon_2d -spine, trace = fault_trace() -faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], 2 * S, +spine, trace = fault_trace() # the fault's own points: they are the rungs +faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], width, spines=[("Fault", spine)], margin_rings=1, clearance=0.7, split=True, mesher="ladder") stokes = uw.systems.Stokes(faulted, ...) set_custom_fmg(stokes, base.multigrid_levels, field_id=0) # coarsest .. base; faulted on top ``` +The fault carries its own resolution (Louis, 2026-11-02): the trace's points are the +rungs, and no parameter sets the spacing along the fault. The drivers still resample +the trace at a spacing $S$ and take the band $2S$ wide; that parameter is to go, and +the band ratio below is the base's finest cell size over the fault's own point spacing. Measured on UWTN 2026-022's fixtures in the tail sweep and reproduced on #912's build -(velocity iterations as the rotated loop reports them; the band ratio is the base's -finest cell size over the rung spacing $S$): +(velocity iterations as the rotated loop reports them): | | band ratio | welded base + LU patch | lean split chain | |---|---|---|---| @@ -73,7 +76,8 @@ run-to-run spread, with the same Newton counts and answers; the box's gain does show there because that model's band ratio is 3.9, below the smallest ratio in the table. The welded tail is no worse and has one hierarchy fewer to build, so the drivers' `uw_chain` default moves to 0 as the campaign builds move -to `development`. Whether split coarse levels stay a library option is not decided. The record, with the superseded +to `development`. Split coarse levels are not developed further (Louis, +2026-11-02); the chain stays in the library as it is, and no new work builds on it. The record, with the superseded table, is the journal topic [Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md) (entries 2026-10-29, 2026-10-31 and 2026-11-01). From cf02281b15b7f6bb9724cf5d304ce3d530e90a93 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Mon, 2 Nov 2026 01:08:23 +0000 Subject: [PATCH 13/16] Fault guides: the drivers' default is done; conform seams cannot place a wall-reaching fault Both guides state the campaign drivers' uw_chain default as moved to 0 (b440b88 in the campaign repository, accepted on kaiju at np 16) and cite the drivers' decisions ledger. The fault-multigrid guide's parallel section replaces "never run here, first run pending" for conform seams with the measured limit: the network mesher it needs cannot place a fault whose cavity reaches the domain wall, and the ladder takes only gather, so the patch cannot be divided across ranks for a surface-reaching fault until one of the two changes. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 23 +++++++++++++------ docs/developer/guides/faults-in-convection.md | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 9959fa194..6cf37c7ff 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -120,8 +120,10 @@ takes 83 to 166 and 28 to 76. On the production annulus at np 16 (kaiju, velocity work, 8.65 against 8.74 F-cycles per solve, in the same time within the run-to-run spread, with the same Newton counts and answers; the welded arm carries a 3584-row patch on one rank and the chain none. The welded tail is no worse there and -has one hierarchy fewer to build, so the drivers' `uw_chain` default moves to 0 as the -campaign builds move to `development`. The annulus shows none of the box's gain +has one hierarchy fewer to build; the campaign drivers' `uw_chain` default moved to 0 +with the build move to `development` (b440b88 in `lmoresi/uw3-campaigns`, accepted on +kaiju at np 16; the drivers' decisions ledger there, +`zg95_lid_regimes/DECISIONS.md`, is the citation). The annulus shows none of the box's gain because its band ratio is 3.9 (base finest cell 0.0156, rung spacing 0.004), below the smallest ratio in the sweep table; the patch's gain appears from a ratio of about 8 upward. Split coarse levels are not developed further (ruling 4); the chain stays @@ -215,9 +217,15 @@ parts, in this order of size: other 15 ranks wait, 5 to 6 % of the Stokes solve. That cost comes from the gather placement, not from the welded tail. `seams="conform"` (#670) meshes the band through the partition boundaries with nothing moved and would divide - the patch into one block per rank; it needs `mesher="network"` (the ladder - takes only `"gather"`), it has never run in these models, and whether the - iteration count holds with the patch divided among ranks is not measured. + the patch into one block per rank, but it cannot be used for a fault that + reaches the domain wall as the library stands: it needs `mesher="network"`, + whose cavity reaches the wall and refuses ("the ribbon's cavity reached the + domain wall", serial reproduction on kaiju, 2026-11-01), and the ladder, which + places a wall-reaching fault, takes only `"gather"`. So every surface-reaching + fault in 2-D runs its patch as one serial block, and whether the iteration + count holds with the patch divided among ranks cannot be measured in those + models until the network mesher places such a fault or the ladder learns + conform. The structural patch is one block per rank with no row cap (the 2000-row cap, `fac_block_rows`, cuts only the blocks built from `fac_zone` masks), the measured 2-D choice; the share grows with the rank count or with several @@ -234,8 +242,9 @@ holds the band; a plate-scale model puts the cap higher, but it is a cap, and it is set by whichever rank holds the band: the inherited partition on the adapt-and-cut route, the gather placement's choice on the placement route. `partition_addon` is the add-on levels partitioned for themselves; what remains -is the serial band on its rank, and dividing it (`seams="conform"`) is -unmeasured. Rebalancing the base has been proposed twice and ruled out twice. +is the serial band on its rank, and dividing it (`seams="conform"`) is blocked +for wall-reaching faults until the network mesher can place one or the ladder +learns conform. Rebalancing the base has been proposed twice and ruled out twice. ## The split-level hierarchy: merged, and now the alternative diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index f219e652d..41a375256 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -75,8 +75,8 @@ work, 8.65 against 8.74 F-cycles per solve, in the same Stokes time within the run-to-run spread, with the same Newton counts and answers; the box's gain does not show there because that model's band ratio is 3.9, below the smallest ratio in the table. The welded tail is no worse and has one hierarchy fewer -to build, so the drivers' `uw_chain` default moves to 0 as the campaign builds move -to `development`. Split coarse levels are not developed further (Louis, +to build; the campaign drivers' `uw_chain` default moved to 0 with the build move to +`development` (b440b88 in `lmoresi/uw3-campaigns`, accepted on kaiju at np 16). Split coarse levels are not developed further (Louis, 2026-11-02); the chain stays in the library as it is, and no new work builds on it. The record, with the superseded table, is the journal topic [Fault multigrid](https://github.com/underworldcode/underworld3-journal/blob/main/topics/fault-multigrid.md) From 1a8f0b5da85b1f7320d568b1f9782f907b027632 Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Tue, 3 Nov 2026 20:43:42 +0000 Subject: [PATCH 14/16] Fault guides: the band is as wide as the local mesh size, and neither the division nor the width is a knob Ruling 4 of the fault-multigrid guide carries Louis's clarification of 2026-11-03: the fault's own points are the rungs and the band's width is the local mesh size (an average where the mesh is not uniform); whether the division is sufficient is observed, not set. The production paragraph and the convection guide's recipe say so, and both say every measurement to date used a band two rung spacings wide. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 13 +++++++++---- docs/developer/guides/faults-in-convection.md | 12 +++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 6cf37c7ff..cfc197e9b 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -46,8 +46,12 @@ Louis in August to October 2026, and they fix everything below. 4. **The add-on is the line, and the fault carries its own resolution** (Louis, 2026-11-02). The fault comes as a divided line or a triangulated surface, and its points are the ladder's rungs: no parameter $S$ sets the - along-strike resolution, and the drivers' trace resampling and $2S$ band - width are to go. The ladder exists to compute stresses and slip rates + along-strike resolution, and the band is as wide as the local mesh size, an + average of it where the mesh is not uniform. Neither is a knob a model turns + (Louis, 2026-11-03, clarifying this ruling): whether the fault's division + is sufficient is observed and recorded, not set. The drivers' trace + resampling and $2S$ band width are to go; every measurement so far used a + $2S$ band. The ladder exists to compute stresses and slip rates accurately with the fewest added nodes; refinement around the fault, for near-fault stress lobes, is a separate question. Split coarse levels are not developed further and stay in the library as they are, for comparison. @@ -104,8 +108,9 @@ drivers, the box-fault driver) place the fault as a structured ladder band into of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", split=True)`, the trace's own points as the rungs; the base and its levels are never modified, and no bisection is involved. (The drivers still resample the trace at a -spacing $S$ and take the band $2S$ wide; under ruling 4 that parameter goes, and the -band ratios below are the base's finest cell over the fault's own point spacing.) The tail under it is ruled (Louis, +spacing $S$, one base cell since 2026-11-02, and take the band $2S$ wide; under ruling 4 +the parameter goes and the band becomes one local cell wide, and the band ratios below +are the base's finest cell over the fault's own point spacing.) The tail under it is ruled (Louis, 2026-11-01, on #892): **the welded base with the split $S$ level on top and the FAC LU patch on that finest level**, `set_custom_fmg(stokes, base.multigrid_levels, field_id=0)`. On UWTN 2026-022's fixtures with the LU blocks running it takes 66 diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index 41a375256..6d8605828 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -34,17 +34,19 @@ from underworld3.utilities.custom_mg import set_custom_fmg from underworld3.utilities.place_surface import place_fault_ribbon_2d spine, trace = fault_trace() # the fault's own points: they are the rungs -faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], width, +faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], width, # the local mesh size spines=[("Fault", spine)], margin_rings=1, clearance=0.7, split=True, mesher="ladder") stokes = uw.systems.Stokes(faulted, ...) set_custom_fmg(stokes, base.multigrid_levels, field_id=0) # coarsest .. base; faulted on top ``` -The fault carries its own resolution (Louis, 2026-11-02): the trace's points are the -rungs, and no parameter sets the spacing along the fault. The drivers still resample -the trace at a spacing $S$ and take the band $2S$ wide; that parameter is to go, and -the band ratio below is the base's finest cell size over the fault's own point spacing. +The fault carries its own resolution (Louis, 2026-11-02, clarified 2026-11-03): the +trace's points are the rungs, no parameter sets the spacing along the fault, and the +band is as wide as the local mesh size. The drivers still resample the trace at a +spacing $S$ and take the band $2S$ wide; that parameter is to go, every measurement so +far used the $2S$ band, and the band ratio below is the base's finest cell size over the +fault's own point spacing. Measured on UWTN 2026-022's fixtures in the tail sweep and reproduced on #912's build (velocity iterations as the rotated loop reports them): From d02e2d6fdb76d2b79a14096fb271c087111a399c Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Tue, 3 Nov 2026 22:42:53 +0000 Subject: [PATCH 15/16] Fault guides: the ladder is equant, half-width one cell and rungs about one cell apart Louis's ruling of 2026-11-03 replaces the one-cell band of the previous commit: the ladder's rungs are the fault's own points about one local cell apart, its half-width is one cell, so the band is two cells wide, and neither is a knob. Ruling 4, the production paragraph and the convection guide's recipe give that form and say every measurement so far used a band twice the rung spacing. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 21 ++++++++++--------- docs/developer/guides/faults-in-convection.md | 12 +++++------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index cfc197e9b..4f5a1aa42 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -46,12 +46,14 @@ Louis in August to October 2026, and they fix everything below. 4. **The add-on is the line, and the fault carries its own resolution** (Louis, 2026-11-02). The fault comes as a divided line or a triangulated surface, and its points are the ladder's rungs: no parameter $S$ sets the - along-strike resolution, and the band is as wide as the local mesh size, an - average of it where the mesh is not uniform. Neither is a knob a model turns - (Louis, 2026-11-03, clarifying this ruling): whether the fault's division - is sufficient is observed and recorded, not set. The drivers' trace - resampling and $2S$ band width are to go; every measurement so far used a - $2S$ band. The ladder exists to compute stresses and slip rates + along-strike resolution, and the ladder is equant, its rungs about one + local cell $h$ apart and its half-width one cell, so the band is $2h$ wide. + Neither the division nor the width is a knob a model turns (Louis, + 2026-11-03, clarifying this ruling): whether the fault's division is + sufficient is observed and recorded, not set. The campaign drivers' recipe + v2 is that ladder (the rung spacing derived as one base cell, the width + twice it); every measurement so far used a band twice the rung spacing, so + those ladders are equant too, UWTN 2026-022's finer than the cells. The ladder exists to compute stresses and slip rates accurately with the fewest added nodes; refinement around the fault, for near-fault stress lobes, is a separate question. Split coarse levels are not developed further and stay in the library as they are, for comparison. @@ -107,10 +109,9 @@ How to get it: drivers, the box-fault driver) place the fault as a structured ladder band into a copy of the base's finest level with `place_fault_ribbon_2d(..., mesher="ladder", split=True)`, the trace's own points as the rungs; the base and its levels are never -modified, and no bisection is involved. (The drivers still resample the trace at a -spacing $S$, one base cell since 2026-11-02, and take the band $2S$ wide; under ruling 4 -the parameter goes and the band becomes one local cell wide, and the band ratios below -are the base's finest cell over the fault's own point spacing.) The tail under it is ruled (Louis, +modified, and no bisection is involved. (The drivers derive the rung spacing $S$ as +one base cell since 2026-11-02 and take the band $2S$ wide, which is ruling 4's equant +ladder; the band ratios below are the base's finest cell over the rung spacing.) The tail under it is ruled (Louis, 2026-11-01, on #892): **the welded base with the split $S$ level on top and the FAC LU patch on that finest level**, `set_custom_fmg(stokes, base.multigrid_levels, field_id=0)`. On UWTN 2026-022's fixtures with the LU blocks running it takes 66 diff --git a/docs/developer/guides/faults-in-convection.md b/docs/developer/guides/faults-in-convection.md index 6d8605828..2e3b398f0 100644 --- a/docs/developer/guides/faults-in-convection.md +++ b/docs/developer/guides/faults-in-convection.md @@ -34,7 +34,7 @@ from underworld3.utilities.custom_mg import set_custom_fmg from underworld3.utilities.place_surface import place_fault_ribbon_2d spine, trace = fault_trace() # the fault's own points: they are the rungs -faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], width, # the local mesh size +faulted, info = place_fault_ribbon_2d(base, [("Fault", trace)], width, # 2S: the ladder is equant spines=[("Fault", spine)], margin_rings=1, clearance=0.7, split=True, mesher="ladder") stokes = uw.systems.Stokes(faulted, ...) @@ -42,11 +42,11 @@ set_custom_fmg(stokes, base.multigrid_levels, field_id=0) # coarsest .. base; ``` The fault carries its own resolution (Louis, 2026-11-02, clarified 2026-11-03): the -trace's points are the rungs, no parameter sets the spacing along the fault, and the -band is as wide as the local mesh size. The drivers still resample the trace at a -spacing $S$ and take the band $2S$ wide; that parameter is to go, every measurement so -far used the $2S$ band, and the band ratio below is the base's finest cell size over the -fault's own point spacing. +trace's points are the rungs, about one local cell apart, and the ladder is equant, +its half-width one cell, so the band is two cells wide; neither is a knob. The drivers +derive the rung spacing $S$ as one base cell and take the band $2S$ wide (recipe v2); +every measurement so far used a $2S$ band, and the band ratio below is the base's +finest cell size over the rung spacing. Measured on UWTN 2026-022's fixtures in the tail sweep and reproduced on #912's build (velocity iterations as the rotated loop reports them): From c2ea1565e7c0a401a7927fcff9bc96839d7d518b Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Thu, 5 Nov 2026 01:53:32 +0000 Subject: [PATCH 16/16] Fault multigrid guide: the coarse-level rule points to the multigrid page and no longer says SVD The rule said the free-slip annulus coarse solve is a dense SVD. Louis ruled on 2026-11-05 that there is no SVD path (#901, the A-prime branch); the coarse solve's rule lives on the multigrid preconditioning page, so this guide keeps only the sizing rule, which holds before and after. Underworld development team with AI support from Claude Code --- docs/developer/guides/fault-multigrid.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/developer/guides/fault-multigrid.md b/docs/developer/guides/fault-multigrid.md index 4f5a1aa42..164c9bdf1 100644 --- a/docs/developer/guides/fault-multigrid.md +++ b/docs/developer/guides/fault-multigrid.md @@ -180,10 +180,10 @@ one campaign and is the error to recognise. declines silently leaves a plausible whole-level solve, and two bugs in the original campaign were caught only because the probe disagreed with the narrative. -- The coarsest level must be small. The free-slip annulus leaves a rotation - null mode, so the coarse solve is a dense SVD on one rank: 900 unknowns are - instant, 14,000 took over thirty minutes. Add a level rather than tolerate a - large bottom; the general rule is on the multigrid page. +- The coarsest level must be small: every rank factors the whole of it. Add + a level rather than tolerate a large bottom. The coarse solve itself, and + the rule for it, are on + [Multigrid preconditioning](../../advanced/multigrid-preconditioning.md). - The augmented-Lagrangian penalty at $\gamma = 1$ shortens the pressure Schur loop once the velocity preconditioner is healthy (22 to 17 iterations, warm solve ahead of GAMG outright); $\gamma = 10$ over-stiffens the velocity block