Skip to content

Rotated path: FAC patch blocks run ILU(0), not the configured LU #892

Open

Description

@lmoresi

On the rotated free-slip path the FAC patch smoother's blocks are not solved by the LU that custom_mg._configure_pcmg configures; they run PCASM's default ILU(0).

_configure_pcmg (composite mode, the default) makes a patch level's PC composite (whole-level SOR, then ASM over the band blocks) and writes mg_levels_<l>_sub_1_sub_pc_type lu and mg_levels_<l>_sub_1_sub_pc_factor_shift_type nonzero into the options DB. solve_rotated_freeslip then calls setUp() on the level smoother and deletes the keys (rotated_bc.py, after "Force the FAC levels' smoother setup NOW", at both call sites). PCSetUp_Composite only hands its children their operators; the ASM child is set up at its first PCApply, after the keys are gone, so its block KSPs read nothing and take ILU(0). The same code is on development (bfd07b7).

Measured on feature/fault-split-integration (799ef02), PETSc 3.25.0. Hierarchy: welded base levels with the split ladder as the finest level, the case that gets the patch. The block PC is read back from the live sub-KSPs.

case block PC velocity iterations
straight frictionless fault in a unit box, simple shear, h_b/S = 24, np 1 (all applications of the velocity solve) ilu (as installed) 344
same, blocks by LU lu 67
2-D yielding-lid convection box with a split fault, one solve from rest, h_b/S = 16, np 4 (per Newton increment) ilu (as installed) 73-200, one cap
same, blocks by LU lu 7

On the box the solve time falls from 1014 s to 267 s (h_b/S 16) and from 1346 s to 193 s (h_b/S 25); the Newton counts and the peak slip are unchanged. With LU blocks the count no longer depends on h_b/S (linear: 66, 65, 67 at 6, 12, 24; box: 7 per increment at 8, 16, 25).

Keeping the _sub_1_sub_ keys in the DB (dropping them from the list _configure_pcmg returns, so the rotated path does not delete them) makes the blocks read LU; that is how the LU rows above were measured. A library fix would set up the composite's children before the keys are deleted, for example after vel_pc.getMGSmoother(_l).setUp():

spc = vel_pc.getMGSmoother(_l).getPC()
if spc.getType() == "composite":
    spc.getCompositePC(1).setUp()

This is untested in the library. It changes the block solver of every rotated solve that gets a FAC patch, so it needs the solver benchmarks before it merges.

Underworld development team with AI support from Claude Code

Activity

  1. lmoresi commented on Oct 29, 2026

    @lmoresi
    MemberAuthor

    Plan (dev hub, 2026-10-31)

    Louis has made this a priority. Below: what the history shows, then the plan.

    History, from the code

    • 2026-08-26, 9f4cb20 (Fault ribbons from the fault's own surface, and the fault-patch multigrid (#629) #638). The fault-patch multigrid lands. Plain ASM replaces the level smoother, with an SOR block solve: unshifted ILU(0) took NUMERIC_ZEROPIVOT on the rotated Galerkin block. The rotated path forces the level smoother's setUp() before it deletes the keys, and for plain ASM that works.
    • 2026-09-03, 891c9bb (Placement gathers far more than the surgery needs, onto one rank for the whole network, and never sends it back #670). Composite (whole-level SOR, then ASM over the band blocks) and an LU block solve with a nonzero shift become the defaults in the same commit. The commit message gives the evidence: 10 velocity iterations against 44 on the TI S-fault rig.
    • On the rotated path the LU was never applied. From that commit on, the composite's ASM child sets up after the keys are deleted. So the blocks have run unshifted ILU(0) ever since, rather than LU being dropped later.
    • Non-rotated callers are probably unaffected. They (custom_mg.py 1302 and 1375 on development) never delete the keys, so their blocks should read LU. That is still to be confirmed below.
    • The chains never had the patch. In C, a level pair touching a split coarse level skips FAC detection (custom_mg.py:1556 at 4698938). The tail sweep found the same.
    • Stale comment. custom_mg.py still says "Subdomain solver: SOR ... no factorization" directly above a default of "lu".

    Plan

    1. Confirm the scope. One fixture, reading the live block sub-PC type at each point:

      • development, rotated path: expected ilu;
      • development, non-rotated FAC path: expected lu; if it is not, this issue is wider than its title;
      • the rotated path at 891c9bb^ against 891c9bb: expected sor, then ilu.
    2. The regression test first, shown failing. It reads back the live block PC of every FAC level and fails unless it is the configured type. It covers composite and plain ASM, rotated and non-rotated, serial and tests/parallel at np 2. An options-DB check would not catch this, because the keys were right all along.

    3. The fix. One helper in custom_mg, called after pc.setUp() from every call site, rather than a third copy of the loop:

      • it sets up each FAC level's smoother and its composite child;
      • it sets the block PC type and shift on the live sub-KSPs, or at least asserts them.

      C has two rotated call sites (rotated_bc.py 2340 and 2493); development has one. So the fix is cleanest stacked on The add-on partitioned once, custom multigrid on empty ranks, and the rotated solve's switch and record #890. If C stalls, rebase onto development.

    4. Benchmarks before the PR.

      • Tier A and B, and tests/parallel at np 2 and 4.
      • Rotated free-slip without a fault (annulus, shell): must be unchanged, since those solves get no patch.
      • The fixtures in this issue and the tail sweep.
      • A watch for PC_FAILED and zero pivots, the reason SOR was chosen in August.
      • The block rows against fac_block_rows (default 2000).
    5. After the merge, re-measure what rested on ILU blocks. Louis then rules on the production tail. What follows from that ruling:

      • the drivers' uw_chain default (science hub);
      • the fault-multigrid and faults-in-convection guides, and UWTN 2026-022's multigrid section and panel (c) (docs hub);
      • whether split coarse levels stay as an option (custom_mg.py:1556).
    6. 3-D and parallel cost of the LU blocks. The 2-D result does not carry over: about 32k block rows summed at ratio 25 on the box.

    7. Write the intent where the next refactor will meet it.

      • the docstring at the block configuration: "FAC blocks are LU; ILU there is a regression";
      • the test from step 2;
      • a journal finding;
      • the guide.
    8. The wider class. The same test should read back every MG level's live configuration (smoother, coarse solve) against what was configured. A key deleted before its lazy consumer runs is the general failure, and Solver introspection does not report the numerical configuration a solve ran with #806 (solvers do not report the configuration they ran with) is its observability half.

    Proposed owner: the laptop integration session, which measured this issue and ran the tail sweep. Steps 1 to 4 wait for Louis's go; the merge waits on #863, #882, #889 and #890 in that order.

    Underworld development team with AI support from Claude Code


    Generated by Claude Code

  2. lmoresi commented on Oct 29, 2026

    @lmoresi
    MemberAuthor

    Step 1: scope

    Only the rotated path runs ILU. The non-rotated FAC installs read LU.

    Method. One fixture: the composed ribbon stack of test_1022 (base L0 + L1, an unsplit mid ribbon, the finest ribbon cut and split), serial, PETSc 3.25.0. After the solve we read every level's smoother back from the live PC objects, not from the options database; the factor shift comes from PCView. In every build the installed custom_mg.py and rotated_bc.py were checked against the source.

    Paths.

    • rotated: frictionless contact, solve_with_fault.
    • velocity: the same split mesh with no fault BC, stokes.solve(). This is the velocity-block install, _install_velocity_block_transfers.
    • poisson: test_1017's NVB band hierarchy. This is the top-level PC install, _install_transfers.
    build rotated: FAC block PC velocity: FAC block PC poisson: FAC block PC
    3bcba66 (891c9bb^) sor, plain ASM, on L2 and L3 (8 its) sor, plain ASM, on L2 and L3 (10 its) sor, plain ASM (4 its)
    891c9bb ilu, no shift, composite, on L2 and L3 (7 its) lu with the nonzero shift, on L2 and L3 (3 its) lu with the nonzero shift (2 its)
    development bfd07b7 ilu, no shift, on L2 and L3 (7 its) lu with the nonzero shift, on L2 and L3 (3 its) lu with the nonzero shift (2 its)
    799ef02 (#890 head) ilu, no shift, on L3 only (7 its) lu with the nonzero shift, on L2 and L3 (3 its) lu with the nonzero shift (2 its)

    "its" is velocity sub-KSP iterations on the last application (KSP iterations for Poisson).

    What the table shows.

    • The switch from SOR to ILU on the rotated path is 891c9bb.
    • On every build and path the coarse solve reads preonly / redundant / preonly / LU as configured, and the non-FAC levels gmres/SOR.
    • The non-rotated paths leave their _sub_1_sub_ keys in the database after the solve; the rotated path deletes them before its ASM sets up.
    • On 799ef02 the rotated path patches only the finest level. The orphan-drop block in rotated_bc.py (B, Split-level fault multigrid, the thin-sheet solver, and sheet extrusion #889) clears every coarse level's patch rows on that path, where CustomMGHierarchy.install keeps the rows of any level the restriction leaves whole. This is unchanged by the fix and is measured separately.

    The regression test's fixture (a fault split in place over a welded 1/8, 1/16 base) shows the same split by path: the rotated saddle-point and rotated thin-sheet solves read ilu in the composite form, serial and np 2; plain ASM (UW_FAC_MODE=asm) and both non-rotated installs read lu.

    The probe and records are in ~/+Simulations/fac_892/ on the laptop (scope_probe.py, records/*.json).

    Underworld development team with AI support from Claude Code

  3. added a commit that references this issue on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions