Function and Expressions¶
Expressions¶
UWexpression¶
- class underworld3.function.expressions.UWexpression[source]¶
Bases:
MathematicalMixin,uw_object,SymbolA SymPy Symbol that wraps a value for lazy evaluation.
UWexpression is a named symbolic placeholder. When used in SymPy expressions, it acts as a Symbol. At evaluation time, the wrapped value is substituted.
Key Design (Simplified 2025-11): - Inherits from Symbol for SymPy compatibility - Does NOT inherit from UWQuantity (expressions don’t have units themselves) - Units are discovered from the wrapped thing when needed - Arithmetic returns pure SymPy expressions
Symbol Disambiguation (2025-12): - Uses _hashable_content() override (like sympy.Dummy) for uniqueness - Clean display names without invisible whitespace hacks - Symbols with same name but different _uw_id are distinct
- Parameters:
Examples
>>> alpha = uw.expression(r"\alpha", uw.quantity(3e-5, "1/K")) >>> rho0 = uw.expression(r"\rho_0", uw.quantity(3300, "kg/m^3")) >>> >>> # Symbolic multiplication >>> product = rho0 * alpha # Returns SymPy Mul >>> >>> # Wrap the product for lazy evaluation >>> combo = uw.expression(r"\rho_0 \alpha", product)
- rename(new_display_name)[source]¶
Change the display name of this expression without changing its identity.
This allows customizing how the expression appears in LaTeX output and string representations while preserving its symbolic identity (hash and equality remain based on the original name and _uw_id).
- Parameters:
new_display_name (str) – The new name to use for display purposes (typically LaTeX).
- Returns:
Returns self to allow method chaining.
- Return type:
Examples
>>> viscosity = uw.expression("eta", 1e21) >>> viscosity.rename(r"\eta_{\mathrm{mantle}}") >>> print(viscosity) # Shows renamed version
Notes
The original symbol name (self.name) is preserved for: - SymPy identity (hash, equality) - Pickling/serialization - Expression matching in solvers
Only the display representation changes via _latex() and _sympystr().
- __init__(name, sym=None, description='No description provided', value=None, units=None, **kwargs)[source]¶
- property sym¶
Get the symbolic/numeric value.
- copy(other)[source]¶
Copy the symbolic value and metadata from another expression.
This method updates this expression’s content while preserving its identity (same Python id). Used by constitutive model parameter setters to enable lazy evaluation - the expression container stays the same, but its content can be updated.
- Parameters:
other (UWexpression or UWQuantity or sympy.Basic or number) – The source to copy from. If UWexpression, copies both ._sym and any unit metadata. Otherwise, updates .sym directly.
Notes
This follows the same pattern as ExpressionDescriptor.__set__ in _api_tools.py to ensure consistent behavior for expression updates.
- property value¶
Get the dimensional value of the wrapped thing.
- property data¶
Get the non-dimensional value for computation.
- property units¶
Get units from the wrapped thing (if it has units).
- property has_units¶
Check if the wrapped thing has units.
- property dimensionality¶
Get dimensionality from the wrapped thing.
- property expression¶
Get the unwrapped expression.
- property description¶
- property expression_number¶
Unique number of the expression instance.
- property quantity¶
Get the wrapped quantity for numeric arithmetic with units.
Returns the underlying UWQuantity if one was provided, or creates one from the value.
- to(target_units)[source]¶
Convert to different units.
Delegates to uw.convert_units() for the actual conversion.
- Parameters:
target_units (str) – Target units (e.g., “m/s”, “km”, “degC”)
- Returns:
New expression with converted value and units
- Return type:
Examples
>>> radius = uw.expression("r", uw.quantity(6370, "km")) >>> radius_m = radius.to("m") >>> print(radius_m.value) # 6370000.0
- to_base_units()[source]¶
Convert to SI base units.
Delegates to uw.to_base_units() for the actual conversion.
- Returns:
New expression with value in SI base units
- Return type:
Examples
>>> velocity = uw.expression("v", uw.quantity(100, "km/h")) >>> velocity_si = velocity.to_base_units() >>> print(velocity_si.value) # 27.78 (m/s)
- to_reduced_units()[source]¶
Simplify units by canceling common factors.
Delegates to uw.to_reduced_units() for the actual simplification.
- Returns:
New expression with simplified units
- Return type:
- to_compact()[source]¶
Convert to most human-readable unit representation.
Automatically chooses unit prefixes (kilo, mega, micro, etc.) to make the number more readable.
Delegates to uw.to_compact() for the actual conversion.
- Returns:
New expression with compact units
- Return type:
Examples
>>> length = uw.expression("L", uw.quantity(0.001, "km")) >>> length_compact = length.to_compact() >>> print(length_compact) # 1.0 [meter]
- property is_number¶
UWexpression is a Symbol, not a number.
- property is_comparable¶
Delegate to wrapped expression.
- property is_extended_real¶
Delegate to wrapped expression.
- property is_positive¶
Delegate to wrapped expression.
- property is_negative¶
Delegate to wrapped expression.
- property is_zero¶
Delegate to wrapped expression.
- property is_finite¶
Delegate to wrapped expression.
- default_assumptions: ClassVar[StdFactKB] = {}¶
expression¶
Factory function for creating UWexpression objects.
- underworld3.function.expression(name, *args, _unique_name_generation=False, **kwargs)¶
A SymPy Symbol that wraps a value for lazy evaluation.
UWexpression is a named symbolic placeholder. When used in SymPy expressions, it acts as a Symbol. At evaluation time, the wrapped value is substituted.
Key Design (Simplified 2025-11): - Inherits from Symbol for SymPy compatibility - Does NOT inherit from UWQuantity (expressions don’t have units themselves) - Units are discovered from the wrapped thing when needed - Arithmetic returns pure SymPy expressions
Symbol Disambiguation (2025-12): - Uses _hashable_content() override (like sympy.Dummy) for uniqueness - Clean display names without invisible whitespace hacks - Symbols with same name but different _uw_id are distinct
- Parameters:
Examples
>>> alpha = uw.expression(r"\alpha", uw.quantity(3e-5, "1/K")) >>> rho0 = uw.expression(r"\rho_0", uw.quantity(3300, "kg/m^3")) >>> >>> # Symbolic multiplication >>> product = rho0 * alpha # Returns SymPy Mul >>> >>> # Wrap the product for lazy evaluation >>> combo = uw.expression(r"\rho_0 \alpha", product)
Mesh Variable Functions¶
UnderworldFunction¶
Symbolic representation of mesh variable fields used in expressions and equations.
- class underworld3.function.UnderworldFunction¶
Bases:
FunctionMetaclass that returns programmatic class objects rather than instances.
This basically follows the pattern of the
sympy.Functionmetaclass, with two key differences. First, we setUnderworldAppliedFunctionas the base class, which allowsisinstance(someobj, UnderworldAppliedFunction)checks. Second, we grab a weakref of the owning meshvariable onto the class itself (not the instance), because SymPy internally usestype(obj)(obj.args)to clone instances and extra info must be on the class so that clones are complete.Consider the calling pattern
>>> newfn = UnderworldFunction(meshvar,name)(*meshvar.mesh.r)
This is equivalent to
>>> newfnclass = UnderworldFunction(meshvar,name) # Here we create a new *class*. >>> newfn = newfnclass(*meshvar.mesh.r) # Here we create an instance of the class.
- Parameters:
name (str) – The name of the function.
meshvar (MeshVariable) – The mesh variable corresponding to this function.
vtype (VarType) – The variable type (scalar, vector, etc).
component (int or tuple) – For vector functions, this is the component of the vector. For example, component
1might correspond tov_y. For tensors, the component is a tuple. For scalars, this value is ignored.
- static __new__(cls, name, meshvar, vtype, component=0, data_loc=None, *args, **options)¶
- default_assumptions: ClassVar[StdFactKB] = {}¶
unwrap¶
Unwrap UWexpressions to their underlying SymPy expressions for compilation.
- underworld3.function.unwrap(fn, depth=None, keep_constants=True, return_self=True)[source]¶
Expand UW expression to reveal SymPy structure.
- Args:
fn: Expression to unwrap depth: Maximum expansion depth (None = complete) keep_constants: If False, use nondimensional mode (for JIT) return_self: If False, use nondimensional mode (for JIT)
- Returns:
Unwrapped SymPy expression
Quantities and Units¶
UWQuantity¶
- class underworld3.function.UWQuantity[source]¶
Bases:
objectA number with units.
Simple, clean, Pint-backed. Follows the MeshVariable pattern: - .value → dimensional (what user sees) - .data → non-dimensional (what solver sees) - .units → Pint Unit object
All arithmetic is delegated to Pint. No symbolic complexity.
- Parameters:
Examples
>>> viscosity = uw.quantity(1e21, "Pa*s") >>> viscosity.value # 1e21 (dimensional) >>> viscosity.data # 1.0 (non-dimensional, if model is set up) >>> viscosity.units # <Unit('pascal * second')>
>>> # Arithmetic via Pint >>> T1 = uw.quantity(1000, "kelvin") >>> T2 = uw.quantity(273, "kelvin") >>> dT = T1 - T2 # UWQuantity(727, "kelvin")
- property value: float | ndarray¶
Dimensional value (what the user sees).
- Returns:
The value in the quantity’s units
- Return type:
float or np.ndarray
- property data: float | ndarray¶
Non-dimensional value (what the solver sees).
Returns the value scaled by the model’s reference quantities. If no model is registered or no scaling is active, returns the dimensional value.
- Returns:
Non-dimensional value for solver use
- Return type:
float or np.ndarray
- property units¶
Get the Pint Unit object.
- Returns:
The unit, or None if dimensionless
- Return type:
pint.Unit or None
- property dimensionality: dict¶
Get the Pint dimensionality dictionary.
- Returns:
e.g., {‘[length]’: 1, ‘[time]’: -1} for velocity
- Return type:
- to(target_units)[source]¶
Convert to different units.
- Parameters:
target_units (str) – Target units (e.g., “m/s”, “km”, “degC”)
- Returns:
New quantity with converted value and units
- Return type:
quantity¶
Factory function for creating UWQuantity objects with units.
Evaluation¶
evaluate¶
- underworld3.function.evaluate(expr, coords, coord_sys=None, other_arguments=None, simplify=False, verbose=False, evalf=False, mode='default', data_layout=None, check_extrapolated=False, smoothing=1e-06, rbf=None, force_l2=None, monotone=False)[source]¶
Evaluate
expratcoordswith automatic unit handling.Thin wrapper over
_evaluate_impl(). See that function for the full parameter documentation and evaluation-mode notes. With the defaultmonotone=Falsethis is bit-identical to the historicalevaluate.- Parameters:
monotone (bool or str, optional) – Opt-in bounded (monotone) interpolation, applied as a post-process to the computed result.
False(default) → no limiting.True/"clamp"→ clip the result into the[min, max]of themesh.dim + 1nearest source-field DOFs."pick"→ keep in-bounds values and re-evaluate only the out-of-bounds subset via (bounded) RBF interpolation. Only single-MeshVariable expressions are supported; composites raiseValueError. See_apply_monotone_limit().
global_evaluate¶
- underworld3.function.global_evaluate(expr, coords=None, coord_sys=None, other_arguments=None, simplify=False, verbose=False, evalf=False, mode='default', data_layout=None, check_extrapolated=False, smoothing=1e-06, rbf=None, force_l2=None, monotone=False)[source]¶
Parallel-safe evaluate with automatic unit-aware results.
Thin wrapper over
_global_evaluate_impl(). See that function andevaluate()for the full parameter documentation. With the defaultmonotone=Falsethis is bit-identical to the historicalglobal_evaluate.- Parameters:
monotone (bool or str, optional) – Opt-in bounded (monotone) interpolation post-process. See
evaluate()for semantics. Not supported together withcheck_extrapolated(raisesNotImplementedError).
evaluate_gradient¶
- underworld3.function.evaluate_gradient(scalar_var, coords, method='interpolant', component=None)[source]¶
Evaluate gradient of a mesh variable at arbitrary coordinates.
Computes the gradient of a MeshVariable (or one of its components) and evaluates it at the specified coordinates.
- Parameters:
scalar_var (MeshVariable) – Field to compute gradient of. Can be scalar (num_components=1) or vector/tensor field. For multi-component fields, use component parameter to specify which component’s gradient to compute.
coords (array-like) – Coordinates at which to evaluate gradient, shape (n_points, dim). Can be numpy array or UnitAwareArray.
method (str, optional) – Gradient computation method: - “interpolant”: Clement interpolant (O(h) accurate, no solve). Default. - “projection”: L2 projection (O(h²) accurate, requires solve).
component (int or None, optional) – For multi-component fields, which component to compute gradient of. If None and field has multiple components, raises ValueError. For scalar fields, this parameter is ignored.
- Returns:
Gradient values at requested coordinates, shape (n_points, dim). gradient[i, j] = ∂f/∂xⱼ at coords[i].
- Return type:
ndarray
Notes
Interpolant (Clement) Method: Uses PETSc’s DMPlexComputeGradientClementInterpolant which averages cell-wise gradients at vertices. This is O(h) accurate - error halves when mesh resolution doubles. Fast but limited to first-order accuracy.
Projection (L2) Method: Solves a mass matrix system to find the optimal L2 projection of the gradient onto the finite element space. This is O(h²) accurate for smooth solutions. The projection is cached on the mesh for repeated calls, using the previous solution as initial guess.
Examples
>>> mesh = uw.meshing.StructuredQuadBox(elementRes=(16, 16)) >>> T = uw.discretisation.MeshVariable('T', mesh, 1) >>> T.array[:, 0, 0] = T.coords[:, 0]**2 # T = x² >>> >>> # Fast gradient (O(h)) >>> grad_fast = evaluate_gradient(T, coords, method="interpolant") >>> >>> # Accurate gradient (O(h²)) >>> grad_accurate = evaluate_gradient(T, coords, method="projection")
See also
uw.systems.ProjectionDirect L2 projection for explicit control
uw.function.evaluateGeneral function evaluation
References
Analytic Functions¶
- class underworld3.function.analytic.AnalyticSolNL_base¶
Bases:
sympy_function_printable- default_assumptions: ClassVar[StdFactKB] = {}¶
- nargs = {5}¶
- class underworld3.function.analytic.AnalyticSolNL_bodyforce¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶
- classmethod eval(*args)¶
Returns a canonical form of cls applied to arguments args.
Explanation¶
The
eval()method is called when the classclsis about to be instantiated and it should return either some simplified instance (possible of some other class), or if the classclsshould be unmodified, return None.Examples of
eval()for the function “sign”@classmethod def eval(cls, arg): if arg is S.NaN: return S.NaN if arg.is_zero: return S.Zero if arg.is_positive: return S.One if arg.is_negative: return S.NegativeOne if isinstance(arg, Mul): coeff, terms = arg.as_coeff_Mul(rational=True) if coeff is not S.One: return cls(coeff) * cls(terms)
- nargs = {5}¶
- class underworld3.function.analytic.AnalyticSolNL_bodyforce_x¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶
- class underworld3.function.analytic.AnalyticSolNL_bodyforce_y¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶
- class underworld3.function.analytic.AnalyticSolNL_velocity¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶
- classmethod eval(*args)¶
Returns a canonical form of cls applied to arguments args.
Explanation¶
The
eval()method is called when the classclsis about to be instantiated and it should return either some simplified instance (possible of some other class), or if the classclsshould be unmodified, return None.Examples of
eval()for the function “sign”@classmethod def eval(cls, arg): if arg is S.NaN: return S.NaN if arg.is_zero: return S.Zero if arg.is_positive: return S.One if arg.is_negative: return S.NegativeOne if isinstance(arg, Mul): coeff, terms = arg.as_coeff_Mul(rational=True) if coeff is not S.One: return cls(coeff) * cls(terms)
- nargs = {5}¶
- class underworld3.function.analytic.AnalyticSolNL_velocity_x¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶
- class underworld3.function.analytic.AnalyticSolNL_velocity_y¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶
- class underworld3.function.analytic.AnalyticSolNL_viscosity¶
Bases:
AnalyticSolNL_base- default_assumptions: ClassVar[StdFactKB] = {}¶