diff --git a/docs/developer/design/fault-parallel-placement-2026-09.md b/docs/developer/design/fault-parallel-placement-2026-09.md index 1e55eea31..8c6443124 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 967ae1c76..f18058b23 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..164c9bdf1 --- /dev/null +++ b/docs/developer/guides/fault-multigrid.md @@ -0,0 +1,307 @@ +--- +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, 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 +--- + +# 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 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 +[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 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 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. + 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` + +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. 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 +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. +- `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` + 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)). + +**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)`, the trace's own points as the rungs; the base and its levels are never +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 +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. 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; 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 +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.) + +**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`. +- 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 + 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: 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 + 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. **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. The placement + route does not have this term: at np > 1 `place_fault_ribbon_2d` first runs + `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 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, 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 + 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 +$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 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 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 + +`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 + 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. + +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. 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). + +## 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; 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..2e3b398f0 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,71 @@ 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() # the fault's own points: they are the rungs +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, ...) -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. +The fault carries its own resolution (Louis, 2026-11-02, clarified 2026-11-03): the +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): + +| | 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). 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 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 +table. The welded tail is no worse and 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). 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). ## The rotated nonlinear loop diff --git a/docs/developer/index.md b/docs/developer/index.md index d260bb4b5..0c155bf61 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -66,6 +66,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 | | [Faults in convection](guides/faults-in-convection.md) | split-node faults in time-dependent Stokes | | [Free-surface convection](guides/free-surface-convection.md) | free-surface Stokes | | [Visualisation](guides/uw-visualisation.md), [CeTZ figures](guides/cetz-figures.md) | figures | @@ -174,6 +175,8 @@ guides/nonlinear-solver 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