Hard baselines instead of drift detectors, and pin cell_size so a redefinition fails on the day - #743
Conversation
…efinition fails on the day Maintainer ruling (2026-09-14): a test asserts against a hard baseline, or it is not a test. A test loose enough to absorb a change in a default does not fail when the default changes — it drifts inside its own margin and breaks later, somewhere else, for reasons that are hard to trace back. #734 is the case study. #692 redefined `mesh.cell_size()`; nothing failed; the Nitsche penalty `gamma*mu/h` moved 43%; the Zhong spherical-shell benchmark slid from 0.2% to 2.4% — still inside its 5% tolerance — and only broke months later on one platform's triangulation, three layers from the cause. **The test that would have caught it on the day.** `cell_size` had two tests and between them they missed the case that changed: one pins an exact value (sqrt(2)/8) on a regular QUAD box, where `cell_size` and `get_min_radius` happen to agree; the other covers simplices but checks the implementation against an independent reading of the SAME DEFINITION, so it stays true if the definition moves on both sides. Added: - `cell_size` on congruent right-isosceles cells is exactly 2h/3, to 1e-12. A redefinition fails immediately, at the quantity that changed. - The two measures agree on TENSOR cells (ratio 1) and differ on uniform SIMPLICES by exactly sqrt(2) — `cell_size` 2h/3 against `get_min_radius` sqrt(2)h/3. The Nitsche penalty scales with that ratio, so pinning it means neither measure can move silently. Negative-controlled against the real 1.745x shift: both fail, and both name the quantity rather than a downstream symptom. **The docstring was false.** `add_nitsche_bc` said the two measures "coincide on a uniform mesh". True on a regular quad box — presumably where it was checked — and false on a uniform simplex mesh, which is what every free-slip and fault model uses. Corrected in both the implementation comment and the user-facing parameter docs. **Four method-comparison assertions removed rather than relocated.** #729 moved them to tier C; that was the wrong fix. In every case the hard baseline was already present and the ratio was discarding it: - `test_1070` computed an absolute error against the PRESCRIBED datum and then asserted `errors["strong"] < 0.5 * errors["penalty"]`. Now asserts the error directly. The ratio also hid which side moved — strong went 1.06e-2 to 1.73e-2 between platforms while penalty barely changed. - `test_0066` and `test_0773` had their baselines asserted in sibling tests already, so the comparisons added nothing actionable. - `test_1060` asserted that the weak constraints must STAY inaccurate (`leak > 1e-5`) — an assertion that fails when the code improves. Its exact half, an essential BC holding v.n to machine precision, is a hard baseline and is now a tier B contract; the weak-path leaks are printed, not gated. Charter §8 leads with the baseline requirement and says why: it must be tight enough to fail the moment a default changes. TESTING-RELIABILITY-SYSTEM.md records that C1 is rare by design — reach for it only after showing no baseline exists. 55 passed, 1 skipped across the five files. Underworld development team with AI support from Claude Code
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved issues affect MPI safety, CI gating, documentation consistency, and baseline tightness.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR strengthens regression coverage for mesh-size semantics, replaces comparison-based checks with direct baselines, and updates related documentation.
Changes:
- Adds closed-form
cell_size()and radius-ratio tests. - Replaces method comparisons with direct baseline assertions.
- Updates Nitsche documentation and testing guidance.
File summaries
| File | Reviewed change and outstanding notes |
|---|---|
tests/test_1070_free_surface_plume.py |
Adds a direct baseline. CI execution, metric wording, baseline tightness, and unused penalty work need correction. |
tests/test_1060_nitsche_freeslip.py |
Adds an essential-BC contract; the test is not currently included in the CI merge gate. |
tests/test_0773_surface_smoother.py |
Removes smoother comparison characterization. |
tests/test_0066_integration_point_slcn.py |
Removes the method comparison; the module description still claims the unverified comparison. |
tests/test_0010_cell_size_geometry.py |
Adds closed-form geometry tests; MPI empty-rank reductions, ratio wording, and public docstrings need correction. |
src/underworld3/cython/petsc_generic_snes_solvers.pyx |
Corrects Nitsche documentation; the simplex ratio needs qualification and other public copies need updating. |
docs/developer/UW3_STYLE_CHARTER.md |
Strengthens baseline guidance, but conflicts with the retained Tier C policy. |
docs/developer/TESTING-RELIABILITY-SYSTEM.md |
Clarifies Tier C characterization-test usage. |
Review details
Suppressed comments (9)
docs/developer/UW3_STYLE_CHARTER.md:121
- This makes every non-baseline assertion a benchmark rather than a test, but the unchanged Tier C section below still defines C1 characterisation tests—including method comparisons—as suite tests, and
TESTING-RELIABILITY-SYSTEM.md:75-100preserves that fallback. Please reconcile the absolute rule with the supported C1 policy (or remove the C1 definition), otherwise contributors have contradictory guidance.
- **Every test asserts against a HARD BASELINE.** An analytic solution, a published
value, a closed-form geometric quantity, a conservation identity, an exactness
property. If a failure cannot name what is broken, it is not a test — it is a
drift detector, and it belongs in a benchmark rather than the suite.
src/underworld3/cython/petsc_generic_snes_solvers.pyx:6762
- This implementation comment repeats the same overgeneralisation: the
sqrt(2)value is a 2-D right-isosceles example, not a universal property of uniform simplex meshes. Keep the exact fixture result, but state that other simplex geometries can have a different ratio.
# The two coincide on TENSOR cells only. On a uniform SIMPLEX mesh --
# which is what the free-slip and fault models are built on -- they
# differ by exactly sqrt(2): on congruent right-isosceles cells of legs
# h, cell_size is 2h/3 and get_min_radius is sqrt(2)h/3. The penalty
# gamma*mu/h moves with that, so the two settings are NOT interchangeable
src/underworld3/cython/petsc_generic_snes_solvers.pyx:6659
- The correction here is not propagated to the other public copies:
src/underworld3/discretisation/discretisation_mesh.py:3253-3255still says a uniform mesh reduces toget_min_radius, anddocs/docstrings/inventory.json:4272still says the two measures coincide on a uniform mesh. Both are false for the uniform simplex case documented here, so regenerate/update those user-facing docstrings with this change.
The two coincide on **tensor** cells only. On a uniform **simplex**
mesh they differ by exactly :math:`\sqrt{2}` — for congruent
right-isosceles cells of legs :math:`h`, :meth:`Mesh.cell_size` is
:math:`2h/3` while :meth:`Mesh.get_min_radius` is
:math:`\sqrt{2}h/3` — so the penalty :math:`\gamma\mu/h` differs
between the two settings on the simplex meshes the free-slip and
fault models use. See ``tests/test_0010_cell_size_geometry.py``.
tests/test_0010_cell_size_geometry.py:120
quad_localis rank-local, so.min()raises on a starved MPI rank before the other ranks can complete the collective. The module advertises MPI use, andget_min_radius()explicitly handles empty partitions; reduce a local minimum with aninfidentity across ranks instead.
quad_ratio = float(quad_local.min()) / quad.get_min_radius()
tests/test_0010_cell_size_geometry.py:133
simplex_localis rank-local, so.min()raises on a starved MPI rank before the other ranks can complete the collective. The module advertises MPI use, andget_min_radius()explicitly handles empty partitions; reduce a local minimum with aninfidentity across ranks instead.
ratio = float(simplex_local.min()) / simplex.get_min_radius()
tests/test_0010_cell_size_geometry.py:103
- This sentence is self-contradictory: the measures do not coincide by
sqrt(2); on simplices their ratio issqrt(2), while tensor cells have ratio 1. Please state the two cases explicitly so the test documents the contract it asserts.
"""The two mesh-size measures coincide on TENSOR cells only, by sqrt(2).
tests/test_0010_cell_size_geometry.py:104
- This new regression establishes that uniform simplex
cell_size()andget_min_radius()differ, but the publicMesh.cell_size()docstring still says the opposite ("On a uniform mesh ... reduces to global get_min_radius",src/underworld3/discretisation/discretisation_mesh.py:3253-3255). Please update that public docstring too; otherwise the API documentation contradicts the contract pinned here.
def test_cell_size_and_min_radius_agree_on_tensor_cells_but_not_simplices():
"""The two mesh-size measures coincide on TENSOR cells only, by sqrt(2).
tests/test_0066_integration_point_slcn.py:124
- Removing the only nodal-vs-integration-point comparison leaves the module docstring's claim that the integration-point scheme is “at least as accurate as” nodal and preserves the peak unverified. Please update that opening description to list only the exact-departure and absolute-accuracy contracts that remain.
tests/test_1070_free_surface_plume.py:330 - After removing the rival-method assertion,
errors['penalty']is no longer consumed by any assertion; the fixture still runs four penalty solves/advances and this print only reports it. That doubles this level-2 test's expensive work without gating any penalty behavior. Either remove the penalty case/diagnostics or move the comparison to an explicit tier-C test.
print(f"datum error: strong={errors['strong']:.2e} penalty={errors['penalty']:.2e}")
- Files reviewed: 8/8 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @pytest.mark.tier_b | ||
| def test_essential_bc_holds_the_wall_normal_velocity_exactly(self, solutions): |
| @pytest.mark.level_2 | ||
| @pytest.mark.tier_c | ||
| def test_freesurface_strong_constraint_against_penalty(constraint_measurements): | ||
| r"""Characterisation: the strong constraint tracks the prescribed rate more | ||
| closely than the weak penalty. | ||
|
|
||
| This compares two METHODS, so it can fail because the penalty path improved — | ||
| which would be good news. Tier C: a failure demands an explanation, not a | ||
| revert. It is NOT the justification for ``consistent_constraint="strong"``; | ||
| the contract that justifies it is the no-net-flux test above. | ||
|
|
||
| Measured 2026-09-12 on the annulus fixture, 4 solve/advance steps: datum | ||
| error strong 1.06e-2 against penalty 3.01e-2. The 0.5 factor characterises | ||
| this fixture and is not a specification. | ||
| @pytest.mark.tier_b | ||
| def test_freesurface_strong_constraint_tracks_the_prescribed_rate(constraint_measurements): |
| f"penalty {errors['penalty']:.2e}. If the penalty path improved, " | ||
| "explain it and re-characterise; do not revert to make this pass.") | ||
| print(f"datum error: strong={errors['strong']:.2e} penalty={errors['penalty']:.2e}") | ||
| assert errors["strong"] < 5.0e-2, ( |
| The quantity here is already an absolute error against a known answer — the | ||
| datum `fs._un_target` that the constraint is asked to reproduce — so it is | ||
| asserted directly. |
Maintainer ruling (2026-09-14): a test asserts against a hard baseline, or it
is not a test. A test loose enough to absorb a change in a default does not
fail when the default changes — it drifts inside its own margin and breaks later,
somewhere else, for reasons that are hard to trace back.
The case study
#734. #692 redefined
mesh.cell_size(); nothing failed; the Nitsche penaltyγμ/hmoved 43%; the Zhong spherical-shell benchmark slid from 0.2% to 2.4%— still inside its 5% tolerance — and only broke months later on one platform's
triangulation, three layers from the cause.
Why the existing tests missed it
cell_sizehad two tests, and between them they missed the case that changed:test_regular_square_cell_size_keeps_global_radiustest_cell_size_matches_own_vertices_and_tracks_deform#692 changed the simplex path.
The test that would have caught it on the day
Plus the relationship, which is itself exact:
cell_size()get_min_radius()The Nitsche penalty scales with that ratio, so pinning it means neither measure
can move silently.
Negative-controlled against the real 1.745× shift — both new tests fail, and
both name the quantity rather than a downstream symptom:
The docstring was false
add_nitsche_bcsaid the two measures "coincide on a uniform mesh". True on aregular quad box — presumably where it was checked — and false on a uniform
simplex mesh, which is what every free-slip and fault model uses. Corrected
in the implementation comment and the user-facing parameter docs.
Four comparisons removed rather than relocated
#729 moved them to tier C. That was the wrong fix — in every case the hard
baseline was already present and the ratio was discarding it:
test_1070computed an absolute error against the prescribed datum, thenasserted
errors["strong"] < 0.5 * errors["penalty"]. Now asserts the errordirectly. The ratio also hid which side moved: strong went 1.06e-2 → 1.73e-2
between platforms while penalty barely changed.
test_0066,test_0773— baselines already asserted in sibling tests,so the comparisons added nothing actionable.
test_1060asserted the weak constraints must stay inaccurate(
leak > 1e-5) — it would fail if Nitsche improved. Its exact half (anessential BC holds
v·nto machine precision) is a hard baseline and is now atier B contract; the weak-path leaks are printed, not gated.
Docs
Charter §8 leads with the baseline requirement and says why it must be tight
enough to fail the moment a default changes. TESTING-RELIABILITY-SYSTEM.md
records that C1 characterisations are rare by design — reach for one only after
showing no baseline exists.
55 passed, 1 skipped across the five files.
Underworld development team with AI support from Claude Code