The grad-div penalty corrupts the recovery of pointwise boundary traction from reaction loads, while leaving the facet-integrated value correct. This is the one thing blocking Stokes.DEFAULT_PENALTY from being turned on (PR #632, where everything else is in place).
Measured, test_1018_rotated_freeslip::test_rotated_freeslip_spherical3d_reaction_topography, spherical shell, rotated free-slip, penalty = 10:
| quantity |
expected |
measured |
|
surface (facet-integrated) |
0.41920 |
0.4192 |
pass |
surface_vertices (pointwise) |
0.41920 |
0.30211 |
-28% |
cmb (facet-integrated) |
0.77060 |
0.7706 |
pass |
At penalty = 0 all of them pass. That test's own docstring is "3D reaction loads must be divided by boundary mass to recover pointwise stress", which is exactly the step that stops working.
Not a p_mech bookkeeping error. The first hypothesis was that sigma_nn was being built from the Lagrange multiplier without the documented p_mech = p - lambda*mu*(div u) correction. It is not: boundary_normal_traction recovers sigma_nn from the Cartesian nodal reaction r_c = A.u - b, which already carries the penalty because the penalty is in the operator. Moving the term into the total stress (PR #632) left the measured value byte-identical at 0.30210742214244796.
Working hypothesis. lambda*mu*(div u) is non-zero cell-by-cell for P2-P0 — the discrete divergence is only weakly zero, against the pressure space — so it averages out over a facet integral but not at a vertex. The de-smearing from reaction loads to pointwise stress divides by a boundary mass that does not account for the augmented contribution.
Why it matters. Dynamic topography is the main product of this machinery, and a 28% error in it is not something to ship silently behind a default. It is also worth knowing independently of the penalty: if the pointwise recovery is sensitive to a term the integral is not, that is a property of the recovery, not of the penalty.
Suggested first step. Compare surface_vertices against surface_midpoints as lambda is swept 0, 1, 10, 100 — the test already computes both, and whether they diverge together or separately should distinguish a mass-weighting error from a sampling one.
Context: the penalty is measured to be a strong lever on the Schur count under FMG (Schur iterations per application 59 -> 18, total velocity work 546 -> 270, 21% faster) and the accuracy cost elsewhere is a consistent perturbation — same convergence rate, gap shrinking 1.102 -> 1.087 -> 1.066 over three refinements. So this is the last thing standing between that and a default.
Underworld development team with AI support from Claude Code
The grad-div penalty corrupts the recovery of pointwise boundary traction from reaction loads, while leaving the facet-integrated value correct. This is the one thing blocking
Stokes.DEFAULT_PENALTYfrom being turned on (PR #632, where everything else is in place).Measured,
test_1018_rotated_freeslip::test_rotated_freeslip_spherical3d_reaction_topography, spherical shell, rotated free-slip,penalty = 10:surface(facet-integrated)surface_vertices(pointwise)cmb(facet-integrated)At
penalty = 0all of them pass. That test's own docstring is "3D reaction loads must be divided by boundary mass to recover pointwise stress", which is exactly the step that stops working.Not a
p_mechbookkeeping error. The first hypothesis was thatsigma_nnwas being built from the Lagrange multiplier without the documentedp_mech = p - lambda*mu*(div u)correction. It is not:boundary_normal_tractionrecoverssigma_nnfrom the Cartesian nodal reactionr_c = A.u - b, which already carries the penalty because the penalty is in the operator. Moving the term into the total stress (PR #632) left the measured value byte-identical at 0.30210742214244796.Working hypothesis.
lambda*mu*(div u)is non-zero cell-by-cell for P2-P0 — the discrete divergence is only weakly zero, against the pressure space — so it averages out over a facet integral but not at a vertex. The de-smearing from reaction loads to pointwise stress divides by a boundary mass that does not account for the augmented contribution.Why it matters. Dynamic topography is the main product of this machinery, and a 28% error in it is not something to ship silently behind a default. It is also worth knowing independently of the penalty: if the pointwise recovery is sensitive to a term the integral is not, that is a property of the recovery, not of the penalty.
Suggested first step. Compare
surface_verticesagainstsurface_midpointsaslambdais swept 0, 1, 10, 100 — the test already computes both, and whether they diverge together or separately should distinguish a mass-weighting error from a sampling one.Context: the penalty is measured to be a strong lever on the Schur count under FMG (Schur iterations per application 59 -> 18, total velocity work 546 -> 270, 21% faster) and the accuracy cost elsewhere is a consistent perturbation — same convergence rate, gap shrinking 1.102 -> 1.087 -> 1.066 over three refinements. So this is the last thing standing between that and a default.
Underworld development team with AI support from Claude Code