Skip to content

CBF flux on driven boundaries, kdtree module identity, global velocity_error#412

Merged
lmoresi merged 3 commits into
developmentfrom
bugfix/cbf-driven-batch
Jul 24, 2026
Merged

CBF flux on driven boundaries, kdtree module identity, global velocity_error#412
lmoresi merged 3 commits into
developmentfrom
bugfix/cbf-driven-batch

Conversation

@lmoresi

@lmoresi lmoresi commented Jul 24, 2026

Copy link
Copy Markdown
Member

Three fixes from the small-issues sweep, with the public adversarial-review round. Closes #407, closes #316, closes #370.

The fixes

#407 — CBF boundary flux was garbage on inhomogeneous Dirichlet walls (2D and 3D). Constrained DOFs don't exist in the global vector, so _assemble_volume_reaction's gather round-trip zero-filled every boundary value before integrating the reaction residual — a g=1 wall read −17.7 for a true unit flux, and every existing test was blind because it measured g=0 walls. DMPlexInsertBoundaryValues now fills the essential values first. Regression tests: exact unit flux through a driven wall (end nodes included), and a corner-semantics test (T=x+y, all four walls driven) that pins the method's inherent corner behaviour — a corner reaction integrates BOTH adjacent walls' fluxes. The review's Couette probe confirms the Stokes case: inhomogeneous velocity walls now give exact (±1, 0) tractions where the old code garbled them; all SolCx/rotated-BC goldens are unchanged (g=0 inserts zeros over zeros), and the rotated-reaction path is untouched by construction.

#316 — order-dependent memprobe failures. A shim kdtree.py shadowed the package attribute on the first import underworld3.kdtree (which test_0000_imports performs), silently dropping the memprobe counters for the rest of the run. Now one module identity via a sys.modules alias; shim deleted; identity regression test. Verified in both import orders.

#370 — the np4 FMG 'tolerance miss' was never a solver problem. velocity_error computed rank-local norms: the same converged solution measured 0.00027 on one rank and 0.00562 on the rank owning the SolCx viscosity jump, and the test asserted per-rank. Now a global reduction (identical on every rank; the seam-DOF weighting approximation is documented). Global np4 error: 0.0006 against the unchanged 5e-3 tolerance; the full test_1017 file passes at np2 and np4. Second instance of the rank-local-diagnostic disease after #351 — worth watching for as a class.

Adversarial review (public, round 1 — posted below)

All three held. The review's probes: Couette all-wall Stokes exactness, mixed natural+Dirichlet bit-identity, golden batteries (test_1018 serial 14 passed, test_1064 np2, test_1065 np2/np4, SolCx goldens), corner-test flake repeats (serial/np2x3/np4x3), both kdtree import orders, pickle/module-name attacks, and an exhaustive collective-safety sweep of every velocity_error call site (the global norm makes it collective — no rank-guarded caller exists). Serial equivalence of the new norm: 1 ulp.

Found on the way, filed separately: #410 — time-dependent Dirichlet BCs NaN out in solve() (upstream PETSC_MIN_REAL insertion reaches mesh.t kernels as −1.8e308; pre-existing, and the reason boundary_flux's time handling is currently moot); #411 — the duplicated compute_*_residual_fields diagnostics still carry the stripped-Dirichlet round trip (READ-27 de-duplication is the right fix).

Verification

  • test_1019 (with new tests): serial, np2, np4 — green, repeats clean.
  • test_0000+test_0780+test_0101 in both orders: green.
  • test_1017 full file: np2 and np4 green.
  • Level_1 + tier_a gate: 444 passed, 0 failures.

Manual close needed on merge: #407, #316, #370.

Underworld development team with AI support from Claude Code

