diff --git a/src/underworld3/discretisation/discretisation_mesh.py b/src/underworld3/discretisation/discretisation_mesh.py index ccfca8c36..2d94eee9f 100644 --- a/src/underworld3/discretisation/discretisation_mesh.py +++ b/src/underworld3/discretisation/discretisation_mesh.py @@ -1933,8 +1933,9 @@ def _surviving_labels(self, subdm, candidates): Returns ------- dict - The subset of ``candidates`` present on ``subdm`` with a - non-empty stratum, preserving candidate order. + Candidates with a non-empty stratum anywhere on the DM communicator, + preserving candidate order. Empty ranks retain the same names and + empty labels, so solver boundary registration is rank independent. """ present = set() for i in range(subdm.getNumLabels()): @@ -1961,6 +1962,14 @@ def _surviving_labels(self, subdm, candidates): lsis = lab.getStratumIS(value) if lsis is not None and lsis.getSize() > 0: present.add(name) + # A boundary may be absent from this rank while present on its peers. + # Its enum and label must nevertheless exist everywhere: natural BC + # registration and solver setup use the same named boundaries on all ranks. + present = set().union(*subdm.comm.tompi4py().allgather(present)) + local_names = {subdm.getLabelName(i) for i in range(subdm.getNumLabels())} + for name in candidates: + if name in present and name not in local_names: + subdm.createLabel(name) return {name: v for name, v in candidates.items() if name in present} def extract_region(self, label_name, label_value=None): diff --git a/tests/parallel/test_1085_submesh_boundary_names.py b/tests/parallel/test_1085_submesh_boundary_names.py new file mode 100644 index 000000000..a6795158d --- /dev/null +++ b/tests/parallel/test_1085_submesh_boundary_names.py @@ -0,0 +1,31 @@ +"""Submesh boundary names must survive on ranks owning no boundary points.""" + +from petsc4py import PETSc +import underworld3 as uw + + +def test_surviving_labels_use_global_presence_and_create_empty_local_labels(): + dm = PETSc.DMPlex().createBoxMesh([2, 2], simplex=False, comm=uw.mpi.comm) + if uw.mpi.rank == 0: + dm.createLabel('OnlyOnZero') + dm.setLabelValue('OnlyOnZero', dm.getChart()[0], 42) + surviving = uw.discretisation.Mesh._surviving_labels( + None, dm, {'OnlyOnZero': 42, 'AbsentEverywhere': 99} + ) + assert surviving == {'OnlyOnZero': 42} + assert dm.getLabel('OnlyOnZero') is not None + assert 'AbsentEverywhere' not in [dm.getLabelName(i) for i in range(dm.getNumLabels())] + if uw.mpi.rank > 0: + values = dm.getLabel('OnlyOnZero').getValueIS() + assert values is None or len(values.getIndices()) == 0 + dm.destroy() + + +def test_extracted_region_boundary_enums_are_identical_on_every_rank(): + mesh = uw.meshing.BoxInternalBoundary( + elementRes=(12,12), zelementRes=(10,2), zintCoord=.85, + minCoords=(0.,0.), maxCoords=(1.,1.), simplex=True, qdegree=2) + child = mesh.extract_region('Outer') + names = [(b.name,b.value) for b in child.boundaries] + assert all(other == names for other in uw.mpi.comm.allgather(names)) + assert 'Top' in dict(names)