From 506cb25c822db7dadbc0d651835572d78018047d Mon Sep 17 00:00:00 2001 From: Louis Moresi Date: Sun, 1 Mar 2026 20:49:05 +1100 Subject: [PATCH] Fix evaluate() NameError with mixed MeshVariable + coordinate expressions When evaluating expressions like `D.sym * mesh.CoordinateSystem.unit_e_0`, lambdify failed with `NameError: name '_uw_x' is not defined`. Root cause: the expression contained both UWCoordinate objects (from unit_e_0) and BaseScalar objects (from zero_matrix additions). Since lambdify uses object identity to match argument symbols, it didn't recognize UWCoordinate as the same variable as the BaseScalar argument, producing generated code with unresolvable `_uw_x`/`_uw_y` names. Fix: canonicalize all coordinate symbols (both UWCoordinate and BaseScalar variants) to a single set of Dummy symbols before lambdify. Closes #57 Underworld development team with AI support from Claude Code --- src/underworld3/function/_function.pyx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/underworld3/function/_function.pyx b/src/underworld3/function/_function.pyx index 7913208d1..41146b2f4 100644 --- a/src/underworld3/function/_function.pyx +++ b/src/underworld3/function/_function.pyx @@ -292,6 +292,27 @@ def _lambdify_and_evaluate(expr, coords, interpolated_results, coord_sys=None, m r = N.base_scalars()[0:dim] + # 2b. Canonicalize coordinate symbols for lambdify. + # The expression may contain UWCoordinate objects (from mesh.X or + # mesh.CoordinateSystem.unit_e_0) alongside BaseScalar objects. Since + # lambdify uses object identity to map arguments to generated code, + # we must ensure only ONE set of coordinate objects appears. + # Strategy: collect all coordinate-like symbols from the expression, + # group by index, and replace all variants with a single canonical + # sympy.Dummy symbol per coordinate. Use the same Dummy as the + # lambdify argument. + from sympy.vector.scalar import BaseScalar + coord_dummies = [sympy.Dummy(f"_coord_{i}") for i in range(dim)] + coord_subs = {} + for sym in subbedexpr.free_symbols: + if isinstance(sym, BaseScalar): + idx = sym._id[0] + if idx < dim: + coord_subs[sym] = coord_dummies[idx] + if coord_subs: + subbedexpr = subbedexpr.xreplace(coord_subs) + r = coord_dummies + # 3. Handle vector/dyadic expressions if isinstance(subbedexpr, sympy.vector.Vector): subbedexpr = subbedexpr.to_matrix(N)[0:dim, 0]