Repository navigation
Custom-P multigrid builds above 16 ranks: UW3's copy of PETSc's nested interpolator, agreed build failures (#902, #886) - #937
Conversation
PETSc's DMPlexComputeInterpolatorNested skips its preallocation pass on a rank with no local rows and no local columns but fills from that rank's cells all the same. A shared point belongs to the highest rank holding it, so a rank whose cells sit wholly inside higher ranks' regions owns nothing, and its entries reach owners that never preallocated them: the owner raises "New nonzero caused a malloc" inside MatAssemblyEnd, and the rank that did not raise waits in the assembly's collective. On kaiju this stopped custom-P multigrid at np 32 and 52 (Mesh._get_coords_for_basis on the 124-cell coarsest level of the faulted annulus). The test forces the case with a shell partition: rank 0 keeps one interior cell of a 4 x 4 grid and owns none of its vertices and edges. It pins the degree 1 and 2 node coordinates and the projection between degrees (project_to_degree, which interpolates between a sub-DM and a clone of one plex the same way). On development at np 2 the degree-2 coordinates raise "New nonzero at (88,32)" on rank 1 and the projection fails after it; a thread timeout turns a hang into a failure. Underworld development team with AI support from Claude Code
…rank (#886) build_transfers retries a failed barycentric build with the RBF builder and degrades to the default preconditioner after that, each rank on its own verdict: a rank whose builder raised retried alone while the others went on into the transfer's MPIAIJ assembly. The test makes the barycentric builder raise on rank 1 only, on a single-field fmg Poisson at np 2. On the current branch it times out with rank 0 inside _assemble_node_transfer; the contract is the RBF rescue on every rank, multigrid installed, the solve converged. Underworld development team with AI support from Claude Code
…902) Mesh._get_coords_for_basis and function.project_to_degree interpolated between two DMs on one plex with createInterpolation, which takes PETSc's DMPlexComputeInterpolatorNested. That function skips its preallocation on a rank with no local rows and no local columns but fills from that rank's cells, so on a rank that holds cells and owns none of their nodes the owners raise inside MatAssemblyEnd and the other ranks wait in its collective. custom-P multigrid reads every level's node coordinates, and this stopped kaiju's faulted annulus at np 32 and 52. The element matrix PETSc applies is the same for every cell (it comes from the reference element alone). utilities.closure_interpolation builds it on one reference cell, on one rank, applies it to each local cell's closure, and hands each node its owner's value through a local-to-global and back. That covers every field whose edge and face points carry at most one node (any discontinuous field, continuous degree 1 and 2). A field with more nodes per edge, ordered by the edge's orientation, keeps PETSc's matrix, and on a partition where that matrix cannot be assembled it raises on every rank instead of hanging. Against PETSc's matrix at np 1 and 4, on triangles, quads, tets, hexes, a degree-2 annulus and a spherical shell, continuous and discontinuous degree 1 and 2: identical bits for continuous P1/P2 on simplices and Q1, round-off (at most 1.6e-15) elsewhere, 2-7x faster. test_1107 passes at np 2 and 4. The remaining interpolations of this kind are marked TODO(BUG): the degree >= 3 trace coordinates of boundary_flux and PETSc's projection of a degree >= 2 coordinate field in Mesh setup. Underworld development team with AI support from Claude Code
In parallel build_transfers caught any exception from the hierarchy build on each rank alone, and that rank retried with the RBF builder (or degraded to the default preconditioner) while the others went on into the transfer's assembly: the ranks met in different collectives and the run hung. The node-transfer builders are rank-local work, so their verdict is agreed right after them (_agreed_node_transfer, used by both parallel builds): if the builder raised on any rank, TransferBuildError is raised on every rank, with each failing rank's message. In parallel build_transfers retries or degrades only on that error; any other exception is one rank's, the others are inside the build's collectives, and it is raised where it happens. Serial keeps the retry for every failure. test_1108 (the barycentric builder raising on rank 1 only) passes at np 2 and 4: every rank takes the RBF rescue and the solve converges on geometric multigrid. Underworld development team with AI support from Claude Code
…rpolator (#902) The cell-by-cell Python route (37ac408) is replaced by a copy of PETSc's DMPlexComputeInterpolatorNested (v3.25.0, BSD-2-Clause), trimmed to the same-plex finite-element case, with its preallocation loop run over the same cells as its fill loop: UW_DMCreateInterpolationWithinPlex in cython/petsc_compat.h, petsc_dm_create_interpolation_within_plex in petsc_discretisation. PETSc has guarded only the preallocation by the local matrix sizes since b4ec2976050 (v3.24.0; still so in v3.25.6, v3.26.0 and main), so a rank that holds cells but owns no rows and no columns fills entries nobody preallocated. Mesh._get_coords_for_basis, function.project_to_degree and boundary_flux._trace_interior_coords call it in place of createInterpolation. It works with any PETSc build, covers degree 3 (two nodes per edge, which the Python route could not), and reproduces PETSc's matrix exactly: max |A - B| 0 and the same nonzeros on triangles, quads, tets, hexes, a degree-2 annulus and a spherical shell, continuous and discontinuous degree 1 to 3, at np 1 and 4, so the node coordinates are bit-identical to development. Every DM section is created on every rank before anything rank-dependent. The Python route created them only on ranks holding cells, so an empty rank went on to the next collective while the others waited (the review found test_0774/0781 at np 2 and test_0779/1103 at np 4 hanging); all four pass. test_1107 now also pins degree 3 on the forced partition, and its partition check is one verdict for every rank. PETSc's own projection of a degree >= 2 coordinate field in Mesh setup stays marked TODO(BUG). Underworld development team with AI support from Claude Code
f4b9128 let only a builder failure be retried or degraded in parallel, so the checks in CustomMGHierarchy.build whose verdict every rank already holds (the finest map against the operator, DOFs divisible by nodes, one component count, all on allreduced sizes) now crashed a parallel solve that development had degraded on every rank together, as serial still does (found in review). They raise CollectiveBuildError, which TransferBuildError becomes: an error raised on every rank together, so build_transfers retries or degrades on it in parallel; a rank-local exception is still raised where it happens. test_1108 gains the case: the finest-map check made to fail on every rank degrades every rank to the default preconditioner and the solve converges (np 2 and 4). Underworld development team with AI support from Claude Code
…#902) The coarse level is the shell-partitioned grid on which rank 0 holds one cell and owns none of its DOFs, under the grid's refinement. The build reads each level's node coordinates, which is where kaiju's np 32/52 runs failed. With PETSc's createInterpolation routed back in, the test fails inside CustomMGHierarchy.build with "New nonzero at (88,32) caused a malloc" at np 2. On the branch it installs multigrid and agrees with GAMG to 1e-6, at np 2 and 4. Underworld development team with AI support from Claude Code
…play agrees on any exception
Review round 2 found two gaps in the narrowed parallel catch of
build_transfers.
- provenance_transfer_parallel's two checks on allreduced counts ("a fine
node belongs to no cell", "fine node(s) lie in no cell") raised a plain
RuntimeError on every rank. In parallel only CollectiveBuildError is
retried or degraded, so the solve crashed where development degraded it.
They raise CollectiveBuildError now.
- split_tail_levels caught only ValueError, RuntimeError and
NotImplementedError before its collective verdict, so an IndexError on
one rank after the split's last collective escaped the verdict. It
catches any exception now. test_1089 is parametrised over IndexError,
which failed on the previous build ("DID NOT WARN", then a hang) and
passes at np 2 and 3.
rotated-freeslip.md says which failures degrade in parallel and which
are raised.
Underworld development team with AI support from Claude Code
…; PETSc's notice, a one-plex check, sharper tests
Review round 2.
- Mesh(degree >= 2) on a rank that holds cells but owns none of their
nodes failed during mesh setup. createCoordinateSpace projects the
coordinates through PETSc's DMCreateInterpolation between the old and
the new coordinate DM. UW_DMPlexCreateCoordinateSpaceWithinPlex installs
the space without projecting, then copies the coordinates (same dual
space, as PETSc does) or interpolates them with
UW_DMCreateInterpolationWithinPlex. Coordinates injected into a higher
coordinate dimension are not projected, as in PETSc. Periodic
coordinates keep PETSc's own projection, which is local for isoperiodic
ones. The TODO(BUG) at that call is gone.
- The copy of PETSc's interpolator carries PETSc's full BSD-2 notice:
the copyright line, the conditions and the disclaimer.
- The copy refuses, on every rank, two DMs that are not on one plex.
PETSc only dispatches there for that case. A pair on two plexes would
otherwise give a wrong matrix without an error.
- test_1107 runs every case on two partitions.
- Rank 0 holds an interior cell, owning no vertex or edge: PETSc's P2
matrices fail there.
- Rank 0 holds a boundary cell, owning its wall edge and no vertex:
PETSc's P1 matrices fail there too, since the wall edge's vertex pair
is in no other cell.
- A degree-2 mesh on each partition checks that the vertices stay on
the grid and each edge coordinate is its midpoint.
- The FMG case asserts its 4 iterations, the same at np 2, 3 and 4
(GAMG takes 12). Coarse nodes displaced by 0.3 of a cell take 5.
- It reads .array, not .data.
- On PETSc's route, run one test at a time at np 2, these fail: the
interior P2 coordinates, the projection, the degree-2 mesh and the
FMG case; and the boundary P1 coordinates and projection. Degree 3
passes there, since rank 0 owns its cell's interior node, so those
cases check the copy's node placement. On the branch all 13 pass at
np 2, 3 and 4.
- The test_1107 and test_1108 docstrings no longer claim the thread
timeout stops a hang inside PETSc. petsc4py holds the GIL there, and
the MPI supervisor's silence limit is what ends it.
Underworld development team with AI support from Claude Code
…w runs (#902) field_projection's module docstring, the XDMF guide's account of vertex projection, and the base-level coordinate repair in _DMLevelView, which said a non-FE coordinate field gives a zero matrix. UW3's copy refuses such a field instead. Underworld development team with AI support from Claude Code
…re is not retried with RBF
Review round 3.
- The split replay catches any exception before its verdict. A rank that
raised before one of the split's own allgathers then entered the verdict's
allgather on COMM_WORLD, and paired with the others' allgather inside the
split. It took their data as the failure reason ("(None, 76)" for an
IndexError) and left with a different hierarchy. Verdicts now go through
uw.mpi.verdict_comm, a Dup of COMM_WORLD used for nothing else, so they
meet only verdicts. The raise before a collective still hangs, at a known
place: the raising rank in the verdict, the others in the split. That part
is #874. The two comments that said it hangs "inside the split" now say
where.
- A CollectiveBuildError that is not a builder failure (the provenance
checks, the operator sizes) is no longer retried with the RBF builder.
The provenance transfer uses no builder, so the retry repeated the same
failure after a full rebuild, and recorded a builder fallback that never
happened. CollectiveBuildError carries builder_failure, set by the agreed
builder verdict only. The degrade record names only the builders tried.
- test_1089 gains the provenance degrade case. One fine node on rank 1 is
made to belong to no cell, and every rank degrades to GAMG with one
provenance call and no builder fallback, to the serial slip. On the
previous build it failed with two calls ([2, 2] != [1, 1]). test_1089,
test_1107 and test_1108 pass at np 2 and 3.
Underworld development team with AI support from Claude Code
…eshes; no space, no change Review round 3. - In PETSc v3.25 only isoperiodic coordinates (DMPlexGetIsoperiodicFaceSF) are projected differently: locally, with no matrix. maxCell/L periodicity projects its non-localized coordinates through DMCreateInterpolation and MatMult, the step UW's copy replaces. Such meshes were sent to PETSc's route, which still fails on a rank owning no nodes. Now only the isoperiodic case keeps it. - For a cell type PETSc builds no Lagrange space for (tensor prisms), the coordinate DM is not replaced, and the copy called PetscFEGetDualSpace on the old PetscContainer field. Now an unchanged coordinate DM leaves the coordinates alone, as PETSc does. No UW constructor builds such cells. Underworld development team with AI support from Claude Code
…fix/custom-mg-many-ranks Clean merge; 99 commits behind before it. Underworld development team with AI support from Claude Code
Pre-PR adversarial reviewThree rounds by independent reviewers, each followed by fixes. The records follow as written at the time. After round 3 the branch merged development (09c7f48), see the end. Adversarial review, round 1 (bugfix/custom-mg-many-ranks at f4b9128)Reviewed by a separate agent against the checklist in Findings and what was done
Issue verdicts
Checked and sound
Adversarial review, round 2 (bugfix/custom-mg-many-ranks at 96d39f7, plus the then-uncommitted FMG case of test_1107)Reviewed by a separate agent against Findings and what was done
Also fixed on the reviewer's #886 verdict (7b39d2c): Issue verdicts
Checked and sound
Adversarial review, round 3 (bugfix/custom-mg-many-ranks at b148115)Reviewed by a separate agent against Findings and what was done
Issue verdicts
Checked and sound
The merge with development (09c7f48)
Verdict update: #902 fixed by this branch; #886 fixed for build_transfers and the split replay (a raise before a collective is #874); the others as in round 3. Underworld development team with AI support from Claude Code |
There was a problem hiding this comment.
🟡 Changes recommended
The compatibility call uses the wrong signature for supported PETSc 3.21–3.23 versions, disabling projection and potentially breaking extension builds even for nonperiodic meshes.
1 open issue
What changed in this PR
This PR addresses custom-P multigrid failures at higher MPI rank counts by fixing interpolation preallocation and coordinating build-failure recovery across ranks.
Changes:
- Adds a PETSc-derived interpolator that handles ranks holding cells but owning none of their nodes.
- Restricts parallel retries and fallback to agreed failures, with a separate communicator for fault error verdicts.
- Adds parallel regression tests and updates interpolation and recovery documentation.
| File | Description |
|---|---|
tests/parallel/test_1108_custom_mg_builder_failure_collective_parallel.py |
Tests coordinated builder retries and fallback. |
tests/parallel/test_1107_interpolation_rank_owning_nothing_parallel.py |
Tests interpolation, coordinate setup, and multigrid on targeted partitions. |
tests/parallel/test_1089_fault_split_multigrid_parallel.py |
Extends provenance-failure and split-replay tests. |
src/underworld3/utilities/fault_mg.py |
Marks agreed failures and broadens split-replay exception handling. |
src/underworld3/utilities/fault_contact.py |
Moves error verdicts to a dedicated communicator. |
src/underworld3/utilities/custom_mg.py |
Coordinates builder failures and limits parallel recovery. |
src/underworld3/utilities/boundary_flux.py |
Uses the replacement interpolator for interior coordinates. |
src/underworld3/mpi.py |
Creates the error-verdict communicator. |
src/underworld3/function/field_projection.py |
Routes degree projection through the replacement interpolator. |
src/underworld3/discretisation/discretisation_mesh.py |
Updates coordinate-space setup and basis-coordinate interpolation. |
src/underworld3/cython/petsc_extras.pxi |
Declares the new C helpers. |
src/underworld3/cython/petsc_discretisation.pyx |
Exposes coordinate-space and interpolation wrappers. |
src/underworld3/cython/petsc_compat.h |
Implements interpolation and coordinate-space compatibility helpers. |
docs/developer/subsystems/rotated-freeslip.md |
Documents parallel multigrid recovery constraints. |
docs/beginner/create_xdmf.md |
Clarifies how vertex interpolation is assembled. |
Review effort: Balanced
| PetscFunctionBeginUser; | ||
| PetscCall(DMPlexGetIsoperiodicFaceSF(dm, &nfaceSF, NULL)); | ||
| if (nfaceSF) { | ||
| PetscCall(DMPlexCreateCoordinateSpace(dm, degree, PETSC_FALSE, PETSC_TRUE)); |
…ned-node-936 #938's branch, which this one was stacked on, is now in development; clean merge. Underworld development team with AI support from Claude Code

Custom-P multigrid failed at np 32 and 52 on kaiju (#902). Each level's node coordinates come from
DMCreateInterpolationbetween two DMs on one plex, which is PETSc'sDMPlexComputeInterpolatorNested.main.MatAssemblyEnd, and the other ranks wait in the assembly.A failed build was then retried on the failing rank alone, so the ranks met different collectives and deadlocked (#886).
The change
UW_DMCreateInterpolationWithinPlex(petsc_compat.h) is UW3's copy of the nested interpolator for the same-plex finite-element case.plexfem.cline by line, without the guard. It adds an FE-only check and a one-plex check, and it is collective. PETSc's BSD-2 notice is in the header.Mesh._get_coords_for_basis(every custom-P level),project_to_degreeandboundary_flux._trace_interior_coords.UW_DMPlexCreateCoordinateSpaceWithinPlexinstalls a mesh's degree >= 2 coordinate space without PETSc's projection and fills it through the copy.Mesh(degree=2)failed during setup on such a rank. Isoperiodic coordinates keep PETSc's route.custom_mg.build_transfers: a rank-local exception decides a collective retry #886.
CollectiveBuildErroris a build failure whose verdict every rank holds:In parallel only that error is retried or degraded; any other exception is raised. The fault split's replay agrees on any exception, and verdicts run on a communicator of their own (
uw.mpi.verdict_comm).Measured on kaiju (1 Nov): the production faulted annulus
Five steps from TAf's checkpoint 20, on one node, with a frictionless split fault, custom-P FMG and rotated walls.
Tests
tests/parallel/test_1107(level 2, tier B, np >= 2). It uses two shell partitions: rank 0 on an interior cell (owning no vertex or edge), and on a boundary cell (owning only its wall edge). It covers node coordinates at degree 1 to 3, projection between degrees, a degree-2 mesh, and a custom-P FMG solve over the interior partition (4 iterations). With PETSc's route put back, these fail at np 2.tests/parallel/test_1108covers a builder failing on one rank and a check failing on every rank. Both hang on development.tests/parallel/test_1089gains the split replay over an IndexError, and a provenance check'sCollectiveBuildError.createInterpolation: max |A - B| = 0, with the same nonzeros, in 114 of 114 cases at np 1 and 2.The merge with development
09c7f48 merges development through the #932-#934 FAC stack (60dbf0e), with no conflict.
Closes #902. Closes #886. Left open in this territory:
Mesh.remesh.Verdicts are in the review comment.
Underworld development team with AI support from Claude Code