Skip to content

Fault networks in parallel: keep the multigrid tail through the split, gauge the weak plane on owned probes - #669

Merged
lmoresi merged 1 commit into
developmentfrom
bugfix/fault-network-mg-tail-and-gauge
Sep 2, 2026
Merged

Fault networks in parallel: keep the multigrid tail through the split, gauge the weak plane on owned probes#669
lmoresi merged 1 commit into
developmentfrom
bugfix/fault-network-mg-tail-and-gauge

Conversation

@lmoresi

@lmoresi lmoresi commented Sep 2, 2026

Copy link
Copy Markdown
Member

Two silent defects on the finite-width fault-network path, found while measuring the 3-D network at np=2 and np=4 after #664.

The split ran on algebraic multigrid. build() adopted the base mesh's geometric tail and then split, and the split child did not inherit the tail (only Mesh.add_fault passes it on). The rotated path fell back to GAMG without saying so. Now build() adopts the tail on the final mesh, split_faults hands a mesh-owned tail to its child as add_fault does, and net.solve() reports any fallback to algebraic multigrid.

The weak-plane gauge over-read by 3x in parallel. Its probe points sit in the band, and the band lives on one rank under gather-first placement. evaluate() answers for every point it is handed, extrapolating from the nearest local cell for points outside the local mesh (#641), so the band-less rank reported a far-field extrapolation and the max across ranks took it. The gauge now masks each probe pair by ownership, after evaluating on every rank. Evaluating only the owned probes hangs the job: the call is collective, and a rank-local skip leaves the peers spinning. A whole-domain integral of v.v agreed with serial to eight digits throughout, so the solve was never wrong.

Measured on the crossing-patches fixture (h = 0.08, w = 0.04, margin 0.5):

np=1 np=2 np=4
split, velocity PC custom-FMG custom-FMG custom-FMG
split, slip Main / Cross_1 / Cross_2 0.17567 / 0.00335 / 0.00295 same same
weak plane, integral v.v 8.35092135e-02 8.35092137e-02 same
weak plane, slip Main (before) 0.29145 0.91797
weak plane, slip Main (after) 0.29145 0.29145 0.29145
cells on the band's rank 8405 8108 of 8406 8012 of 8405

Parallel is a correctness mode for this path, not a speed-up: the placed region is one rank's, and the solve is not faster than serial. The docs say so now.

Not in this change: the earlier parallel port of the FAC patch smoother. With it on, the np=2 weak-plane velocity solve stalls at a flat residual; whole-level smoothing on the adopted tail is the parallel configuration.

Tests: ptest_0863 gains the weak-plane case and pins the velocity preconditioner in both realisations (np=2 and np=4 run green); test_0863 pins it in serial (9 passed).

Underworld development team with AI support from Claude Code

…eak-plane gauge answers only for owned probes

Two defects on the finite-width network path, both silent.

The split realisation ran every velocity solve on algebraic multigrid.
build() adopted the base's geometric tail before splitting, and the
split child did not inherit it (only Mesh.add_fault's wrapper does),
so the adoption was lost and the rotated path fell back to GAMG
without a word. build() now adopts the tail on the FINAL mesh, and
split_faults hands a mesh-owned tail to its child the way add_fault
does (the FAC zone is not inherited: a split needs no patch).
net.solve() reports a fallback to algebraic multigrid so the
substitution cannot decline quietly again. Serial, np=2 and np=4 all
run the crossing-patches fixture on the geometric tail to the same
slips (Main 0.17567 / Cross_1 0.00335 / Cross_2 0.00295).

The weak-plane gauge reported 3x the true slip at np=2. Its probes sit
in the band, which lives on one rank under gather-first placement;
evaluate() answers for any point it is handed, extrapolating from the
nearest local cell when the point is not in the local mesh (#641), so
the band-less rank returned a far-field extrapolation and the max
across ranks took it. The gauge now masks each probe pair by
ownership. Every rank still evaluates every probe: the call is
collective, and skipping it on a rank-local test is the
conditional-collective hang (measured: peers spin at 100%). A
whole-domain integral of v.v agrees with serial to eight digits at
np=2 and np=4, so the solve was never wrong; the gauge was.

The previous parallel port of the FAC patch smoother is not in this
change: with it on, the np=2 weak-plane velocity solve stalled at a
flat residual. Whole-level smoothing on the adopted tail is the
parallel configuration for now.

ptest_0863 gains the weak-plane case (np=2 and np=4 checked) and pins
the velocity preconditioner in both realisations; test_0863 pins it in
serial.

Underworld development team with AI support from Claude Code
Copilot AI lite review requested due to automatic review settings September 2, 2026 15:39
@lmoresi

lmoresi commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Adversarial review (we read the diff against the measurements, looking for what it does not cover).

  1. Seam-straddling probe pairs go unsampled on both sides. The gauge keeps a pair only when this rank owns both points. Under gather-first placement the band and its skirt are one rank's, so no pair straddles a seam on this path; on a mesh where the band is partitioned, a piece could drop out of every rank's result and the max across ranks would miss its true peak silently. The docstring says so. A loud version would count kept pairs collectively and refuse when a piece has none anywhere; we left that out because the placement makes it unreachable today.
  2. Serial and parallel smoothers now differ silently for the weak plane. In serial the band's fac_zone keys the FAC patch smoother on the finest level; in parallel the zone is stored but unused and the level smooths whole. Both converge in one outer iteration on this fixture. The difference is not reported anywhere; pc_fallbacks stays empty because nothing was substituted, only never engaged.
  3. The gauge reaches into a private mesh locator (_robust_owning_cells) to ask "does this rank hold this point". That question has no public answer on the mesh; points_in_domain answers a different one. If the locator's tolerance changes, the gauge's ownership mask changes with it.
  4. net.solve() reports the GAMG fallback by printing, not by warning, and only on the network's convenience wrapper. A user who calls solve_with_fault directly still gets the silent fallback record on the solver and nothing on the console.
  5. The measurement is one fixture at three rank counts. The np=4 partition puts 8012 of 8405 cells on one rank; a fixture where the far field dominates would exercise the tail's cross-partition transfers harder than this one does.

None of these change the answer on the fixture. Item 1 is the one to watch when placement stops gathering.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The changes are targeted, align with existing MG-tail inheritance patterns, and are reinforced by expanded serial/MPI tests and documentation.

Pull request overview

Fixes two parallel-only correctness regressions in the finite-width 3‑D fault-network workflow: (1) geometric multigrid “tail” metadata being dropped across network splitting (leading to silent GAMG fallback), and (2) weak-plane slip gauges over-reading in parallel due to non-owned probe evaluation/extrapolation on ranks that don’t own the band region.

Changes:

  • Ensure the final network mesh (and split children) retain/adopt the base mesh’s geometric-MG hierarchy so velocity solves consistently run with the intended custom-FMG preconditioner.
  • Make weak-plane slip gauges rank-local by masking probe pairs based on point ownership after collective evaluation, eliminating parallel over-read.
  • Extend tests (serial + MPI) and update docs to codify the parallel behavior and constraints.
File summaries
File Description
src/underworld3/meshing/fault_network.py Adopt MG hierarchy on the final built mesh; add GAMG fallback reporting; mask weak-plane gauge probes by rank ownership.
src/underworld3/utilities/fault_split.py Propagate mesh-owned MG tail metadata through split_faults() children (matching Mesh.add_fault behavior).
tests/test_0863_fault_network_3d_width.py Add assertion that split realisation uses custom-FMG (guards against silent GAMG fallback).
tests/parallel/ptest_0863_fault_network_3d_width_parallel.py Add weak-plane MPI test and assert split solve uses custom-FMG; validate gauge behavior and a partition-independent integral.
docs/advanced/fault-networks.md Document rank-local gauge semantics and clarify MG tail + gather-first placement implications for parallel runs.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Lite

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

Comment on lines +1260 to +1267
info = solve_with_fault(solver, **kwargs)
if info.get("velocity_pc") == "GAMG":
uw.pprint(
"[FaultNetwork.solve] the velocity block ran on ALGEBRAIC "
"multigrid: this mesh owns no geometric tail, or it was "
"not engaged. Production runs want the FMG tail — "
"build(width=...) adopts it from its own base "
"automatically.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants