From 63b76d0caed7cf794383435a19da9f619bcc579e Mon Sep 17 00:00:00 2001 From: lmoresi Date: Wed, 19 Aug 2026 21:09:28 +1000 Subject: [PATCH] Quick-fix batch: pressure nullspace, mesh cache filename, test number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small independent fixes, each closing an issue that did not warrant its own change. #577. CylindricalStokes(boundary="zero") returned before the line that removes the pressure nullspace, so the zero-slip case ran a singular saddle and could return a quiet, wrong pressure. The nullspace belongs to the DOMAIN rather than to the wall type — both cases here are an enclosed annulus — so it is now set once after the branch. Verified by reading petsc_use_pressure_nullspace back off the solver for both boundary values; it was None for "zero" before and is True for both now. #618. The StructuredQuadBox mesh cache filename omitted elementRes, so every box on the same coordinates shared one path whatever its resolution and two processes at different resolutions raced on it. Resolution is now part of the name, as the annulus already does with its cell size. Verified: 4x4 and 8x8 now write uw_structuredQuadBox_..._res(4, 4).msh and _res(8, 8).msh. #600. test_1029_analytic_zhong2008.py collided with test_1029_analytic_faulted_medium.py — the two PRs were written against different bases and neither could see the other's number. Renamed to 1030; the faulted medium keeps 1029 because it merged first. The reference in docs/developer/subsystems/analytic-solutions.md is updated and no stale mention remains. Closes #577, #600, #618. Full ./uw test 1556 passed; the analytic contract, conformance and Zhong files 132 passed. Underworld development team with AI support from Claude Code --- .../developer/subsystems/analytic-solutions.md | 2 +- src/underworld3/analytic/kramer.py | 18 +++++++++--------- src/underworld3/meshing/cartesian.py | 11 ++++++++++- ...2008.py => test_1030_analytic_zhong2008.py} | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) rename tests/{test_1029_analytic_zhong2008.py => test_1030_analytic_zhong2008.py} (99%) 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