Field Expressions#
BOUT++ field algebra now supports lazy expressions for many common
operations. Instead of creating a temporary field for every +,
-, *, /, sqrt or abs, BOUT++ can keep the expression
symbolic and evaluate it only when a concrete field or scalar result is
needed.
This keeps ordinary model code readable while reducing temporary allocations and extra loops over the mesh. It is especially helpful for accelerator backends, where launching fewer kernels matters.
What stays lazy#
The following operations can form lazy expressions over Field2D,
Field3D, Field3DParallel, and FieldPerp where the combination makes
sense:
Arithmetic operators:
+,-,*,/Unary algebraic operators such as
sqrt,abs,exp,log,sin,cos,tan,sinh,cosh,tanh,floor, andSQSimple conditionals with
if_elseandif_else_zeroReductions such as
min,max, andmean
For example:
Field3D n, T;
Field3D result;
result = sqrt(SQ(n) + SQ(T));
The right-hand side can stay lazy until the assignment to result.
When evaluation happens#
An expression is evaluated when BOUT++ needs actual storage or a scalar answer. Common triggers are:
assigning to a field
constructing a field from an expression
assigning a field expression into an
Optionsobjectcalling scalar reductions such as
min,max, ormean
Examples:
Field3D result = n + T;
options["rhs"] = n + T;
BoutReal max_value = max(abs(n + T), true);
Region-limited expressions#
Many algebraic operators take a region argument, usually defaulting
to RGN_ALL. A lazy expression keeps track of that region.
Only values inside the requested region are guaranteed to be valid after materialization. This is useful for skipping guard-cell work when the result will only be used in a smaller region:
Field3D interior = abs(n, "RGN_NOBNDRY");
As with other region-limited field operations in BOUT++, code that later uses guard cells should communicate or otherwise fill those cells before relying on them.
Metadata propagation#
When an expression is materialized into a field, BOUT++ propagates the field metadata carried by the expression:
mesh pointer
cell location
field directions
for
FieldPerp, the y-index
This means expressions are intended to behave like ordinary field operations in user code. Compatibility checks still apply: combining fields on different meshes or incompatible staggered locations is an error.
Mixed field types#
Several mixed-type combinations are supported directly:
Field2DwithField3D: the 2D quantity is broadcast inzFieldPerpwith matching perpendicular data: the operation uses theFieldPerpy-indexexpressions involving metric components may return
Coordinates::FieldMetric, which isField2DorField3Ddepending on how BOUT++ was built
In practice, this means code such as:
Coordinates::FieldMetric grad = coords->J / coords->g_22;
Field3D rhs = density * temperature + background_2d;
can use the same algebraic style even when metric dimensionality or field rank differs.
Conditionals#
if_else selects between two algebraic branches without forcing the
branches to be precomputed:
Field3D rhs = if_else(use_source, source * density, sink * density);
if_else_zero(condition, expr) is a shorthand for selecting either an
expression or zero:
Field3D rhs = if_else_zero(include_drive, drive * profile);
This is particularly convenient when optional source terms are enabled or disabled by compile-time or run-time logic.
Reductions on expressions#
Reductions can operate directly on expressions instead of requiring an intermediate field:
BoutReal rms = sqrt(mean(SQ(n - n0), true, "RGN_NOBNDRY"));
BoutReal max_error = max(abs(lhs - rhs), true);
This is often clearer than explicitly constructing a temporary field, and it avoids extra storage.
Relation to GPU execution#
Lazy field expressions are the high-level path to reducing temporary work. They are a good default when ordinary field algebra expresses the operation clearly.
For more control, especially when you want to fuse derivative operators into a single explicit loop, see GPU support.