diff --git a/docs/developer/subsystems/analytic-solutions.md b/docs/developer/subsystems/analytic-solutions.md index 1cff3d8e..8ec412ff 100644 --- a/docs/developer/subsystems/analytic-solutions.md +++ b/docs/developer/subsystems/analytic-solutions.md @@ -990,7 +990,7 @@ are under `.self_gravity`. Velocity is unchanged by that postprocessing. ### Validation -`tests/test_1029_analytic_zhong2008.py` checks every parenthesized analytical +`tests/test_1030_analytic_zhong2008.py` checks every parenthesized analytical entry in Zhong et al. (2008) Tables 2 and 3: three load depths, four harmonic degrees, isoviscous and `10^4`-lid cases, and eight response quantities per case. The 192 comparisons agree within the precision printed in the paper. The diff --git a/src/underworld3/analytic/kramer.py b/src/underworld3/analytic/kramer.py index 13330513..b57942b6 100644 --- a/src/underworld3/analytic/kramer.py +++ b/src/underworld3/analytic/kramer.py @@ -266,16 +266,16 @@ def apply_boundary_conditions(self, solver): to use" section of ``docs/developer/subsystems/rotated-freeslip.md``. """ + # The pressure nullspace belongs to the DOMAIN, not to the wall type. + # Both cases here are an enclosed annulus — velocity prescribed + # everywhere on both arcs, or wall-normal flow zero on both — so in + # either the pressure is determined only up to a constant and the + # nullspace has to be removed. It used to be set after an early + # `return` that the zero-slip branch took, so that case ran a singular + # saddle and could return a quiet, wrong answer (#577). if self.boundary == "zero": - # TODO(BUG): issue #577 — the zero-slip case leaves the pressure - # nullspace in place, where every other enclosed case in the suite - # removes it. An annulus with both arcs held at zero velocity is - # enclosed, so its pressure is determined only up to a constant and - # a direct solve on the singular saddle can return a quiet, wrong - # answer. Preserved here because this refactor is - # behaviour-preserving by contract; fix it under its own test. prescribed_velocity(solver, self.boundaries, (0.0, 0.0)) - return + else: + free_slip(solver, self.boundaries) - free_slip(solver, self.boundaries) solver.petsc_use_pressure_nullspace = True diff --git a/src/underworld3/meshing/cartesian.py b/src/underworld3/meshing/cartesian.py index 3e4d3ca2..1dab453b 100644 --- a/src/underworld3/meshing/cartesian.py +++ b/src/underworld3/meshing/cartesian.py @@ -1427,7 +1427,16 @@ class boundary_normals_3D(Enum): if uw.mpi.rank == 0: os.makedirs(mesh_file_dir(), exist_ok=True) - uw_filename = f"{mesh_file_dir()}/uw_structuredQuadBox_minC{minCoords}_maxC{maxCoords}.msh" + # `elementRes` is part of the name because it is part of the mesh. + # Without it every StructuredQuadBox on the same box — 16x16, 32x32, + # a resolution sweep — writes and reads ONE path, so two processes at + # different resolutions race on it and one reads the other's mesh + # (#618). The annulus already carries its cell size for this reason: + # `uw_annulus_ro1.0_ri0.5_csize0.05.msh`. + uw_filename = ( + f"{mesh_file_dir()}/uw_structuredQuadBox_minC{minCoords}" + f"_maxC{maxCoords}_res{tuple(elementRes)}.msh" + ) else: uw_filename = filename diff --git a/tests/test_1029_analytic_zhong2008.py b/tests/test_1030_analytic_zhong2008.py similarity index 99% rename from tests/test_1029_analytic_zhong2008.py rename to tests/test_1030_analytic_zhong2008.py index 859f2b8a..131b2d3d 100644 --- a/tests/test_1029_analytic_zhong2008.py +++ b/tests/test_1030_analytic_zhong2008.py @@ -5,7 +5,7 @@ published propagator row so a sign, stress scaling, layer-order, or self-gravity regression cannot hide behind agreement with one selected case. -Run: pixi run python -m pytest tests/test_1029_analytic_zhong2008.py -v +Run: pixi run python -m pytest tests/test_1030_analytic_zhong2008.py -v """ import numpy as np