A uw.function.expression whose value is zero is folded out of expressions by sympy at construction, so a runtime constant that starts at zero never updates #696
Description
What happens
UWexpression reports is_zero, is_positive and is_negative from its current value (expressions.py, the assumption properties delegate to self._sym). Sympy uses those assumptions in automatic evaluation, so an expression whose value is zero when a formula is built is evaluated away before any JIT sees it, and changing the value later has no effect:
t = uw.function.expression(r"t", 0.0, "time")
F = sympy.exp(-0.02 * t) # exp(-0.02*t): survives on its own
U = sympy.Matrix([[-sympy.sin(x) * sympy.cos(y) * F, 0]]) # -> -sin(x)*cos(y): F is gone
(-0.02 * t).is_zero # True, from the valueA solver boundary condition built this way is frozen at the value it had at construction. With c = expression("c", 0.0) and a lid velocity exp(-c) * x * (1 - x), the Stokes and Navier-Stokes solvers keep the lid at exp(0) after c.sym = 2.0; with c created at 1.0 the same code follows the value exactly. We found it on the Taylor-Green vortex benchmark, where a time-dependent exact Dirichlet velocity built from a time expression created at t = 0 gave the same 4.7e-3 error on 1/16, 1/32 and 1/64 meshes at every time step, with the decay 10% too slow; freezing the time deliberately gave the identical answer to four digits.
The same folding will hit any formula in which a runtime constant multiplies or feeds a function that sympy evaluates on a zero argument (Mul, exp, Pow, Piecewise conditions): a body force, a source, a ramped boundary value, all of which naturally start at zero.
Where
src/underworld3/function/expressions.py, the is_zero / is_positive / is_negative / is_finite properties (added in 9b738ea for the units work). is_comparable was already made unconditionally False for a related reason (#415: Max/Min tried to compare the contents).
Not patched here
Reported rather than changed: the assumption properties may be relied on elsewhere (the rotated free-slip path tests sympify(conds).is_zero, and possibly the units and non-dimensionalisation code), so the choice of what a runtime constant should advertise to sympy is a design decision. The Taylor-Green driver works around it by creating the time expression at a non-zero value.
🤖 Generated with Claude Code
Activity
Design note: the cylinder drag was the missing viscous traction (#695)…
UWexpression never reports is_zero, is_positive or is_negative from i…
lmoresi commentedon Sep 7, 2026
Fixed in 92ed8ac on #688:
UWexpression.is_zero,is_positiveandis_negativeanswer None instead of reading the current value, so sympy no longer foldsexp(c)(or a sign-dependent simplification) on a runtime constant created at zero. Control intests/test_0503_integral_expression_constants.py::test_a_constant_created_at_zero_still_reaches_the_kernel(fails before the fix, passes after); the level-1 suite passes (1704). Reopen if #688 does not land.🤖 Generated with Claude Code
https://claude.ai/code/session_018T2VHUGaZiQVJ95qQ4DiSL
Navier-Stokes with Eulerian SUPG momentum transport, and a partition-…
Parameters given with units are known real (underworldcode#823)