Function and Expressions

Expressions

UWexpression

class underworld3.function.expressions.UWexpression[source]

Bases: MathematicalMixin, uw_object, Symbol

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:
  • name (str) – LaTeX-style name for display (e.g., r”alpha”, r”rho_0”)

  • sym (any, optional) – The wrapped value. Can be: - A number - A UWQuantity (carries units) - Another UWexpression (nested lazy evaluation) - A SymPy expression

  • description (str, optional) – Human-readable description

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)
static __new__(cls, name, *args, _unique_name_generation=False, **kwargs)[source]
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:

UWexpression

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:

UWexpression

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:

UWexpression

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:

UWexpression

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:

UWexpression

Examples

>>> length = uw.expression("L", uw.quantity(0.001, "km"))
>>> length_compact = length.to_compact()
>>> print(length_compact)  # 1.0 [meter]
atoms(*types)[source]

Use Symbol’s atoms() method.

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.

is_constant(*wrt, **flags)[source]

SymPy-compatible is_constant - delegate to Symbol.

is_uw_constant()[source]

UW-specific: does this have no mesh variable dependencies?

constant()[source]

Deprecated - use is_uw_constant().

diff(*args, **kwargs)[source]

Differentiation - delegate to Symbol.

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:
  • name (str) – LaTeX-style name for display (e.g., r”alpha”, r”rho_0”)

  • sym (any, optional) – The wrapped value. Can be: - A number - A UWQuantity (carries units) - Another UWexpression (nested lazy evaluation) - A SymPy expression

  • description (str, optional) – Human-readable description

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: Function

Metaclass that returns programmatic class objects rather than instances.

This basically follows the pattern of the sympy.Function metaclass, with two key differences. First, we set UnderworldAppliedFunction as the base class, which allows isinstance(someobj, UnderworldAppliedFunction) checks. Second, we grab a weakref of the owning meshvariable onto the class itself (not the instance), because SymPy internally uses type(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 1 might correspond to v_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)
Parameters:
  • name (str)

  • meshvar (EnhancedMeshVariable)

  • vtype (VarType)

  • component (int | tuple)

  • data_loc (int)

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: object

A 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:
  • value (float, int, array-like) – The numerical value (dimensional)

  • units (str or Pint Unit, optional) – Units specification (e.g., “Pa*s”, “cm/year”, “K”)

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")
__init__(value, units=None)[source]

Initialize a UWQuantity.

Parameters:
  • value (float, int, or array-like) – The dimensional value

  • units (str or Pint Unit, optional) – Units specification

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 magnitude: float | ndarray

Alias for .value (Pint compatibility).

property units

Get the Pint Unit object.

Returns:

The unit, or None if dimensionless

Return type:

pint.Unit or None

property has_units: bool

Check if this quantity has units.

property dimensionality: dict

Get the Pint dimensionality dictionary.

Returns:

e.g., {‘[length]’: 1, ‘[time]’: -1} for velocity

Return type:

dict

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:

UWQuantity

to_base_units()[source]

Convert to SI base units.

Return type:

UWQuantity

to_reduced_units()[source]

Simplify units by canceling common factors.

Return type:

UWQuantity

to_compact()[source]

Convert to most readable unit representation.

Return type:

UWQuantity

diff(*args, **kwargs)[source]

Derivative of a constant is zero.

quantity

Factory function for creating UWQuantity objects with units.

underworld3.function.quantity(value, units=None)[source]

Create a unit-aware quantity.

Parameters:
  • value (float, int, or array-like) – The numerical value

  • units (str, optional) – Units specification (e.g., “Pa*s”, “cm/year”, “K”)

Returns:

Unit-aware quantity

Return type:

UWQuantity

Examples

>>> viscosity = uw.quantity(1e21, "Pa*s")
>>> velocity = uw.quantity(5, "cm/year")
>>> dT = uw.quantity(1000, "K") - uw.quantity(273, "K")

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 expr at coords with automatic unit handling.

Thin wrapper over _evaluate_impl(). See that function for the full parameter documentation and evaluation-mode notes. With the default monotone=False this is bit-identical to the historical evaluate.

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 the mesh.dim + 1 nearest 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 raise ValueError. 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 and evaluate() for the full parameter documentation. With the default monotone=False this is bit-identical to the historical global_evaluate.

Parameters:

monotone (bool or str, optional) – Opt-in bounded (monotone) interpolation post-process. See evaluate() for semantics. Not supported together with check_extrapolated (raises NotImplementedError).

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.Projection

Direct L2 projection for explicit control

uw.function.evaluate

General 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 class cls is about to be instantiated and it should return either some simplified instance (possible of some other class), or if the class cls should 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 class cls is about to be instantiated and it should return either some simplified instance (possible of some other class), or if the class cls should 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] = {}
class underworld3.function.analytic.sympy_function_printable

Bases: Function

This help function simply does most of the work for c-code printing. Inherit from this and set self._printstr and self._header as necessary. See AnalyticSolNL for example.

default_assumptions: ClassVar[StdFactKB] = {}