What
uw.analytic.CylindricalStokes with boundary="zero" configures a solver without setting petsc_use_pressure_nullspace. Every other enclosed solution in the suite sets it.
src/underworld3/analytic/kramer.py, CylindricalStokes.apply_boundary_conditions:
if self.boundary == "zero":
prescribed_velocity(solver, self.boundaries, (0.0, 0.0))
return # <- returns before the nullspace line
free_slip(solver, self.boundaries)
solver.petsc_use_pressure_nullspace = True
Why it matters
An annulus held at zero velocity on both arcs is an enclosed domain with velocity prescribed everywhere on its boundary. Its pressure is determined only up to a constant, so the saddle-point operator is singular in the pressure direction. A direct solve on that system does not necessarily fail — it can return a quiet, wrong answer with an arbitrary pressure offset, which is exactly the failure mode an exact solution exists to expose.
The free-slip branch of the same class is unaffected: it sets the nullspace.
How it hid
The nullspace was set once at the end of the method, after the zero branch's early return. Under the (now removed) FreeSlipWalls/FixedWalls mixins the setting was buried inside a wall type, so the omission was not visible where the conditions are written. With each solution stating the nullspace for itself it is one line on the page.
Found while making the mixin -> composition refactor behaviour-preserving; preserved verbatim there under a TODO(BUG) because that change is byte-equivalent by contract.
Fix
Set the nullspace in both branches. Needs its own regression test — the natural oracle is the pressure error against the exact Kramer solution for boundary="zero", which should be checked against the solution's own gauge rather than assumed zero-mean.
Requires the assess package (the case is skipped without it), so the test must carry the same skip as the rest of the Kramer coverage.
What
uw.analytic.CylindricalStokeswithboundary="zero"configures a solver without settingpetsc_use_pressure_nullspace. Every other enclosed solution in the suite sets it.src/underworld3/analytic/kramer.py,CylindricalStokes.apply_boundary_conditions:Why it matters
An annulus held at zero velocity on both arcs is an enclosed domain with velocity prescribed everywhere on its boundary. Its pressure is determined only up to a constant, so the saddle-point operator is singular in the pressure direction. A direct solve on that system does not necessarily fail — it can return a quiet, wrong answer with an arbitrary pressure offset, which is exactly the failure mode an exact solution exists to expose.
The free-slip branch of the same class is unaffected: it sets the nullspace.
How it hid
The nullspace was set once at the end of the method, after the
zerobranch's earlyreturn. Under the (now removed)FreeSlipWalls/FixedWallsmixins the setting was buried inside a wall type, so the omission was not visible where the conditions are written. With each solution stating the nullspace for itself it is one line on the page.Found while making the mixin -> composition refactor behaviour-preserving; preserved verbatim there under a
TODO(BUG)because that change is byte-equivalent by contract.Fix
Set the nullspace in both branches. Needs its own regression test — the natural oracle is the pressure error against the exact Kramer solution for
boundary="zero", which should be checked against the solution's own gauge rather than assumed zero-mean.Requires the
assesspackage (the case is skipped without it), so the test must carry the same skip as the rest of the Kramer coverage.