CBF flux on driven boundaries, kdtree module identity, global velocity_error#412
Conversation
…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
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
#316 — kdtree module identityHELD. 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_errorHELD — 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. VerdictMerge. Follow-ups #410 and #411 filed; neither blocks. Underworld development team with AI support from Claude Code |
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.DMPlexInsertBoundaryValuesnow 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.pyshadowed the package attribute on the firstimport underworld3.kdtree(whichtest_0000_importsperforms), silently dropping the memprobe counters for the rest of the run. Now one module identity via asys.modulesalias; shim deleted; identity regression test. Verified in both import orders.#370 — the np4 FMG 'tolerance miss' was never a solver problem.
velocity_errorcomputed 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_errorcall 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()(upstreamPETSC_MIN_REALinsertion reachesmesh.tkernels as −1.8e308; pre-existing, and the reasonboundary_flux's time handling is currently moot); #411 — the duplicatedcompute_*_residual_fieldsdiagnostics still carry the stripped-Dirichlet round trip (READ-27 de-duplication is the right fix).Verification
Manual close needed on merge: #407, #316, #370.
Underworld development team with AI support from Claude Code