Field Expressions#
BOUT++ field algebra now supports lazy expressions for many common
operations. Instead of creating a temporary field for every +,
-, *, /, pow, 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:
+,-,*,/Binary algebraic helpers such as
powandfloorUnary algebraic operators such as
sqrt,abs,exp,log,sin,cos,tan,sinh,cosh,tanh, andSQSimple conditionals with
if_elseandif_else_zeroReductions such as
min,max, andmean
For example:
Field3D n, T;
Field3D result;
result = sqrt(SQ(n) + pow(T + 1.0, 2.0));
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-indexpowfollows the same mixed-rank pattern, so either operand can beField2DorField3Dand the result is aField3Dexpressions 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.
Field3DParallel and FCI#
Lazy expressions can also carry parallel-slice information. This matters
when working with Field3DParallel on FCI meshes:
materializing into
Field3DParallelpreservesyupandydownslicesmaterializing the same expression into plain
Field3Dkeeps only the main field valueson FCI meshes, operands contributing to a
Field3DParallelexpression must have compatible parallel slices available
For example:
Field3DParallel f_par, g_par;
Field3DParallel result = sqrt(f_par + g_par);
On a non-FCI mesh, assigning a lazy expression to Field3DParallel
still evaluates the main field, but no parallel slices are retained.
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.