Operator

Operator classification

Operator is another building block of OpFlow. All the possible operations can be applied on expressions are classified as operators. Operators are classified by the number of required expressions, i.e., nullary operator (operators taking no argument), unary operator (operators taking one argument), binary operator, etc. They can also be classified by their function, e.g., arithmetic operator, finite difference operator, interpolation operator, logical operator, etc. Currently available operators are:

Arithmetic

FDM

Interpolator

Logical

Add

D1FirstOrderBiasedDownwind

D1IntpCenterToCorner

And

Mul

D1FirstOrderBiasedUpwind

D1IntpCornerToCenter

Or

Sub

D1FirstOrderCenteredDownwind

IndexShifter

Not

Div

D1FirstOrderCenteredUpwind

IndexShifter1D

BitXor

Mod

D1WENO53Upwind

BitAnd

Pow

D1WENO53Downwind

BitOr

Neg

D2SecondOrderCentered

GreaterThan

Pos

GreaterThanOrEqual

Sqrt

LessThan

Min

LessThanOrEqual

Max

NotEqualTo

Apply an operator

To apply an operator to an expression, you can use the general expression builder function:

auto u, v = ...; // u and v are two expressions
auto t = makeExpression<AddOp>(u, v); // build an expression of u Add v and assign to t

Most operators provides convenient functions or operator overloads which can help you compose expressions more naturally and easily:

auto u, v = ...; // u and v are two expressions
auto t = u + v;  // operator+ is overloaded to produce an expression
                 // equivalent to makeExpression<AddOp>(u, v)
auto d = dx<D1FirstOrderBiasedDownwind>(u); // FDM operators provide dx, dy & dz wrappers
                                            // which take an operator as template parameter

Operator can be applied on fields, as well as results of expressions. Therefore, it’s totally appropriate to steam an expression into another operator and construct an complex expression:

auto u, v, w = ...; // assume u, v and w are from a 3D velocity field
auto div = dx<D1FirstOrderBiasedUpwind>(u)
         + dy<D1FirstOrderBiasedUpwind>(v)
         + dz<D1FirstOrderBiasedUpwind>(w); // compute the divergence of the velocity field

Tip

Try compose one complex expression rather than multiple intermediate assignment. This can max the probability of data reuse.

You may start to get the feeling of what OpFlow stands for. Actually, OpFlow tries to follow the so called “Data oriented programming” paradigm. The whole program supplies as a pipeline from source data to the results. Operators take data from different streams, modifies them, and supplies its result into a new stream. For details about OpFlow’s design, please refer to the Design Notes section.

Special operators

There are also some situations where we need to combine several operators together and pick one of them to perform the actual calculation depending on the local criteria. OpFlow currently provides two composable operators: CondOp for conditional expression and DecableOp for operator series with priority.

CondOp can be applied by using the conditional method:

// construct the flux using either the upwind scheme and the downwind scheme
auto flux_upwind = ...;
auto flux_downwind = ...;
// take either flux depending on the local wind direction
u = u + dt * u * conditional(u > 0, flux_downwind, flux_upwind);

conditional takes three arguments: the condition expression, the true branch expression and the false branch expression. Each of the three arguments can be a legal expression.

DecableOp are used to handle computations on the boundaries. High order operators are often used to provide high fidelity results. But they usually requires a wide stencil to do the computation, which cannot be satisfied at the boundary. By using DecableOp, you can pack several operators into a priority queue forming a composed operator. During evaluation of the composed operator, it will try to use the operator with the highest priority while assuring the computation is legal. For example, we want to use the WENO scheme at central field, and fall back to 1st order upwind scheme at the boundary, then we can write:

auto flux_upwind = d1<DecableOp<D1WENO53Upwind<0>, D1FirstOrderBiasedUpwind<0>>>(u);

DecableOp can also construct from DecableOps, i.e.,

// Make a complex op from N ops
using CplxOp = DecableOp<Op0, DecableOp<Op1, DecableOp<Op2, ..., OpN>>...>;

As always, the composed operator can automatically handle boundary conditions. Focus on your masterpiece and trust the orchestra. ^_^

Note

You can now checkout the 1D convection example in the Example section. Try it out!