lmoresi added 3 commits July 24, 2026 13:18
…al (#407)

Constrained (Dirichlet) DOFs are absent from the global vector, so
_assemble_volume_reaction's localToGlobal/globalToLocal round trip left
them ZERO in the state it integrated. The residual-based reaction was
therefore evaluated against a solution with wrong boundary values
wherever g != 0: flux through an inhomogeneous Dirichlet wall was
garbage (measured -17.7 for a true unit flux), interior residuals near
the wall were O(1) despite a converged solve, and g=0 boundaries were
accidentally exact — which is why every existing test was blind.
DMPlexInsertBoundaryValues now fills the essential values (with the
solver's non-dimensional time) before DMPlexSNESComputeResidualFEM.

Regression tests: exact unit flux through a g=1 wall including end nodes
(natural sides); and the corner-semantics case — T=x+y with all four
walls driven pins that a corner reaction integrates BOTH adjacent walls'
fluxes (cancellation/doubling), the method's inherent corner behaviour
that consumers must expect. The pre-existing 'serial' heat-flux test is
skip-guarded under MPI (0/0 on non-owning ranks); the file now passes at
np2 and np4.

Not affected (verified separately on #407): the rotated-constraint
reaction path — it reads the assembled A*u-b, and rotated constraints
keep their DOFs in the global system.

Closes #407

Underworld development team with AI support from Claude Code
…dowing shim (#316)

The package __init__ bound underworld3.kdtree to the ckdtree module, but
a real kdtree.py shim also existed: the first 'import underworld3.kdtree'
anywhere loaded the shim and REBOUND the package attribute to it —
silently dropping the memprobe counters (live_count/total_constructed)
for every test that ran afterwards. test_0000_imports triggers exactly
that import, which is why the level-1 suite failed in test_0780_memprobe
while the same file passed in isolation (issue #316's order dependence).

The alias is now registered in sys.modules, so 'import underworld3.kdtree'
and the package attribute are the SAME module object; the shim is deleted
and an identity regression test pins it.

Closes #316

Underworld development team with AI support from Claude Code
The np4 'tolerance miss' in test_parallel_custom_fmg_stokes_constrained
was never a solver accuracy problem: velocity_error computed RANK-LOCAL
norms, so each rank reported its own partition's error — 0.00027 to
0.00562 for the SAME converged solution at np4, with the rank owning the
SolCx viscosity jump reporting 20x its neighbours. The np2/np4
'difference' was which rank's local norm happened to trip the assertion.

The error is now reduced globally (identical on every rank; shared
seam DOFs counted once per owning rank — a small documented weighting
approximation for a benchmark diagnostic). The global np4 error is
0.0006, comfortably inside the unchanged 5e-3 tolerance; the full
test_1017 file passes at np2 and np4.

Closes #370

Underworld development team with AI support from Claude Code
Copilot AI review requested due to automatic review settings July 24, 2026 03:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lmoresi

lmoresi commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Adversarial review — round 1 (public record)

Independent skeptic pass over the branch diff; live probes serial/np2/np4 in the branch's own build.

#407 — essential-value insert before the reaction residual

  • Stokes / mixed-BC corruption attack: HELD. All-wall inhomogeneous Couette (u=(y,0), P2-P1, pressure nullspace): solution exact to 2e-12, recovered tractions exactly (±1, 0) — precisely the case the old code garbled. Adding a natural wall alongside: bit-identical results. Rotated-BC path untouched by construction (reads the stashed solve reaction).
  • Golden regression attack: HELD. test_1018 serial (14 passed, incl. SolCx sigma_nn + dynamic-topography goldens), test_1064 np2, test_1065 np2/np4, SolCx goldens — nothing shifted (for g=0 the insert writes zeros over zeros).
  • Time-dependent BC attack: could not be realized — and found a pre-existing solver bug instead. No caller passes time= today, and no solve can reach boundary_flux with a live mesh.t Dirichlet wall because solve() itself inserts essential values at PETSC_MIN_REAL — mesh.t kernels see −1.8e308 and the solve NaNs. Filed as Time-dependent Dirichlet BCs are broken in solve(): essential values inserted at PETSC_MIN_REAL, mesh.t kernels see -1.8e308 #410 (with the note that boundary_flux must forward the solve time once that is fixed). The time= plumb in this diff was verified to insert g(t) correctly.
  • Scope gap found: the duplicated compute_volume/boundary_residual_fields diagnostics did NOT get the insert (no in-repo callers; READ-27 de-duplication territory). Filed as compute_volume_residual_fields / boundary sibling still strip Dirichlet values (the #407 fix was applied to one of three duplicated variants) #411.
  • Corner-semantics test: no flake across serial + np2 x3 + np4 x3.

#316 — kdtree module identity

HELD. All import spellings resolve to the one module, both import orders green, pickle/module-name attacks dead on arrival (KDTree was never picklable; module was always ckdtree). One packaging note, fixed before push: the stale setuptools build cache re-shipped the deleted shim into the local wheel — build/lib.* cleaned; clean checkouts/CI unaffected, and the sys.modules alias preempts a stale file anyway (verified: the identity test passed even with the stale shim present).

#370 — global velocity_error

HELD — the collective-deadlock attack found no victim. The global norm makes the function collective; an exhaustive sweep of every call site (10 test files, src, docs, scripts) found none inside rank guards or rank subsets. Serial equivalence to the old formula: ~1 ulp. Seam-DOF duplication quantified at ~4% of rows at np2 — matches the docstring's disclosed approximation. Bonus: empty ranks now get the finite global value instead of 0/0 NaN.

Verdict

Merge. Follow-ups #410 and #411 filed; neither blocks.

Underworld development team with AI support from Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants