Operator ======== Overview ^^^^^^^^ An `Operator` is an essential building block to represent expressions in NeoNs dsl. Here is an example of combining multiple `Operator` to an expression: .. code-block:: cpp dsl::imp::ddt(U) + dsl::imp::div(phi, U) - dsl::imp::laplacian(nu, U) NeoN's implements explicit (`exp`) and implicit (`imp`) of operator in the `dsl` namespace. Note that, temporal operators are currently, part of the implicit (`imp`) namespace eventhough an explicit time integration can be selected at runtime. An `Operator` can be instantiated on fields of different value types, e.g. `VolumeField` or `VolumeField`, thus `Operator` are templated over the field `ValueType`. Since the purpose of an `Operator` is to encode an expression, an `Operator` merely stores constant references to the field(s) it operates on and provides a connection to kernel which are executed at runtime when an expression is solved and operators are evaluated. Additionally, since expressions are evaluated lazily, a simple instantiation of an operator does not execute any computational kernel. Implementation ^^^^^^^^^^^^^^ Operators are implemented via multiple functions and classes: 1. `dsl/implicit.hpp` and `dsl/explicit.hpp` implement free standing functions such as `dsl::imp::div(phi, U)` or `dsl::exp::grad(p)` or which act as factories to construct the operator instance. 2. The expression stores operators separately either as temporal or spatial operator. Thus, an implementation of both classes can be found in `spatialOperator.hpp` and `temporalOperator.hpp`. 3. The Operator holder classes `spatialOperator` and `temporalOperator` implement an interface which expression trigger evaluation of the operator via the `explicitOperation` and `implicitOperation` member function. These function forward the operation call to the concrete model. 4. The concrete model here is constructed at run time via NeoN run time factory mechanism. Thus a base class for the registry is implemented for example in `divOperator.hpp` for which concrete implementation an its kernels are implemented in `gaussGreenDiv.hpp` .. mermaid:: flowchart TD n2["dsl/implicit.hpp"] -- imp::div --> n3["spatialOperator<>
spatialOperator.hpp"] n3 --> n5["divOperator"] --> n6["gaussGreenDiv"] n2@{ shape: text} n3@{ shape: proc} Details ^^^^^^^ An `Operator` is either explicit, implicit or temporal, and can be scalable by an additional coefficient, for example a scalar value or a further field. The `Operator` implementation uses Type Erasure (more details `[1] `_ `[2] `_ `[3] `_) to achieve polymorphism without inheritance. Consequently, the class needs only to implement the interface which is used in the DSL and which is shown in the below example: Example: .. code-block:: cpp NeoN::dsl::Operator divTerm = Divergence(NeoN::dsl::Operator::Type::Explicit, exec, ...); NeoN::dsl::Operator ddtTerm = TimeTerm(NeoN::dsl::Operator::Type::Temporal, exec, ..); To fit the specification of the Expression (storage in a vector), the Operator needs to be able to be scaled: .. code-block:: cpp NeoN::Vector scalingVector(exec, nCells, 2.0); auto sF = scalingVector.view(); dsl::Operator customTerm = CustomTerm(dsl::Operator::Type::Explicit, exec, nCells, 1.0); auto constantScaledTerm = 2.0 * customTerm; // A constant scaling factor of 2 for the term. auto fieldScaledTerm = scalingVector * customTerm; // scalingVector is used to scale the term. auto multiScaledTerm = (scale + scale + scale + scale) * customTerm; // Operator also supports the use of a lambda as scaling function to reduce the number of temporaries generated auto lambdaScaledTerm = (KOKKOS_LAMBDA(const NeoN::size_t i) { return sF[i] + sF[i] + sF[i] + sF[i]; }) * customTerm; To add a user-defined `Operator`, a new derived class must be created, inheriting from `OperatorMixin`, and provide the definitions of the below virtual functions that are required for the `Operator` interface: - build: build the term - explicitOperation: perform the explicit operation - implicitOperation: perform the implicit operation - getType: get the type of the term - exec: get the executor - volumeVector: get the volume field An example can be found in `test/dsl/operator.cpp`. The required scaling of the term is handled by the `Coeff` type which can be retrieved by the `getCoefficient` function of `Operator`.