In rotated_bc._boundary_velocity_nodes, an analytic (sympy) normal is unwrapped and then validated with
stray = set().union(*[c.free_symbols for c in comps]) - set(solver.mesh.X)
unwrap(...) canonicalises coordinate BaseScalars onto the FIRST mesh's coordinate frame (multi-mesh symbol identity), so on any mesh created after the first, the unwrapped x is not mesh.X[0] by identity even though it prints identically. The stray check then raises
ValueError: analytic normal for boundary '...' contains symbols ['N.x', 'N.y'] that are not mesh coordinates
for a normal correctly written in that mesh's own mesh.X.
Reproducer (second mesh in one session):
import sympy, underworld3 as uw
from underworld3.function.expressions import unwrap
m1 = uw.meshing.UnstructuredSimplexBox(cellSize=0.2)
m2 = uw.meshing.UnstructuredSimplexBox(cellSize=0.15)
x, y = m2.X
u = sympy.sympify(unwrap(x - 0.5, keep_constants=False, return_self=False))
print(u.free_symbols - set(m2.X)) # {N.x} — same object as m1's x
Fix (already applied to the fault-side twin fault_contact._compiled_normal_override on feature/fault-split-node, where the check was mirrored from rotated_bc): re-tag the unwrapped symbols by NAME onto the solver's own mesh.X before the stray check and the lambdify:
by_name = {str(xk): xk for xk in solver.mesh.X}
retag = {s: by_name[str(s)] for c in comps for s in c.free_symbols
if str(s) in by_name and s is not by_name[str(s)]}
if retag:
comps = [c.subs(retag) for c in comps]
Genuine stray symbols (names not matching any coordinate) are still caught. Note lambdify was already coincidentally correct when the check passed, because it binds by printed name.
Underworld development team with AI support from Claude Code
In
rotated_bc._boundary_velocity_nodes, an analytic (sympy) normal is unwrapped and then validated withunwrap(...)canonicalises coordinate BaseScalars onto the FIRST mesh's coordinate frame (multi-mesh symbol identity), so on any mesh created after the first, the unwrappedxis notmesh.X[0]by identity even though it prints identically. The stray check then raisesfor a normal correctly written in that mesh's own
mesh.X.Reproducer (second mesh in one session):
Fix (already applied to the fault-side twin
fault_contact._compiled_normal_overrideonfeature/fault-split-node, where the check was mirrored from rotated_bc): re-tag the unwrapped symbols by NAME onto the solver's ownmesh.Xbefore the stray check and the lambdify:Genuine stray symbols (names not matching any coordinate) are still caught. Note
lambdifywas already coincidentally correct when the check passed, because it binds by printed name.Underworld development team with AI support from Claude Code