Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/underworld3/cython/petsc_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,26 @@ PetscErrorCode UW_DMPlexComputeBdIntegral(DM dm, Vec X,
{
PetscSection section;
PetscInt Nf;
PetscInt localCount = 0;

PetscFunctionBeginUser;

PetscCall(DMGetLocalSection(dm, &section));
PetscCall(PetscSectionGetNumFields(section, &Nf));

// If label is NULL (boundary not present on this rank), contribute 0
// but still participate in the MPI Allreduce to avoid hangs.
if (!label) {
// If the label is NULL or the requested boundary has no local entities on
// this rank, contribute 0 but still participate in the MPI Allreduce to
// avoid hangs. Parallel DMPlex boundary assembly can deadlock if some
// ranks enter with an empty local stratum.
if (label) {
for (PetscInt i = 0; i < numVals; ++i) {
PetscInt stratumSize = 0;
PetscCall(DMLabelGetStratumSize(label, vals[i], &stratumSize));
localCount += stratumSize;
}
}

if (!label || localCount == 0) {
PetscScalar zero = 0.0;
PetscCallMPI(MPIU_Allreduce(&zero, result, 1, MPIU_SCALAR, MPIU_SUM,
PetscObjectComm((PetscObject)dm)));
Expand Down
54 changes: 54 additions & 0 deletions tests/parallel/test_0765_internal_boundary_integral_mpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ghost/internal facet handling in PETSc boundary assembly paths.
"""

import math
import numpy as np
import pytest
import underworld3 as uw
Expand Down Expand Up @@ -59,3 +60,56 @@ def test_outer_boundary_circumference_parallel():

rel_err = abs(value - expected) / expected
assert rel_err < 2.0e-2, f"Outer circumference rel_err={rel_err:.3e}, value={value}, expected={expected}"


@pytest.mark.mpi(min_size=2)
def test_deformed_spherical_shell_boundary_area_parallel():
"""
Boundary integrals must remain valid after coordinate deformation in MPI.

This specifically guards the BdIntegral path when some ranks have no local
entities for the requested boundary after mesh deformation.
"""

mesh = uw.meshing.SphericalShell(
radiusOuter=1.0,
radiusInner=0.5,
cellSize=1.0 / 4.0,
degree=1,
qdegree=2,
)
uw.discretisation.MeshVariable("T_spherical_deformed_bd", mesh, 1, degree=1, continuous=True)

coords = np.asarray(mesh.X.coords, dtype=np.float64).copy()
radii = np.linalg.norm(coords, axis=1)
thickness = 0.5
t = (radii - 0.5) / thickness
a = math.log(2.0)
mapped = (np.exp(a * t) - 1.0) / (math.exp(a) - 1.0)
new_radii = 0.5 + thickness * mapped
mesh._deform_mesh(coords * (new_radii / radii)[:, None])

lower = float(uw.maths.BdIntegral(mesh=mesh, fn=1.0, boundary="Lower").evaluate())
upper = float(uw.maths.BdIntegral(mesh=mesh, fn=1.0, boundary="Upper").evaluate())

expected_lower = 4.0 * math.pi * 0.5**2
expected_upper = 4.0 * math.pi * 1.0**2

rel_err_lower = abs(lower - expected_lower) / expected_lower
rel_err_upper = abs(upper - expected_upper) / expected_upper

assert rel_err_lower < 5.0e-2, (
f"Deformed lower area rel_err={rel_err_lower:.3e}, value={lower}, expected={expected_lower}"
)
assert rel_err_upper < 5.0e-2, (
f"Deformed upper area rel_err={rel_err_upper:.3e}, value={upper}, expected={expected_upper}"
)

gathered_lower = uw.mpi.comm.allgather(lower)
gathered_upper = uw.mpi.comm.allgather(upper)
assert max(gathered_lower) - min(gathered_lower) < 1.0e-12, (
f"Rank mismatch in lower-boundary integral values: {gathered_lower}"
)
assert max(gathered_upper) - min(gathered_upper) < 1.0e-12, (
f"Rank mismatch in upper-boundary integral values: {gathered_upper}"
)
Loading