The new analytic-normal validation in _boundary_velocity_nodes (development, 2026-07)
raises
ValueError: analytic normal for boundary 'Upper' contains symbols ['N.x', 'N.y']
that are not mesh coordinates
on a NORMAL that was correctly built from the solver's own mesh.X — but only when a
SECOND mesh has been created before the recovery call. Reproduce: build mesh A + Stokes
- add_rotated_freeslip_bc(normal = X/|X| from mesh A) + solve (passes — guard OK at
solve time); build mesh B (a second annulus); then call
solverA.boundary_normal_traction(...) -> guard fires. Same call BEFORE mesh B exists:
fine.
Mechanism (suspected): the guard unwraps the stored normal at read-out time
(unwrap(normal[0,k], keep_constants=False)) and compares free symbols against
set(solver.mesh.X) by identity; with two meshes alive the expression-registry
cross-talk returns coordinate BaseScalars that are not identical to mesh A's — the
multi-mesh symbol-identity class (see SYMBOL_DISAMBIGUATION design note).
Impact: any workflow that solves on one mesh and reads the reaction after constructing
another mesh (comparison studies, A/B harnesses) hard-fails. Workaround: read
reactions before constructing the next mesh. Fix direction: compare by symbol NAME
within the solver's coordinate system, or unwrap against the solver's own registry
scope rather than the global one.
Found while running the #438 TI pre-test (results in that thread).
Underworld development team with AI support from Claude Code
The new analytic-normal validation in _boundary_velocity_nodes (development, 2026-07)
raises
on a NORMAL that was correctly built from the solver's own mesh.X — but only when a
SECOND mesh has been created before the recovery call. Reproduce: build mesh A + Stokes
solve time); build mesh B (a second annulus); then call
solverA.boundary_normal_traction(...) -> guard fires. Same call BEFORE mesh B exists:
fine.
Mechanism (suspected): the guard unwraps the stored normal at read-out time
(
unwrap(normal[0,k], keep_constants=False)) and compares free symbols againstset(solver.mesh.X)by identity; with two meshes alive the expression-registrycross-talk returns coordinate BaseScalars that are not identical to mesh A's — the
multi-mesh symbol-identity class (see SYMBOL_DISAMBIGUATION design note).
Impact: any workflow that solves on one mesh and reads the reaction after constructing
another mesh (comparison studies, A/B harnesses) hard-fails. Workaround: read
reactions before constructing the next mesh. Fix direction: compare by symbol NAME
within the solver's coordinate system, or unwrap against the solver's own registry
scope rather than the global one.
Found while running the #438 TI pre-test (results in that thread).
Underworld development team with AI support from Claude Code