diff --git a/src/algebra/TinyMatrix.hpp b/src/algebra/TinyMatrix.hpp
index bc64d550979c76055c2669b0555679daa5d351b5..636ff19c18112c582c8968e8dc6f4df5ebdee726 100644
--- a/src/algebra/TinyMatrix.hpp
+++ b/src/algebra/TinyMatrix.hpp
@@ -57,11 +57,11 @@ class [[nodiscard]] TinyMatrix
   PUGS_INLINE
   constexpr TinyMatrix operator-() const
   {
-    TinyMatrix opposed;
+    TinyMatrix opposite;
     for (size_t i = 0; i < N * N; ++i) {
-      opposed.m_values[i] = -m_values[i];
+      opposite.m_values[i] = -m_values[i];
     }
-    return opposed;
+    return opposite;
   }
 
   PUGS_INLINE
diff --git a/src/algebra/TinyVector.hpp b/src/algebra/TinyVector.hpp
index 3f3c10920f5ae75c6b37a0761393cad19a77786b..c3146a2be9932353388d34d15862056f952631fb 100644
--- a/src/algebra/TinyVector.hpp
+++ b/src/algebra/TinyVector.hpp
@@ -34,11 +34,11 @@ class [[nodiscard]] TinyVector
   PUGS_INLINE
   constexpr TinyVector operator-() const
   {
-    TinyVector opposed;
+    TinyVector opposite;
     for (size_t i = 0; i < N; ++i) {
-      opposed.m_values[i] = -m_values[i];
+      opposite.m_values[i] = -m_values[i];
     }
-    return opposed;
+    return opposite;
   }
 
   PUGS_INLINE
diff --git a/src/language/ast/ASTNodeAffectationExpressionBuilder.cpp b/src/language/ast/ASTNodeAffectationExpressionBuilder.cpp
index 32117039587e2c53907ea030ac73a7e10e544255..cc26d368ee6cb0d6a3f105604ae6484628d69890 100644
--- a/src/language/ast/ASTNodeAffectationExpressionBuilder.cpp
+++ b/src/language/ast/ASTNodeAffectationExpressionBuilder.cpp
@@ -37,6 +37,12 @@ ASTNodeAffectationExpressionBuilder::ASTNodeAffectationExpressionBuilder(ASTNode
     ASTNode& lhs_node = *node.children[0];
     ASTNode& rhs_node = *node.children[1];
 
+    if (rhs_node.m_data_type == ASTNodeDataType::list_t) {
+      if ((lhs_node.m_data_type == ASTNodeDataType::vector_t) or (lhs_node.m_data_type == ASTNodeDataType::matrix_t)) {
+        ASTNodeNaturalConversionChecker(rhs_node, lhs_node.m_data_type);
+      }
+    }
+
     node.m_node_processor = optional_processor_builder.value()->getNodeProcessor(lhs_node, rhs_node);
   } else {
     std::ostringstream error_message;
diff --git a/src/language/ast/ASTNodeDataTypeBuilder.cpp b/src/language/ast/ASTNodeDataTypeBuilder.cpp
index 24841d797529750ba2adc6664c278a56c615fdd2..44e30ec2e11fba9db2701268d582089f9cb839f4 100644
--- a/src/language/ast/ASTNodeDataTypeBuilder.cpp
+++ b/src/language/ast/ASTNodeDataTypeBuilder.cpp
@@ -203,7 +203,7 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
 
         if (nb_parameter_domains != nb_parameter_names) {
           std::ostringstream message;
-          message << "note: number of product spaces (" << nb_parameter_domains << ") " << rang::fgB::yellow
+          message << "number of product spaces (" << nb_parameter_domains << ") " << rang::fgB::yellow
                   << parameters_domain_node.string() << rang::style::reset << rang::style::bold
                   << " differs from number of variables (" << nb_parameter_names << ") " << rang::fgB::yellow
                   << parameters_name_node.string() << rang::style::reset;
@@ -255,7 +255,7 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
               std::ostringstream message;
               message << "expecting " << image_type.dimension() << " scalar expressions or an "
                       << dataTypeName(image_type) << ", found " << nb_image_expressions << " scalar expressions";
-              throw ParseError(message.str(), image_domain_node.begin());
+              throw ParseError(message.str(), image_expression_node.begin());
             }
           } else if (image_domain_node.is_type<language::matrix_type>()) {
             ASTNodeDataType image_type = getMatrixDataType(image_domain_node);
@@ -263,7 +263,7 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
               std::ostringstream message;
               message << "expecting " << image_type.nbRows() * image_type.nbColumns() << " scalar expressions or an "
                       << dataTypeName(image_type) << ", found " << nb_image_expressions << " scalar expressions";
-              throw ParseError(message.str(), image_domain_node.begin());
+              throw ParseError(message.str(), image_expression_node.begin());
             }
           } else {
             std::ostringstream message;
@@ -290,8 +290,26 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
         } else if (symbol_table->has(n.string(), n.begin())) {
           n.m_data_type = ASTNodeDataType::build<ASTNodeDataType::builtin_function_t>();
         } else {
+          // LCOV_EXCL_START
           throw UnexpectedError("could not find symbol " + n.string());
+          // LCOV_EXCL_STOP
         }
+      } else if (n.is_type<language::type_name_id>()) {
+        const std::string& type_name_id = n.string();
+
+        auto& symbol_table = *n.m_symbol_table;
+
+        const auto [i_type_symbol, found] = symbol_table.find(type_name_id, n.begin());
+        if (not found) {
+          throw ParseError("undefined type identifier", std::vector{n.begin()});
+        } else if (i_type_symbol->attributes().dataType() != ASTNodeDataType::type_name_id_t) {
+          std::ostringstream os;
+          os << "invalid type identifier, '" << type_name_id << "' was previously defined as a '"
+             << dataTypeName(i_type_symbol->attributes().dataType()) << '\'';
+          throw ParseError(os.str(), std::vector{n.begin()});
+        }
+
+        n.m_data_type = ASTNodeDataType::build<ASTNodeDataType::type_id_t>(type_name_id);
       }
     }
     for (auto& child : n.children) {
@@ -339,22 +357,6 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
           value_type = getMatrixDataType(image_node);
         } else if (image_node.is_type<language::string_type>()) {
           value_type = ASTNodeDataType::build<ASTNodeDataType::string_t>();
-        } else if (image_node.is_type<language::type_name_id>()) {
-          const std::string& type_name_id = image_node.string();
-
-          auto& symbol_table = *image_node.m_symbol_table;
-
-          const auto [i_type_symbol, found] = symbol_table.find(type_name_id, image_node.begin());
-          if (not found) {
-            throw ParseError("undefined type identifier", std::vector{image_node.begin()});
-          } else if (i_type_symbol->attributes().dataType() != ASTNodeDataType::type_name_id_t) {
-            std::ostringstream os;
-            os << "invalid type identifier, '" << type_name_id << "' was previously defined as a '"
-               << dataTypeName(i_type_symbol->attributes().dataType()) << '\'';
-            throw ParseError(os.str(), std::vector{image_node.begin()});
-          }
-
-          value_type = ASTNodeDataType::build<ASTNodeDataType::type_id_t>(type_name_id);
         }
 
         // LCOV_EXCL_START
diff --git a/src/language/ast/ASTSymbolTableBuilder.cpp b/src/language/ast/ASTSymbolTableBuilder.cpp
index 44058de6f3739580dbcf3d34e22970647c6bb2ad..2e019d8db10301f274cfa2946d39f290ed5a4da5 100644
--- a/src/language/ast/ASTSymbolTableBuilder.cpp
+++ b/src/language/ast/ASTSymbolTableBuilder.cpp
@@ -77,10 +77,12 @@ ASTSymbolTableBuilder::buildSymbolTable(ASTNode& n, std::shared_ptr<SymbolTable>
       } else if (n.is_type<language::function_definition>()) {
         auto register_and_initialize_symbol = [&](const ASTNode& argument_node) {
           if (symbol_table->getBuiltinFunctionSymbolList(argument_node.string(), argument_node.begin()).size() > 0) {
+            // LCOV_EXCL_START
             std::ostringstream error_message;
             error_message << "symbol '" << rang::fg::red << argument_node.string() << rang::fg::reset
                           << "' already denotes a builtin function!";
             throw ParseError(error_message.str(), std::vector{argument_node.begin()});
+            // LCOV_EXCL_STOP
           }
 
           auto [i_symbol, success] = symbol_table->add(argument_node.string(), argument_node.begin());
diff --git a/src/language/modules/BinaryOperatorRegisterForVh.cpp b/src/language/modules/BinaryOperatorRegisterForVh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..90f0c295a77578a59a7eae42706333d37d9084cc
--- /dev/null
+++ b/src/language/modules/BinaryOperatorRegisterForVh.cpp
@@ -0,0 +1,305 @@
+#include <language/modules/BinaryOperatorRegisterForVh.hpp>
+
+#include <language/modules/SchemeModule.hpp>
+#include <language/utils/BinaryOperatorProcessorBuilder.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/DataVariant.hpp>
+#include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
+#include <language/utils/OperatorRepository.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+
+void
+BinaryOperatorRegisterForVh::_register_plus()
+{
+  OperatorRepository& repository = OperatorRepository::instance();
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>, bool,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>, double,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, bool>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, int64_t>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, uint64_t>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, double>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyVector<1>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyVector<2>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyVector<3>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<1>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<2>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<3>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<1>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<2>>>());
+
+  repository.addBinaryOperator<language::plus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<3>>>());
+}
+
+void
+BinaryOperatorRegisterForVh::_register_minus()
+{
+  OperatorRepository& repository = OperatorRepository::instance();
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>, bool,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, bool>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, int64_t>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, uint64_t>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, double>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyVector<1>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyVector<2>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyVector<3>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<1>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<2>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<3>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<1>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<2>>>());
+
+  repository.addBinaryOperator<language::minus_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<3>>>());
+}
+
+void
+BinaryOperatorRegisterForVh::_register_multiply()
+{
+  OperatorRepository& repository = OperatorRepository::instance();
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    bool, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, bool>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, int64_t>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, uint64_t>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, double>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<1>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<2>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<3>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<1>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<2>>>());
+
+  repository.addBinaryOperator<language::multiply_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<3>>>());
+}
+
+void
+BinaryOperatorRegisterForVh::_register_divide()
+{
+  OperatorRepository& repository = OperatorRepository::instance();
+
+  repository.addBinaryOperator<language::divide_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::divide_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>, bool,
+                                                    std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::divide_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::divide_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+
+  repository.addBinaryOperator<language::divide_op>(
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
+                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
+}
+
+BinaryOperatorRegisterForVh::BinaryOperatorRegisterForVh()
+{
+  this->_register_plus();
+  this->_register_minus();
+  this->_register_multiply();
+  this->_register_divide();
+}
diff --git a/src/language/modules/BinaryOperatorRegisterForVh.hpp b/src/language/modules/BinaryOperatorRegisterForVh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..86848e1b3037418aab36159b78d0c9f2578b4fde
--- /dev/null
+++ b/src/language/modules/BinaryOperatorRegisterForVh.hpp
@@ -0,0 +1,16 @@
+#ifndef BINARY_OPERATOR_REGISTER_FOR_VH_HPP
+#define BINARY_OPERATOR_REGISTER_FOR_VH_HPP
+
+class BinaryOperatorRegisterForVh
+{
+ private:
+  void _register_plus();
+  void _register_minus();
+  void _register_multiply();
+  void _register_divide();
+
+ public:
+  BinaryOperatorRegisterForVh();
+};
+
+#endif   // BINARY_OPERATOR_REGISTER_FOR_VH_HPP
diff --git a/src/language/modules/CMakeLists.txt b/src/language/modules/CMakeLists.txt
index dfc777168d454337ffbd38ef084f37f50924b117..f7f1baeafe34261a38033dd3d2db828df334ce25 100644
--- a/src/language/modules/CMakeLists.txt
+++ b/src/language/modules/CMakeLists.txt
@@ -1,13 +1,16 @@
 # ------------------- Source files --------------------
 
 add_library(PugsLanguageModules
+  BinaryOperatorRegisterForVh.cpp
   BuiltinModule.cpp
   CoreModule.cpp
   LinearSolverModule.cpp
+  MathFunctionRegisterForVh.cpp
   MathModule.cpp
   MeshModule.cpp
   ModuleRepository.cpp
   SchemeModule.cpp
+  UnaryOperatorRegisterForVh.cpp
   UtilsModule.cpp
   WriterModule.cpp
 )
diff --git a/src/language/modules/MathFunctionRegisterForVh.cpp b/src/language/modules/MathFunctionRegisterForVh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..68b22b552f932a1fb48baeb96d6742a74a4717c2
--- /dev/null
+++ b/src/language/modules/MathFunctionRegisterForVh.cpp
@@ -0,0 +1,150 @@
+#include <language/modules/MathFunctionRegisterForVh.hpp>
+
+#include <language/modules/SchemeModule.hpp>
+#include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+
+MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module)
+{
+  scheme_module._addBuiltinFunction("sqrt",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return sqrt(a); }));
+
+  scheme_module._addBuiltinFunction("abs",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return abs(a); }));
+
+  scheme_module._addBuiltinFunction("sin",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return sin(a); }));
+
+  scheme_module._addBuiltinFunction("cos",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return cos(a); }));
+
+  scheme_module._addBuiltinFunction("tan",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return tan(a); }));
+
+  scheme_module._addBuiltinFunction("asin",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return asin(a); }));
+
+  scheme_module._addBuiltinFunction("acos",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return acos(a); }));
+
+  scheme_module._addBuiltinFunction("atan",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return atan(a); }));
+
+  scheme_module
+    ._addBuiltinFunction("atan2",
+                         std::make_shared<BuiltinFunctionEmbedder<
+                           std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IDiscreteFunction>,
+                                                                    std::shared_ptr<const IDiscreteFunction>)>>(
+                           [](std::shared_ptr<const IDiscreteFunction> a, std::shared_ptr<const IDiscreteFunction> b)
+                             -> std::shared_ptr<const IDiscreteFunction> { return atan2(a, b); }));
+
+  scheme_module
+    ._addBuiltinFunction("atan2",
+                         std::make_shared<BuiltinFunctionEmbedder<
+                           std::shared_ptr<const IDiscreteFunction>(double, std::shared_ptr<const IDiscreteFunction>)>>(
+                           [](double a, std::shared_ptr<const IDiscreteFunction> b)
+                             -> std::shared_ptr<const IDiscreteFunction> { return atan2(a, b); }));
+
+  scheme_module
+    ._addBuiltinFunction("atan2",
+                         std::make_shared<BuiltinFunctionEmbedder<
+                           std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IDiscreteFunction>, double)>>(
+                           [](std::shared_ptr<const IDiscreteFunction> a,
+                              double b) -> std::shared_ptr<const IDiscreteFunction> { return atan2(a, b); }));
+
+  scheme_module._addBuiltinFunction("sinh",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return sinh(a); }));
+
+  scheme_module._addBuiltinFunction("cosh",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return cosh(a); }));
+
+  scheme_module._addBuiltinFunction("tanh",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return tanh(a); }));
+
+  scheme_module._addBuiltinFunction("asinh",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return asinh(a); }));
+
+  scheme_module._addBuiltinFunction("acosh",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return acosh(a); }));
+
+  scheme_module._addBuiltinFunction("atanh",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return atanh(a); }));
+
+  scheme_module._addBuiltinFunction("exp",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return exp(a); }));
+
+  scheme_module._addBuiltinFunction("log",
+                                    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunction>(
+                                      std::shared_ptr<const IDiscreteFunction>)>>(
+                                      [](std::shared_ptr<const IDiscreteFunction> a)
+                                        -> std::shared_ptr<const IDiscreteFunction> { return log(a); }));
+
+  scheme_module
+    ._addBuiltinFunction("pow",
+                         std::make_shared<BuiltinFunctionEmbedder<
+                           std::shared_ptr<const IDiscreteFunction>(double, std::shared_ptr<const IDiscreteFunction>)>>(
+                           [](double a, std::shared_ptr<const IDiscreteFunction> b)
+                             -> std::shared_ptr<const IDiscreteFunction> { return pow(a, b); }));
+
+  scheme_module
+    ._addBuiltinFunction("pow",
+                         std::make_shared<BuiltinFunctionEmbedder<
+                           std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IDiscreteFunction>, double)>>(
+                           [](std::shared_ptr<const IDiscreteFunction> a,
+                              double b) -> std::shared_ptr<const IDiscreteFunction> { return pow(a, b); }));
+
+  scheme_module
+    ._addBuiltinFunction("pow",
+                         std::make_shared<BuiltinFunctionEmbedder<
+                           std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IDiscreteFunction>,
+                                                                    std::shared_ptr<const IDiscreteFunction>)>>(
+                           [](std::shared_ptr<const IDiscreteFunction> a, std::shared_ptr<const IDiscreteFunction> b)
+                             -> std::shared_ptr<const IDiscreteFunction> { return pow(a, b); }));
+}
diff --git a/src/language/modules/MathFunctionRegisterForVh.hpp b/src/language/modules/MathFunctionRegisterForVh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..95ee461bc8e999fc75e273c31c0aea4505edda0e
--- /dev/null
+++ b/src/language/modules/MathFunctionRegisterForVh.hpp
@@ -0,0 +1,12 @@
+#ifndef MATH_FUNCTION_REGISTER_FOR_VH_HPP
+#define MATH_FUNCTION_REGISTER_FOR_VH_HPP
+
+class SchemeModule;
+
+class MathFunctionRegisterForVh
+{
+ public:
+  MathFunctionRegisterForVh(SchemeModule& module);
+};
+
+#endif   // MATH_FUNCTION_REGISTER_FOR_VH_HPP
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index 7c0d55cf1f23e9f5b186f220fbe2608e2ada7b7f..d6f9227f4b66e1f1abc442ca36f2cc21a8b9b04b 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -1,14 +1,16 @@
 #include <language/modules/SchemeModule.hpp>
 
+#include <language/modules/BinaryOperatorRegisterForVh.hpp>
+#include <language/modules/MathFunctionRegisterForVh.hpp>
+#include <language/modules/UnaryOperatorRegisterForVh.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
-#include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
-#include <language/utils/OperatorRepository.hpp>
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/Mesh.hpp>
 #include <scheme/AcousticSolver.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
+#include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 #include <scheme/DiscreteFunctionUtils.hpp>
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
@@ -43,6 +45,15 @@ SchemeModule::SchemeModule()
 
                                     ));
 
+  this->_addBuiltinFunction("P0Vector",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunctionDescriptor>()>>(
+                              []() -> std::shared_ptr<const IDiscreteFunctionDescriptor> {
+                                return std::make_shared<DiscreteFunctionDescriptorP0Vector>();
+                              }
+
+                              ));
+
   this->_addBuiltinFunction(
     "interpolate",
     std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
@@ -64,7 +75,17 @@ SchemeModule::SchemeModule()
       [](std::shared_ptr<const IMesh> mesh,
          std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
          const FunctionSymbolId& function_id) -> std::shared_ptr<const IDiscreteFunction> {
-        return DiscreteFunctionInterpoler{mesh, discrete_function_descriptor, function_id}.interpolate();
+        switch (discrete_function_descriptor->type()) {
+        case DiscreteFunctionType::P0: {
+          return DiscreteFunctionInterpoler{mesh, discrete_function_descriptor, function_id}.interpolate();
+        }
+        case DiscreteFunctionType::P0Vector: {
+          return DiscreteFunctionVectorInterpoler{mesh, discrete_function_descriptor, {function_id}}.interpolate();
+        }
+        default: {
+          throw NormalError("invalid function descriptor type");
+        }
+        }
       }
 
       ));
@@ -296,82 +317,13 @@ SchemeModule::SchemeModule()
                               [](const std::shared_ptr<const IDiscreteFunction>& c) -> double { return acoustic_dt(c); }
 
                               ));
+
+  MathFunctionRegisterForVh{*this};
 }
 
 void
 SchemeModule::registerOperators() const
 {
-  OperatorRepository& repository = OperatorRepository::instance();
-
-  repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::divide_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    bool, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<1>, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<2>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<3>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<1>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<2>>>());
-
-  repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyMatrix<3>>>());
+  BinaryOperatorRegisterForVh{};
+  UnaryOperatorRegisterForVh{};
 }
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index 8e5ee95d8f6797e705cba0436829a565c7714e6c..758b5f9c56fcdc928adfc4678a0a02a8dcfa5bd8 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -27,6 +27,8 @@ inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IDiscreteFu
 
 class SchemeModule : public BuiltinModule
 {
+  friend class MathFunctionRegisterForVh;
+
  public:
   std::string_view
   name() const final
diff --git a/src/language/modules/UnaryOperatorRegisterForVh.cpp b/src/language/modules/UnaryOperatorRegisterForVh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b1dc85aa9dd11be2f5a6d159d8f1a70254d68c7b
--- /dev/null
+++ b/src/language/modules/UnaryOperatorRegisterForVh.cpp
@@ -0,0 +1,24 @@
+#include <language/modules/UnaryOperatorRegisterForVh.hpp>
+
+#include <language/modules/SchemeModule.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/DataVariant.hpp>
+#include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
+#include <language/utils/OperatorRepository.hpp>
+#include <language/utils/UnaryOperatorProcessorBuilder.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+
+void
+UnaryOperatorRegisterForVh::_register_unary_minus()
+{
+  OperatorRepository& repository = OperatorRepository::instance();
+
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, std::shared_ptr<const IDiscreteFunction>,
+                                                   std::shared_ptr<const IDiscreteFunction>>>());
+}
+
+UnaryOperatorRegisterForVh::UnaryOperatorRegisterForVh()
+{
+  this->_register_unary_minus();
+}
diff --git a/src/language/modules/UnaryOperatorRegisterForVh.hpp b/src/language/modules/UnaryOperatorRegisterForVh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..65ea35bb6d661e6257845141b4301ca8921bb169
--- /dev/null
+++ b/src/language/modules/UnaryOperatorRegisterForVh.hpp
@@ -0,0 +1,13 @@
+#ifndef UNARY_OPERATOR_REGISTER_FOR_VH_HPP
+#define UNARY_OPERATOR_REGISTER_FOR_VH_HPP
+
+class UnaryOperatorRegisterForVh
+{
+ private:
+  void _register_unary_minus();
+
+ public:
+  UnaryOperatorRegisterForVh();
+};
+
+#endif   // UNARY_OPERATOR_REGISTER_FOR_VH_HPP
diff --git a/src/language/node_processor/BinaryExpressionProcessor.hpp b/src/language/node_processor/BinaryExpressionProcessor.hpp
index cfbff72617d7b99e68ea9693eca96fa718c3f918..3af45c3871d6d6b4886f3bd3bf510d7c53f333aa 100644
--- a/src/language/node_processor/BinaryExpressionProcessor.hpp
+++ b/src/language/node_processor/BinaryExpressionProcessor.hpp
@@ -8,6 +8,9 @@
 
 #include <type_traits>
 
+template <typename DataType>
+class DataHandler;
+
 template <typename Op>
 struct BinOp;
 
@@ -201,4 +204,113 @@ struct BinaryExpressionProcessor final : public INodeProcessor
   BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
 };
 
+template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
+struct BinaryExpressionProcessor<BinaryOpT, std::shared_ptr<ValueT>, std::shared_ptr<A_DataT>, std::shared_ptr<B_DataT>>
+  final : public INodeProcessor
+{
+ private:
+  ASTNode& m_node;
+
+  PUGS_INLINE DataVariant
+  _eval(const DataVariant& a, const DataVariant& b)
+  {
+    const auto& embedded_a = std::get<EmbeddedData>(a);
+    const auto& embedded_b = std::get<EmbeddedData>(b);
+
+    std::shared_ptr a_ptr = dynamic_cast<const DataHandler<A_DataT>&>(embedded_a.get()).data_ptr();
+
+    std::shared_ptr b_ptr = dynamic_cast<const DataHandler<B_DataT>&>(embedded_b.get()).data_ptr();
+
+    return EmbeddedData(std::make_shared<DataHandler<ValueT>>(BinOp<BinaryOpT>().eval(a_ptr, b_ptr)));
+  }
+
+ public:
+  DataVariant
+  execute(ExecutionPolicy& exec_policy)
+  {
+    try {
+      return this->_eval(m_node.children[0]->execute(exec_policy), m_node.children[1]->execute(exec_policy));
+    }
+    catch (const NormalError& error) {
+      throw ParseError(error.what(), m_node.begin());
+    }
+  }
+
+  BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
+};
+
+template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
+struct BinaryExpressionProcessor<BinaryOpT, std::shared_ptr<ValueT>, A_DataT, std::shared_ptr<B_DataT>> final
+  : public INodeProcessor
+{
+ private:
+  ASTNode& m_node;
+
+  PUGS_INLINE DataVariant
+  _eval(const DataVariant& a, const DataVariant& b)
+  {
+    if constexpr ((std::is_arithmetic_v<A_DataT>) or (is_tiny_vector_v<A_DataT>) or (is_tiny_matrix_v<A_DataT>)) {
+      const auto& a_value    = std::get<A_DataT>(a);
+      const auto& embedded_b = std::get<EmbeddedData>(b);
+
+      std::shared_ptr b_ptr = dynamic_cast<const DataHandler<B_DataT>&>(embedded_b.get()).data_ptr();
+
+      return EmbeddedData(std::make_shared<DataHandler<ValueT>>(BinOp<BinaryOpT>().eval(a_value, b_ptr)));
+    } else {
+      static_assert(std::is_arithmetic_v<A_DataT>, "invalid left hand side type");
+    }
+  }
+
+ public:
+  DataVariant
+  execute(ExecutionPolicy& exec_policy)
+  {
+    try {
+      return this->_eval(m_node.children[0]->execute(exec_policy), m_node.children[1]->execute(exec_policy));
+    }
+    catch (const NormalError& error) {
+      throw ParseError(error.what(), m_node.begin());
+    }
+  }
+
+  BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
+};
+
+template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
+struct BinaryExpressionProcessor<BinaryOpT, std::shared_ptr<ValueT>, std::shared_ptr<A_DataT>, B_DataT> final
+  : public INodeProcessor
+{
+ private:
+  ASTNode& m_node;
+
+  PUGS_INLINE DataVariant
+  _eval(const DataVariant& a, const DataVariant& b)
+  {
+    if constexpr ((std::is_arithmetic_v<B_DataT>) or (is_tiny_matrix_v<B_DataT>) or (is_tiny_vector_v<B_DataT>)) {
+      const auto& embedded_a = std::get<EmbeddedData>(a);
+      const auto& b_value    = std::get<B_DataT>(b);
+
+      std::shared_ptr a_ptr = dynamic_cast<const DataHandler<A_DataT>&>(embedded_a.get()).data_ptr();
+
+      return EmbeddedData(std::make_shared<DataHandler<ValueT>>(BinOp<BinaryOpT>().eval(a_ptr, b_value)));
+    } else {
+      static_assert(std::is_arithmetic_v<B_DataT>, "invalid right hand side type");
+    }
+  }
+
+ public:
+  DataVariant
+  execute(ExecutionPolicy& exec_policy)
+  {
+    try {
+      return this->_eval(m_node.children[0]->execute(exec_policy), m_node.children[1]->execute(exec_policy));
+    }
+    catch (const NormalError& error) {
+      throw ParseError(error.what(), m_node.begin());
+    }
+  }
+
+  BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
+};
+
 #endif   // BINARY_EXPRESSION_PROCESSOR_HPP
diff --git a/src/language/node_processor/UnaryExpressionProcessor.hpp b/src/language/node_processor/UnaryExpressionProcessor.hpp
index 055e3a028be043c8a30189b7e4cccddd99aeef4a..cdc4fc54111fd3a52854160347b5753e3787bcd5 100644
--- a/src/language/node_processor/UnaryExpressionProcessor.hpp
+++ b/src/language/node_processor/UnaryExpressionProcessor.hpp
@@ -5,6 +5,9 @@
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
 
+template <typename DataType>
+class DataHandler;
+
 template <typename Op>
 struct UnaryOp;
 
@@ -52,4 +55,30 @@ class UnaryExpressionProcessor final : public INodeProcessor
   UnaryExpressionProcessor(ASTNode& node) : m_node{node} {}
 };
 
+template <typename UnaryOpT, typename ValueT, typename DataT>
+class UnaryExpressionProcessor<UnaryOpT, std::shared_ptr<ValueT>, std::shared_ptr<DataT>> final : public INodeProcessor
+{
+ private:
+  ASTNode& m_node;
+
+  PUGS_INLINE DataVariant
+  _eval(const DataVariant& a)
+  {
+    const auto& embedded_a = std::get<EmbeddedData>(a);
+
+    std::shared_ptr a_ptr = dynamic_cast<const DataHandler<DataT>&>(embedded_a.get()).data_ptr();
+
+    return EmbeddedData(std::make_shared<DataHandler<ValueT>>(UnaryOp<UnaryOpT>().eval(a_ptr)));
+  }
+
+ public:
+  DataVariant
+  execute(ExecutionPolicy& exec_policy)
+  {
+    return this->_eval(m_node.children[0]->execute(exec_policy));
+  }
+
+  UnaryExpressionProcessor(ASTNode& node) : m_node{node} {}
+};
+
 #endif   // UNARY_EXPRESSION_PROCESSOR_HPP
diff --git a/src/language/utils/BinaryOperatorProcessorBuilder.hpp b/src/language/utils/BinaryOperatorProcessorBuilder.hpp
index eb07b35d028f3eaea3f59537753dfd936a185837..053fd83f51e029b394302c1ef319421e60e4e38f 100644
--- a/src/language/utils/BinaryOperatorProcessorBuilder.hpp
+++ b/src/language/utils/BinaryOperatorProcessorBuilder.hpp
@@ -8,11 +8,6 @@
 #include <language/utils/IBinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/ParseError.hpp>
 
-#include <type_traits>
-
-template <typename DataType>
-class DataHandler;
-
 template <typename OperatorT, typename ValueT, typename A_DataT, typename B_DataT>
 class BinaryOperatorProcessorBuilder final : public IBinaryOperatorProcessorBuilder
 {
@@ -44,113 +39,4 @@ class BinaryOperatorProcessorBuilder final : public IBinaryOperatorProcessorBuil
   }
 };
 
-template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
-struct BinaryExpressionProcessor<BinaryOpT, std::shared_ptr<ValueT>, std::shared_ptr<A_DataT>, std::shared_ptr<B_DataT>>
-  final : public INodeProcessor
-{
- private:
-  ASTNode& m_node;
-
-  PUGS_INLINE DataVariant
-  _eval(const DataVariant& a, const DataVariant& b)
-  {
-    const auto& embedded_a = std::get<EmbeddedData>(a);
-    const auto& embedded_b = std::get<EmbeddedData>(b);
-
-    std::shared_ptr a_ptr = dynamic_cast<const DataHandler<A_DataT>&>(embedded_a.get()).data_ptr();
-
-    std::shared_ptr b_ptr = dynamic_cast<const DataHandler<B_DataT>&>(embedded_b.get()).data_ptr();
-
-    return EmbeddedData(std::make_shared<DataHandler<ValueT>>(BinOp<BinaryOpT>().eval(a_ptr, b_ptr)));
-  }
-
- public:
-  DataVariant
-  execute(ExecutionPolicy& exec_policy)
-  {
-    try {
-      return this->_eval(m_node.children[0]->execute(exec_policy), m_node.children[1]->execute(exec_policy));
-    }
-    catch (const NormalError& error) {
-      throw ParseError(error.what(), m_node.begin());
-    }
-  }
-
-  BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
-};
-
-template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
-struct BinaryExpressionProcessor<BinaryOpT, std::shared_ptr<ValueT>, A_DataT, std::shared_ptr<B_DataT>> final
-  : public INodeProcessor
-{
- private:
-  ASTNode& m_node;
-
-  PUGS_INLINE DataVariant
-  _eval(const DataVariant& a, const DataVariant& b)
-  {
-    if constexpr ((std::is_arithmetic_v<A_DataT>) or (is_tiny_vector_v<A_DataT>) or (is_tiny_matrix_v<A_DataT>)) {
-      const auto& a_value    = std::get<A_DataT>(a);
-      const auto& embedded_b = std::get<EmbeddedData>(b);
-
-      std::shared_ptr b_ptr = dynamic_cast<const DataHandler<B_DataT>&>(embedded_b.get()).data_ptr();
-
-      return EmbeddedData(std::make_shared<DataHandler<ValueT>>(BinOp<BinaryOpT>().eval(a_value, b_ptr)));
-    } else {
-      static_assert(std::is_arithmetic_v<A_DataT>, "invalid left hand side type");
-    }
-  }
-
- public:
-  DataVariant
-  execute(ExecutionPolicy& exec_policy)
-  {
-    try {
-      return this->_eval(m_node.children[0]->execute(exec_policy), m_node.children[1]->execute(exec_policy));
-    }
-    catch (const NormalError& error) {
-      throw ParseError(error.what(), m_node.begin());
-    }
-  }
-
-  BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
-};
-
-template <typename BinaryOpT, typename ValueT, typename A_DataT, typename B_DataT>
-struct BinaryExpressionProcessor<BinaryOpT, std::shared_ptr<ValueT>, std::shared_ptr<A_DataT>, B_DataT> final
-  : public INodeProcessor
-{
- private:
-  ASTNode& m_node;
-
-  PUGS_INLINE DataVariant
-  _eval(const DataVariant& a, const DataVariant& b)
-  {
-    if constexpr ((std::is_arithmetic_v<B_DataT>) or (is_tiny_matrix_v<B_DataT>) or (is_tiny_vector_v<B_DataT>)) {
-      const auto& embedded_a = std::get<EmbeddedData>(a);
-      const auto& b_value    = std::get<B_DataT>(b);
-
-      std::shared_ptr a_ptr = dynamic_cast<const DataHandler<A_DataT>&>(embedded_a.get()).data_ptr();
-
-      return EmbeddedData(std::make_shared<DataHandler<ValueT>>(BinOp<BinaryOpT>().eval(a_ptr, b_value)));
-    } else {
-      static_assert(std::is_arithmetic_v<B_DataT>, "invalid right hand side type");
-    }
-  }
-
- public:
-  DataVariant
-  execute(ExecutionPolicy& exec_policy)
-  {
-    try {
-      return this->_eval(m_node.children[0]->execute(exec_policy), m_node.children[1]->execute(exec_policy));
-    }
-    catch (const NormalError& error) {
-      throw ParseError(error.what(), m_node.begin());
-    }
-  }
-
-  BinaryExpressionProcessor(ASTNode& node) : m_node{node} {}
-};
-
 #endif   // BINARY_OPERATOR_PROCESSOR_BUILDER_HPP
diff --git a/src/language/utils/CMakeLists.txt b/src/language/utils/CMakeLists.txt
index bed3da329d5534e8aa4f48ef48cc0751dfa00f3b..216a6be6a15971fe93ca2f4341f720e8b6e5cde3 100644
--- a/src/language/utils/CMakeLists.txt
+++ b/src/language/utils/CMakeLists.txt
@@ -24,6 +24,7 @@ add_library(PugsLanguageUtils
   DataVariant.cpp
   EmbeddedData.cpp
   EmbeddedIDiscreteFunctionOperators.cpp
+  EmbeddedIDiscreteFunctionMathFunctions.cpp
   FunctionSymbolId.cpp
   IncDecOperatorRegisterForN.cpp
   IncDecOperatorRegisterForR.cpp
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2939e011af950fc414cde730e2c1206bf8f4ae01
--- /dev/null
+++ b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
@@ -0,0 +1,307 @@
+#include <language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp>
+
+#include <language/utils/EmbeddedIDiscreteFunctionUtils.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+
+#define DISCRETE_FUNCTION_CALL(FUNCTION, ARG)                                                                         \
+  if (ARG->dataType() == ASTNodeDataType::double_t and ARG->descriptor().type() == DiscreteFunctionType::P0) {        \
+    switch (f->mesh()->dimension()) {                                                                                 \
+    case 1: {                                                                                                         \
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;                                                     \
+      return std::make_shared<const DiscreteFunctionType>(FUNCTION(dynamic_cast<const DiscreteFunctionType&>(*ARG))); \
+    }                                                                                                                 \
+    case 2: {                                                                                                         \
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;                                                     \
+      return std::make_shared<const DiscreteFunctionType>(FUNCTION(dynamic_cast<const DiscreteFunctionType&>(*ARG))); \
+    }                                                                                                                 \
+    case 3: {                                                                                                         \
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;                                                     \
+      return std::make_shared<const DiscreteFunctionType>(FUNCTION(dynamic_cast<const DiscreteFunctionType&>(*ARG))); \
+    }                                                                                                                 \
+    default: {                                                                                                        \
+      throw UnexpectedError("invalid mesh dimension");                                                                \
+    }                                                                                                                 \
+    }                                                                                                                 \
+  } else {                                                                                                            \
+    throw UnexpectedError("invalid operand type " + operand_type_name(ARG));                                          \
+  }
+
+std::shared_ptr<const IDiscreteFunction>
+sqrt(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(sqrt, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+abs(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(abs, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+sin(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(sin, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+cos(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(cos, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+tan(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(tan, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+asin(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(asin, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+acos(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(acos, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+atan(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(atan, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+atan2(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  if ((f->dataType() == ASTNodeDataType::double_t and f->descriptor().type() == DiscreteFunctionType::P0) and
+      (f->dataType() == ASTNodeDataType::double_t and g->descriptor().type() == DiscreteFunctionType::P0)) {
+    std::shared_ptr mesh = getCommonMesh({f, g});
+
+    if (mesh.use_count() == 0) {
+      throw NormalError("operands are defined on different meshes");
+    }
+
+    switch (mesh->dimension()) {
+    case 1: {
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;
+      return std::make_shared<const DiscreteFunctionType>(
+        atan2(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
+    }
+    case 2: {
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
+      return std::make_shared<const DiscreteFunctionType>(
+        atan2(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
+    }
+    case 3: {
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
+      return std::make_shared<const DiscreteFunctionType>(
+        atan2(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  } else {
+    std::stringstream os;
+    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    throw NormalError(os.str());
+  }
+}
+
+std::shared_ptr<const IDiscreteFunction>
+atan2(const double a, const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  if (f->dataType() == ASTNodeDataType::double_t and f->descriptor().type() == DiscreteFunctionType::P0) {
+    switch (f->mesh()->dimension()) {
+    case 1: {
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;
+      return std::make_shared<const DiscreteFunctionType>(atan2(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
+    }
+    case 2: {
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
+      return std::make_shared<const DiscreteFunctionType>(atan2(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
+    }
+    case 3: {
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
+      return std::make_shared<const DiscreteFunctionType>(atan2(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  } else {
+    throw UnexpectedError("invalid operand type " + operand_type_name(f));
+  }
+}
+
+std::shared_ptr<const IDiscreteFunction>
+atan2(const std::shared_ptr<const IDiscreteFunction>& f, const double a)
+{
+  if (f->dataType() == ASTNodeDataType::double_t and f->descriptor().type() == DiscreteFunctionType::P0) {
+    switch (f->mesh()->dimension()) {
+    case 1: {
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;
+      return std::make_shared<const DiscreteFunctionType>(atan2(dynamic_cast<const DiscreteFunctionType&>(*f), a));
+    }
+    case 2: {
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
+      return std::make_shared<const DiscreteFunctionType>(atan2(dynamic_cast<const DiscreteFunctionType&>(*f), a));
+    }
+    case 3: {
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
+      return std::make_shared<const DiscreteFunctionType>(atan2(dynamic_cast<const DiscreteFunctionType&>(*f), a));
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  } else {
+    throw UnexpectedError("invalid operand type " + operand_type_name(f));
+  }
+}
+
+std::shared_ptr<const IDiscreteFunction>
+sinh(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(sinh, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+cosh(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(cosh, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+tanh(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(tanh, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+asinh(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(asinh, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+acosh(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(acosh, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+atanh(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(atanh, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+exp(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(exp, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+log(const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  DISCRETE_FUNCTION_CALL(log, f);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+pow(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  if ((f->dataType() == ASTNodeDataType::double_t and f->descriptor().type() == DiscreteFunctionType::P0) and
+      (f->dataType() == ASTNodeDataType::double_t and g->descriptor().type() == DiscreteFunctionType::P0)) {
+    std::shared_ptr mesh = getCommonMesh({f, g});
+
+    if (mesh.use_count() == 0) {
+      throw NormalError("operands are defined on different meshes");
+    }
+
+    switch (mesh->dimension()) {
+    case 1: {
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;
+      return std::make_shared<const DiscreteFunctionType>(
+        pow(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
+    }
+    case 2: {
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
+      return std::make_shared<const DiscreteFunctionType>(
+        pow(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
+    }
+    case 3: {
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
+      return std::make_shared<const DiscreteFunctionType>(
+        pow(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  } else {
+    std::stringstream os;
+    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    throw NormalError(os.str());
+  }
+}
+
+std::shared_ptr<const IDiscreteFunction>
+pow(const double a, const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  if (f->dataType() == ASTNodeDataType::double_t and f->descriptor().type() == DiscreteFunctionType::P0) {
+    switch (f->mesh()->dimension()) {
+    case 1: {
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;
+      return std::make_shared<const DiscreteFunctionType>(pow(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
+    }
+    case 2: {
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
+      return std::make_shared<const DiscreteFunctionType>(pow(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
+    }
+    case 3: {
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
+      return std::make_shared<const DiscreteFunctionType>(pow(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  } else {
+    throw UnexpectedError("invalid operand type " + operand_type_name(f));
+  }
+}
+
+std::shared_ptr<const IDiscreteFunction>
+pow(const std::shared_ptr<const IDiscreteFunction>& f, const double a)
+{
+  if (f->dataType() == ASTNodeDataType::double_t and f->descriptor().type() == DiscreteFunctionType::P0) {
+    switch (f->mesh()->dimension()) {
+    case 1: {
+      using DiscreteFunctionType = DiscreteFunctionP0<1, double>;
+      return std::make_shared<const DiscreteFunctionType>(pow(dynamic_cast<const DiscreteFunctionType&>(*f), a));
+    }
+    case 2: {
+      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
+      return std::make_shared<const DiscreteFunctionType>(pow(dynamic_cast<const DiscreteFunctionType&>(*f), a));
+    }
+    case 3: {
+      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
+      return std::make_shared<const DiscreteFunctionType>(pow(dynamic_cast<const DiscreteFunctionType&>(*f), a));
+    }
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+    }
+  } else {
+    throw UnexpectedError("invalid operand type " + operand_type_name(f));
+  }
+}
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0030f46759dfe683a09228131140e98ec205fc8b
--- /dev/null
+++ b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp
@@ -0,0 +1,57 @@
+#ifndef EMBEDDED_I_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
+#define EMBEDDED_I_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <memory>
+
+class IDiscreteFunction;
+
+std::shared_ptr<const IDiscreteFunction> sqrt(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> abs(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> sin(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> cos(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> tan(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> asin(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> acos(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> atan(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> atan2(const std::shared_ptr<const IDiscreteFunction>&,
+                                               const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> atan2(const double, const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> atan2(const std::shared_ptr<const IDiscreteFunction>&, const double);
+
+std::shared_ptr<const IDiscreteFunction> sinh(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> cosh(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> tanh(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> asinh(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> acosh(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> atanh(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> exp(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> log(const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> pow(const std::shared_ptr<const IDiscreteFunction>&,
+                                             const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> pow(const double, const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> pow(const std::shared_ptr<const IDiscreteFunction>&, const double);
+
+#endif   // EMBEDDED_I_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp b/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
index af0ac957a430cecc6bcdd8b643064816a2402b94..0e5a789ce31e645b8ccdde6c48009eca4275d76b 100644
--- a/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
+++ b/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
@@ -1,56 +1,133 @@
 #include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
 
 #include <language/node_processor/BinaryExpressionProcessor.hpp>
+#include <language/node_processor/UnaryExpressionProcessor.hpp>
+#include <language/utils/EmbeddedIDiscreteFunctionUtils.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
 #include <scheme/IDiscreteFunction.hpp>
 #include <utils/Exceptions.hpp>
 
-template <typename T>
+template <typename LHS_T, typename RHS_T>
 PUGS_INLINE std::string
-name(const T&)
+invalid_operands(const LHS_T& f, const RHS_T& g)
 {
-  return dataTypeName(ast_node_data_type_from<T>);
+  std::ostringstream os;
+  os << "undefined binary operator\n";
+  os << "note: incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+  return os.str();
 }
 
-template <>
-PUGS_INLINE std::string
-name(const IDiscreteFunction& f)
+// unary operators
+template <typename UnaryOperatorT, typename DiscreteFunctionT>
+std::shared_ptr<const IDiscreteFunction>
+applyUnaryOperation(const DiscreteFunctionT& f)
 {
-  return "Vh(" + name(f.descriptor().type()) + ":" + dataTypeName(f.dataType()) + ")";
+  return std::make_shared<decltype(UnaryOp<UnaryOperatorT>{}.eval(f))>(UnaryOp<UnaryOperatorT>{}.eval(f));
 }
 
-template <>
-PUGS_INLINE std::string
-name(const std::shared_ptr<const IDiscreteFunction>& f)
+template <typename UnaryOperatorT, size_t Dimension>
+std::shared_ptr<const IDiscreteFunction>
+applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
 {
-  return "Vh(" + name(f->descriptor().type()) + ":" + dataTypeName(f->dataType()) + ")";
+  switch (f->descriptor().type()) {
+  case DiscreteFunctionType::P0: {
+    switch (f->dataType()) {
+    case ASTNodeDataType::double_t: {
+      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*f);
+      return applyUnaryOperation<UnaryOperatorT>(fh);
+    }
+    case ASTNodeDataType::vector_t: {
+      switch (f->dataType().dimension()) {
+      case 1: {
+        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*f);
+        return applyUnaryOperation<UnaryOperatorT>(fh);
+      }
+      case 2: {
+        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*f);
+        return applyUnaryOperation<UnaryOperatorT>(fh);
+      }
+      case 3: {
+        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*f);
+        return applyUnaryOperation<UnaryOperatorT>(fh);
+      }
+      default: {
+        throw UnexpectedError("invalid operand type " + operand_type_name(f));
+      }
+      }
+    }
+    case ASTNodeDataType::matrix_t: {
+      Assert(f->dataType().nbRows() == f->dataType().nbColumns());
+      switch (f->dataType().nbRows()) {
+      case 1: {
+        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*f);
+        return applyUnaryOperation<UnaryOperatorT>(fh);
+      }
+      case 2: {
+        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*f);
+        return applyUnaryOperation<UnaryOperatorT>(fh);
+      }
+      case 3: {
+        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*f);
+        return applyUnaryOperation<UnaryOperatorT>(fh);
+      }
+      default: {
+        throw UnexpectedError("invalid operand type " + operand_type_name(f));
+      }
+      }
+    }
+    default: {
+      throw UnexpectedError("invalid operand type " + operand_type_name(f));
+    }
+    }
+    break;
+  }
+  case DiscreteFunctionType::P0Vector: {
+    switch (f->dataType()) {
+    case ASTNodeDataType::double_t: {
+      auto fh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*f);
+      return applyUnaryOperation<UnaryOperatorT>(fh);
+    }
+    default: {
+      throw UnexpectedError("invalid operand type " + operand_type_name(f));
+    }
+    }
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid operand type " + operand_type_name(f));
+  }
+  }
 }
 
-PUGS_INLINE
-bool
-isSameDiscretization(const IDiscreteFunction& f, const IDiscreteFunction& g)
+template <typename UnaryOperatorT>
+std::shared_ptr<const IDiscreteFunction>
+applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
 {
-  return (f.dataType() == g.dataType()) and (f.descriptor().type() == g.descriptor().type());
+  switch (f->mesh()->dimension()) {
+  case 1: {
+    return applyUnaryOperation<UnaryOperatorT, 1>(f);
+  }
+  case 2: {
+    return applyUnaryOperation<UnaryOperatorT, 2>(f);
+  }
+  case 3: {
+    return applyUnaryOperation<UnaryOperatorT, 3>(f);
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
 }
 
-PUGS_INLINE
-bool
-isSameDiscretization(const std::shared_ptr<const IDiscreteFunction>& f,
-                     const std::shared_ptr<const IDiscreteFunction>& g)
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f)
 {
-  return (f->dataType() == g->dataType()) and (f->descriptor().type() == g->descriptor().type());
+  return applyUnaryOperation<language::unary_minus>(f);
 }
 
-template <typename LHS_T, typename RHS_T>
-PUGS_INLINE std::string
-invalid_operands(const LHS_T& f, const RHS_T& g)
-{
-  std::ostringstream os;
-  os << "undefined binary operator\n";
-  os << "note: incompatible operand types " << name(f) << " and " << name(g);
-  return os.str();
-}
+// binary operators
 
 template <typename BinOperatorT, typename DiscreteFunctionT>
 std::shared_ptr<const IDiscreteFunction>
@@ -89,7 +166,7 @@ innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
         auto gh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*g);
 
         if (fh.size() != gh.size()) {
-          throw NormalError(name(f) + " spaces have different sizes");
+          throw NormalError(operand_type_name(f) + " spaces have different sizes");
         }
 
         return innerCompositionLaw<BinOperatorT>(fh, gh);
@@ -149,12 +226,12 @@ innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
       return innerCompositionLaw<BinOperatorT>(fh, gh);
     }
     default: {
-      throw UnexpectedError("invalid data type Vh(" + dataTypeName(g->dataType()) + ")");
+      throw UnexpectedError("invalid data type " + operand_type_name(f));
     }
     }
   }
   default: {
-    throw UnexpectedError("invalid data type Vh(" + dataTypeName(g->dataType()) + ")");
+    throw UnexpectedError("invalid data type " + operand_type_name(f));
   }
   }
 }
@@ -164,14 +241,16 @@ std::shared_ptr<const IDiscreteFunction>
 innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
                     const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  if (f->mesh() != g->mesh()) {
-    throw NormalError("discrete functions defined on different meshes");
+  std::shared_ptr mesh = getCommonMesh({f, g});
+  if (mesh.use_count() == 0) {
+    throw NormalError("operands are defined on different meshes");
   }
+
   if (not isSameDiscretization(f, g)) {
     throw NormalError(invalid_operands(f, g));
   }
 
-  switch (f->mesh()->dimension()) {
+  switch (mesh->dimension()) {
   case 1: {
     return innerCompositionLaw<BinOperatorT, 1>(f, g);
   }
@@ -221,7 +300,6 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
                          std::is_same_v<DiscreteFunctionT, DiscreteFunctionP0<Dimension, double>>) {
       if (g->descriptor().type() == DiscreteFunctionType::P0Vector) {
         auto gh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*g);
-
         return applyBinaryOperation<BinOperatorT>(fh, gh);
       } else {
         throw NormalError(invalid_operands(fh, g));
@@ -264,7 +342,7 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
         }
       }
       default: {
-        throw UnexpectedError("invalid rhs data type Vh(" + dataTypeName(g->dataType()) + ")");
+        throw UnexpectedError("invalid rhs data type " + operand_type_name(g));
       }
       }
     } else {
@@ -291,7 +369,7 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
         return applyBinaryOperation<BinOperatorT>(fh, gh);
       }
       default: {
-        throw UnexpectedError("invalid rhs data type Vh(" + dataTypeName(g->dataType()) + ")");
+        throw UnexpectedError("invalid rhs data type " + operand_type_name(g));
       }
       }
     } else {
@@ -299,7 +377,7 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
     }
   }
   default: {
-    throw UnexpectedError("invalid rhs data type Vh(" + dataTypeName(g->dataType()) + ")");
+    throw UnexpectedError("invalid rhs data type " + operand_type_name(g));
   }
   }
 }
@@ -337,7 +415,7 @@ applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
       return applyBinaryOperation<BinOperatorT, Dimension>(fh, g);
     }
     default: {
-      throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+      throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
     }
     }
   }
@@ -352,13 +430,14 @@ std::shared_ptr<const IDiscreteFunction>
 applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
                      const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  if (f->mesh() != g->mesh()) {
-    throw NormalError("functions defined on different meshes");
+  std::shared_ptr mesh = getCommonMesh({f, g});
+  if (mesh.use_count() == 0) {
+    throw NormalError("operands are defined on different meshes");
   }
 
   Assert(not isSameDiscretization(f, g), "should call inner composition instead");
 
-  switch (f->mesh()->dimension()) {
+  switch (mesh->dimension()) {
   case 1: {
     return applyBinaryOperation<BinOperatorT, 1>(f, g);
   }
@@ -377,13 +456,21 @@ applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
 std::shared_ptr<const IDiscreteFunction>
 operator+(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  return innerCompositionLaw<language::plus_op>(f, g);
+  if (isSameDiscretization(f, g)) {
+    return innerCompositionLaw<language::plus_op>(f, g);
+  } else {
+    throw NormalError(invalid_operands(f, g));
+  }
 }
 
 std::shared_ptr<const IDiscreteFunction>
 operator-(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  return innerCompositionLaw<language::minus_op>(f, g);
+  if (isSameDiscretization(f, g)) {
+    return innerCompositionLaw<language::minus_op>(f, g);
+  } else {
+    throw NormalError(invalid_operands(f, g));
+  }
 }
 
 std::shared_ptr<const IDiscreteFunction>
@@ -413,6 +500,42 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const DiscreteFunctionT&
   using lhs_data_type = std::decay_t<DataType>;
   using rhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
 
+  if constexpr (std::is_same_v<language::multiply_op, BinOperatorT>) {
+    if constexpr (std::is_same_v<lhs_data_type, double>) {
+      return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(a, f))>(BinOp<BinOperatorT>{}.eval(a, f));
+    } else if constexpr (is_tiny_matrix_v<lhs_data_type> and
+                         (is_tiny_matrix_v<rhs_data_type> or is_tiny_vector_v<rhs_data_type>)) {
+      return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(a, f))>(BinOp<BinOperatorT>{}.eval(a, f));
+    } else {
+      throw NormalError(invalid_operands(a, f));
+    }
+  } else if constexpr (std::is_same_v<language::plus_op, BinOperatorT> or
+                       std::is_same_v<language::minus_op, BinOperatorT>) {
+    if constexpr (std::is_same_v<lhs_data_type, double> and std::is_arithmetic_v<rhs_data_type>) {
+      return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(a, f))>(BinOp<BinOperatorT>{}.eval(a, f));
+    } else if constexpr (std::is_same_v<lhs_data_type, rhs_data_type>) {
+      return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(a, f))>(BinOp<BinOperatorT>{}.eval(a, f));
+    } else {
+      throw NormalError(invalid_operands(a, f));
+    }
+  } else if constexpr (std::is_same_v<language::divide_op, BinOperatorT>) {
+    if constexpr (std::is_same_v<lhs_data_type, double> and std::is_arithmetic_v<rhs_data_type>) {
+      return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(a, f))>(BinOp<BinOperatorT>{}.eval(a, f));
+    } else {
+      throw NormalError(invalid_operands(a, f));
+    }
+  } else {
+    throw NormalError(invalid_operands(a, f));
+  }
+}
+
+template <typename BinOperatorT, typename DataType, typename DiscreteFunctionT>
+std::shared_ptr<const IDiscreteFunction>
+applyBinaryOperationToVectorWithLeftConstant(const DataType& a, const DiscreteFunctionT& f)
+{
+  using lhs_data_type = std::decay_t<DataType>;
+  using rhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
+
   if constexpr (std::is_same_v<language::multiply_op, BinOperatorT>) {
     if constexpr (std::is_same_v<lhs_data_type, double>) {
       return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(a, f))>(BinOp<BinOperatorT>{}.eval(a, f));
@@ -441,7 +564,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
       return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
     } else if (f->descriptor().type() == DiscreteFunctionType::P0Vector) {
       auto fh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*f);
-      return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
+      return applyBinaryOperationToVectorWithLeftConstant<BinOperatorT>(a, fh);
     } else {
       throw NormalError(invalid_operands(a, f));
     }
@@ -474,7 +597,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         }
       }
       default: {
-        throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
       }
       }
     } else {
@@ -492,7 +615,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
       }
       default: {
-        throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
       }
       }
     }
@@ -526,7 +649,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         }
       }
       default: {
-        throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
       }
       }
     } else {
@@ -544,7 +667,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
       }
       default: {
-        throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
       }
       }
     }
@@ -579,7 +702,7 @@ template <typename BinOperatorT, typename DataType, typename DiscreteFunctionT>
 std::shared_ptr<const IDiscreteFunction>
 applyBinaryOperationWithRightConstant(const DiscreteFunctionT& f, const DataType& a)
 {
-  Assert(f.descriptor().type() != DiscreteFunctionType::P0);
+  Assert(f.descriptor().type() == DiscreteFunctionType::P0);
 
   using lhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
   using rhs_data_type = std::decay_t<DataType>;
@@ -588,7 +711,16 @@ applyBinaryOperationWithRightConstant(const DiscreteFunctionT& f, const DataType
     if constexpr (is_tiny_matrix_v<lhs_data_type> and is_tiny_matrix_v<rhs_data_type>) {
       return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(f, a))>(BinOp<BinOperatorT>{}.eval(f, a));
     } else if constexpr (std::is_same_v<lhs_data_type, double> and
-                         (is_tiny_matrix_v<rhs_data_type> or is_tiny_vector_v<rhs_data_type>)) {
+                         (is_tiny_matrix_v<rhs_data_type> or is_tiny_vector_v<rhs_data_type> or
+                          std::is_arithmetic_v<rhs_data_type>)) {
+      return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(f, a))>(BinOp<BinOperatorT>{}.eval(f, a));
+    } else {
+      throw NormalError(invalid_operands(f, a));
+    }
+  } else if constexpr (std::is_same_v<language::plus_op, BinOperatorT> or
+                       std::is_same_v<language::minus_op, BinOperatorT>) {
+    if constexpr ((std::is_same_v<lhs_data_type, rhs_data_type>) or
+                  (std::is_arithmetic_v<lhs_data_type> and std::is_arithmetic_v<rhs_data_type>)) {
       return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(f, a))>(BinOp<BinOperatorT>{}.eval(f, a));
     } else {
       throw NormalError(invalid_operands(f, a));
@@ -643,7 +775,7 @@ applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunct
         }
       }
       default: {
-        throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
       }
       }
     } else {
@@ -661,7 +793,7 @@ applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunct
         return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
       }
       default: {
-        throw UnexpectedError("invalid lhs data type Vh(" + dataTypeName(f->dataType()) + ")");
+        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
       }
       }
     }
@@ -692,12 +824,186 @@ applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunct
   }
 }
 
+std::shared_ptr<const IDiscreteFunction>
+operator+(const double& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const double& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const TinyVector<1>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const TinyVector<2>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const TinyVector<3>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const TinyMatrix<1>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const TinyMatrix<2>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const TinyMatrix<3>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<1>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<2>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<3>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const TinyMatrix<1>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const TinyMatrix<2>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator+(const std::shared_ptr<const IDiscreteFunction>& f, const TinyMatrix<3>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const double& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const double& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const TinyVector<1>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const TinyVector<2>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const TinyVector<3>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const TinyMatrix<1>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const TinyMatrix<2>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const TinyMatrix<3>& f, const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<1>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<2>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<3>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const TinyMatrix<1>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const TinyMatrix<2>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
+std::shared_ptr<const IDiscreteFunction>
+operator-(const std::shared_ptr<const IDiscreteFunction>& f, const TinyMatrix<3>& g)
+{
+  return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+}
+
 std::shared_ptr<const IDiscreteFunction>
 operator*(const double& a, const std::shared_ptr<const IDiscreteFunction>& f)
 {
   return applyBinaryOperationWithLeftConstant<language::multiply_op>(a, f);
 }
 
+std::shared_ptr<const IDiscreteFunction>
+operator*(const std::shared_ptr<const IDiscreteFunction>& f, const double& a)
+{
+  return applyBinaryOperationWithRightConstant<language::multiply_op>(f, a);
+}
+
 std::shared_ptr<const IDiscreteFunction>
 operator*(const TinyMatrix<1>& A, const std::shared_ptr<const IDiscreteFunction>& B)
 {
@@ -751,3 +1057,9 @@ operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyMatrix<3>
 {
   return applyBinaryOperationWithRightConstant<language::multiply_op>(a, A);
 }
+
+std::shared_ptr<const IDiscreteFunction>
+operator/(const double& a, const std::shared_ptr<const IDiscreteFunction>& f)
+{
+  return applyBinaryOperationWithLeftConstant<language::divide_op>(a, f);
+}
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionOperators.hpp b/src/language/utils/EmbeddedIDiscreteFunctionOperators.hpp
index 7969828000c4440ea9aa9d36014ee18d55d643c1..f797a95d03e19a796d9af965d81bbd3970c6cddd 100644
--- a/src/language/utils/EmbeddedIDiscreteFunctionOperators.hpp
+++ b/src/language/utils/EmbeddedIDiscreteFunctionOperators.hpp
@@ -8,20 +8,105 @@
 
 class IDiscreteFunction;
 
+// unary minus
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&);
+
+// sum
 std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
                                                    const std::shared_ptr<const IDiscreteFunction>&);
 
+std::shared_ptr<const IDiscreteFunction> operator+(const double&, const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&, const double&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const TinyVector<1>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const TinyVector<2>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const TinyVector<3>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const TinyMatrix<1>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const TinyMatrix<2>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const TinyMatrix<3>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyVector<1>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyVector<2>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyVector<3>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyMatrix<1>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyMatrix<2>&);
+
+std::shared_ptr<const IDiscreteFunction> operator+(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyMatrix<3>&);
+
+// difference
 std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
                                                    const std::shared_ptr<const IDiscreteFunction>&);
 
-std::shared_ptr<const IDiscreteFunction> operator*(const std::shared_ptr<const IDiscreteFunction>&,
+std::shared_ptr<const IDiscreteFunction> operator-(const double&, const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&, const double&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const TinyVector<1>&,
                                                    const std::shared_ptr<const IDiscreteFunction>&);
 
-std::shared_ptr<const IDiscreteFunction> operator/(const std::shared_ptr<const IDiscreteFunction>&,
+std::shared_ptr<const IDiscreteFunction> operator-(const TinyVector<2>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const TinyVector<3>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const TinyMatrix<1>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const TinyMatrix<2>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const TinyMatrix<3>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyVector<1>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyVector<2>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyVector<3>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyMatrix<1>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyMatrix<2>&);
+
+std::shared_ptr<const IDiscreteFunction> operator-(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const TinyMatrix<3>&);
+
+// product
+std::shared_ptr<const IDiscreteFunction> operator*(const std::shared_ptr<const IDiscreteFunction>&,
                                                    const std::shared_ptr<const IDiscreteFunction>&);
 
 std::shared_ptr<const IDiscreteFunction> operator*(const double&, const std::shared_ptr<const IDiscreteFunction>&);
 
+std::shared_ptr<const IDiscreteFunction> operator*(const std::shared_ptr<const IDiscreteFunction>&, const double&);
+
 std::shared_ptr<const IDiscreteFunction> operator*(const TinyMatrix<1>&,
                                                    const std::shared_ptr<const IDiscreteFunction>&);
 
@@ -49,4 +134,10 @@ std::shared_ptr<const IDiscreteFunction> operator*(const std::shared_ptr<const I
 std::shared_ptr<const IDiscreteFunction> operator*(const std::shared_ptr<const IDiscreteFunction>&,
                                                    const TinyMatrix<3>&);
 
+// ratio
+std::shared_ptr<const IDiscreteFunction> operator/(const double&, const std::shared_ptr<const IDiscreteFunction>&);
+
+std::shared_ptr<const IDiscreteFunction> operator/(const std::shared_ptr<const IDiscreteFunction>&,
+                                                   const std::shared_ptr<const IDiscreteFunction>&);
+
 #endif   // EMBEDDED_I_DISCRETE_FUNCTION_OPERATORS_HPP
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp b/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9321c3273d7955e77934f9f4233ae3603cf14990
--- /dev/null
+++ b/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
@@ -0,0 +1,58 @@
+#ifndef EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP
+#define EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP
+
+#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <utils/Exceptions.hpp>
+
+#include <sstream>
+#include <string>
+
+template <typename T>
+PUGS_INLINE std::string
+operand_type_name(const T& t)
+{
+  if constexpr (is_shared_ptr_v<T>) {
+    Assert(t.use_count() > 0);
+    return operand_type_name(*t);
+  } else if constexpr (std::is_base_of_v<IDiscreteFunction, std::decay_t<T>>) {
+    return "Vh(" + name(t.descriptor().type()) + ':' + dataTypeName(t.dataType()) + ')';
+  } else {
+    return dataTypeName(ast_node_data_type_from<T>);
+  }
+}
+
+PUGS_INLINE
+bool
+isSameDiscretization(const IDiscreteFunction& f, const IDiscreteFunction& g)
+{
+  if ((f.dataType() == g.dataType()) and (f.descriptor().type() == g.descriptor().type())) {
+    switch (f.dataType()) {
+    case ASTNodeDataType::double_t: {
+      return true;
+    }
+    case ASTNodeDataType::vector_t: {
+      return f.dataType().dimension() == g.dataType().dimension();
+    }
+    case ASTNodeDataType::matrix_t: {
+      return (f.dataType().nbRows() == g.dataType().nbRows()) and
+             (f.dataType().nbColumns() == g.dataType().nbColumns());
+    }
+    default: {
+      throw UnexpectedError("invalid data type " + operand_type_name(f));
+    }
+    }
+  } else {
+    return false;
+  }
+}
+
+PUGS_INLINE
+bool
+isSameDiscretization(const std::shared_ptr<const IDiscreteFunction>& f,
+                     const std::shared_ptr<const IDiscreteFunction>& g)
+{
+  return isSameDiscretization(*f, *g);
+}
+
+#endif   // EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP
diff --git a/src/language/utils/IUnaryOperatorProcessorBuilder.hpp b/src/language/utils/IUnaryOperatorProcessorBuilder.hpp
index 19b36e2feb42b9efc558ff5e554532c0f3d32824..6868036e88ce63addcce46f16b1639af96fd21b0 100644
--- a/src/language/utils/IUnaryOperatorProcessorBuilder.hpp
+++ b/src/language/utils/IUnaryOperatorProcessorBuilder.hpp
@@ -13,6 +13,7 @@ class IUnaryOperatorProcessorBuilder
  public:
   [[nodiscard]] virtual std::unique_ptr<INodeProcessor> getNodeProcessor(ASTNode& node) const = 0;
 
+  [[nodiscard]] virtual ASTNodeDataType getOperandDataType() const = 0;
   [[nodiscard]] virtual ASTNodeDataType getReturnValueType() const = 0;
 
   virtual ~IUnaryOperatorProcessorBuilder() = default;
diff --git a/src/language/utils/InterpolateItemArray.hpp b/src/language/utils/InterpolateItemArray.hpp
index fcef1db90a2e17b07e54fe55966a96973d443b9e..c507c527ca9f1c59070410c09f802f524a3de083 100644
--- a/src/language/utils/InterpolateItemArray.hpp
+++ b/src/language/utils/InterpolateItemArray.hpp
@@ -16,7 +16,7 @@ class InterpolateItemArray<OutputType(InputType)> : public PugsFunctionAdapter<O
 
  public:
   template <ItemType item_type>
-  static inline ItemArray<OutputType, item_type>
+  PUGS_INLINE static ItemArray<OutputType, item_type>
   interpolate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
               const ItemValue<const InputType, item_type>& position)
   {
@@ -35,9 +35,9 @@ class InterpolateItemArray<OutputType(InputType)> : public PugsFunctionAdapter<O
   }
 
   template <ItemType item_type>
-  static inline Table<OutputType>
+  PUGS_INLINE static Table<OutputType>
   interpolate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
-              const ItemArray<const InputType, item_type>& position,
+              const ItemValue<const InputType, item_type>& position,
               const Array<const ItemIdT<item_type>>& list_of_items)
   {
     Table<OutputType> table{list_of_items.size(), function_symbol_id_list.size()};
@@ -53,6 +53,15 @@ class InterpolateItemArray<OutputType(InputType)> : public PugsFunctionAdapter<O
 
     return table;
   }
+
+  template <ItemType item_type>
+  PUGS_INLINE static Table<OutputType>
+  interpolate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+              const ItemValue<const InputType, item_type>& position,
+              const Array<ItemIdT<item_type>>& list_of_items)
+  {
+    return interpolate(function_symbol_id_list, position, Array<const ItemIdT<item_type>>{list_of_items});
+  }
 };
 
 #endif   // INTERPOLATE_ITEM_ARRAY_HPP
diff --git a/src/language/utils/InterpolateItemValue.hpp b/src/language/utils/InterpolateItemValue.hpp
index 16a87b4952c7cb0c2907833705e8142d9c0a7178..de12b563a19ded640f0b4bbae3184f383d90a2f3 100644
--- a/src/language/utils/InterpolateItemValue.hpp
+++ b/src/language/utils/InterpolateItemValue.hpp
@@ -15,7 +15,7 @@ class InterpolateItemValue<OutputType(InputType)> : public PugsFunctionAdapter<O
 
  public:
   template <ItemType item_type>
-  static inline ItemValue<OutputType, item_type>
+  PUGS_INLINE static ItemValue<OutputType, item_type>
   interpolate(const FunctionSymbolId& function_symbol_id, const ItemValue<const InputType, item_type>& position)
   {
     auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
@@ -46,7 +46,7 @@ class InterpolateItemValue<OutputType(InputType)> : public PugsFunctionAdapter<O
   }
 
   template <ItemType item_type>
-  static inline Array<OutputType>
+  PUGS_INLINE static Array<OutputType>
   interpolate(const FunctionSymbolId& function_symbol_id,
               const ItemValue<const InputType, item_type>& position,
               const Array<const ItemIdT<item_type>>& list_of_items)
@@ -77,6 +77,15 @@ class InterpolateItemValue<OutputType(InputType)> : public PugsFunctionAdapter<O
 
     return value;
   }
+
+  template <ItemType item_type>
+  PUGS_INLINE static Array<OutputType>
+  interpolate(const FunctionSymbolId& function_symbol_id,
+              const ItemValue<const InputType, item_type>& position,
+              const Array<ItemIdT<item_type>>& list_of_items)
+  {
+    return interpolate(function_symbol_id, position, Array<const ItemIdT<item_type>>{list_of_items});
+  }
 };
 
 #endif   // INTERPOLATE_ITEM_VALUE_HPP
diff --git a/src/language/utils/OperatorRepository.hpp b/src/language/utils/OperatorRepository.hpp
index cf4b322e7f9da12311a0e1b1dcd47ee0c25e7936..4ce6ee83c7652a7e3cd93f5658b716a10b4e4b15 100644
--- a/src/language/utils/OperatorRepository.hpp
+++ b/src/language/utils/OperatorRepository.hpp
@@ -68,10 +68,11 @@ class OperatorRepository
     const std::string binary_operator_type_name =
       binaryOperatorMangler<BinaryOperatorTypeT>(processor_builder->getDataTypeOfA(),
                                                  processor_builder->getDataTypeOfB());
-    if (not m_binary_operator_builder_list
-              .try_emplace(binary_operator_type_name,
-                           Descriptor{processor_builder->getReturnValueType(), processor_builder})
-              .second) {
+    if (auto [i_descriptor, success] =
+          m_binary_operator_builder_list.try_emplace(binary_operator_type_name,
+                                                     Descriptor{processor_builder->getReturnValueType(),
+                                                                processor_builder});
+        not success) {
       // LCOV_EXCL_START
       throw UnexpectedError(binary_operator_type_name + " has already an entry");
       // LCOV_EXCL_STOP
@@ -85,10 +86,11 @@ class OperatorRepository
                  const std::shared_ptr<const IAffectationProcessorBuilder>& processor_builder)
   {
     const std::string affectation_type_name = affectationMangler<OperatorTypeT>(lhs_type, rhs_type);
-    if (not m_affectation_builder_list
-              .try_emplace(affectation_type_name,
-                           Descriptor{ASTNodeDataType::build<ASTNodeDataType::void_t>(), processor_builder})
-              .second) {
+    if (auto [i_descriptor, success] =
+          m_affectation_builder_list.try_emplace(affectation_type_name,
+                                                 Descriptor{ASTNodeDataType::build<ASTNodeDataType::void_t>(),
+                                                            processor_builder});
+        not success) {
       // LCOV_EXCL_START
       throw UnexpectedError(affectation_type_name + " has already an entry");
       // LCOV_EXCL_STOP
@@ -114,10 +116,10 @@ class OperatorRepository
 
   template <typename OperatorTypeT>
   void
-  addUnaryOperator(const ASTNodeDataType& operand_type,
-                   const std::shared_ptr<const IUnaryOperatorProcessorBuilder>& processor_builder)
+  addUnaryOperator(const std::shared_ptr<const IUnaryOperatorProcessorBuilder>& processor_builder)
   {
-    const std::string unary_operator_type_name = unaryOperatorMangler<OperatorTypeT>(operand_type);
+    const std::string unary_operator_type_name =
+      unaryOperatorMangler<OperatorTypeT>(processor_builder->getOperandDataType());
     if (auto [i_descriptor, success] =
           m_unary_operator_builder_list.try_emplace(unary_operator_type_name,
                                                     Descriptor{processor_builder->getReturnValueType(),
diff --git a/src/language/utils/UnaryOperatorProcessorBuilder.hpp b/src/language/utils/UnaryOperatorProcessorBuilder.hpp
index d99d2e17536de2b31a3ac2c48f39800a2461f17d..8dd4b283cf3c628db8cb98aba6b36147990c279d 100644
--- a/src/language/utils/UnaryOperatorProcessorBuilder.hpp
+++ b/src/language/utils/UnaryOperatorProcessorBuilder.hpp
@@ -15,6 +15,12 @@ class UnaryOperatorProcessorBuilder final : public IUnaryOperatorProcessorBuilde
  public:
   UnaryOperatorProcessorBuilder() = default;
 
+  ASTNodeDataType
+  getOperandDataType() const
+  {
+    return ast_node_data_type_from<DataT>;
+  }
+
   ASTNodeDataType
   getReturnValueType() const
   {
diff --git a/src/language/utils/UnaryOperatorRegisterForB.cpp b/src/language/utils/UnaryOperatorRegisterForB.cpp
index 33173e82b896e8d013f125a3b47ce42682ac132d..9eabfbe255691c7cbe2601cfd86a7ef9d449e87d 100644
--- a/src/language/utils/UnaryOperatorRegisterForB.cpp
+++ b/src/language/utils/UnaryOperatorRegisterForB.cpp
@@ -8,10 +8,8 @@ UnaryOperatorRegisterForB::_register_unary_minus()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto B = ASTNodeDataType::build<ASTNodeDataType::bool_t>();
-
-  repository.addUnaryOperator<
-    language::unary_minus>(B, std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, int64_t, bool>>());
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, int64_t, bool>>());
 }
 
 void
@@ -19,10 +17,8 @@ UnaryOperatorRegisterForB::_register_unary_not()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto B = ASTNodeDataType::build<ASTNodeDataType::bool_t>();
-
-  repository.addUnaryOperator<
-    language::unary_not>(B, std::make_shared<UnaryOperatorProcessorBuilder<language::unary_not, bool, bool>>());
+  repository.addUnaryOperator<language::unary_not>(
+    std::make_shared<UnaryOperatorProcessorBuilder<language::unary_not, bool, bool>>());
 }
 
 UnaryOperatorRegisterForB::UnaryOperatorRegisterForB()
diff --git a/src/language/utils/UnaryOperatorRegisterForN.cpp b/src/language/utils/UnaryOperatorRegisterForN.cpp
index baed0e43eef169229cfbff58fb0592f894b83163..26af42be06cbf05ac070bc563af02a821dd526fa 100644
--- a/src/language/utils/UnaryOperatorRegisterForN.cpp
+++ b/src/language/utils/UnaryOperatorRegisterForN.cpp
@@ -8,11 +8,8 @@ UnaryOperatorRegisterForN::_register_unary_minus()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto N = ASTNodeDataType::build<ASTNodeDataType::unsigned_int_t>();
-
-  repository.addUnaryOperator<
-    language::unary_minus>(N,
-                           std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, int64_t, uint64_t>>());
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, int64_t, uint64_t>>());
 }
 
 UnaryOperatorRegisterForN::UnaryOperatorRegisterForN()
diff --git a/src/language/utils/UnaryOperatorRegisterForR.cpp b/src/language/utils/UnaryOperatorRegisterForR.cpp
index c0c60a31f5f6c222232999c3b392a18f30a767c1..cc1f637fa839d54cf8a8b612f7abe9147f79b61c 100644
--- a/src/language/utils/UnaryOperatorRegisterForR.cpp
+++ b/src/language/utils/UnaryOperatorRegisterForR.cpp
@@ -8,10 +8,8 @@ UnaryOperatorRegisterForR::_register_unary_minus()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto R = ASTNodeDataType::build<ASTNodeDataType::double_t>();
-
-  repository.addUnaryOperator<
-    language::unary_minus>(R, std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, double, double>>());
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, double, double>>());
 }
 
 UnaryOperatorRegisterForR::UnaryOperatorRegisterForR()
diff --git a/src/language/utils/UnaryOperatorRegisterForRn.cpp b/src/language/utils/UnaryOperatorRegisterForRn.cpp
index 85cc03fa476a34a6bec65f2c7f88f1d76fbad809..a35b92d8a2e53d5850c18aa4e4891a1d4c27f822 100644
--- a/src/language/utils/UnaryOperatorRegisterForRn.cpp
+++ b/src/language/utils/UnaryOperatorRegisterForRn.cpp
@@ -9,12 +9,9 @@ UnaryOperatorRegisterForRn<Dimension>::_register_unary_minus()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto Rn = ASTNodeDataType::build<ASTNodeDataType::vector_t>(Dimension);
-
-  repository
-    .addUnaryOperator<language::unary_minus>(Rn,
-                                             std::make_shared<UnaryOperatorProcessorBuilder<
-                                               language::unary_minus, TinyVector<Dimension>, TinyVector<Dimension>>>());
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<
+      UnaryOperatorProcessorBuilder<language::unary_minus, TinyVector<Dimension>, TinyVector<Dimension>>>());
 }
 
 template <size_t Dimension>
diff --git a/src/language/utils/UnaryOperatorRegisterForRnxn.cpp b/src/language/utils/UnaryOperatorRegisterForRnxn.cpp
index 798a60086b30d008901f0d9b5fa2502e745d6fe9..01a53c096b8bc13481deb774fefa1d34b343338a 100644
--- a/src/language/utils/UnaryOperatorRegisterForRnxn.cpp
+++ b/src/language/utils/UnaryOperatorRegisterForRnxn.cpp
@@ -9,12 +9,9 @@ UnaryOperatorRegisterForRnxn<Dimension>::_register_unary_minus()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto Rnxn = ASTNodeDataType::build<ASTNodeDataType::matrix_t>(Dimension, Dimension);
-
-  repository
-    .addUnaryOperator<language::unary_minus>(Rnxn,
-                                             std::make_shared<UnaryOperatorProcessorBuilder<
-                                               language::unary_minus, TinyMatrix<Dimension>, TinyMatrix<Dimension>>>());
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<
+      UnaryOperatorProcessorBuilder<language::unary_minus, TinyMatrix<Dimension>, TinyMatrix<Dimension>>>());
 }
 
 template <size_t Dimension>
diff --git a/src/language/utils/UnaryOperatorRegisterForZ.cpp b/src/language/utils/UnaryOperatorRegisterForZ.cpp
index 89e0b4972a0a1e2a2556d2210ab4658b35f7b976..5326157d43d621b30a238a758fe4a1feb0308204 100644
--- a/src/language/utils/UnaryOperatorRegisterForZ.cpp
+++ b/src/language/utils/UnaryOperatorRegisterForZ.cpp
@@ -8,11 +8,8 @@ UnaryOperatorRegisterForZ::_register_unary_minus()
 {
   OperatorRepository& repository = OperatorRepository::instance();
 
-  auto Z = ASTNodeDataType::build<ASTNodeDataType::int_t>();
-
-  repository.addUnaryOperator<
-    language::unary_minus>(Z,
-                           std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, int64_t, int64_t>>());
+  repository.addUnaryOperator<language::unary_minus>(
+    std::make_shared<UnaryOperatorProcessorBuilder<language::unary_minus, int64_t, int64_t>>());
 }
 
 UnaryOperatorRegisterForZ::UnaryOperatorRegisterForZ()
diff --git a/src/mesh/ItemArray.hpp b/src/mesh/ItemArray.hpp
index 78e5f8dd64c430ec8149c3e608d444897987f108..1a2b004c9533921ab6531b6fa73c9a955badff57 100644
--- a/src/mesh/ItemArray.hpp
+++ b/src/mesh/ItemArray.hpp
@@ -47,6 +47,19 @@ class ItemArray
     return image;
   }
 
+  template <typename ConnectivityPtr2>
+  friend PUGS_INLINE void copy_to(const ItemArray<std::add_const_t<DataType>, item_type, ConnectivityPtr>& source,
+                                  const ItemArray<DataType, item_type, ConnectivityPtr2>& destination);
+
+  template <typename ConnectivityPtr2>
+  friend PUGS_INLINE void
+  copy_to(const ItemArray<DataType, item_type, ConnectivityPtr>& source,
+          const ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr2>& destination)
+  {
+    Assert(destination.connectivity_ptr() == source.connectivity_ptr());
+    copy_to(source.m_values, destination.m_values);
+  }
+
   PUGS_INLINE
   bool
   isBuilt() const noexcept
diff --git a/src/mesh/ItemValue.hpp b/src/mesh/ItemValue.hpp
index 0f7fe61717fc9d8c571d0107b5ae422cc6b911bb..ebc5f40511a0be6d1eeb519a0660b494d268f150 100644
--- a/src/mesh/ItemValue.hpp
+++ b/src/mesh/ItemValue.hpp
@@ -47,6 +47,19 @@ class ItemValue
     return image;
   }
 
+  template <typename ConnectivityPtr2>
+  friend PUGS_INLINE void copy_to(const ItemValue<std::add_const_t<DataType>, item_type, ConnectivityPtr>& source,
+                                  const ItemValue<DataType, item_type, ConnectivityPtr2>& destination);
+
+  template <typename ConnectivityPtr2>
+  friend PUGS_INLINE void
+  copy_to(const ItemValue<DataType, item_type, ConnectivityPtr>& source,
+          const ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr2>& destination)
+  {
+    Assert(destination.connectivity_ptr() == source.connectivity_ptr());
+    copy_to(source.m_values, destination.m_values);
+  }
+
   PUGS_INLINE
   bool
   isBuilt() const noexcept
diff --git a/src/mesh/Mesh.hpp b/src/mesh/Mesh.hpp
index 45f4c8d050d7fd7c63e44caf002b6bbcb1c7599a..e5817a2a9394b96bea04e78d061e166d3f0fbc02 100644
--- a/src/mesh/Mesh.hpp
+++ b/src/mesh/Mesh.hpp
@@ -18,7 +18,7 @@ class Mesh final : public IMesh
 
  private:
   const std::shared_ptr<const Connectivity> m_connectivity;
-  NodeValue<const Rd> m_xr;
+  const NodeValue<const Rd> m_xr;
 
  public:
   PUGS_INLINE
@@ -85,7 +85,7 @@ class Mesh final : public IMesh
   }
 
   PUGS_INLINE
-  Mesh(const std::shared_ptr<const Connectivity>& connectivity, NodeValue<Rd>& xr)
+  Mesh(const std::shared_ptr<const Connectivity>& connectivity, const NodeValue<const Rd>& xr)
     : m_connectivity{connectivity}, m_xr{xr}
   {
     ;
diff --git a/src/mesh/MeshData.hpp b/src/mesh/MeshData.hpp
index 99cc8aeeb4ca127e8e60ba3e7ee75fcc93e8db76..f891f5ccb40e92cfbd0aa9e4cf3a600f352a8790 100644
--- a/src/mesh/MeshData.hpp
+++ b/src/mesh/MeshData.hpp
@@ -42,36 +42,75 @@ class MeshData : public IMeshData
   CellValue<const Rd> m_cell_iso_barycenter;
   CellValue<const double> m_Vj;
   CellValue<const double> m_sum_r_ljr;
+  FaceValue<const Rd> m_Nl;
+  FaceValue<const Rd> m_nl;
   FaceValue<const double> m_ll;
 
   PUGS_INLINE
   void
-  _compute_ll()
+  _computeNl()
   {
     if constexpr (Dimension == 1) {
-      static_assert(Dimension != 1, "ll does not make sense in 1d");
+      static_assert(Dimension != 1, "Nl does not make sense in 1d");
     } else {
       const auto& Nlr = this->Nlr();
 
-      FaceValue<double> ll{m_mesh.connectivity()};
+      FaceValue<Rd> Nl{m_mesh.connectivity()};
 
       const auto& face_to_node_matrix = m_mesh.connectivity().faceToNodeMatrix();
       parallel_for(
         m_mesh.numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
           const auto& face_nodes = face_to_node_matrix[face_id];
 
-          double lenght = 0;
+          Rd N = zero;
           for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
-            lenght += l2Norm(Nlr(face_id, i_node));
+            N += Nlr(face_id, i_node);
           }
-
-          ll[face_id] = lenght;
+          Nl[face_id] = N;
         });
 
+      m_Nl = Nl;
+    }
+  }
+
+  PUGS_INLINE
+  void
+  _compute_ll()
+  {
+    if constexpr (Dimension == 1) {
+      static_assert(Dimension != 1, "ll does not make sense in 1d");
+    } else {
+      const auto& Nl = this->Nl();
+
+      FaceValue<double> ll{m_mesh.connectivity()};
+
+      parallel_for(
+        m_mesh.numberOfFaces(), PUGS_LAMBDA(FaceId face_id) { ll[face_id] = L2Norm(Nl[face_id]); });
+
       m_ll = ll;
     }
   }
 
+  PUGS_INLINE
+  void
+  _compute_nl()
+  {
+    if constexpr (Dimension == 1) {
+      static_assert(Dimension != 1, "Nl does not make sense in 1d");
+    } else {
+      const auto& Nl = this->Nl();
+      const auto& ll = this->ll();
+
+      FaceValue<Rd> nl{m_mesh.connectivity()};
+
+      const auto& face_to_node_matrix = m_mesh.connectivity().faceToNodeMatrix();
+      parallel_for(
+        m_mesh.numberOfFaces(), PUGS_LAMBDA(FaceId face_id) { nl[face_id] = (1. / ll[face_id]) * Nl[face_id]; });
+
+      m_Nl = Nl;
+    }
+  }
+
   PUGS_INLINE
   void
   _compute_nlr()
@@ -310,7 +349,7 @@ class MeshData : public IMeshData
             Rd Nr            = zero;
             const Rd two_dxr = 2 * dxr[r];
             for (size_t s = 0; s < nb_nodes; ++s) {
-              Nr += crossProduct((two_dxr - dxr[s]), xr[face_nodes[s]]);
+              Nr += crossProduct(two_dxr - dxr[s], xr[face_nodes[s]]);
             }
             Nr *= inv_12_nb_nodes;
             Nr -= (1. / 6.) * crossProduct(dxr[r], xr[face_nodes[r]]);
@@ -358,8 +397,7 @@ class MeshData : public IMeshData
       const auto& cell_face_is_reversed = m_mesh.connectivity().cellFaceIsReversed();
 
       NodeValuePerCell<Rd> Cjr(m_mesh.connectivity());
-      parallel_for(
-        Cjr.numberOfValues(), PUGS_LAMBDA(size_t jr) { Cjr[jr] = zero; });
+      Cjr.fill(zero);
 
       parallel_for(
         m_mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
@@ -491,6 +529,16 @@ class MeshData : public IMeshData
     return m_mesh;
   }
 
+  PUGS_INLINE
+  FaceValue<const Rd>
+  Nl()
+  {
+    if (not m_Nl.isBuilt()) {
+      this->_computeNl();
+    }
+    return m_Nl;
+  }
+
   PUGS_INLINE
   FaceValue<const double>
   ll()
@@ -501,6 +549,16 @@ class MeshData : public IMeshData
     return m_ll;
   }
 
+  PUGS_INLINE
+  FaceValue<const Rd>
+  nl()
+  {
+    if (not m_nl.isBuilt()) {
+      this->_compute_nl();
+    }
+    return m_nl;
+  }
+
   PUGS_INLINE
   NodeValuePerFace<const Rd>
   Nlr()
diff --git a/src/mesh/SubItemArrayPerItem.hpp b/src/mesh/SubItemArrayPerItem.hpp
index d39bff6a3015b61fbbe853dbeb9970e237068068..501762ea6270b7ebf59b26c4afdea6edd552cc3e 100644
--- a/src/mesh/SubItemArrayPerItem.hpp
+++ b/src/mesh/SubItemArrayPerItem.hpp
@@ -10,8 +10,6 @@
 #include <utils/PugsAssert.hpp>
 #include <utils/Table.hpp>
 
-#include <utils/SubArray.hpp>
-
 #include <memory>
 
 template <typename DataType, typename ItemOfItem, typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
diff --git a/src/scheme/AcousticSolver.cpp b/src/scheme/AcousticSolver.cpp
index 3336fd716822a86f86fc335a3f02cb80882a26fc..6bac5a6b3d920f9f3b5654e826bf9b974a9fcd0f 100644
--- a/src/scheme/AcousticSolver.cpp
+++ b/src/scheme/AcousticSolver.cpp
@@ -1,5 +1,6 @@
 #include <scheme/AcousticSolver.hpp>
 
+#include <language/utils/InterpolateItemValue.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/MeshNodeBoundary.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
diff --git a/src/scheme/DiscreteFunctionInterpoler.cpp b/src/scheme/DiscreteFunctionInterpoler.cpp
index fda64fdcb82d1eb36b8925e9cdc438989cd92306..c8eeaf94cb8d4f1037b28ebf65d377660464c093 100644
--- a/src/scheme/DiscreteFunctionInterpoler.cpp
+++ b/src/scheme/DiscreteFunctionInterpoler.cpp
@@ -1,5 +1,6 @@
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 
+#include <language/utils/InterpolateItemValue.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <utils/Exceptions.hpp>
 
@@ -7,8 +8,14 @@ template <size_t Dimension, typename DataType>
 std::shared_ptr<IDiscreteFunction>
 DiscreteFunctionInterpoler::_interpolate() const
 {
-  std::shared_ptr mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
-  return std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh, m_function_id);
+  std::shared_ptr mesh    = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
+  using MeshDataType      = MeshData<Dimension>;
+  MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+  return std::make_shared<
+    DiscreteFunctionP0<Dimension, DataType>>(mesh,
+                                             InterpolateItemValue<DataType(TinyVector<Dimension>)>::
+                                               template interpolate<ItemType::cell>(m_function_id, mesh_data.xj()));
 }
 
 template <size_t Dimension>
diff --git a/src/scheme/DiscreteFunctionP0.hpp b/src/scheme/DiscreteFunctionP0.hpp
index d41d98db99a0fa74bbe8e05496d5324f33db0634..0b77aaea481451c6662dd450756affc02ce0226d 100644
--- a/src/scheme/DiscreteFunctionP0.hpp
+++ b/src/scheme/DiscreteFunctionP0.hpp
@@ -3,7 +3,6 @@
 
 #include <scheme/IDiscreteFunction.hpp>
 
-#include <language/utils/InterpolateItemValue.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/Mesh.hpp>
 #include <mesh/MeshData.hpp>
@@ -19,6 +18,9 @@ class DiscreteFunctionP0 : public IDiscreteFunction
 
   static constexpr HandledItemDataType handled_data_type = HandledItemDataType::value;
 
+  friend class DiscreteFunctionP0<Dimension, std::add_const_t<DataType>>;
+  friend class DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>;
+
  private:
   std::shared_ptr<const MeshType> m_mesh;
   CellValue<DataType> m_cell_values;
@@ -26,24 +28,28 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   DiscreteFunctionDescriptorP0 m_discrete_function_descriptor;
 
  public:
+  PUGS_INLINE
   ASTNodeDataType
   dataType() const final
   {
     return ast_node_data_type_from<DataType>;
   }
 
+  PUGS_INLINE
   const CellValue<DataType>&
   cellValues() const
   {
     return m_cell_values;
   }
 
+  PUGS_INLINE
   std::shared_ptr<const IMesh>
   mesh() const
   {
     return m_mesh;
   }
 
+  PUGS_INLINE
   const IDiscreteFunctionDescriptor&
   descriptor() const final
   {
@@ -51,86 +57,470 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   }
 
   PUGS_FORCEINLINE
-  DataType&
+  operator DiscreteFunctionP0<Dimension, const DataType>() const
+  {
+    return DiscreteFunctionP0<Dimension, const DataType>(m_mesh, m_cell_values);
+  }
+
+  PUGS_INLINE
+  void
+  fill(const DataType& data) const noexcept
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot modify ItemValue of const");
+    m_cell_values.fill(data);
+  }
+
+  PUGS_INLINE DiscreteFunctionP0
+  operator=(const DiscreteFunctionP0& f)
+  {
+    Assert(m_mesh == f.m_mesh);
+    m_cell_values = f.m_cell_values;
+    return *this;
+  }
+
+  friend PUGS_INLINE DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
+  copy(const DiscreteFunctionP0& source)
+  {
+    return DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>{source.m_mesh, copy(source.cellValues())};
+  }
+
+  friend PUGS_INLINE void
+  copy_to(const DiscreteFunctionP0<Dimension, DataType>& source,
+          DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>& destination)
+  {
+    Assert(source.m_mesh == destination.m_mesh);
+    copy_to(source.m_cell_values, destination.m_cell_values);
+  }
+
+  PUGS_FORCEINLINE DataType&
   operator[](const CellId cell_id) const noexcept(NO_ASSERT)
   {
     return m_cell_values[cell_id];
   }
 
-  friend DiscreteFunctionP0
-  operator+(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
+  PUGS_INLINE DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
+  operator-() const
+  {
+    Assert(m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> opposite = copy(*this);
+    parallel_for(
+      m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { opposite[cell_id] = -opposite[cell_id]; });
+
+    return opposite;
+  }
+
+  template <typename DataType2T>
+  PUGS_INLINE DiscreteFunctionP0<Dimension, decltype(DataType{} + DataType2T{})>
+  operator+(const DiscreteFunctionP0<Dimension, DataType2T>& g) const
   {
-    Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0 sum(mesh);
+    const DiscreteFunctionP0& f = *this;
+    Assert(f.m_cell_values.isBuilt() and g.m_cell_values.isBuilt());
+    Assert(f.mesh() == g.mesh(), "functions are not defined on the same mesh");
+    DiscreteFunctionP0<Dimension, decltype(DataType{} + DataType2T{})> sum(f.m_mesh);
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum[cell_id] = f[cell_id] + g[cell_id]; });
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum[cell_id] = f[cell_id] + g[cell_id]; });
     return sum;
   }
 
-  friend DiscreteFunctionP0
-  operator-(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
+  template <typename LHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(LHSDataType{} + DataType{})>
+  operator+(const LHSDataType& a, const DiscreteFunctionP0& g)
   {
-    Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0 difference(mesh);
+    using SumDataType = decltype(LHSDataType{} + DataType{});
+    Assert(g.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, SumDataType> sum(g.m_mesh);
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference[cell_id] = f[cell_id] - g[cell_id]; });
+      g.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum[cell_id] = a + g[cell_id]; });
+    return sum;
+  }
+
+  template <typename RHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(DataType{} + RHSDataType{})>
+  operator+(const DiscreteFunctionP0& f, const RHSDataType& b)
+  {
+    using SumDataType = decltype(DataType{} + RHSDataType{});
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, SumDataType> sum(f.m_mesh);
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum[cell_id] = f[cell_id] + b; });
+    return sum;
+  }
+
+  template <typename DataType2T>
+  PUGS_INLINE DiscreteFunctionP0<Dimension, decltype(DataType{} - DataType2T{})>
+  operator-(const DiscreteFunctionP0<Dimension, DataType2T>& g) const
+  {
+    const DiscreteFunctionP0& f = *this;
+    Assert(f.m_cell_values.isBuilt() and g.m_cell_values.isBuilt());
+    Assert(f.mesh() == g.mesh(), "functions are not defined on the same mesh");
+    DiscreteFunctionP0<Dimension, decltype(DataType{} - DataType2T{})> difference(f.m_mesh);
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference[cell_id] = f[cell_id] - g[cell_id]; });
     return difference;
   }
 
-  friend DiscreteFunctionP0
-  operator*(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
+  template <typename LHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(LHSDataType{} - DataType{})>
+  operator-(const LHSDataType& a, const DiscreteFunctionP0& g)
   {
-    Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0 product(mesh);
+    using DifferenceDataType = decltype(LHSDataType{} - DataType{});
+    Assert(g.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, DifferenceDataType> difference(g.m_mesh);
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * g[cell_id]; });
-    return product;
+      g.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference[cell_id] = a - g[cell_id]; });
+    return difference;
+  }
+
+  template <typename RHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(DataType{} - RHSDataType{})>
+  operator-(const DiscreteFunctionP0& f, const RHSDataType& b)
+  {
+    using DifferenceDataType = decltype(DataType{} - RHSDataType{});
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, DifferenceDataType> difference(f.m_mesh);
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference[cell_id] = f[cell_id] - b; });
+    return difference;
   }
 
   template <typename DataType2T>
-  friend DiscreteFunctionP0<Dimension, decltype(DataType2T{} * DataType{})>
-  operator*(const DiscreteFunctionP0<Dimension, DataType2T>& f, const DiscreteFunctionP0& g)
+  PUGS_INLINE DiscreteFunctionP0<Dimension, decltype(DataType{} * DataType2T{})>
+  operator*(const DiscreteFunctionP0<Dimension, DataType2T>& g) const
   {
-    Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0<Dimension, decltype(DataType2T{} * DataType{})> product(mesh);
+    const DiscreteFunctionP0& f = *this;
+    Assert(f.m_cell_values.isBuilt());
+    Assert(f.mesh() == g.mesh(), "functions are not defined on the same mesh");
+    DiscreteFunctionP0<Dimension, decltype(DataType{} * DataType2T{})> product(f.m_mesh);
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * g[cell_id]; });
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * g[cell_id]; });
     return product;
   }
 
-  friend DiscreteFunctionP0
-  operator*(const double& a, const DiscreteFunctionP0& f)
+  template <typename LHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(LHSDataType{} * DataType{})>
+  operator*(const LHSDataType& a, const DiscreteFunctionP0& f)
   {
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0 product(mesh);
+    using ProductDataType = decltype(LHSDataType{} * DataType{});
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, ProductDataType> product(f.m_mesh);
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = a * f[cell_id]; });
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = a * f[cell_id]; });
     return product;
   }
 
-  friend DiscreteFunctionP0
-  operator/(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
+  template <typename RHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(DataType{} * RHSDataType{})>
+  operator*(const DiscreteFunctionP0& f, const RHSDataType& b)
   {
-    Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0 ratio(mesh);
+    using ProductDataType = decltype(DataType{} * RHSDataType{});
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, ProductDataType> product(f.m_mesh);
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio[cell_id] = f[cell_id] / g[cell_id]; });
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * b; });
+    return product;
+  }
+
+  template <typename DataType2T>
+  PUGS_INLINE DiscreteFunctionP0<Dimension, decltype(DataType{} / DataType2T{})>
+  operator/(const DiscreteFunctionP0<Dimension, DataType2T>& g) const
+  {
+    const DiscreteFunctionP0& f = *this;
+    Assert(f.m_cell_values.isBuilt() and g.m_cell_values.isBuilt());
+    Assert(f.mesh() == g.mesh(), "functions are not defined on the same mesh");
+    DiscreteFunctionP0<Dimension, decltype(DataType{} / DataType2T{})> ratio(f.m_mesh);
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio[cell_id] = f[cell_id] / g[cell_id]; });
     return ratio;
   }
 
-  DiscreteFunctionP0(const std::shared_ptr<const MeshType>& mesh, const FunctionSymbolId& function_id) : m_mesh(mesh)
+  template <typename LHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(LHSDataType{} / DataType{})>
+  operator/(const LHSDataType& a, const DiscreteFunctionP0& f)
+  {
+    using RatioDataType = decltype(LHSDataType{} / DataType{});
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, RatioDataType> ratio(f.m_mesh);
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio[cell_id] = a / f[cell_id]; });
+    return ratio;
+  }
+
+  template <typename RHSDataType>
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, decltype(DataType{} / RHSDataType{})>
+  operator/(const DiscreteFunctionP0& f, const RHSDataType& b)
+  {
+    using RatioDataType = decltype(DataType{} / RHSDataType{});
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, RatioDataType> ratio(f.m_mesh);
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio[cell_id] = f[cell_id] / b; });
+    return ratio;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  sqrt(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::sqrt(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  abs(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::abs(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  sin(const DiscreteFunctionP0& f)
   {
-    using MeshDataType      = MeshData<Dimension>;
-    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::sin(f[cell_id]); });
 
-    m_cell_values =
-      InterpolateItemValue<DataType(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(function_id,
-                                                                                                  mesh_data.xj());
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  cos(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::cos(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  tan(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::tan(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  asin(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::asin(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  acos(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::acos(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  atan(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::atan(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  sinh(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::sinh(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  cosh(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::cosh(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  tanh(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::tanh(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  asinh(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::asinh(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  acosh(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::acosh(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  atanh(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::atanh(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  exp(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::exp(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  log(const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::log(f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0
+  atan2(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt() and g.m_cell_values.isBuilt());
+    Assert(f.m_mesh == g.m_mesh);
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::atan2(f[cell_id], g[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0
+  atan2(const double a, const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::atan2(a, f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0
+  atan2(const DiscreteFunctionP0& f, const double a)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::atan2(f[cell_id], a); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0
+  pow(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt() and g.m_cell_values.isBuilt());
+    Assert(f.m_mesh == g.m_mesh);
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::pow(f[cell_id], g[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0
+  pow(const double a, const DiscreteFunctionP0& f)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::pow(a, f[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0
+  pow(const DiscreteFunctionP0& f, const double a)
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(f.m_cell_values.isBuilt());
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{f.m_mesh};
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = std::pow(f[cell_id], a); });
+
+    return result;
   }
 
   DiscreteFunctionP0(const std::shared_ptr<const MeshType>& mesh) : m_mesh{mesh}, m_cell_values{mesh->connectivity()} {}
@@ -138,73 +528,15 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   DiscreteFunctionP0(const std::shared_ptr<const MeshType>& mesh, const CellValue<DataType>& cell_value)
     : m_mesh{mesh}, m_cell_values{cell_value}
   {
-    Assert(mesh->connectivity().shared_ptr() == cell_value.connectivity_ptr());
+    Assert(mesh->shared_connectivity() == cell_value.connectivity_ptr());
   }
 
+  DiscreteFunctionP0() noexcept = delete;
+
   DiscreteFunctionP0(const DiscreteFunctionP0&) noexcept = default;
   DiscreteFunctionP0(DiscreteFunctionP0&&) noexcept      = default;
 
   ~DiscreteFunctionP0() = default;
 };
 
-template <size_t Dimension, size_t ValueDimension>
-DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>>
-operator*(const TinyMatrix<ValueDimension>& A, const DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>>& f)
-{
-  using MeshType       = typename DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>>::MeshType;
-  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-  DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>> product(mesh);
-  parallel_for(
-    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = A * f[cell_id]; });
-  return product;
-}
-
-template <size_t Dimension, size_t ValueDimension>
-DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>
-operator*(const TinyMatrix<ValueDimension>& A, const DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>& f)
-{
-  using MeshType       = typename DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>::MeshType;
-  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-  DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>> product(mesh);
-  parallel_for(
-    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = A * f[cell_id]; });
-  return product;
-}
-
-template <size_t Dimension, size_t ValueDimension>
-DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>
-operator*(const DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>& f, const TinyMatrix<ValueDimension>& A)
-{
-  using MeshType       = typename DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>::MeshType;
-  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-  DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>> product(mesh);
-  parallel_for(
-    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * A; });
-  return product;
-}
-
-template <size_t Dimension, size_t ValueDimension>
-DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>
-operator*(const DiscreteFunctionP0<Dimension, double>& f, const TinyMatrix<ValueDimension>& A)
-{
-  using MeshType       = typename DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>>::MeshType;
-  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-  DiscreteFunctionP0<Dimension, TinyMatrix<ValueDimension>> product(mesh);
-  parallel_for(
-    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * A; });
-  return product;
-}
-
-template <size_t Dimension, size_t ValueDimension>
-DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>>
-operator*(const DiscreteFunctionP0<Dimension, double>& f, const TinyVector<ValueDimension>& A)
-{
-  using MeshType       = typename DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>>::MeshType;
-  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-  DiscreteFunctionP0<Dimension, TinyVector<ValueDimension>> product(mesh);
-  parallel_for(
-    mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product[cell_id] = f[cell_id] * A; });
-  return product;
-}
-
 #endif   // DISCRETE_FUNCTION_P0_HPP
diff --git a/src/scheme/DiscreteFunctionP0Vector.hpp b/src/scheme/DiscreteFunctionP0Vector.hpp
index 77ddf70f1d7f14a1c4dcab4cb352d4c6280d1715..103718b125e123692241715e697ab89869d078eb 100644
--- a/src/scheme/DiscreteFunctionP0Vector.hpp
+++ b/src/scheme/DiscreteFunctionP0Vector.hpp
@@ -3,7 +3,6 @@
 
 #include <scheme/IDiscreteFunction.hpp>
 
-#include <language/utils/InterpolateItemArray.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemArray.hpp>
 #include <mesh/Mesh.hpp>
@@ -22,6 +21,9 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
 
   static constexpr HandledItemDataType handled_data_type = HandledItemDataType::vector;
 
+  friend class DiscreteFunctionP0Vector<Dimension, std::add_const_t<DataType>>;
+  friend class DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>;
+
   static_assert(std::is_arithmetic_v<DataType>, "DiscreteFunctionP0Vector are only defined for arithmetic data type");
 
  private:
@@ -31,6 +33,7 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
   DiscreteFunctionDescriptorP0Vector m_discrete_function_descriptor;
 
  public:
+  PUGS_INLINE
   ASTNodeDataType
   dataType() const final
   {
@@ -44,18 +47,21 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
     return m_cell_arrays.sizeOfArrays();
   }
 
+  PUGS_INLINE
   const CellArray<DataType>&
   cellArrays() const
   {
     return m_cell_arrays;
   }
 
+  PUGS_INLINE
   std::shared_ptr<const IMesh>
   mesh() const
   {
     return m_mesh;
   }
 
+  PUGS_INLINE
   const IDiscreteFunctionDescriptor&
   descriptor() const final
   {
@@ -63,21 +69,75 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
   }
 
   PUGS_FORCEINLINE
-  Array<double>
+  operator DiscreteFunctionP0Vector<Dimension, const DataType>() const
+  {
+    return DiscreteFunctionP0Vector<Dimension, const DataType>(m_mesh, m_cell_arrays);
+  }
+
+  PUGS_INLINE
+  void
+  fill(const DataType& data) const noexcept
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot modify ItemValue of const");
+    m_cell_arrays.fill(data);
+  }
+
+  PUGS_INLINE DiscreteFunctionP0Vector
+  operator=(const DiscreteFunctionP0Vector& f)
+  {
+    Assert(m_mesh == f.m_mesh);
+    Assert(this->size() == f.size());
+    m_cell_arrays = f.m_cell_arrays;
+    return *this;
+  }
+
+  friend PUGS_INLINE DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>
+  copy(const DiscreteFunctionP0Vector& source)
+  {
+    return DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>{source.m_mesh, copy(source.cellArrays())};
+  }
+
+  friend PUGS_INLINE void
+  copy_to(const DiscreteFunctionP0Vector<Dimension, DataType>& source,
+          DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>& destination)
+  {
+    Assert(source.m_mesh == destination.m_mesh);
+    copy_to(source.m_cell_arrays, destination.m_cell_arrays);
+  }
+
+  PUGS_FORCEINLINE
+  Array<DataType>
   operator[](const CellId cell_id) const noexcept(NO_ASSERT)
   {
     return m_cell_arrays[cell_id];
   }
 
-  friend DiscreteFunctionP0Vector
+  PUGS_INLINE DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>
+  operator-() const
+  {
+    Assert(m_cell_arrays.isBuilt());
+    DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>> opposite = copy(*this);
+
+    const size_t size_of_arrays = this->size();
+    parallel_for(
+      m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+        auto array = opposite[cell_id];
+        for (size_t i = 0; i < size_of_arrays; ++i) {
+          array[i] = -array[i];
+        }
+      });
+
+    return opposite;
+  }
+
+  friend DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>
   operator+(const DiscreteFunctionP0Vector& f, const DiscreteFunctionP0Vector& g)
   {
-    Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
+    Assert(f.m_mesh == g.m_mesh, "functions are nor defined on the same mesh");
     Assert(f.size() == g.size(), "P0 vectors have different sizes");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0Vector sum(mesh, f.size());
+    DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>> sum(f.m_mesh, f.size());
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
         for (size_t i = 0; i < f.size(); ++i) {
           sum[cell_id][i] = f[cell_id][i] + g[cell_id][i];
         }
@@ -85,15 +145,14 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
     return sum;
   }
 
-  friend DiscreteFunctionP0Vector
+  friend DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>
   operator-(const DiscreteFunctionP0Vector& f, const DiscreteFunctionP0Vector& g)
   {
     Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
     Assert(f.size() == g.size(), "P0 vectors have different sizes");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0Vector difference(mesh, f.size());
+    DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>> difference(f.m_mesh, f.size());
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
         for (size_t i = 0; i < f.size(); ++i) {
           difference[cell_id][i] = f[cell_id][i] - g[cell_id][i];
         }
@@ -102,63 +161,47 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
   }
 
   template <typename DataType2>
-  friend DiscreteFunctionP0Vector
+  friend DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>
   operator*(const DiscreteFunctionP0<Dimension, DataType2>& f, const DiscreteFunctionP0Vector& g)
   {
     static_assert(std::is_arithmetic_v<DataType2>, "left operand must be arithmetic");
 
     Assert(f.mesh() == g.mesh(), "functions are nor defined on the same mesh");
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0Vector product(mesh, g.size());
+    DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>> product(g.m_mesh, g.size());
+    const size_t size_of_arrays = g.size();
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-        const double f_value = f[cell_id];
-        for (size_t i = 0; i < g.size(); ++i) {
+      g.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+        const DataType2& f_value = f[cell_id];
+        for (size_t i = 0; i < size_of_arrays; ++i) {
           product[cell_id][i] = f_value * g[cell_id][i];
         }
       });
     return product;
   }
 
-  friend DiscreteFunctionP0Vector
-  operator*(double a, const DiscreteFunctionP0Vector& f)
+  template <typename DataType2>
+  friend DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>>
+  operator*(const DataType2& a, const DiscreteFunctionP0Vector& f)
   {
-    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
-    DiscreteFunctionP0Vector product(mesh, f.size());
+    DiscreteFunctionP0Vector<Dimension, std::remove_const_t<DataType>> product(f.m_mesh, f.size());
+    const size_t size_of_arrays = f.size();
     parallel_for(
-      mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
-        for (size_t i = 0; i < f.size(); ++i) {
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+        for (size_t i = 0; i < size_of_arrays; ++i) {
           product[cell_id][i] = a * f[cell_id][i];
         }
       });
     return product;
   }
 
-  DiscreteFunctionP0Vector(const std::shared_ptr<const MeshType>& mesh,
-                           const std::vector<FunctionSymbolId>& function_symbol_id_list)
-    : m_mesh(mesh)
-  {
-    using MeshDataType      = MeshData<Dimension>;
-    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
-
-    m_cell_arrays = InterpolateItemArray<DataType(
-      TinyVector<Dimension>)>::template interpolate<ItemType::cell>(function_symbol_id_list, mesh_data.xj());
-  }
-
   DiscreteFunctionP0Vector(const std::shared_ptr<const MeshType>& mesh, size_t size)
     : m_mesh{mesh}, m_cell_arrays{mesh->connectivity(), size}
   {}
 
-  template <typename DataType2>
-  DiscreteFunctionP0Vector(const std::shared_ptr<const MeshType>& mesh, const CellArray<DataType2>& cell_arrays)
-    : m_mesh{mesh}
+  DiscreteFunctionP0Vector(const std::shared_ptr<const MeshType>& mesh, const CellArray<DataType>& cell_arrays)
+    : m_mesh{mesh}, m_cell_arrays{cell_arrays}
   {
-    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>);
-    static_assert(std::is_const_v<DataType> or not std::is_const_v<DataType2>);
-
-    Assert(mesh->connectivity_ptr() == cell_arrays.connectivity_ptr());
-
-    m_cell_arrays = cell_arrays;
+    Assert(mesh->shared_connectivity() == cell_arrays.connectivity_ptr());
   }
 
   DiscreteFunctionP0Vector(const DiscreteFunctionP0Vector&) noexcept = default;
diff --git a/src/scheme/DiscreteFunctionType.hpp b/src/scheme/DiscreteFunctionType.hpp
index 8532a25e9111d40a8fcfbd4d0fe8cb6805d525c9..6dd002be688133cbc96a5780d7933fad51bf86cb 100644
--- a/src/scheme/DiscreteFunctionType.hpp
+++ b/src/scheme/DiscreteFunctionType.hpp
@@ -21,7 +21,9 @@ name(DiscreteFunctionType type)
   case DiscreteFunctionType::P0Vector:
     return "P0Vector";
   }
+  // LCOV_EXCL_START
   return {};
+  // LCOV_EXCL_STOP
 }
 
 #endif   // DISCRETE_FUNCTION_TYPE_HPP
diff --git a/src/scheme/DiscreteFunctionVectorInterpoler.cpp b/src/scheme/DiscreteFunctionVectorInterpoler.cpp
index 1a5077377dd1858f5f3c2fa495621598784e8360..4827345a5864ccae5425f6463e00163d455a4aa8 100644
--- a/src/scheme/DiscreteFunctionVectorInterpoler.cpp
+++ b/src/scheme/DiscreteFunctionVectorInterpoler.cpp
@@ -1,5 +1,6 @@
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
 
+#include <language/utils/InterpolateItemArray.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
 #include <utils/Exceptions.hpp>
 
@@ -8,7 +9,14 @@ std::shared_ptr<IDiscreteFunction>
 DiscreteFunctionVectorInterpoler::_interpolate() const
 {
   std::shared_ptr mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
-  return std::make_shared<DiscreteFunctionP0Vector<Dimension, DataType>>(mesh, m_function_id_list);
+
+  using MeshDataType      = MeshData<Dimension>;
+  MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+  return std::make_shared<
+    DiscreteFunctionP0Vector<Dimension, DataType>>(mesh, InterpolateItemArray<DataType(TinyVector<Dimension>)>::
+                                                           template interpolate<ItemType::cell>(m_function_id_list,
+                                                                                                mesh_data.xj()));
 }
 
 template <size_t Dimension>
@@ -41,6 +49,10 @@ DiscreteFunctionVectorInterpoler::_interpolate() const
 std::shared_ptr<IDiscreteFunction>
 DiscreteFunctionVectorInterpoler::interpolate() const
 {
+  if (m_discrete_function_descriptor->type() != DiscreteFunctionType::P0Vector) {
+    throw NormalError("invalid discrete function type for vector interpolation");
+  }
+
   std::shared_ptr<IDiscreteFunction> discrete_function;
   switch (m_mesh->dimension()) {
   case 1: {
@@ -56,5 +68,4 @@ DiscreteFunctionVectorInterpoler::interpolate() const
     throw UnexpectedError("invalid dimension");
   }
   }
-  return nullptr;
 }
diff --git a/src/utils/Array.hpp b/src/utils/Array.hpp
index 1fe0aacaac25b7541acab4dc532ab93e7f65070e..5eb834b85fb8f0679636531e740863286e912c3d 100644
--- a/src/utils/Array.hpp
+++ b/src/utils/Array.hpp
@@ -36,6 +36,13 @@ class [[nodiscard]] Array
     return image;
   }
 
+  friend PUGS_INLINE void copy_to(const Array<DataType>& source,
+                                  const Array<std::remove_const_t<DataType>>& destination)
+  {
+    Assert(source.size() == destination.size());
+    Kokkos::deep_copy(destination.m_values, source.m_values);
+  }
+
   template <typename DataType2, typename... RT>
   friend PUGS_INLINE Array<DataType2> encapsulate(const Kokkos::View<DataType2*, RT...>& values);
 
diff --git a/src/utils/SubArray.hpp b/src/utils/SubArray.hpp
deleted file mode 100644
index a5019dec9521039e420aeeef48c0439930a7840c..0000000000000000000000000000000000000000
--- a/src/utils/SubArray.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-#ifndef SUB_ARRAY_HPP
-#define SUB_ARRAY_HPP
-
-#include <utils/Array.hpp>
-#include <utils/PugsAssert.hpp>
-#include <utils/PugsMacros.hpp>
-#include <utils/PugsUtils.hpp>
-
-#include <algorithm>
-
-template <typename DataType>
-class [[nodiscard]] SubArray
-{
- public:
-  using data_type  = DataType;
-  using index_type = size_t;
-
- private:
-  PUGS_RESTRICT DataType* const m_sub_values;
-  const size_t m_size;
-
-  // Allows const version to access our data
-  friend SubArray<std::add_const_t<DataType>>;
-
- public:
-  PUGS_INLINE size_t size() const noexcept
-  {
-    return m_size;
-  }
-
-  PUGS_INLINE DataType& operator[](index_type i) const noexcept(NO_ASSERT)
-  {
-    Assert(i < m_size);
-    return m_sub_values[i];
-  }
-
-  PUGS_INLINE
-  void fill(const DataType& data) const
-  {
-    static_assert(not std::is_const<DataType>(), "Cannot modify SubArray of const");
-
-    // could consider to use std::fill
-    parallel_for(
-      this->size(), PUGS_LAMBDA(index_type i) { m_sub_values[i] = data; });
-  }
-
-  PUGS_INLINE
-  SubArray& operator=(const SubArray&) = delete;
-
-  PUGS_INLINE
-  SubArray& operator=(SubArray&&) = delete;
-
-  PUGS_INLINE
-  explicit SubArray(const Array<DataType>& array, size_t begin, size_t size)
-    : m_sub_values{&array[0] + begin}, m_size{size}
-  {
-    Assert(begin + size <= array.size(), "SubView is not contained in the source Array");
-  }
-
-  PUGS_INLINE
-  explicit SubArray(DataType* const raw_array, size_t begin, size_t size)
-    : m_sub_values{raw_array + begin}, m_size{size}
-  {}
-
-  PUGS_INLINE
-  SubArray() = delete;
-
-  PUGS_INLINE
-  SubArray(const SubArray&) = delete;
-
-  PUGS_INLINE
-  SubArray(SubArray &&) = delete;
-
-  PUGS_INLINE
-  ~SubArray() = default;
-};
-
-#endif   // SUB_ARRAY_HPP
diff --git a/src/utils/Table.hpp b/src/utils/Table.hpp
index 67f544b997f32086131a549237de091e75ba60c1..eedc5e7d72c4c2f935f0ecafe0d24747c3b2d00e 100644
--- a/src/utils/Table.hpp
+++ b/src/utils/Table.hpp
@@ -46,6 +46,14 @@ class [[nodiscard]] Table
     return image;
   }
 
+  friend PUGS_INLINE void copy_to(const Table<DataType>& source,
+                                  const Table<std::remove_const_t<DataType>>& destination)
+  {
+    Assert(source.nbRows() == destination.nbRows());
+    Assert(source.nbColumns() == destination.nbColumns());
+    Kokkos::deep_copy(destination.m_values, source.m_values);
+  }
+
   template <typename DataType2, typename... RT>
   friend PUGS_INLINE Table<DataType2> encapsulate(const Kokkos::View<DataType2**, RT...>& values);
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c36830918d3d769870250d212ea6d04e5d8316fc..4bb227aac16a433471caa45bdfb8b8bc48f4f2bb 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -58,6 +58,9 @@ add_executable (unit_tests
   test_CRSMatrix.cpp
   test_DataVariant.cpp
   test_Demangle.cpp
+  test_DiscreteFunctionDescriptorP0.cpp
+  test_DiscreteFunctionDescriptorP0Vector.cpp
+  test_DiscreteFunctionType.cpp
   test_DoWhileProcessor.cpp
   test_EmbeddedData.cpp
   test_EscapedString.cpp
@@ -98,12 +101,16 @@ add_executable (unit_tests
 
 add_executable (mpi_unit_tests
   mpi_test_main.cpp
-  test_Messenger.cpp
-  test_Partitioner.cpp
+  test_DiscreteFunctionP0.cpp
+  test_DiscreteFunctionP0Vector.cpp
+  test_InterpolateItemArray.cpp
+  test_InterpolateItemValue.cpp
   test_ItemArray.cpp
   test_ItemArrayUtils.cpp
   test_ItemValue.cpp
   test_ItemValueUtils.cpp
+  test_Messenger.cpp
+  test_Partitioner.cpp
   test_SubItemValuePerItem.cpp
   test_SubItemArrayPerItem.cpp
   )
diff --git a/tests/MeshDataBaseForTests.cpp b/tests/MeshDataBaseForTests.cpp
index 8debe252a86fac67463ffc74ab17e1f9ccf141ea..95399d4564f38313f443f144cf214298577f315a 100644
--- a/tests/MeshDataBaseForTests.cpp
+++ b/tests/MeshDataBaseForTests.cpp
@@ -7,13 +7,14 @@ const MeshDataBaseForTests* MeshDataBaseForTests::m_instance = nullptr;
 
 MeshDataBaseForTests::MeshDataBaseForTests()
 {
-  m_cartesian_1d_mesh = CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh();
+  m_cartesian_1d_mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<1>>>(
+    CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{23}}.mesh());
 
-  m_cartesian_2d_mesh =
-    CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh();
+  m_cartesian_2d_mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<2>>>(
+    CartesianMeshBuilder{TinyVector<2>{0, -1}, TinyVector<2>{3, 2}, TinyVector<2, size_t>{6, 7}}.mesh());
 
-  m_cartesian_3d_mesh =
-    CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh();
+  m_cartesian_3d_mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<3>>>(
+    CartesianMeshBuilder{TinyVector<3>{0, 1, 0}, TinyVector<3>{2, -1, 3}, TinyVector<3, size_t>{6, 7, 4}}.mesh());
 }
 
 const MeshDataBaseForTests&
@@ -37,19 +38,20 @@ MeshDataBaseForTests::destroy()
   m_instance = nullptr;
 }
 
-template <size_t Dimension>
-const Mesh<Connectivity<Dimension>>&
-MeshDataBaseForTests::cartesianMesh() const
+std::shared_ptr<const Mesh<Connectivity<1>>>
+MeshDataBaseForTests::cartesianMesh1D() const
 {
-  if constexpr (Dimension == 1) {
-    return dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*m_cartesian_1d_mesh);
-  } else if constexpr (Dimension == 2) {
-    return dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*m_cartesian_2d_mesh);
-  } else if constexpr (Dimension == 3) {
-    return dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*m_cartesian_3d_mesh);
-  }
+  return m_cartesian_1d_mesh;
 }
 
-template const Mesh<Connectivity<1>>& MeshDataBaseForTests::cartesianMesh<1>() const;
-template const Mesh<Connectivity<2>>& MeshDataBaseForTests::cartesianMesh<2>() const;
-template const Mesh<Connectivity<3>>& MeshDataBaseForTests::cartesianMesh<3>() const;
+std::shared_ptr<const Mesh<Connectivity<2>>>
+MeshDataBaseForTests::cartesianMesh2D() const
+{
+  return m_cartesian_2d_mesh;
+}
+
+std::shared_ptr<const Mesh<Connectivity<3>>>
+MeshDataBaseForTests::cartesianMesh3D() const
+{
+  return m_cartesian_3d_mesh;
+}
diff --git a/tests/MeshDataBaseForTests.hpp b/tests/MeshDataBaseForTests.hpp
index 2a1b1c06c864f37981fd275067e0994a589afaac..589ec9b82f9be6451afd496aab3399c13bb3d718 100644
--- a/tests/MeshDataBaseForTests.hpp
+++ b/tests/MeshDataBaseForTests.hpp
@@ -18,13 +18,14 @@ class MeshDataBaseForTests
 
   static const MeshDataBaseForTests* m_instance;
 
-  std::shared_ptr<const IMesh> m_cartesian_1d_mesh;
-  std::shared_ptr<const IMesh> m_cartesian_2d_mesh;
-  std::shared_ptr<const IMesh> m_cartesian_3d_mesh;
+  std::shared_ptr<const Mesh<Connectivity<1>>> m_cartesian_1d_mesh;
+  std::shared_ptr<const Mesh<Connectivity<2>>> m_cartesian_2d_mesh;
+  std::shared_ptr<const Mesh<Connectivity<3>>> m_cartesian_3d_mesh;
 
  public:
-  template <size_t Dimension>
-  const Mesh<Connectivity<Dimension>>& cartesianMesh() const;
+  std::shared_ptr<const Mesh<Connectivity<1>>> cartesianMesh1D() const;
+  std::shared_ptr<const Mesh<Connectivity<2>>> cartesianMesh2D() const;
+  std::shared_ptr<const Mesh<Connectivity<3>>> cartesianMesh3D() const;
 
   static const MeshDataBaseForTests& get();
   static void create();
diff --git a/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp b/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
index d9b5d245624eaba1ebb14e1e7869ac89e58b28b9..b99aa045958dc356cb03f2b548e8c632d58b124d 100644
--- a/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
+++ b/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeBinaryOperatorExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -22,6 +23,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
diff --git a/tests/test_ASTNodeDataTypeBuilder.cpp b/tests/test_ASTNodeDataTypeBuilder.cpp
index 79e01aef9a34ab2324c0c731e1a2079d3e985d99..346807df3b58e189a7e1283850fbdda54264abbb 100644
--- a/tests/test_ASTNodeDataTypeBuilder.cpp
+++ b/tests/test_ASTNodeDataTypeBuilder.cpp
@@ -2,7 +2,9 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 #include <language/utils/ASTNodeDataTypeTraits.hpp>
 #include <language/utils/ASTPrinter.hpp>
@@ -20,6 +22,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
@@ -78,7 +83,16 @@ import a_module_name;
      `-(language::module_name:string)
 )";
 
-    CHECK_AST(data, result);
+    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+    auto ast = ASTBuilder::build(input);
+
+    ASTSymbolTableBuilder{*ast};
+    ASTNodeDataTypeBuilder{*ast};
+
+    std::stringstream ast_output;
+    ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}};
+
+    REQUIRE(ast_output.str() == result);
   }
 
   SECTION("integer")
@@ -392,7 +406,7 @@ let  (x,y) : R;
       SECTION("invalid R-list -> R^d")
       {
         std::string_view data = R"(
-let square : R -> R^3, x -> (x, x*x);
+let square : R -> R^3, x -> (x, 2);
 )";
 
         TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
@@ -999,7 +1013,8 @@ let f : R*Z -> B, x -> 3;
         auto ast = ASTBuilder::build(input);
         ASTSymbolTableBuilder{*ast};
 
-        REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
+                            "number of product spaces (2) R*Z  differs from number of variables (1) x");
       }
 
       SECTION("wrong parameter number 2")
@@ -1011,7 +1026,8 @@ let f : R -> B, (x,y) -> 3;
         auto ast = ASTBuilder::build(input);
         ASTSymbolTableBuilder{*ast};
 
-        REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
+                            "number of product spaces (1) R differs from number of variables (2) (x,y) ");
       }
 
       SECTION("wrong image size")
@@ -1034,6 +1050,10 @@ let f : R -> R*R, x -> x*x*x;
 )";
         TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
         auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
@@ -1047,11 +1067,67 @@ let f : R -> R^2x2, x -> (x, 2*x, 2);
 )";
         TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
         auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "expecting 4 scalar expressions or an R^2x2, found 3 scalar expressions");
       }
+
+      SECTION("undefined type identifier")
+      {
+        std::string_view data = R"(
+let x:X;
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        auto ast = ASTBuilder::build(input);
+        ASTSymbolTableBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
+      }
+
+      SECTION("undefined type identifier")
+      {
+        std::string_view data = R"(
+let X:R, X = 3;
+let x:X;
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        auto ast = ASTBuilder::build(input);
+        ASTSymbolTableBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
+                            "invalid type identifier, 'X' was previously defined as a 'R'");
+      }
+
+      SECTION("undefined image type identifier")
+      {
+        std::string_view data = R"(
+let f: R -> X, x -> x;
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        auto ast = ASTBuilder::build(input);
+        ASTSymbolTableBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
+      }
+
+      SECTION("invalid image type identifier")
+      {
+        std::string_view data = R"(
+let X: R, X = 3;
+let f: R -> X, x -> x;
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        auto ast = ASTBuilder::build(input);
+        ASTSymbolTableBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
+                            "invalid type identifier, 'X' was previously defined as a 'R'");
+      }
     }
   }
 
@@ -1277,7 +1353,8 @@ not_a_function(2,3);
         auto ast = ASTBuilder::build(input);
         ASTSymbolTableBuilder{*ast};
 
-        REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid function call\n"
+                                                          "note: 'not_a_function' (type: R) is not a function!");
       }
     }
   }
@@ -1732,7 +1809,7 @@ if ("string");
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
-      REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
     }
   }
 
@@ -1818,7 +1895,7 @@ while ("string");
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
-      REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
     }
   }
 
@@ -1904,7 +1981,7 @@ do 1; while ("string");
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
-      REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
     }
   }
 
@@ -2223,7 +2300,8 @@ true xor false;
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
-      REQUIRE_THROWS_AS(ASTNodeDataTypeBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined binary operator\n"
+                                                        "note: incompatible operand types Z and string");
     }
   }
 }
diff --git a/tests/test_ASTNodeDataTypeChecker.cpp b/tests/test_ASTNodeDataTypeChecker.cpp
index 4c8dc8cf03e475b97bd91a1422e55a909dede2b7..5c0fc6d8fd38054e4343470ef8f6196d7e34ac60 100644
--- a/tests/test_ASTNodeDataTypeChecker.cpp
+++ b/tests/test_ASTNodeDataTypeChecker.cpp
@@ -2,8 +2,10 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeChecker.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 #include <language/utils/ParseError.hpp>
 
@@ -25,6 +27,9 @@ for(let i:Z, i=0; i<10; ++i) {
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
     auto ast = ASTBuilder::build(input);
 
+    ASTModulesImporter{*ast};
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
     ASTSymbolTableBuilder{*ast};
     ASTNodeDataTypeBuilder{*ast};
 
@@ -43,6 +48,9 @@ for(let i:Z, i=0; i<10; ++i) {
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
     auto ast = ASTBuilder::build(input);
 
+    ASTModulesImporter{*ast};
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
     ASTSymbolTableBuilder{*ast};
     ASTNodeDataTypeBuilder{*ast};
 
diff --git a/tests/test_ASTNodeDataTypeFlattener.cpp b/tests/test_ASTNodeDataTypeFlattener.cpp
index c49613ca827ed265e3e338a5639a7edfa862823c..d4b4bbe6c65e7bc0a110d70b406b24f678f82576 100644
--- a/tests/test_ASTNodeDataTypeFlattener.cpp
+++ b/tests/test_ASTNodeDataTypeFlattener.cpp
@@ -2,12 +2,15 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeFlattener.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 
+#include <test_BuiltinFunctionRegister.hpp>
+
 #include <pegtl/string_input.hpp>
 
 // clazy:excludeall=non-pod-global-static
@@ -26,6 +29,9 @@ b;
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto root_node = ASTBuilder::build(input);
 
+      ASTModulesImporter{*root_node};
+      ASTNodeTypeCleaner<language::import_instruction>{*root_node};
+
       ASTSymbolTableBuilder{*root_node};
       ASTNodeDataTypeBuilder{*root_node};
 
@@ -49,6 +55,9 @@ n;
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto root_node = ASTBuilder::build(input);
 
+      ASTModulesImporter{*root_node};
+      ASTNodeTypeCleaner<language::import_instruction>{*root_node};
+
       ASTSymbolTableBuilder{*root_node};
       ASTNodeDataTypeBuilder{*root_node};
 
@@ -75,6 +84,9 @@ f(2);
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto root_node = ASTBuilder::build(input);
 
+      ASTModulesImporter{*root_node};
+      ASTNodeTypeCleaner<language::import_instruction>{*root_node};
+
       ASTSymbolTableBuilder{*root_node};
       ASTNodeDataTypeBuilder{*root_node};
 
@@ -104,6 +116,9 @@ f(2);
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto root_node = ASTBuilder::build(input);
 
+      ASTModulesImporter{*root_node};
+      ASTNodeTypeCleaner<language::import_instruction>{*root_node};
+
       ASTSymbolTableBuilder{*root_node};
       ASTNodeDataTypeBuilder{*root_node};
 
@@ -136,6 +151,9 @@ f(2);
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
       auto root_node = ASTBuilder::build(input);
 
+      ASTModulesImporter{*root_node};
+      ASTNodeTypeCleaner<language::import_instruction>{*root_node};
+
       ASTSymbolTableBuilder{*root_node};
       ASTNodeDataTypeBuilder{*root_node};
 
@@ -155,5 +173,39 @@ f(2);
       REQUIRE(flattened_datatype_list[1].m_data_type == ASTNodeDataType::vector_t);
       REQUIRE(flattened_datatype_list[1].m_data_type.dimension() == 3);
     }
+
+    SECTION("builtin_function -> R*R")
+    {
+      std::string_view data = R"(
+sum_vector(2,3);
+)";
+
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+      auto root_node = ASTBuilder::build(input);
+
+      ASTModulesImporter{*root_node};
+      ASTNodeTypeCleaner<language::import_instruction>{*root_node};
+
+      test_only::test_BuiltinFunctionRegister{*root_node};
+
+      ASTSymbolTableBuilder{*root_node};
+      ASTNodeDataTypeBuilder{*root_node};
+
+      // optimizations
+      ASTNodeDeclarationToAffectationConverter{*root_node};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*root_node};
+      ASTNodeTypeCleaner<language::fct_declaration>{*root_node};
+
+      REQUIRE(root_node->children[0]->is_type<language::function_evaluation>());
+
+      ASTNodeDataTypeFlattener::FlattenedDataTypeList flattened_datatype_list;
+      ASTNodeDataTypeFlattener{*root_node->children[0], flattened_datatype_list};
+
+      REQUIRE(flattened_datatype_list.size() == 2);
+      REQUIRE(flattened_datatype_list[0].m_data_type == ASTNodeDataType::double_t);
+      REQUIRE(flattened_datatype_list[1].m_data_type == ASTNodeDataType::vector_t);
+      REQUIRE(flattened_datatype_list[1].m_data_type.dimension() == 2);
+    }
   }
 }
diff --git a/tests/test_ASTNodeDeclarationToAffectationConverter.cpp b/tests/test_ASTNodeDeclarationToAffectationConverter.cpp
index c102d5f6599f40a390bdb7b67004f3786624540f..f38b7aaf7c57d60d7fb7a6a44b1241b3d71aa6c0 100644
--- a/tests/test_ASTNodeDeclarationToAffectationConverter.cpp
+++ b/tests/test_ASTNodeDeclarationToAffectationConverter.cpp
@@ -2,8 +2,10 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 #include <language/utils/ASTPrinter.hpp>
 
@@ -17,6 +19,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                 \
     auto ast = ASTBuilder::build(input);                                                       \
                                                                                                \
+    ASTModulesImporter{*ast};                                                                  \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                    \
+                                                                                               \
     ASTSymbolTableBuilder{*ast};                                                               \
     ASTNodeDataTypeBuilder{*ast};                                                              \
                                                                                                \
diff --git a/tests/test_ASTNodeEmptyBlockCleaner.cpp b/tests/test_ASTNodeEmptyBlockCleaner.cpp
index e9a7c4a5e9b786f9c9e4ba6fe34504f320bb5472..baf6978c6a6d04d8a4e68a75e5e61fad635f8b55 100644
--- a/tests/test_ASTNodeEmptyBlockCleaner.cpp
+++ b/tests/test_ASTNodeEmptyBlockCleaner.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeEmptyBlockCleaner.hpp>
@@ -22,6 +23,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
diff --git a/tests/test_ASTNodeExpressionBuilder.cpp b/tests/test_ASTNodeExpressionBuilder.cpp
index 11eb9f16177612f0c150a9b9f363fc219578c9cf..cb6fdbfef85884a013e112a978207729ba965835 100644
--- a/tests/test_ASTNodeExpressionBuilder.cpp
+++ b/tests/test_ASTNodeExpressionBuilder.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -21,6 +22,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
@@ -39,6 +43,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
@@ -896,6 +903,10 @@ continue;
 
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
     auto ast = ASTBuilder::build(input);
+
+    ASTModulesImporter{*ast};
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
     ASTSymbolTableBuilder{*ast};
     ASTNodeDataTypeBuilder{*ast};
 
diff --git a/tests/test_ASTNodeIncDecExpressionBuilder.cpp b/tests/test_ASTNodeIncDecExpressionBuilder.cpp
index 88d1518fcad3511bba5cb6d426f752f72dd5c912..78391f1ee8e41f9b0d53cbddc2cea7aefe5e54ec 100644
--- a/tests/test_ASTNodeIncDecExpressionBuilder.cpp
+++ b/tests/test_ASTNodeIncDecExpressionBuilder.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -23,6 +24,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
@@ -46,6 +50,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                \
     auto ast = ASTBuilder::build(input);                                                      \
                                                                                               \
+    ASTModulesImporter{*ast};                                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                   \
+                                                                                              \
     ASTSymbolTableBuilder{*ast};                                                              \
     ASTNodeDataTypeBuilder{*ast};                                                             \
                                                                                               \
diff --git a/tests/test_ASTNodeListAffectationExpressionBuilder.cpp b/tests/test_ASTNodeListAffectationExpressionBuilder.cpp
index bcb8942e785f3ad9f5320a4ab321986736ae6b97..8ae86fa804a704c10e32c3fe3e5b986987e22de0 100644
--- a/tests/test_ASTNodeListAffectationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeListAffectationExpressionBuilder.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -22,6 +23,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
@@ -45,6 +49,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                     \
     auto ast = ASTBuilder::build(input);                                           \
                                                                                    \
+    ASTModulesImporter{*ast};                                                      \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                        \
+                                                                                   \
     ASTSymbolTableBuilder{*ast};                                                   \
     ASTNodeDataTypeBuilder{*ast};                                                  \
                                                                                    \
diff --git a/tests/test_ASTNodeTypeCleaner.cpp b/tests/test_ASTNodeTypeCleaner.cpp
index e2ae69b4695d785745dce55a5dc1bf617b980773..e1abf88d600d45ba9ae77d3c25b4904adf6992d4 100644
--- a/tests/test_ASTNodeTypeCleaner.cpp
+++ b/tests/test_ASTNodeTypeCleaner.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
@@ -15,6 +16,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                 \
     auto ast = ASTBuilder::build(input);                                                       \
                                                                                                \
+    ASTModulesImporter{*ast};                                                                  \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                    \
+                                                                                               \
     ASTSymbolTableBuilder{*ast};                                                               \
     ASTNodeDataTypeBuilder{*ast};                                                              \
                                                                                                \
diff --git a/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp b/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp
index ae51cf9a0d77b642f7e3b9f6cdcd1c00d59c492a..d320aee0257de0e2cabf852ee9426885c755e7e4 100644
--- a/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp
+++ b/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -22,6 +23,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
+    ASTModulesImporter{*ast};                                                                       \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
+                                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                                    \
     ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                     \
@@ -41,6 +45,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
diff --git a/tests/test_ASTSymbolTableBuilder.cpp b/tests/test_ASTSymbolTableBuilder.cpp
index 581bd1fae668aef70e17544393f7632fe564e1fc..e336d7841a3fbc54903a16584d0eb224252b464d 100644
--- a/tests/test_ASTSymbolTableBuilder.cpp
+++ b/tests/test_ASTSymbolTableBuilder.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 #include <language/utils/ParseError.hpp>
 
@@ -78,7 +79,7 @@ let n:N, n = a;
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_AS(ASTSymbolTableBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "undefined symbol 'a'");
     }
 
     SECTION("Re-declared symbol")
@@ -91,7 +92,7 @@ let n:N, n = 1;
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_AS(ASTSymbolTableBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'n' was already defined!");
     }
 
     SECTION("Re-declared symbol (function)")
@@ -104,7 +105,36 @@ let f : R -> R, x -> 1;
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_AS(ASTSymbolTableBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'f' was already defined!");
+    }
+
+    SECTION("Re-declared symbol (builtin function)")
+    {
+      std::string_view data = R"(
+import math;
+let cos:N;
+)";
+
+      string_input input{data, "test.pgs"};
+      auto ast = ASTBuilder::build(input);
+      ASTModulesImporter{*ast};
+
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'cos' already denotes a builtin function!");
+    }
+
+    SECTION("Re-declared symbol (builtin function) 2")
+    {
+      std::string_view data = R"(
+import math;
+let cos: R -> R, x->2*x;
+)";
+
+      string_input input{data, "test.pgs"};
+      auto ast = ASTBuilder::build(input);
+      ASTModulesImporter{*ast};
+
+      // REQUIRE_THROWS_AS(ASTSymbolTableBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'cos' already denotes a builtin function!");
     }
 
     SECTION("Re-declared parameter (function)")
@@ -116,7 +146,7 @@ let f : R*R*N -> R, (x,y,x) -> 1;
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_AS(ASTSymbolTableBuilder{*ast}, ParseError);
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'x' was already defined!");
     }
   }
 }
diff --git a/tests/test_AffectationProcessor.cpp b/tests/test_AffectationProcessor.cpp
index ec130a135320bf1ceba5ee1ff532b57437296d2e..b6f73afdb4ec60f6b754eea3eafcbe2bf0ce0759 100644
--- a/tests/test_AffectationProcessor.cpp
+++ b/tests/test_AffectationProcessor.cpp
@@ -482,6 +482,13 @@ TEST_CASE("AffectationProcessor", "[language]")
         CHECK_AFFECTATION_THROWS_WITH("let x : R^2, x = 3.2;", "undefined affectation type: R^2 = R");
         CHECK_AFFECTATION_THROWS_WITH("let x : R^3, x = 2.3;", "undefined affectation type: R^3 = R");
 
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^1; let y : R^1x1, y = 1; x = y;",
+                                      "undefined affectation type: R^1 = R^1x1");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2; let y : R^2x2, y = (1,2,4,4); x = y;",
+                                      "undefined affectation type: R^2 = R^2x2");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3; let y : R^3x3, y = (1,2,3,4,5,6,7,8,9); x = y;",
+                                      "undefined affectation type: R^3 = R^3x3");
+
         CHECK_AFFECTATION_THROWS_WITH("let x : R^2, x = 4;", "invalid integral value (0 is the solely valid value)");
         CHECK_AFFECTATION_THROWS_WITH("let x : R^3, x = 3;", "invalid integral value (0 is the solely valid value)");
 
@@ -499,6 +506,65 @@ TEST_CASE("AffectationProcessor", "[language]")
                                       "undefined affectation type: R^1 = R^3");
         CHECK_AFFECTATION_THROWS_WITH("let x : R^3, x = 0; let y : R^2, y = x;",
                                       "undefined affectation type: R^2 = R^3");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^1, x = (1,2);", "undefined affectation type: R^1 = list");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2, x = (1,2,3);",
+                                      "incompatible dimensions in affectation: expecting 2, but provided 3");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3, x = (1,2);",
+                                      "incompatible dimensions in affectation: expecting 3, but provided 2");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3, x = (1,2,3,4);",
+                                      "incompatible dimensions in affectation: expecting 3, but provided 4");
+      }
+
+      SECTION("-> R^nxn")
+      {
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = \"foobar\";", "undefined affectation type: R^2x2 = string");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = \"foobar\";", "undefined affectation type: R^3x3 = string");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = 3.2;", "undefined affectation type: R^2x2 = R");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = 2.3;", "undefined affectation type: R^3x3 = R");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^1x1; let y : R^1, y = 1; x = y;",
+                                      "undefined affectation type: R^1x1 = R^1");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2; let y : R^2, y = (1,2); x = y;",
+                                      "undefined affectation type: R^2x2 = R^2");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3; let y : R^3, y = (1,2,3); x = y;",
+                                      "undefined affectation type: R^3x3 = R^3");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2; x = 3.2;", "undefined affectation type: R^2x2 = R");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3; x = 2.3;", "undefined affectation type: R^3x3 = R");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = 4;", "invalid integral value (0 is the solely valid value)");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = 3;", "invalid integral value (0 is the solely valid value)");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^1x1, x = 0; let y : R^2x2, y = x;",
+                                      "undefined affectation type: R^2x2 = R^1x1");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^1x1, x = 0; let y : R^3x3, y = x;",
+                                      "undefined affectation type: R^3x3 = R^1x1");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = 0; let y : R^1x1, y = x;",
+                                      "undefined affectation type: R^1x1 = R^2x2");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = 0; let y : R^3x3, y = x;",
+                                      "undefined affectation type: R^3x3 = R^2x2");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = 0; let y : R^1x1, y = x;",
+                                      "undefined affectation type: R^1x1 = R^3x3");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = 0; let y : R^2x2, y = x;",
+                                      "undefined affectation type: R^2x2 = R^3x3");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^1x1, x = (1,2);", "undefined affectation type: R^1x1 = list");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = (1,2,3);",
+                                      "incompatible dimensions in affectation: expecting 4, but provided 3");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^2x2, x = (1,2,3,4,5);",
+                                      "incompatible dimensions in affectation: expecting 4, but provided 5");
+
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = (1,2,3,4,5,6,7,8);",
+                                      "incompatible dimensions in affectation: expecting 9, but provided 8");
+        CHECK_AFFECTATION_THROWS_WITH("let x : R^3x3, x = (1,2,3,4,5,6,7,8,9,10);",
+                                      "incompatible dimensions in affectation: expecting 9, but provided 10");
       }
     }
   }
diff --git a/tests/test_AffectationToStringProcessor.cpp b/tests/test_AffectationToStringProcessor.cpp
index a793a5cd82a6bd53d7e45369e5e9ebae9044745c..01c7ba7b938945dc9f929f602a3fe071b052379c 100644
--- a/tests/test_AffectationToStringProcessor.cpp
+++ b/tests/test_AffectationToStringProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -20,6 +21,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
diff --git a/tests/test_AffectationToTupleProcessor.cpp b/tests/test_AffectationToTupleProcessor.cpp
index df2de1d07c100154a9728a1c68197121fd18f4dd..650e60ca61bb6bc1d8b2e239c9e4c67713c3212a 100644
--- a/tests/test_AffectationToTupleProcessor.cpp
+++ b/tests/test_AffectationToTupleProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -20,6 +21,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
diff --git a/tests/test_Array.cpp b/tests/test_Array.cpp
index ac61be590dbfafe8bee60a216eb3ba01819b6279..23d63a4a2dd769c679811ddc00265ba4aa615dac 100644
--- a/tests/test_Array.cpp
+++ b/tests/test_Array.cpp
@@ -105,6 +105,23 @@ TEST_CASE("Array", "[utils]")
 
     REQUIRE(((c[0] == 2) and (c[1] == 2) and (c[2] == 2) and (c[3] == 2) and (c[4] == 2) and (c[5] == 2) and
              (c[6] == 2) and (c[7] == 2) and (c[8] == 2) and (c[9] == 2)));
+
+    Array<int> d{a.size()};
+    copy_to(a, d);
+
+    REQUIRE(((d[0] == 0) and (d[1] == 2) and (d[2] == 4) and (d[3] == 6) and (d[4] == 8) and (d[5] == 10) and
+             (d[6] == 12) and (d[7] == 14) and (d[8] == 16) and (d[9] == 18)));
+
+    REQUIRE(((c[0] == 2) and (c[1] == 2) and (c[2] == 2) and (c[3] == 2) and (c[4] == 2) and (c[5] == 2) and
+             (c[6] == 2) and (c[7] == 2) and (c[8] == 2) and (c[9] == 2)));
+
+    copy_to(c, d);
+
+    REQUIRE(((a[0] == 0) and (a[1] == 2) and (a[2] == 4) and (a[3] == 6) and (a[4] == 8) and (a[5] == 10) and
+             (a[6] == 12) and (a[7] == 14) and (a[8] == 16) and (a[9] == 18)));
+
+    REQUIRE(((d[0] == 2) and (d[1] == 2) and (d[2] == 2) and (d[3] == 2) and (d[4] == 2) and (d[5] == 2) and
+             (d[6] == 2) and (d[7] == 2) and (d[8] == 2) and (d[9] == 2)));
   }
 
   SECTION("checking for std container conversion")
@@ -220,5 +237,11 @@ TEST_CASE("Array", "[utils]")
   {
     REQUIRE_THROWS_AS(a[10], AssertError);
   }
+
+  SECTION("invalid copy_to")
+  {
+    Array<int> b{2 * a.size()};
+    REQUIRE_THROWS_AS(copy_to(a, b), AssertError);
+  }
 #endif   // NDEBUG
 }
diff --git a/tests/test_BinaryExpressionProcessor_utils.hpp b/tests/test_BinaryExpressionProcessor_utils.hpp
index 7b32bff86087bf871eb70decafe952f273991b65..c8e3440cfd75b1320c146d5eb342a2a5d343a20f 100644
--- a/tests/test_BinaryExpressionProcessor_utils.hpp
+++ b/tests/test_BinaryExpressionProcessor_utils.hpp
@@ -2,6 +2,7 @@
 #define TEST_BINARY_EXPRESSION_PROCESSOR_UTILS_HPP
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -18,6 +19,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
@@ -46,6 +50,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
diff --git a/tests/test_BuiltinFunctionRegister.hpp b/tests/test_BuiltinFunctionRegister.hpp
index e062fcd92222796da9071243bda7c3e5dd253e32..7c361c9df4a6e4c3071ccef00c070313dba2bb5b 100644
--- a/tests/test_BuiltinFunctionRegister.hpp
+++ b/tests/test_BuiltinFunctionRegister.hpp
@@ -7,6 +7,8 @@
 #include <language/utils/TypeDescriptor.hpp>
 #include <utils/Exceptions.hpp>
 
+#include <unordered_map>
+
 template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const double>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("builtin_t");
@@ -66,7 +68,7 @@ class test_BuiltinFunctionRegister
                                     [](const TinyVector<3>& x) -> double { return x[0] + x[1] + x[2]; })));
 
     m_name_builtin_function_map.insert(
-      std::make_pair("R3R2toR:R^3,R^2",
+      std::make_pair("R3R2toR:R^3*R^2",
                      std::make_shared<BuiltinFunctionEmbedder<double(TinyVector<3>, TinyVector<2>)>>(
                        [](TinyVector<3> x, TinyVector<2> y) -> double { return x[0] * y[1] + (y[0] - x[2]) * x[1]; })));
 
@@ -159,6 +161,14 @@ class test_BuiltinFunctionRegister
       std::make_pair("tuple_R33ToR:(R^3x3...)",
                      std::make_shared<BuiltinFunctionEmbedder<double(const std::vector<TinyMatrix<3>>)>>(
                        [](const std::vector<TinyMatrix<3>>&) -> double { return 0; })));
+
+    m_name_builtin_function_map.insert(
+      std::make_pair("sum_vector:R*R^2",
+                     std::make_shared<
+                       BuiltinFunctionEmbedder<std::tuple<double, TinyVector<2>>(const double, const double)>>(
+                       [](const double& x, const double& y) -> std::tuple<double, TinyVector<2>> {
+                         return std::make_tuple(x + y, TinyVector<2>{x, y});
+                       })));
   }
 
   void
diff --git a/tests/test_ConcatExpressionProcessor.cpp b/tests/test_ConcatExpressionProcessor.cpp
index eaa0f76f245ae0f14394396a6593f9118a2e879d..4c5e202b71a1b9630683145aa4ffb30863ce9a27 100644
--- a/tests/test_ConcatExpressionProcessor.cpp
+++ b/tests/test_ConcatExpressionProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -20,6 +21,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
diff --git a/tests/test_DiscreteFunctionDescriptorP0.cpp b/tests/test_DiscreteFunctionDescriptorP0.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2f80bd84e36ee1a5d18b1682ec2f2cbe35203d90
--- /dev/null
+++ b/tests/test_DiscreteFunctionDescriptorP0.cpp
@@ -0,0 +1,26 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <scheme/DiscreteFunctionDescriptorP0.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("DiscreteFunctionDescriptorP0", "[scheme]")
+{
+  SECTION("type")
+  {
+    DiscreteFunctionDescriptorP0 descriptor;
+    REQUIRE(descriptor.type() == DiscreteFunctionType::P0);
+
+    {
+      auto copy = [](const DiscreteFunctionDescriptorP0& d) -> DiscreteFunctionDescriptorP0 { return d; };
+
+      DiscreteFunctionDescriptorP0 descriptor_copy{copy(descriptor)};
+      REQUIRE(descriptor_copy.type() == DiscreteFunctionType::P0);
+    }
+
+    DiscreteFunctionDescriptorP0 temp;
+    DiscreteFunctionDescriptorP0 descriptor_move{std::move(temp)};
+    REQUIRE(descriptor_move.type() == DiscreteFunctionType::P0);
+  }
+}
diff --git a/tests/test_DiscreteFunctionDescriptorP0Vector.cpp b/tests/test_DiscreteFunctionDescriptorP0Vector.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5caa63b236c3414aef6ee46668c73b0b0c3f6e3f
--- /dev/null
+++ b/tests/test_DiscreteFunctionDescriptorP0Vector.cpp
@@ -0,0 +1,26 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("DiscreteFunctionDescriptorP0Vector", "[scheme]")
+{
+  SECTION("type")
+  {
+    DiscreteFunctionDescriptorP0Vector descriptor;
+    REQUIRE(descriptor.type() == DiscreteFunctionType::P0Vector);
+
+    {
+      auto copy = [](const DiscreteFunctionDescriptorP0Vector& d) -> DiscreteFunctionDescriptorP0Vector { return d; };
+
+      DiscreteFunctionDescriptorP0Vector descriptor_copy{copy(descriptor)};
+      REQUIRE(descriptor_copy.type() == DiscreteFunctionType::P0Vector);
+    }
+
+    DiscreteFunctionDescriptorP0Vector temp;
+    DiscreteFunctionDescriptorP0Vector descriptor_move{std::move(temp)};
+    REQUIRE(descriptor_move.type() == DiscreteFunctionType::P0Vector);
+  }
+}
diff --git a/tests/test_DiscreteFunctionP0.cpp b/tests/test_DiscreteFunctionP0.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1fefcdc6ea1e50e4c48fdff2be95d737a118de5a
--- /dev/null
+++ b/tests/test_DiscreteFunctionP0.cpp
@@ -0,0 +1,2376 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("DiscreteFunctionP0", "[scheme]")
+{
+  auto same_values = [](const auto& f, const auto& g) {
+    size_t number_of_cells = f.cellValues().numberOfItems();
+    for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+      if (f[cell_id] != g[cell_id]) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  SECTION("constructors")
+  {
+    SECTION("1D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      DiscreteFunctionP0<Dimension, double> f{mesh};
+      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
+
+      REQUIRE(f.mesh().get() == mesh.get());
+
+      DiscreteFunctionP0 g{f};
+      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
+
+      CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
+      h_values.fill(ZeroType{});
+
+      DiscreteFunctionP0 zero{mesh, [&] {
+                                CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
+                                cell_value.fill(ZeroType{});
+                                return cell_value;
+                              }()};
+
+      DiscreteFunctionP0 h{mesh, h_values};
+      REQUIRE(same_values(h, zero));
+      REQUIRE(same_values(h, h_values));
+
+      DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
+      shallow_h = h;
+
+      copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
+
+      REQUIRE(same_values(shallow_h, h_values));
+      REQUIRE(same_values(h, h_values));
+      REQUIRE(not same_values(h, zero));
+
+      DiscreteFunctionP0 moved_h{std::move(h)};
+      REQUIRE(same_values(moved_h, h_values));
+    }
+
+    SECTION("2D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+
+      constexpr size_t Dimension = 2;
+
+      DiscreteFunctionP0<Dimension, double> f{mesh};
+      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
+
+      REQUIRE(f.mesh().get() == mesh.get());
+
+      DiscreteFunctionP0 g{f};
+      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
+
+      CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
+      h_values.fill(ZeroType{});
+
+      DiscreteFunctionP0 zero{mesh, [&] {
+                                CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
+                                cell_value.fill(ZeroType{});
+                                return cell_value;
+                              }()};
+
+      DiscreteFunctionP0 h{mesh, h_values};
+      REQUIRE(same_values(h, zero));
+      REQUIRE(same_values(h, h_values));
+
+      DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
+      shallow_h = h;
+
+      copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
+
+      REQUIRE(same_values(shallow_h, h_values));
+      REQUIRE(same_values(h, h_values));
+      REQUIRE(not same_values(h, zero));
+
+      DiscreteFunctionP0 moved_h{std::move(h)};
+      REQUIRE(same_values(moved_h, h_values));
+    }
+
+    SECTION("3D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+
+      constexpr size_t Dimension = 3;
+
+      DiscreteFunctionP0<Dimension, double> f{mesh};
+      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0);
+
+      REQUIRE(f.mesh().get() == mesh.get());
+
+      DiscreteFunctionP0 g{f};
+      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0);
+
+      CellValue<TinyVector<Dimension>> h_values{mesh->connectivity()};
+      h_values.fill(ZeroType{});
+
+      DiscreteFunctionP0 zero{mesh, [&] {
+                                CellValue<TinyVector<Dimension>> cell_value{mesh->connectivity()};
+                                cell_value.fill(ZeroType{});
+                                return cell_value;
+                              }()};
+
+      DiscreteFunctionP0 h{mesh, h_values};
+      REQUIRE(same_values(h, zero));
+      REQUIRE(same_values(h, h_values));
+
+      DiscreteFunctionP0<Dimension, TinyVector<Dimension>> shallow_h{mesh};
+      shallow_h = h;
+
+      copy_to(MeshDataManager::instance().getMeshData(*mesh).xj(), h_values);
+
+      REQUIRE(same_values(shallow_h, h_values));
+      REQUIRE(same_values(h, h_values));
+      REQUIRE(not same_values(h, zero));
+
+      DiscreteFunctionP0 moved_h{std::move(h)};
+      REQUIRE(same_values(moved_h, h_values));
+    }
+  }
+
+  SECTION("fill")
+  {
+    auto all_values_equal = [](const auto& f, const auto& g) {
+      size_t number_of_cells = f.cellValues().numberOfItems();
+      for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+        if (f[cell_id] != g) {
+          return false;
+        }
+      }
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
+      constexpr size_t Dimension = 1;
+
+      DiscreteFunctionP0<Dimension, double> f{mesh};
+      f.fill(3);
+
+      REQUIRE(all_values_equal(f, 3));
+
+      DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+      v.fill(TinyVector<3>{1, 2, 3});
+
+      REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
+
+      DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
+      A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
+
+      REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+    }
+
+    SECTION("2D")
+    {
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
+      constexpr size_t Dimension = 2;
+
+      DiscreteFunctionP0<Dimension, double> f{mesh};
+      f.fill(3);
+
+      REQUIRE(all_values_equal(f, 3));
+
+      DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+      v.fill(TinyVector<3>{1, 2, 3});
+
+      REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
+
+      DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
+      A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
+
+      REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+    }
+
+    SECTION("3D")
+    {
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
+      constexpr size_t Dimension = 3;
+
+      DiscreteFunctionP0<Dimension, double> f{mesh};
+      f.fill(3);
+
+      REQUIRE(all_values_equal(f, 3));
+
+      DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+      v.fill(TinyVector<3>{1, 2, 3});
+
+      REQUIRE(all_values_equal(v, TinyVector<3>{1, 2, 3}));
+
+      DiscreteFunctionP0<Dimension, TinyMatrix<3>> A{mesh};
+      A.fill(TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9});
+
+      REQUIRE(all_values_equal(A, TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9}));
+    }
+  }
+
+  SECTION("copies")
+  {
+    auto all_values_equal = [](const auto& f, const auto& g) {
+      size_t number_of_cells = f.cellValues().numberOfItems();
+      for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+        if (f[cell_id] != g) {
+          return false;
+        }
+      }
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      SECTION("scalar")
+      {
+        const size_t value = parallel::rank() + 1;
+        const size_t zero  = 0;
+
+        DiscreteFunctionP0<Dimension, size_t> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+
+      SECTION("vector")
+      {
+        const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
+        const TinyVector<2, size_t> zero{ZeroType{}};
+        DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+
+      SECTION("matrix")
+      {
+        const TinyMatrix<3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
+        const TinyMatrix<3, size_t> zero{ZeroType{}};
+        DiscreteFunctionP0<Dimension, TinyMatrix<3, size_t>> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const TinyMatrix<3, size_t>> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+
+      constexpr size_t Dimension = 2;
+
+      SECTION("scalar")
+      {
+        const size_t value = parallel::rank() + 1;
+        const size_t zero  = 0;
+
+        DiscreteFunctionP0<Dimension, size_t> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+
+      SECTION("vector")
+      {
+        const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
+        const TinyVector<2, size_t> zero{ZeroType{}};
+        DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+
+      SECTION("matrix")
+      {
+        const TinyMatrix<3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
+        const TinyMatrix<3, size_t> zero{ZeroType{}};
+        DiscreteFunctionP0<Dimension, TinyMatrix<3, size_t>> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const TinyMatrix<3, size_t>> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+
+      constexpr size_t Dimension = 3;
+
+      SECTION("scalar")
+      {
+        const size_t value = parallel::rank() + 1;
+        const size_t zero  = 0;
+
+        DiscreteFunctionP0<Dimension, size_t> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const size_t> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+
+      SECTION("vector")
+      {
+        const TinyVector<2, size_t> value{parallel::rank() + 1, 3};
+        const TinyVector<2, size_t> zero{ZeroType{}};
+        DiscreteFunctionP0<Dimension, TinyVector<2, size_t>> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const TinyVector<2, size_t>> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+
+      SECTION("matrix")
+      {
+        const TinyMatrix<3, size_t> value{1, 2, 3, 4, 5, 6, 7, 8, 9};
+        const TinyMatrix<3, size_t> zero{ZeroType{}};
+        DiscreteFunctionP0<Dimension, TinyMatrix<3, size_t>> f{mesh};
+        f.fill(value);
+
+        REQUIRE(all_values_equal(f, value));
+
+        DiscreteFunctionP0 g = copy(f);
+        f.fill(zero);
+
+        REQUIRE(all_values_equal(f, zero));
+        REQUIRE(all_values_equal(g, value));
+
+        copy_to(g, f);
+        g.fill(zero);
+
+        DiscreteFunctionP0<Dimension, const TinyMatrix<3, size_t>> h = copy(f);
+
+        REQUIRE(all_values_equal(f, value));
+        REQUIRE(all_values_equal(g, zero));
+        REQUIRE(all_values_equal(h, value));
+
+        copy_to(h, g);
+
+        REQUIRE(all_values_equal(g, value));
+      }
+    }
+  }
+
+  SECTION("unary operators")
+  {
+    SECTION("1D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("unary minus")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id]     = 2 * x + 1;
+            });
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+          Array<double> minus_values{mesh->numberOfCells()};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
+
+          REQUIRE(same_values(-f, minus_values));
+          REQUIRE(same_values(-const_f, minus_values));
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{x, 2 - x};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+          Array<TinyVector<VectorDimension>> minus_values{mesh->numberOfCells()};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
+
+          REQUIRE(same_values(-f, minus_values));
+          REQUIRE(same_values(-const_f, minus_values));
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+          Array<TinyMatrix<MatrixDimension>> minus_values{mesh->numberOfCells()};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { minus_values[cell_id] = -f[cell_id]; });
+
+          REQUIRE(same_values(-f, minus_values));
+          REQUIRE(same_values(-const_f, minus_values));
+        }
+      }
+    }
+  }
+
+  SECTION("binary operators")
+  {
+    SECTION("1D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("inner operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id]     = 2 * x + 1;
+            });
+
+          DiscreteFunctionP0<Dimension, double> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              g[cell_id]     = std::abs((x + 1) * (x - 2)) + 1;
+            });
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+          DiscreteFunctionP0<Dimension, const double> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<double> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<double> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+
+          SECTION("product")
+          {
+            Array<double> product_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+            REQUIRE(same_values(f * g, product_values));
+            REQUIRE(same_values(const_f * g, product_values));
+            REQUIRE(same_values(f * const_g, product_values));
+            REQUIRE(same_values(const_f * const_g, product_values));
+          }
+
+          SECTION("ratio")
+          {
+            Array<double> ratio_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+
+            REQUIRE(same_values(f / g, ratio_values));
+            REQUIRE(same_values(const_f / g, ratio_values));
+            REQUIRE(same_values(f / const_g, ratio_values));
+            REQUIRE(same_values(const_f / const_g, ratio_values));
+          }
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{x, 2 - x};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
+              g[cell_id] = X;
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
+              g[cell_id] = A;
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+
+          SECTION("product")
+          {
+            Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+            REQUIRE(same_values(f * g, product_values));
+            REQUIRE(same_values(const_f * g, product_values));
+            REQUIRE(same_values(f * const_g, product_values));
+            REQUIRE(same_values(const_f * const_g, product_values));
+          }
+        }
+      }
+
+      SECTION("external operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id]     = std::abs(2 * x) + 1;
+            });
+
+          const double a = 3;
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+          SECTION("sum")
+          {
+            {
+              Array<double> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+
+              REQUIRE(same_values(a + f, sum_values));
+              REQUIRE(same_values(a + const_f, sum_values));
+            }
+            {
+              Array<double> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+
+              REQUIRE(same_values(f + a, sum_values));
+              REQUIRE(same_values(const_f + a, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            {
+              Array<double> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
+              REQUIRE(same_values(a - f, difference_values));
+              REQUIRE(same_values(a - const_f, difference_values));
+            }
+
+            {
+              Array<double> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
+              REQUIRE(same_values(f - a, difference_values));
+              REQUIRE(same_values(const_f - a, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              Array<double> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+            {
+              Array<double> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+
+              REQUIRE(same_values(f * a, product_values));
+              REQUIRE(same_values(const_f * a, product_values));
+            }
+
+            {
+              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+              const TinyVector<3> v{1, 2, 3};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+
+              REQUIRE(same_values(f * v, product_values));
+              REQUIRE(same_values(const_f * v, product_values));
+            }
+
+            {
+              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+
+              REQUIRE(same_values(f * v, product_values));
+              REQUIRE(same_values(const_f * v, product_values));
+            }
+
+            {
+              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+              const TinyMatrix<2> A{1, 2, 3, 4};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+              REQUIRE(same_values(f * A, product_values));
+              REQUIRE(same_values(const_f * A, product_values));
+            }
+
+            {
+              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+
+              REQUIRE(same_values(f * M, product_values));
+              REQUIRE(same_values(const_f * M, product_values));
+            }
+          }
+
+          SECTION("ratio")
+          {
+            {
+              Array<double> ratio_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+
+              REQUIRE(same_values(a / f, ratio_values));
+              REQUIRE(same_values(a / const_f, ratio_values));
+            }
+            {
+              Array<double> ratio_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+
+              REQUIRE(same_values(f / a, ratio_values));
+              REQUIRE(same_values(const_f / a, ratio_values));
+            }
+          }
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{x, 2 - x};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+          SECTION("sum")
+          {
+            const TinyVector<VectorDimension> v{1, 2};
+            {
+              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+
+              REQUIRE(same_values(v + f, sum_values));
+              REQUIRE(same_values(v + const_f, sum_values));
+            }
+            {
+              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+
+              REQUIRE(same_values(f + v, sum_values));
+              REQUIRE(same_values(const_f + v, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            const TinyVector<VectorDimension> v{1, 2};
+            {
+              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+
+              REQUIRE(same_values(v - f, difference_values));
+              REQUIRE(same_values(v - const_f, difference_values));
+            }
+            {
+              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+
+              REQUIRE(same_values(f - v, difference_values));
+              REQUIRE(same_values(const_f - v, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              const double a = 2.3;
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              DiscreteFunctionP0<Dimension, double> a{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  a[cell_id]     = 2 * x * x - 1;
+                });
+
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+              REQUIRE(same_values(A * f, product_values));
+              REQUIRE(same_values(A * const_f, product_values));
+            }
+
+            {
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(M * f, product_values));
+              REQUIRE(same_values(M * const_f, product_values));
+            }
+          }
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> X{x, 2 - x, x * x, x * 3};
+              f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+          SECTION("sum")
+          {
+            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+            {
+              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+
+              REQUIRE(same_values(A + f, sum_values));
+              REQUIRE(same_values(A + const_f, sum_values));
+            }
+            {
+              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+
+              REQUIRE(same_values(f + A, sum_values));
+              REQUIRE(same_values(const_f + A, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+            {
+              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+
+              REQUIRE(same_values(A - f, difference_values));
+              REQUIRE(same_values(A - const_f, difference_values));
+            }
+            {
+              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+
+              REQUIRE(same_values(f - A, difference_values));
+              REQUIRE(same_values(const_f - A, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              const double a = 2.3;
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              DiscreteFunctionP0<Dimension, double> a{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  a[cell_id]     = 2 * x * x - 1;
+                });
+
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+              REQUIRE(same_values(A * f, product_values));
+              REQUIRE(same_values(A * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+              REQUIRE(same_values(f * A, product_values));
+              REQUIRE(same_values(const_f * A, product_values));
+            }
+          }
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+
+      constexpr size_t Dimension = 2;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("inner operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              f[cell_id]     = 2 * x + y + 1;
+            });
+
+          DiscreteFunctionP0<Dimension, double> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              g[cell_id]     = std::abs((x + 1) * (x - 2) + y * (1 + y)) + 1;
+            });
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+          DiscreteFunctionP0<Dimension, const double> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<double> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<double> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+
+          SECTION("product")
+          {
+            Array<double> product_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+            REQUIRE(same_values(f * g, product_values));
+            REQUIRE(same_values(const_f * g, product_values));
+            REQUIRE(same_values(f * const_g, product_values));
+            REQUIRE(same_values(const_f * const_g, product_values));
+          }
+
+          SECTION("ratio")
+          {
+            Array<double> ratio_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+
+            REQUIRE(same_values(f / g, ratio_values));
+            REQUIRE(same_values(const_f / g, ratio_values));
+            REQUIRE(same_values(f / const_g, ratio_values));
+            REQUIRE(same_values(const_f / const_g, ratio_values));
+          }
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{x, 2 - x};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
+              g[cell_id] = X;
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
+              g[cell_id] = A;
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+
+          SECTION("product")
+          {
+            Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+            REQUIRE(same_values(f * g, product_values));
+            REQUIRE(same_values(const_f * g, product_values));
+            REQUIRE(same_values(f * const_g, product_values));
+            REQUIRE(same_values(const_f * const_g, product_values));
+          }
+        }
+      }
+
+      SECTION("external operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              f[cell_id]     = std::abs(2 * x + y) + 1;
+            });
+
+          const double a = 3;
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+          SECTION("sum")
+          {
+            {
+              Array<double> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+
+              REQUIRE(same_values(a + f, sum_values));
+              REQUIRE(same_values(a + const_f, sum_values));
+            }
+            {
+              Array<double> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+
+              REQUIRE(same_values(f + a, sum_values));
+              REQUIRE(same_values(const_f + a, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            {
+              Array<double> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
+              REQUIRE(same_values(a - f, difference_values));
+              REQUIRE(same_values(a - const_f, difference_values));
+            }
+
+            {
+              Array<double> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
+              REQUIRE(same_values(f - a, difference_values));
+              REQUIRE(same_values(const_f - a, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              Array<double> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+            {
+              Array<double> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+
+              REQUIRE(same_values(f * a, product_values));
+              REQUIRE(same_values(const_f * a, product_values));
+            }
+
+            {
+              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+              const TinyVector<3> v{1, 2, 3};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+
+              REQUIRE(same_values(f * v, product_values));
+              REQUIRE(same_values(const_f * v, product_values));
+            }
+
+            {
+              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+
+              REQUIRE(same_values(f * v, product_values));
+              REQUIRE(same_values(const_f * v, product_values));
+            }
+
+            {
+              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+              const TinyMatrix<2> A{1, 2, 3, 4};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+              REQUIRE(same_values(f * A, product_values));
+              REQUIRE(same_values(const_f * A, product_values));
+            }
+
+            {
+              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+
+              REQUIRE(same_values(f * M, product_values));
+              REQUIRE(same_values(const_f * M, product_values));
+            }
+          }
+
+          SECTION("ratio")
+          {
+            {
+              Array<double> ratio_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+
+              REQUIRE(same_values(a / f, ratio_values));
+              REQUIRE(same_values(a / const_f, ratio_values));
+            }
+            {
+              Array<double> ratio_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+
+              REQUIRE(same_values(f / a, ratio_values));
+              REQUIRE(same_values(const_f / a, ratio_values));
+            }
+          }
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const TinyVector<VectorDimension> X{x + y, 2 - x * y};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+          SECTION("sum")
+          {
+            const TinyVector<VectorDimension> v{1, 2};
+            {
+              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+
+              REQUIRE(same_values(v + f, sum_values));
+              REQUIRE(same_values(v + const_f, sum_values));
+            }
+            {
+              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+
+              REQUIRE(same_values(f + v, sum_values));
+              REQUIRE(same_values(const_f + v, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            const TinyVector<VectorDimension> v{1, 2};
+            {
+              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+
+              REQUIRE(same_values(v - f, difference_values));
+              REQUIRE(same_values(v - const_f, difference_values));
+            }
+            {
+              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+
+              REQUIRE(same_values(f - v, difference_values));
+              REQUIRE(same_values(const_f - v, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              const double a = 2.3;
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              DiscreteFunctionP0<Dimension, double> a{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  a[cell_id]     = 2 * x * x - 1;
+                });
+
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+              REQUIRE(same_values(A * f, product_values));
+              REQUIRE(same_values(A * const_f, product_values));
+            }
+
+            {
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(M * f, product_values));
+              REQUIRE(same_values(M * const_f, product_values));
+            }
+          }
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const TinyMatrix<MatrixDimension> X{x, 2 - y, x * y, y * 3};
+              f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+          SECTION("sum")
+          {
+            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+            {
+              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+
+              REQUIRE(same_values(A + f, sum_values));
+              REQUIRE(same_values(A + const_f, sum_values));
+            }
+            {
+              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+
+              REQUIRE(same_values(f + A, sum_values));
+              REQUIRE(same_values(const_f + A, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+            {
+              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+
+              REQUIRE(same_values(A - f, difference_values));
+              REQUIRE(same_values(A - const_f, difference_values));
+            }
+            {
+              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+
+              REQUIRE(same_values(f - A, difference_values));
+              REQUIRE(same_values(const_f - A, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              const double a = 2.3;
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              DiscreteFunctionP0<Dimension, double> a{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  a[cell_id]     = 2 * x * x - 1;
+                });
+
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+              REQUIRE(same_values(A * f, product_values));
+              REQUIRE(same_values(A * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+              REQUIRE(same_values(f * A, product_values));
+              REQUIRE(same_values(const_f * A, product_values));
+            }
+          }
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+
+      constexpr size_t Dimension = 3;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("inner operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              f[cell_id]     = 2 * x + y - z;
+            });
+
+          DiscreteFunctionP0<Dimension, double> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              g[cell_id]     = std::abs((x + 1) * (x - 2) + y * (1 + y) + 2 * z) + 1;
+            });
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+          DiscreteFunctionP0<Dimension, const double> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<double> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<double> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+
+          SECTION("product")
+          {
+            Array<double> product_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+            REQUIRE(same_values(f * g, product_values));
+            REQUIRE(same_values(const_f * g, product_values));
+            REQUIRE(same_values(f * const_g, product_values));
+            REQUIRE(same_values(const_f * const_g, product_values));
+          }
+
+          SECTION("ratio")
+          {
+            Array<double> ratio_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / g[cell_id]; });
+
+            REQUIRE(same_values(f / g, ratio_values));
+            REQUIRE(same_values(const_f / g, ratio_values));
+            REQUIRE(same_values(f / const_g, ratio_values));
+            REQUIRE(same_values(const_f / const_g, ratio_values));
+          }
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{x, 2 - x};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyVector<VectorDimension> X{3 * x + 1, 2 + x};
+              g[cell_id] = X;
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{x, 2 - x, 2 * x, x * x - 3};
+              f[cell_id] = 2 * A + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> g{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const TinyMatrix<MatrixDimension> A{3 * x + 1, 2 + x, 1 - 2 * x, 2 * x * x};
+              g[cell_id] = A;
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_g{g};
+
+          SECTION("sum")
+          {
+            Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + g[cell_id]; });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - g[cell_id]; });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+
+          SECTION("product")
+          {
+            Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(),
+              PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * g[cell_id]; });
+
+            REQUIRE(same_values(f * g, product_values));
+            REQUIRE(same_values(const_f * g, product_values));
+            REQUIRE(same_values(f * const_g, product_values));
+            REQUIRE(same_values(const_f * const_g, product_values));
+          }
+        }
+      }
+
+      SECTION("external operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0<Dimension, double> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              f[cell_id]     = std::abs(2 * x + y * z) + 1;
+            });
+
+          const double a = 3;
+
+          DiscreteFunctionP0<Dimension, const double> const_f = f;
+
+          SECTION("sum")
+          {
+            {
+              Array<double> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = a + f[cell_id]; });
+
+              REQUIRE(same_values(a + f, sum_values));
+              REQUIRE(same_values(a + const_f, sum_values));
+            }
+            {
+              Array<double> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + a; });
+
+              REQUIRE(same_values(f + a, sum_values));
+              REQUIRE(same_values(const_f + a, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            {
+              Array<double> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = a - f[cell_id]; });
+              REQUIRE(same_values(a - f, difference_values));
+              REQUIRE(same_values(a - const_f, difference_values));
+            }
+
+            {
+              Array<double> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - a; });
+              REQUIRE(same_values(f - a, difference_values));
+              REQUIRE(same_values(const_f - a, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              Array<double> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+            {
+              Array<double> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * a; });
+
+              REQUIRE(same_values(f * a, product_values));
+              REQUIRE(same_values(const_f * a, product_values));
+            }
+
+            {
+              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+              const TinyVector<3> v{1, 2, 3};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v; });
+
+              REQUIRE(same_values(f * v, product_values));
+              REQUIRE(same_values(const_f * v, product_values));
+            }
+
+            {
+              Array<TinyVector<3>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyVector<3>> v{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  v[cell_id]     = TinyVector<3>{x, 2 * x, 1 - x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * v[cell_id]; });
+
+              REQUIRE(same_values(f * v, product_values));
+              REQUIRE(same_values(const_f * v, product_values));
+            }
+
+            {
+              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+              const TinyMatrix<2> A{1, 2, 3, 4};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+              REQUIRE(same_values(f * A, product_values));
+              REQUIRE(same_values(const_f * A, product_values));
+            }
+
+            {
+              Array<TinyMatrix<2>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<2>> M{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * M[cell_id]; });
+
+              REQUIRE(same_values(f * M, product_values));
+              REQUIRE(same_values(const_f * M, product_values));
+            }
+          }
+
+          SECTION("ratio")
+          {
+            {
+              Array<double> ratio_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = a / f[cell_id]; });
+
+              REQUIRE(same_values(a / f, ratio_values));
+              REQUIRE(same_values(a / const_f, ratio_values));
+            }
+            {
+              Array<double> ratio_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { ratio_values[cell_id] = f[cell_id] / a; });
+
+              REQUIRE(same_values(f / a, ratio_values));
+              REQUIRE(same_values(const_f / a, ratio_values));
+            }
+          }
+        }
+
+        SECTION("vector functions")
+        {
+          constexpr std::uint64_t VectorDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              const TinyVector<VectorDimension> X{x + y - z, 2 - x * y};
+              f[cell_id] = 2 * X + TinyVector<2>{1, 2};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyVector<VectorDimension>> const_f = f;
+
+          SECTION("sum")
+          {
+            const TinyVector<VectorDimension> v{1, 2};
+            {
+              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = v + f[cell_id]; });
+
+              REQUIRE(same_values(v + f, sum_values));
+              REQUIRE(same_values(v + const_f, sum_values));
+            }
+            {
+              Array<TinyVector<VectorDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + v; });
+
+              REQUIRE(same_values(f + v, sum_values));
+              REQUIRE(same_values(const_f + v, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            const TinyVector<VectorDimension> v{1, 2};
+            {
+              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = v - f[cell_id]; });
+
+              REQUIRE(same_values(v - f, difference_values));
+              REQUIRE(same_values(v - const_f, difference_values));
+            }
+            {
+              Array<TinyVector<VectorDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - v; });
+
+              REQUIRE(same_values(f - v, difference_values));
+              REQUIRE(same_values(const_f - v, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              const double a = 2.3;
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              DiscreteFunctionP0<Dimension, double> a{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  a[cell_id]     = 2 * x * x - 1;
+                });
+
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<VectorDimension> A{1, 2, 3, 4};
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+              REQUIRE(same_values(A * f, product_values));
+              REQUIRE(same_values(A * const_f, product_values));
+            }
+
+            {
+              Array<TinyVector<VectorDimension>> product_values{mesh->numberOfCells()};
+              DiscreteFunctionP0<Dimension, TinyMatrix<VectorDimension>> M{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  M[cell_id]     = TinyMatrix<2>{x, 2 * x, 1 - x, 2 - x * x};
+                });
+
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = M[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(M * f, product_values));
+              REQUIRE(same_values(M * const_f, product_values));
+            }
+          }
+        }
+
+        SECTION("matrix functions")
+        {
+          constexpr std::uint64_t MatrixDimension = 2;
+
+          DiscreteFunctionP0<Dimension, TinyMatrix<MatrixDimension>> f{mesh};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              const TinyMatrix<MatrixDimension> X{x, 2 - y, x * y, y * z + 3};
+              f[cell_id] = 2 * X + TinyMatrix<2>{1, 2, 3, 4};
+            });
+
+          DiscreteFunctionP0<Dimension, const TinyMatrix<MatrixDimension>> const_f = f;
+
+          SECTION("sum")
+          {
+            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+            {
+              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = A + f[cell_id]; });
+
+              REQUIRE(same_values(A + f, sum_values));
+              REQUIRE(same_values(A + const_f, sum_values));
+            }
+            {
+              Array<TinyMatrix<MatrixDimension>> sum_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { sum_values[cell_id] = f[cell_id] + A; });
+
+              REQUIRE(same_values(f + A, sum_values));
+              REQUIRE(same_values(const_f + A, sum_values));
+            }
+          }
+
+          SECTION("difference")
+          {
+            const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+            {
+              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = A - f[cell_id]; });
+
+              REQUIRE(same_values(A - f, difference_values));
+              REQUIRE(same_values(A - const_f, difference_values));
+            }
+            {
+              Array<TinyMatrix<MatrixDimension>> difference_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { difference_values[cell_id] = f[cell_id] - A; });
+
+              REQUIRE(same_values(f - A, difference_values));
+              REQUIRE(same_values(const_f - A, difference_values));
+            }
+          }
+
+          SECTION("product")
+          {
+            {
+              const double a = 2.3;
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              DiscreteFunctionP0<Dimension, double> a{mesh};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                  const double x = xj[cell_id][0];
+                  a[cell_id]     = 2 * x * x - 1;
+                });
+
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(),
+                PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = a[cell_id] * f[cell_id]; });
+
+              REQUIRE(same_values(a * f, product_values));
+              REQUIRE(same_values(a * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = A * f[cell_id]; });
+
+              REQUIRE(same_values(A * f, product_values));
+              REQUIRE(same_values(A * const_f, product_values));
+            }
+
+            {
+              const TinyMatrix<MatrixDimension> A{1, 2, 3, 4};
+              Array<TinyMatrix<MatrixDimension>> product_values{mesh->numberOfCells()};
+              parallel_for(
+                mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { product_values[cell_id] = f[cell_id] * A; });
+
+              REQUIRE(same_values(f * A, product_values));
+              REQUIRE(same_values(const_f * A, product_values));
+            }
+          }
+        }
+      }
+    }
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("different meshes")
+    {
+      SECTION("1D")
+      {
+        constexpr size_t Dimension = 1;
+
+        std::shared_ptr mesh_1 = MeshDataBaseForTests::get().cartesianMesh1D();
+        std::shared_ptr mesh_2 =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
+
+        DiscreteFunctionP0<Dimension, double> f1{mesh_1};
+        DiscreteFunctionP0<Dimension, double> f2{mesh_2};
+
+        REQUIRE_THROWS_AS(f1 = f2, AssertError);
+        REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
+        REQUIRE_THROWS_AS(f1 + f2, AssertError);
+        REQUIRE_THROWS_AS(f1 - f2, AssertError);
+        REQUIRE_THROWS_AS(f1 * f2, AssertError);
+        REQUIRE_THROWS_AS(f1 / f2, AssertError);
+      }
+
+      SECTION("2D")
+      {
+        constexpr size_t Dimension = 2;
+
+        std::shared_ptr mesh_1 = MeshDataBaseForTests::get().cartesianMesh2D();
+        std::shared_ptr mesh_2 =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
+
+        DiscreteFunctionP0<Dimension, double> f1{mesh_1};
+        DiscreteFunctionP0<Dimension, double> f2{mesh_2};
+
+        REQUIRE_THROWS_AS(f1 = f2, AssertError);
+        REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
+        REQUIRE_THROWS_AS(f1 + f2, AssertError);
+        REQUIRE_THROWS_AS(f1 - f2, AssertError);
+        REQUIRE_THROWS_AS(f1 * f2, AssertError);
+        REQUIRE_THROWS_AS(f1 / f2, AssertError);
+      }
+
+      SECTION("3D")
+      {
+        constexpr size_t Dimension = 3;
+
+        std::shared_ptr mesh_1 = MeshDataBaseForTests::get().cartesianMesh3D();
+        std::shared_ptr mesh_2 =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh_1->shared_connectivity(), mesh_1->xr());
+
+        DiscreteFunctionP0<Dimension, double> f1{mesh_1};
+        DiscreteFunctionP0<Dimension, double> f2{mesh_2};
+
+        REQUIRE_THROWS_AS(f1 = f2, AssertError);
+        REQUIRE_THROWS_AS(copy_to(f1, f2), AssertError);
+        REQUIRE_THROWS_AS(f1 + f2, AssertError);
+        REQUIRE_THROWS_AS(f1 - f2, AssertError);
+        REQUIRE_THROWS_AS(f1 * f2, AssertError);
+        REQUIRE_THROWS_AS(f1 / f2, AssertError);
+      }
+    }
+  }
+#endif   // NDEBUG
+}
diff --git a/tests/test_DiscreteFunctionP0Vector.cpp b/tests/test_DiscreteFunctionP0Vector.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c004ba5732c41dce1af56d3074b9c038b3a1bf8c
--- /dev/null
+++ b/tests/test_DiscreteFunctionP0Vector.cpp
@@ -0,0 +1,845 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
+{
+  auto same_values = [](const auto& f, const auto& g) {
+    const size_t number_of_cells = f.cellArrays().numberOfItems();
+    const size_t size_of_arrays  = f.cellArrays().sizeOfArrays();
+    for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+      for (size_t i = 0; i < size_of_arrays; ++i) {
+        if (f[cell_id][i] != g[cell_id][i]) {
+          return false;
+        }
+      }
+    }
+    return true;
+  };
+
+  SECTION("constructors")
+  {
+    SECTION("1D")
+    {
+      const size_t size = 3;
+
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
+      constexpr size_t Dimension = 1;
+
+      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
+      REQUIRE(f.size() == size);
+
+      REQUIRE(f.mesh().get() == mesh.get());
+
+      DiscreteFunctionP0Vector g{f};
+      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
+      REQUIRE(g.size() == size);
+
+      CellArray<double> h_arrays{mesh->connectivity(), size};
+      h_arrays.fill(0);
+
+      DiscreteFunctionP0Vector zero{mesh, [&] {
+                                      CellArray<double> cell_array{mesh->connectivity(), size};
+                                      cell_array.fill(0);
+                                      return cell_array;
+                                    }()};
+
+      DiscreteFunctionP0Vector h{mesh, h_arrays};
+      REQUIRE(same_values(h, zero));
+      REQUIRE(same_values(h, h_arrays));
+
+      h_arrays.fill(1);
+
+      REQUIRE(same_values(h, h_arrays));
+      REQUIRE(not same_values(h, zero));
+
+      DiscreteFunctionP0Vector moved_h{std::move(h)};
+      REQUIRE(same_values(moved_h, h_arrays));
+    }
+
+    SECTION("2D")
+    {
+      const size_t size = 3;
+
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
+      constexpr size_t Dimension = 2;
+
+      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
+      REQUIRE(f.size() == size);
+
+      REQUIRE(f.mesh().get() == mesh.get());
+
+      DiscreteFunctionP0Vector g{f};
+      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
+      REQUIRE(g.size() == size);
+
+      CellArray<double> h_arrays{mesh->connectivity(), size};
+      h_arrays.fill(0);
+
+      DiscreteFunctionP0Vector zero{mesh, [&] {
+                                      CellArray<double> cell_array{mesh->connectivity(), size};
+                                      cell_array.fill(0);
+                                      return cell_array;
+                                    }()};
+
+      DiscreteFunctionP0Vector h{mesh, h_arrays};
+      REQUIRE(same_values(h, zero));
+      REQUIRE(same_values(h, h_arrays));
+
+      h_arrays.fill(1);
+
+      REQUIRE(same_values(h, h_arrays));
+      REQUIRE(not same_values(h, zero));
+
+      DiscreteFunctionP0Vector moved_h{std::move(h)};
+      REQUIRE(same_values(moved_h, h_arrays));
+    }
+
+    SECTION("3D")
+    {
+      const size_t size = 2;
+
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
+      constexpr size_t Dimension = 3;
+
+      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
+      REQUIRE(f.size() == size);
+
+      REQUIRE(f.mesh().get() == mesh.get());
+
+      DiscreteFunctionP0Vector g{f};
+      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
+      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
+      REQUIRE(g.size() == size);
+
+      CellArray<double> h_arrays{mesh->connectivity(), size};
+      h_arrays.fill(0);
+
+      DiscreteFunctionP0Vector zero{mesh, [&] {
+                                      CellArray<double> cell_array{mesh->connectivity(), size};
+                                      cell_array.fill(0);
+                                      return cell_array;
+                                    }()};
+
+      DiscreteFunctionP0Vector h{mesh, h_arrays};
+      REQUIRE(same_values(h, zero));
+      REQUIRE(same_values(h, h_arrays));
+
+      h_arrays.fill(1);
+
+      REQUIRE(same_values(h, h_arrays));
+      REQUIRE(not same_values(h, zero));
+
+      DiscreteFunctionP0Vector moved_h{std::move(h)};
+      REQUIRE(same_values(moved_h, h_arrays));
+    }
+  }
+
+  SECTION("fill")
+  {
+    auto all_values_equal = [](const auto& f, const auto& g) {
+      const size_t number_of_cells = f.cellArrays().numberOfItems();
+      size_t size_of_arrays        = f.cellArrays().sizeOfArrays();
+      for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+        for (size_t i = 0; i < size_of_arrays; ++i) {
+          if (f[cell_id][i] != g) {
+            return false;
+          }
+        }
+      }
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      const size_t size = 3;
+
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
+      constexpr size_t Dimension = 1;
+
+      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+      f.fill(3);
+
+      REQUIRE(all_values_equal(f, 3));
+    }
+
+    SECTION("2D")
+    {
+      const size_t size = 3;
+
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
+      constexpr size_t Dimension = 2;
+
+      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+      f.fill(2.3);
+
+      REQUIRE(all_values_equal(f, 2.3));
+    }
+
+    SECTION("3D")
+    {
+      const size_t size = 2;
+
+      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
+      constexpr size_t Dimension = 3;
+
+      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+      f.fill(3.2);
+
+      REQUIRE(all_values_equal(f, 3.2));
+    }
+  }
+
+  SECTION("copies")
+  {
+    auto all_values_equal = [](const auto& f, const auto& g) {
+      const size_t number_of_cells = f.cellArrays().numberOfItems();
+      const size_t size_of_arrays  = f.cellArrays().sizeOfArrays();
+
+      for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+        for (size_t i = 0; i < size_of_arrays; ++i) {
+          if (f[cell_id][i] != g) {
+            return false;
+          }
+        }
+      }
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      const size_t size  = 3;
+      const size_t value = parallel::rank() + 1;
+      const size_t zero  = 0;
+
+      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
+      f.fill(value);
+
+      REQUIRE(all_values_equal(f, value));
+
+      DiscreteFunctionP0Vector g = copy(f);
+      f.fill(zero);
+
+      REQUIRE(all_values_equal(f, zero));
+      REQUIRE(all_values_equal(g, value));
+
+      copy_to(g, f);
+      g.fill(zero);
+
+      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
+
+      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
+      shallow_g = g;
+
+      REQUIRE(all_values_equal(f, value));
+      REQUIRE(all_values_equal(g, zero));
+      REQUIRE(all_values_equal(shallow_g, zero));
+      REQUIRE(all_values_equal(h, value));
+
+      copy_to(h, g);
+
+      REQUIRE(all_values_equal(g, value));
+      REQUIRE(all_values_equal(shallow_g, value));
+    }
+
+    SECTION("2D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+
+      constexpr size_t Dimension = 2;
+
+      const size_t size  = 3;
+      const size_t value = parallel::rank() + 1;
+      const size_t zero  = 0;
+
+      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
+      f.fill(value);
+
+      REQUIRE(all_values_equal(f, value));
+
+      DiscreteFunctionP0Vector g = copy(f);
+      f.fill(zero);
+
+      REQUIRE(all_values_equal(f, zero));
+      REQUIRE(all_values_equal(g, value));
+
+      copy_to(g, f);
+      g.fill(zero);
+
+      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
+
+      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
+      shallow_g = g;
+
+      REQUIRE(all_values_equal(f, value));
+      REQUIRE(all_values_equal(g, zero));
+      REQUIRE(all_values_equal(shallow_g, zero));
+      REQUIRE(all_values_equal(h, value));
+
+      copy_to(h, g);
+
+      REQUIRE(all_values_equal(g, value));
+      REQUIRE(all_values_equal(shallow_g, value));
+    }
+
+    SECTION("3D")
+    {
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+
+      constexpr size_t Dimension = 3;
+
+      const size_t size  = 3;
+      const size_t value = parallel::rank() + 1;
+      const size_t zero  = 0;
+
+      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
+      f.fill(value);
+
+      REQUIRE(all_values_equal(f, value));
+
+      DiscreteFunctionP0Vector g = copy(f);
+      f.fill(zero);
+
+      REQUIRE(all_values_equal(f, zero));
+      REQUIRE(all_values_equal(g, value));
+
+      copy_to(g, f);
+      g.fill(zero);
+
+      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);
+
+      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
+      shallow_g = g;
+
+      REQUIRE(all_values_equal(f, value));
+      REQUIRE(all_values_equal(g, zero));
+      REQUIRE(all_values_equal(h, value));
+
+      copy_to(h, g);
+
+      REQUIRE(all_values_equal(g, value));
+      REQUIRE(all_values_equal(shallow_g, value));
+    }
+  }
+
+  SECTION("unary operators")
+  {
+    SECTION("1D")
+    {
+      const size_t size    = 3;
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("unary minus")
+      {
+        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            const double x = xj[cell_id][0];
+            for (size_t i = 0; i < size; ++i) {
+              f[cell_id][i] = 2 * x + i;
+            }
+          });
+
+        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+        Table<double> minus_values{mesh->numberOfCells(), size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            for (size_t i = 0; i < size; ++i) {
+              minus_values[cell_id][i] = -f[cell_id][i];
+            }
+          });
+
+        REQUIRE(same_values(-f, minus_values));
+        REQUIRE(same_values(-const_f, minus_values));
+      }
+    }
+
+    SECTION("2D")
+    {
+      const size_t size    = 3;
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+
+      constexpr size_t Dimension = 2;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("unary minus")
+      {
+        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            const double x = xj[cell_id][0];
+            const double y = xj[cell_id][1];
+            for (size_t i = 0; i < size; ++i) {
+              f[cell_id][i] = 2 * x + i * y;
+            }
+          });
+
+        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+        Table<double> minus_values{mesh->numberOfCells(), size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            for (size_t i = 0; i < size; ++i) {
+              minus_values[cell_id][i] = -f[cell_id][i];
+            }
+          });
+
+        REQUIRE(same_values(-f, minus_values));
+        REQUIRE(same_values(-const_f, minus_values));
+      }
+    }
+
+    SECTION("3D")
+    {
+      const size_t size    = 2;
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+
+      constexpr size_t Dimension = 3;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("unary minus")
+      {
+        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            const double x = xj[cell_id][0];
+            const double y = xj[cell_id][1];
+            const double z = xj[cell_id][2];
+            for (size_t i = 0; i < size; ++i) {
+              f[cell_id][i] = 2 * x + i * y - z;
+            }
+          });
+
+        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+        Table<double> minus_values{mesh->numberOfCells(), size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            for (size_t i = 0; i < size; ++i) {
+              minus_values[cell_id][i] = -f[cell_id][i];
+            }
+          });
+
+        REQUIRE(same_values(-f, minus_values));
+        REQUIRE(same_values(-const_f, minus_values));
+      }
+    }
+  }
+
+  SECTION("binary operators")
+  {
+    SECTION("1D")
+    {
+      const size_t size = 3;
+
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();
+
+      constexpr size_t Dimension = 1;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("inner operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id][0]  = 2 * x + 1;
+              f[cell_id][1]  = x * x - 1;
+              f[cell_id][2]  = 2 + x;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              g[cell_id][0]  = (x + 1) * (x - 2) + 1;
+              g[cell_id][1]  = 3 * (x + 2) - 1;
+              g[cell_id][2]  = (x + 3) * 5;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+          SECTION("sum")
+          {
+            Table<double> sum_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Table<double> difference_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+        }
+      }
+
+      SECTION("external operators")
+      {
+        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            const double x = xj[cell_id][0];
+            for (size_t i = 0; i < size; ++i) {
+              f[cell_id][i] = std::abs(2 * x) + i;
+            }
+          });
+
+        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+        SECTION("product")
+        {
+          SECTION("scalar lhs")
+          {
+            const double a = 3.2;
+            Table<double> product_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  product_values[cell_id][i] = a * f[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(a * f, product_values));
+            REQUIRE(same_values(a * const_f, product_values));
+          }
+
+          SECTION("DiscreteFunctionP0 lhs")
+          {
+            DiscreteFunctionP0<Dimension, double> a{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                const double x = xj[cell_id][0];
+                a[cell_id]     = 2 * x + 1;
+              });
+
+            Table<double> product_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(a * f, product_values));
+            REQUIRE(same_values(a * const_f, product_values));
+
+            DiscreteFunctionP0<Dimension, const double> const_a = a;
+            REQUIRE(same_values(const_a * f, product_values));
+            REQUIRE(same_values(const_a * const_f, product_values));
+          }
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      const size_t size = 3;
+
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();
+
+      constexpr size_t Dimension = 2;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("inner operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              f[cell_id][0]  = 2 * x + 1;
+              f[cell_id][1]  = x * x - y;
+              f[cell_id][2]  = 2 + x * y;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              g[cell_id][0]  = (x + 1) * (y - 2) + 1;
+              g[cell_id][1]  = 3 * (x + 2) - y;
+              g[cell_id][2]  = (x + 3) + 5 * y;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+          SECTION("sum")
+          {
+            Table<double> sum_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Table<double> difference_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+        }
+      }
+
+      SECTION("external operators")
+      {
+        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            const double x = xj[cell_id][0];
+            const double y = xj[cell_id][1];
+            for (size_t i = 0; i < size; ++i) {
+              f[cell_id][i] = std::abs(2 * x) + i * y;
+            }
+          });
+
+        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+        SECTION("product")
+        {
+          SECTION("scalar lhs")
+          {
+            const double a = 3.2;
+            Table<double> product_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  product_values[cell_id][i] = a * f[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(a * f, product_values));
+            REQUIRE(same_values(a * const_f, product_values));
+          }
+
+          SECTION("DiscreteFunctionP0 lhs")
+          {
+            DiscreteFunctionP0<Dimension, double> a{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                const double x = xj[cell_id][0];
+                const double y = xj[cell_id][1];
+                a[cell_id]     = 2 * x + 1 - y;
+              });
+
+            Table<double> product_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(a * f, product_values));
+            REQUIRE(same_values(a * const_f, product_values));
+
+            DiscreteFunctionP0<Dimension, const double> const_a = a;
+            REQUIRE(same_values(const_a * f, product_values));
+            REQUIRE(same_values(const_a * const_f, product_values));
+          }
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      const size_t size = 2;
+
+      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();
+
+      constexpr size_t Dimension = 3;
+
+      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      SECTION("inner operators")
+      {
+        SECTION("scalar functions")
+        {
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              f[cell_id][0]  = 2 * x * z + 1;
+              f[cell_id][1]  = x * z - y;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              const double y = xj[cell_id][1];
+              const double z = xj[cell_id][2];
+              g[cell_id][0]  = (x + 1) * (y - 2) + 1 - z;
+              g[cell_id][1]  = 3 * (x + 2) - y * z;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+          SECTION("sum")
+          {
+            Table<double> sum_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(f + g, sum_values));
+            REQUIRE(same_values(const_f + g, sum_values));
+            REQUIRE(same_values(f + const_g, sum_values));
+            REQUIRE(same_values(const_f + const_g, sum_values));
+          }
+
+          SECTION("difference")
+          {
+            Table<double> difference_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(f - g, difference_values));
+            REQUIRE(same_values(const_f - g, difference_values));
+            REQUIRE(same_values(f - const_g, difference_values));
+            REQUIRE(same_values(const_f - const_g, difference_values));
+          }
+        }
+      }
+
+      SECTION("external operators")
+      {
+        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            const double x = xj[cell_id][0];
+            const double y = xj[cell_id][1];
+            const double z = xj[cell_id][2];
+            for (size_t i = 0; i < size; ++i) {
+              f[cell_id][i] = std::abs(2 * x) + i * y + z;
+            }
+          });
+
+        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+
+        SECTION("product")
+        {
+          SECTION("scalar lhs")
+          {
+            const double a = 3.2;
+            Table<double> product_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  product_values[cell_id][i] = a * f[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(a * f, product_values));
+            REQUIRE(same_values(a * const_f, product_values));
+          }
+
+          SECTION("DiscreteFunctionP0 lhs")
+          {
+            DiscreteFunctionP0<Dimension, double> a{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                const double x = xj[cell_id][0];
+                const double y = xj[cell_id][1];
+                const double z = xj[cell_id][2];
+                a[cell_id]     = 2 * x + 1 - y * z;
+              });
+
+            Table<double> product_values{mesh->numberOfCells(), size};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                for (size_t i = 0; i < size; ++i) {
+                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
+                }
+              });
+
+            REQUIRE(same_values(a * f, product_values));
+            REQUIRE(same_values(a * const_f, product_values));
+
+            DiscreteFunctionP0<Dimension, const double> const_a = a;
+            REQUIRE(same_values(const_a * f, product_values));
+            REQUIRE(same_values(const_a * const_f, product_values));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_DiscreteFunctionType.cpp b/tests/test_DiscreteFunctionType.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c5e00470e267cef8614281a03801ae4395975c45
--- /dev/null
+++ b/tests/test_DiscreteFunctionType.cpp
@@ -0,0 +1,15 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <scheme/DiscreteFunctionType.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("DiscreteFunctionType", "[scheme]")
+{
+  SECTION("name")
+  {
+    REQUIRE(name(DiscreteFunctionType::P0) == "P0");
+    REQUIRE(name(DiscreteFunctionType::P0Vector) == "P0Vector");
+  }
+}
diff --git a/tests/test_DoWhileProcessor.cpp b/tests/test_DoWhileProcessor.cpp
index ccb056399c665a683c1f71cf1e4a224962fb4876..8e4f7a2c95b5cc435ba4365129aa8d207ef0adcf 100644
--- a/tests/test_DoWhileProcessor.cpp
+++ b/tests/test_DoWhileProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -20,6 +21,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
@@ -48,6 +52,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
diff --git a/tests/test_ForProcessor.cpp b/tests/test_ForProcessor.cpp
index e3d8a634e29d80f6a1be0534660065b02a0a3f6f..a83482550be288ed2104c6f28ed348955aee2914 100644
--- a/tests/test_ForProcessor.cpp
+++ b/tests/test_ForProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -20,6 +21,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
@@ -48,6 +52,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
diff --git a/tests/test_IfProcessor.cpp b/tests/test_IfProcessor.cpp
index 9b9da7d4026b2e136279ef0d482384ddbfa34418..461f3a98f8279a55e098e22fc5b63cec8276cc35 100644
--- a/tests/test_IfProcessor.cpp
+++ b/tests/test_IfProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -18,6 +19,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
@@ -46,6 +50,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
diff --git a/tests/test_IncDecExpressionProcessor.cpp b/tests/test_IncDecExpressionProcessor.cpp
index 62e3d531facb5fe956f775b6dcad000ced5c625a..5075ee900bdedb76acc9e7301b06732b7266753d 100644
--- a/tests/test_IncDecExpressionProcessor.cpp
+++ b/tests/test_IncDecExpressionProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -18,6 +19,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
@@ -46,6 +50,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTModulesImporter{*ast};                                         \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
diff --git a/tests/test_InterpolateItemArray.cpp b/tests/test_InterpolateItemArray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf65afdf42c803b15900be7fc96fdad23e9c230f
--- /dev/null
+++ b/tests/test_InterpolateItemArray.cpp
@@ -0,0 +1,467 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionEvaluationExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <language/utils/SymbolTable.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+
+#include <language/utils/InterpolateItemArray.hpp>
+
+#include <pegtl/string_input.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("InterpolateItemArray", "[language]")
+{
+  SECTION("interpolate on all items")
+  {
+    auto same_cell_array = [](auto f, auto g) -> bool {
+      using ItemIdType = typename decltype(f)::index_type;
+
+      for (ItemIdType item_id = 0; item_id < f.numberOfItems(); ++item_id) {
+        for (size_t i = 0; i < f.sizeOfArrays(); ++i) {
+          if (f[item_id][i] != g[item_id][i]) {
+            return false;
+          }
+        }
+      }
+
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+
+      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
+let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      std::vector<FunctionSymbolId> function_symbol_id_list;
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      CellArray<double> cell_array{mesh_1d->connectivity(), 2};
+      parallel_for(
+        cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+          const TinyVector<Dimension>& x = xj[cell_id];
+          cell_array[cell_id][0]         = 2 * x[0] + 2;
+          cell_array[cell_id][1]         = 2 * exp(x[0]) + 3;
+        });
+
+      CellArray<const double> interpolate_array =
+        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
+
+      REQUIRE(same_cell_array(cell_array, interpolate_array));
+    }
+
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
+
+      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
+let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      std::vector<FunctionSymbolId> function_symbol_id_list;
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      CellArray<double> cell_array{mesh_2d->connectivity(), 2};
+      parallel_for(
+        cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+          const TinyVector<Dimension>& x = xj[cell_id];
+          cell_array[cell_id][0]         = 2 * x[0] + 3 * x[1] + 2;
+          cell_array[cell_id][1]         = 2 * exp(x[0]) * sin(x[1]) + 3;
+        });
+
+      CellArray<const double> interpolate_array =
+        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
+
+      REQUIRE(same_cell_array(cell_array, interpolate_array));
+    }
+
+    SECTION("3D")
+    {
+      constexpr size_t Dimension = 3;
+
+      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      std::vector<FunctionSymbolId> function_symbol_id_list;
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      CellArray<double> cell_array{mesh_3d->connectivity(), 2};
+      parallel_for(
+        cell_array.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+          const TinyVector<Dimension>& x = xj[cell_id];
+          cell_array[cell_id][0]         = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+          cell_array[cell_id][1]         = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+        });
+
+      CellArray<const double> interpolate_array =
+        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
+
+      REQUIRE(same_cell_array(cell_array, interpolate_array));
+    }
+  }
+
+  SECTION("interpolate on items list")
+  {
+    auto same_cell_value = [](auto interpolated, auto reference) -> bool {
+      for (size_t i = 0; i < interpolated.nbRows(); ++i) {
+        for (size_t j = 0; j < interpolated.nbColumns(); ++j) {
+          if (interpolated[i][j] != reference[i][j]) {
+            return false;
+          }
+        }
+      }
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+
+      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+      Array<const CellId> cell_id_list = [&] {
+        Array<CellId> cell_ids{mesh_1d->numberOfCells() / 2};
+        for (size_t i_cell = 0; i_cell < cell_ids.size(); ++i_cell) {
+          cell_ids[i_cell] = static_cast<CellId>(2 * i_cell);
+        }
+        return cell_ids;
+      }();
+
+      std::string_view data = R"(
+  import math;
+  let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
+  let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
+  )";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      std::vector<FunctionSymbolId> function_symbol_id_list;
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      Table<double> cell_array{cell_id_list.size(), 2};
+      parallel_for(
+        cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
+          const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+          cell_array[i][0]               = 2 * x[0] + 2;
+          cell_array[i][1]               = 2 * exp(x[0]) + 3;
+        });
+
+      Table<const double> interpolate_array =
+        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
+
+      REQUIRE(same_cell_value(cell_array, interpolate_array));
+    }
+
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
+
+      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+      Array<CellId> cell_id_list{mesh_2d->numberOfCells() / 2};
+      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+      }
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
+let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      std::vector<FunctionSymbolId> function_symbol_id_list;
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      Table<double> cell_array{cell_id_list.size(), 2};
+      parallel_for(
+        cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
+          const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+          cell_array[i][0]               = 2 * x[0] + 3 * x[1] + 2;
+          cell_array[i][1]               = 2 * exp(x[0]) * sin(x[1]) + 3;
+        });
+
+      Table<const double> interpolate_array =
+        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
+
+      REQUIRE(same_cell_value(cell_array, interpolate_array));
+    }
+
+    SECTION("3D")
+    {
+      constexpr size_t Dimension = 3;
+
+      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+      Array<CellId> cell_id_list{mesh_3d->numberOfCells() / 2};
+      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+      }
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      std::vector<FunctionSymbolId> function_symbol_id_list;
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        function_symbol_id_list.push_back(
+          FunctionSymbolId(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table));
+      }
+
+      Table<double> cell_array{cell_id_list.size(), 2};
+      parallel_for(
+        cell_id_list.size(), PUGS_LAMBDA(const size_t i) {
+          const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+          cell_array[i][0]               = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+          cell_array[i][1]               = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+        });
+
+      Table<const double> interpolate_array =
+        InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
+
+      REQUIRE(same_cell_value(cell_array, interpolate_array));
+    }
+  }
+}
diff --git a/tests/test_InterpolateItemValue.cpp b/tests/test_InterpolateItemValue.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fa786632c10660c9197842196440ae383a489edd
--- /dev/null
+++ b/tests/test_InterpolateItemValue.cpp
@@ -0,0 +1,1026 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionEvaluationExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <language/utils/SymbolTable.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+
+#include <language/utils/InterpolateItemValue.hpp>
+
+#include <pegtl/string_input.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("InterpolateItemValue", "[language]")
+{
+  SECTION("interpolate on all items")
+  {
+    auto same_cell_value = [](auto f, auto g) -> bool {
+      using ItemIdType = typename decltype(f)::index_type;
+      for (ItemIdType item_id = 0; item_id < f.numberOfItems(); ++item_id) {
+        if (f[item_id] != g[item_id]) {
+          return false;
+        }
+      }
+
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+
+      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
+let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
+let R3_affine_1d: R^1 -> R^3, x -> (2 * x[0] + 2, 3 * x[0], 2);
+let R3_non_linear_1d: R^1 -> R^3, x -> (2 * exp(x[0]) + 3, x[0] - 2, 3);
+let R2x2_affine_1d: R^1 -> R^2x2, x -> (2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2);
+let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]);
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      SECTION("scalar_affine_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<double> cell_value{mesh_1d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = 2 * x[0] + 2;
+          });
+
+        CellValue<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("scalar_non_linear_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<double> cell_value{mesh_1d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = 2 * exp(x[0]) + 3;
+          });
+
+        CellValue<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_affine_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyVector<3>> cell_value{mesh_1d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = TinyVector<3>{2 * x[0] + 2, 3 * x[0], 2};
+          });
+
+        CellValue<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_non_linear_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyVector<3>> cell_value{mesh_1d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = TinyVector<3>{2 * exp(x[0]) + 3, x[0] - 2, 3};
+          });
+
+        CellValue<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_affine_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyMatrix<2>> cell_value{mesh_1d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = TinyMatrix<2>{2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2};
+          });
+
+        CellValue<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_non_linear_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyMatrix<2>> cell_value{mesh_1d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
+          });
+
+        CellValue<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+    }
+
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
+
+      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
+let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
+let R3_affine_2d: R^2 -> R^3, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]);
+let R3_non_linear_2d: R^2 -> R^3, x -> (2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3);
+let R2x2_affine_2d: R^2 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2);
+let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1]), 3, x[0]*x[1]);
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      SECTION("scalar_affine_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<double> cell_value{mesh_2d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = 2 * x[0] + 3 * x[1] + 2;
+          });
+        CellValue<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("scalar_non_linear_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<double> cell_value{mesh_2d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = 2 * exp(x[0]) * sin(x[1]) + 3;
+          });
+        CellValue<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_affine_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyVector<3>> cell_value{mesh_2d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]};
+          });
+        CellValue<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_non_linear_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyVector<3>> cell_value{mesh_2d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + 3, x[0] - 2 * x[1], 3};
+          });
+        CellValue<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_affine_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyMatrix<2>> cell_value{mesh_2d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2};
+          });
+        CellValue<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_non_linear_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyMatrix<2>> cell_value{mesh_2d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3, sin(x[0] - 2 * x[1]), 3, x[0] * x[1]};
+          });
+        CellValue<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+    }
+
+    SECTION("3D")
+    {
+      constexpr size_t Dimension = 3;
+
+      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+let R3_affine_3d: R^3 -> R^3, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]);
+let R3_non_linear_3d: R^3 -> R^3, x -> (2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3);
+let R2x2_affine_3d: R^3 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2);
+let R2x2_non_linear_3d: R^3 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]);
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      SECTION("scalar_affine_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<double> cell_value{mesh_3d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+          });
+        CellValue<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("scalar_non_linear_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<double> cell_value{mesh_3d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id]            = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+          });
+        CellValue<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_affine_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyVector<3>> cell_value{mesh_3d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]};
+          });
+        CellValue<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_non_linear_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyVector<3>> cell_value{mesh_3d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3};
+          });
+        CellValue<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_affine_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyMatrix<2>> cell_value{mesh_3d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] =
+              TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2};
+          });
+        CellValue<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_non_linear_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        CellValue<TinyMatrix<2>> cell_value{mesh_3d->connectivity()};
+        parallel_for(
+          cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<Dimension>& x = xj[cell_id];
+            cell_value[cell_id] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]),
+                                                3, x[0] * x[1] * x[2]};
+          });
+        CellValue<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+    }
+  }
+
+  SECTION("interpolate on items list")
+  {
+    auto same_cell_value = [](auto interpolated, auto reference) -> bool {
+      for (size_t i = 0; i < interpolated.size(); ++i) {
+        if (interpolated[i] != reference[i]) {
+          return false;
+        }
+      }
+
+      return true;
+    };
+
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+
+      const auto& mesh_1d = MeshDataBaseForTests::get().cartesianMesh1D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+
+      Array<const CellId> cell_id_list = [&] {
+        Array<CellId> cell_ids{mesh_1d->numberOfCells() / 2};
+        for (size_t i_cell = 0; i_cell < cell_ids.size(); ++i_cell) {
+          cell_ids[i_cell] = static_cast<CellId>(2 * i_cell);
+        }
+        return cell_ids;
+      }();
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_1d: R^1 -> R, x -> 2*x[0] + 2;
+let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
+let R3_affine_1d: R^1 -> R^3, x -> (2 * x[0] + 2, 3 * x[0], 2);
+let R3_non_linear_1d: R^1 -> R^3, x -> (2 * exp(x[0]) + 3, x[0] - 2, 3);
+let R2x2_affine_1d: R^1 -> R^2x2, x -> (2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2);
+let R2x2_non_linear_1d: R^1 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]);
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      SECTION("scalar_affine_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<double> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = 2 * x[0] + 2;
+          });
+
+        Array<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("scalar_non_linear_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<double> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = 2 * exp(x[0]) + 3;
+          });
+
+        Array<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_affine_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyVector<3>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = TinyVector<3>{2 * x[0] + 2, 3 * x[0], 2};
+          });
+
+        Array<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_non_linear_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyVector<3>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = TinyVector<3>{2 * exp(x[0]) + 3, x[0] - 2, 3};
+          });
+
+        Array<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_affine_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_affine_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = TinyMatrix<2>{2 * x[0] + 3 + 2, 3 * x[0], 2 * x[0], 2};
+          });
+
+        Array<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_non_linear_1d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
+          });
+
+        Array<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+    }
+
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
+
+      const auto& mesh_2d = MeshDataBaseForTests::get().cartesianMesh2D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_2d).xj();
+
+      Array<CellId> cell_id_list{mesh_2d->numberOfCells() / 2};
+      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+      }
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_2d: R^2 -> R, x -> 2*x[0] + 3*x[1] + 2;
+let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
+let R3_affine_2d: R^2 -> R^3, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]);
+let R3_non_linear_2d: R^2 -> R^3, x -> (2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3);
+let R2x2_affine_2d: R^2 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2);
+let R2x2_non_linear_2d: R^2 -> R^2x2, x -> (2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1]), 3, x[0]*x[1]);
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      SECTION("scalar_affine_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<double> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = 2 * x[0] + 3 * x[1] + 2;
+          });
+
+        Array<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("scalar_non_linear_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<double> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = 2 * exp(x[0]) * sin(x[1]) + 3;
+          });
+        Array<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_affine_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyVector<3>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[1]};
+          });
+        Array<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_non_linear_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyVector<3>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + 3, x[0] - 2 * x[1], 3};
+          });
+        Array<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_affine_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_affine_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] = TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1], 2 * x[0] + x[1], 2};
+          });
+        Array<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_non_linear_2d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_2d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3, sin(x[0] - 2 * x[1]), 3, x[0] * x[1]};
+          });
+        Array<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+    }
+
+    SECTION("3D")
+    {
+      constexpr size_t Dimension = 3;
+
+      const auto& mesh_3d = MeshDataBaseForTests::get().cartesianMesh3D();
+      auto xj             = MeshDataManager::instance().getMeshData(*mesh_3d).xj();
+
+      Array<CellId> cell_id_list{mesh_3d->numberOfCells() / 2};
+      for (size_t i_cell = 0; i_cell < cell_id_list.size(); ++i_cell) {
+        cell_id_list[i_cell] = static_cast<CellId>(2 * i_cell);
+      }
+
+      std::string_view data = R"(
+import math;
+let scalar_affine_3d: R^3 -> R, x -> 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+let R3_affine_3d: R^3 -> R^3, x -> (2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]);
+let R3_non_linear_3d: R^3 -> R^3, x -> (2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3);
+let R2x2_affine_3d: R^3 -> R^2x2, x -> (2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2);
+let R2x2_non_linear_3d: R^3 -> R^2x2, x -> (2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]);
+)";
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+      TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+      position.byte = data.size();   // ensure that variables are declared at this point
+
+      SECTION("scalar_affine_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<double> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = 2 * x[0] + 3 * x[1] + 2 * x[2] - 1;
+          });
+        Array<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("scalar_non_linear_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("scalar_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<double> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i]                  = 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
+          });
+        Array<const double> interpolate_value =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_affine_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyVector<3>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] = TinyVector<3>{2 * x[0] + 3 * x[1] + 2, 3 * x[0] + x[1] + 2 * x[2], 2 * x[2]};
+          });
+        Array<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R3_non_linear_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyVector<3>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] = TinyVector<3>{2 * exp(x[0]) * sin(x[1]) + x[2] + 3, x[0] * x[2] - 2 * x[1], 3};
+          });
+        Array<const TinyVector<3>> interpolate_value =
+          InterpolateItemValue<TinyVector<3>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_affine_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_affine_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] =
+              TinyMatrix<2>{2 * x[0] + 3 * x[1] + 2 * x[2] + 1, 3 * x[0] + x[1] + 2 * x[2], 2 * x[0] + x[1] + x[2], 2};
+          });
+        Array<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+
+      SECTION("R2x2_non_linear_3d")
+      {
+        auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
+        REQUIRE(found);
+        REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+        FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+        Array<TinyMatrix<2>> cell_value{cell_id_list.size()};
+        parallel_for(
+          cell_value.size(), PUGS_LAMBDA(const size_t i) {
+            const TinyVector<Dimension>& x = xj[cell_id_list[i]];
+            cell_value[i] = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin(x[0] - 2 * x[1] * x[2]), 3,
+                                          x[0] * x[1] * x[2]};
+          });
+        Array<const TinyMatrix<2>> interpolate_value =
+          InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj, cell_id_list);
+
+        REQUIRE(same_cell_value(cell_value, interpolate_value));
+      }
+    }
+  }
+}
diff --git a/tests/test_ItemArray.cpp b/tests/test_ItemArray.cpp
index 8cf05df5df90f3886771905f9a4fe2b727698150..2a76275324c4922ed0bad182ebcfa106c3d9b462 100644
--- a/tests/test_ItemArray.cpp
+++ b/tests/test_ItemArray.cpp
@@ -28,7 +28,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
   SECTION("1D")
   {
-    const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+    const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
     const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
     REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
@@ -58,7 +58,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
   SECTION("2D")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     REQUIRE_NOTHROW(NodeArray<int>{connectivity, 2});
@@ -86,7 +86,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
   SECTION("3D")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
@@ -112,7 +112,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
   SECTION("set values from array")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     CellArray<size_t> cell_array{connectivity, 3};
@@ -155,7 +155,7 @@ TEST_CASE("ItemArray", "[mesh]")
       return is_same;
     };
 
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     CellArray<int> cell_array{connectivity, 4};
@@ -169,16 +169,20 @@ TEST_CASE("ItemArray", "[mesh]")
     CellArray<const int> const_cell_array;
     const_cell_array = copy(cell_array);
 
+    CellArray<int> duplicated_cell_array{connectivity, cell_array.sizeOfArrays()};
+    copy_to(const_cell_array, duplicated_cell_array);
+
     cell_array.fill(0);
 
     REQUIRE(is_same(cell_array, 0));
     REQUIRE(is_same(cell_array_const_view, 0));
     REQUIRE(is_same(const_cell_array, static_cast<std::int64_t>(parallel::rank())));
+    REQUIRE(is_same(duplicated_cell_array, static_cast<std::int64_t>(parallel::rank())));
   }
 
   SECTION("WeakItemArray")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     WeakFaceArray<int> weak_face_array{connectivity, 5};
@@ -210,7 +214,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellArray<int> cell_array{connectivity, 1};
@@ -232,7 +236,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
     SECTION("set values from invalid array size")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellArray<size_t> cell_array{connectivity, 2};
diff --git a/tests/test_ItemArrayUtils.cpp b/tests/test_ItemArrayUtils.cpp
index a8c4b7fbf50eb3c4bb4f9af89926eedcd66c5992..6eb6894d41aa3700fdc86e6c0d7c9d83acda328e 100644
--- a/tests/test_ItemArrayUtils.cpp
+++ b/tests/test_ItemArrayUtils.cpp
@@ -19,7 +19,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       SECTION("node")
@@ -269,7 +269,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       SECTION("node")
@@ -519,7 +519,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       SECTION("node")
diff --git a/tests/test_ItemValue.cpp b/tests/test_ItemValue.cpp
index 942b4f57bafd0528ed31e82709213207c2dea619..1c4ab49faf757cc0b2e871f9bf4e0ba33061ece7 100644
--- a/tests/test_ItemValue.cpp
+++ b/tests/test_ItemValue.cpp
@@ -26,7 +26,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("1D")
   {
-    const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+    const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
     const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
     REQUIRE_NOTHROW(NodeValue<int>{connectivity});
@@ -51,7 +51,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("2D")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     REQUIRE_NOTHROW(NodeValue<int>{connectivity});
@@ -72,7 +72,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("3D")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     REQUIRE_NOTHROW(NodeValue<int>{connectivity});
@@ -88,7 +88,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("set values from array")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     CellValue<size_t> cell_value{connectivity};
@@ -107,7 +107,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("copy")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     CellValue<int> cell_value{connectivity};
@@ -129,7 +129,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
   SECTION("WeakItemValue")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     WeakFaceValue<int> weak_face_value{connectivity};
@@ -161,7 +161,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellValue<int> cell_value{connectivity};
@@ -183,7 +183,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
     SECTION("set values from invalid array size")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellValue<size_t> cell_value{connectivity};
@@ -191,6 +191,19 @@ TEST_CASE("ItemValue", "[mesh]")
       Array<size_t> values{3 + cell_value.numberOfItems()};
       REQUIRE_THROWS_AS(cell_value = values, AssertError);
     }
+
+    SECTION("invalid copy_to")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d   = *MeshDataBaseForTests::get().cartesianMesh2D();
+      const Connectivity<2>& connectivity_2d = mesh_2d.connectivity();
+
+      const Mesh<Connectivity<3>>& mesh_3d   = *MeshDataBaseForTests::get().cartesianMesh3D();
+      const Connectivity<3>& connectivity_3d = mesh_3d.connectivity();
+
+      CellValue<int> cell_2d_value{connectivity_2d};
+      CellValue<int> cell_3d_value{connectivity_3d};
+      REQUIRE_THROWS_AS(copy_to(cell_2d_value, cell_3d_value), AssertError);
+    }
   }
 #endif   // NDEBUG
 }
diff --git a/tests/test_ItemValueUtils.cpp b/tests/test_ItemValueUtils.cpp
index f54362188d6e9519d28abb47e69385ba11ccfdb8..b77dfecd58fe2aa2ee2addcc483f631f3e3ec62f 100644
--- a/tests/test_ItemValueUtils.cpp
+++ b/tests/test_ItemValueUtils.cpp
@@ -17,7 +17,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 {
   SECTION("Synchronize")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     WeakFaceValue<int> weak_face_value{connectivity};
@@ -57,7 +57,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       CellValue<int> cell_value{connectivity};
@@ -76,7 +76,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       CellValue<int> cell_value{connectivity};
@@ -95,7 +95,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellValue<int> cell_value{connectivity};
@@ -117,7 +117,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       CellValue<size_t> cell_value{connectivity};
@@ -136,7 +136,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       CellValue<size_t> cell_value{connectivity};
@@ -155,7 +155,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellValue<size_t> cell_value{connectivity};
@@ -177,7 +177,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       CellValue<size_t> cell_value{connectivity};
@@ -198,7 +198,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       FaceValue<size_t> face_value{connectivity};
@@ -219,7 +219,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       NodeValue<size_t> node_value{connectivity};
diff --git a/tests/test_ListAffectationProcessor.cpp b/tests/test_ListAffectationProcessor.cpp
index f809884c3e660f16cc88f58c709040c58c96fe70..94dc1845cddcd898a3b3b0f2f08c80e164898660 100644
--- a/tests/test_ListAffectationProcessor.cpp
+++ b/tests/test_ListAffectationProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -18,6 +19,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
diff --git a/tests/test_NameProcessor.cpp b/tests/test_NameProcessor.cpp
index ac6b8174d6345e4be81fe5515bca14233fa8f1f4..30e20339aac2096b61f4a12b3131959531061475 100644
--- a/tests/test_NameProcessor.cpp
+++ b/tests/test_NameProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -27,6 +28,9 @@ n = 2;
   TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
   auto ast = ASTBuilder::build(input);
 
+  ASTModulesImporter{*ast};
+  ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
   ASTSymbolTableBuilder{*ast};
   ASTNodeDataTypeBuilder{*ast};
 
diff --git a/tests/test_PugsFunctionAdapter.cpp b/tests/test_PugsFunctionAdapter.cpp
index 7d048738e6a20484dfe5f0431d7dab9bf3f3c5f2..76f3a06aa3f96c208e6c02c16f8006e22160a8f6 100644
--- a/tests/test_PugsFunctionAdapter.cpp
+++ b/tests/test_PugsFunctionAdapter.cpp
@@ -1,10 +1,6 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <language/ast/ASTBuilder.hpp>
-#include <language/utils/PugsFunctionAdapter.hpp>
-#include <language/utils/SymbolTable.hpp>
-
 #include <language/ast/ASTBuilder.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -13,8 +9,8 @@
 #include <language/ast/ASTNodeFunctionExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
-#include <language/utils/ASTPrinter.hpp>
-#include <utils/Demangle.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <language/utils/SymbolTable.hpp>
 
 #include <pegtl/string_input.hpp>
 
diff --git a/tests/test_SubArray.cpp b/tests/test_SubArray.cpp
deleted file mode 100644
index b0fdec7882d7ee3504cb24bdfb76b796649f8202..0000000000000000000000000000000000000000
--- a/tests/test_SubArray.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/matchers/catch_matchers_all.hpp>
-
-#include <utils/PugsAssert.hpp>
-#include <utils/SubArray.hpp>
-#include <utils/Types.hpp>
-
-// Instantiate to ensure full coverage is performed
-template class SubArray<int>;
-
-// clazy:excludeall=non-pod-global-static
-
-TEST_CASE("SubArray", "[utils]")
-{
-  Array<int> a(10);
-  REQUIRE(a.size() == 10);
-
-  SECTION("shared values")
-  {
-    SubArray sub_a{a, 0, 10};
-    for (size_t i = 0; i < sub_a.size(); ++i) {
-      sub_a[i] = 2 * i;
-    }
-
-    REQUIRE(((a[0] == 0) and (a[1] == 2) and (a[2] == 4) and (a[3] == 6) and (a[4] == 8) and (a[5] == 10) and
-             (a[6] == 12) and (a[7] == 14) and (a[8] == 16) and (a[9] == 18)));
-
-    for (size_t i = 0; i < a.size(); ++i) {
-      a[i] = (i + 1) * (2 * i + 1);
-    }
-
-    REQUIRE(((sub_a[0] == 1) and (sub_a[1] == 6) and (sub_a[2] == 15) and (sub_a[3] == 28) and (sub_a[4] == 45) and
-             (sub_a[5] == 66) and (sub_a[6] == 91) and (sub_a[7] == 120) and (sub_a[8] == 153) and (sub_a[9] == 190)));
-  }
-
-  SECTION("sub array")
-  {
-    a.fill(0);
-    SubArray sub_a{a, 5, 5};
-    for (size_t i = 0; i < sub_a.size(); ++i) {
-      sub_a[i] = i + 1;
-    }
-
-    REQUIRE(((a[0] == 0) and (a[1] == 0) and (a[2] == 0) and (a[3] == 0) and (a[4] == 0) and (a[5] == 1) and
-             (a[6] == 2) and (a[7] == 3) and (a[8] == 4) and (a[9] == 5)));
-
-    for (size_t i = 0; i < a.size(); ++i) {
-      a[i] = (i + 1) * (2 * i + 1);
-    }
-
-    REQUIRE(((sub_a[0] == 66) and (sub_a[1] == 91) and (sub_a[2] == 120) and (sub_a[3] == 153) and (sub_a[4] == 190)));
-  }
-
-#ifndef NDEBUG
-  SECTION("errors")
-  {
-    a.fill(0);
-    SubArray<int> sub_a{a, 5, 5};
-    for (size_t i = 0; i < sub_a.size(); ++i) {
-      sub_a[i] = i + 1;
-    }
-
-    REQUIRE_THROWS_AS(sub_a[5], AssertError);
-  }
-#endif   // NDEBUG
-}
diff --git a/tests/test_SubItemArrayPerItem.cpp b/tests/test_SubItemArrayPerItem.cpp
index 7636ad420dc87ddf029d21b154ea0067a4eb5684..b03353f0ebbff8fe3d6c5634020d1ea739668f57 100644
--- a/tests/test_SubItemArrayPerItem.cpp
+++ b/tests/test_SubItemArrayPerItem.cpp
@@ -64,7 +64,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       SECTION("per cell")
@@ -178,7 +178,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       SECTION("per cell")
@@ -335,7 +335,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     }
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       SECTION("per cell")
@@ -524,7 +524,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       EdgeArrayPerCell<size_t> edge_arrays_per_cell{connectivity, 3};
@@ -589,7 +589,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       CellArrayPerFace<size_t> cell_arrays_per_face{connectivity, 3};
@@ -653,7 +653,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       FaceArrayPerNode<size_t> face_arrays_per_node{connectivity, 3};
@@ -717,7 +717,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
   SECTION("copy")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     SECTION("classic")
@@ -818,7 +818,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
   SECTION("WeakSubItemArrayPerItem")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     WeakFaceArrayPerCell<int> weak_face_array_per_cell{connectivity, 3};
@@ -889,7 +889,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
diff --git a/tests/test_SubItemValuePerItem.cpp b/tests/test_SubItemValuePerItem.cpp
index e7c9434aa7362f7cb27a87ee6b144df27f9c2f26..f9b71010b411c3a53b968703202882f1d4fd0df1 100644
--- a/tests/test_SubItemValuePerItem.cpp
+++ b/tests/test_SubItemValuePerItem.cpp
@@ -64,7 +64,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       SECTION("per cell")
@@ -172,7 +172,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       SECTION("per cell")
@@ -320,7 +320,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       SECTION("per cell")
@@ -497,7 +497,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
   {
     SECTION("1D")
     {
-      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Mesh<Connectivity<1>>& mesh_1d = *MeshDataBaseForTests::get().cartesianMesh1D();
       const Connectivity<1>& connectivity  = mesh_1d.connectivity();
 
       EdgeValuePerCell<size_t> edge_values_per_cell{connectivity};
@@ -534,7 +534,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("2D")
     {
-      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
       const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
       CellValuePerFace<size_t> cell_values_per_face{connectivity};
@@ -570,7 +570,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("3D")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       FaceValuePerNode<size_t> face_values_per_node{connectivity};
@@ -608,7 +608,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
   SECTION("copy")
   {
-    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
     const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
     SECTION("classic")
@@ -698,7 +698,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
   SECTION("WeakSubItemValuePerItem")
   {
-    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Mesh<Connectivity<2>>& mesh_2d = *MeshDataBaseForTests::get().cartesianMesh2D();
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     WeakFaceValuePerCell<int> weak_face_value_per_cell{connectivity};
@@ -758,7 +758,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
     SECTION("checking for bounds violation")
     {
-      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Mesh<Connectivity<3>>& mesh_3d = *MeshDataBaseForTests::get().cartesianMesh3D();
       const Connectivity<3>& connectivity  = mesh_3d.connectivity();
 
       CellValuePerFace<int> cell_value_per_face{connectivity};
diff --git a/tests/test_Table.cpp b/tests/test_Table.cpp
index 7b736f3d76f87129b45885fed737a361bf73562e..3521476c375dba6a31e57dd2f55ad09430026d8a 100644
--- a/tests/test_Table.cpp
+++ b/tests/test_Table.cpp
@@ -137,6 +137,23 @@ TEST_CASE("Table", "[utils]")
     REQUIRE(((c(0, 0) == 2) and (c(1, 0) == 2) and (c(2, 0) == 2) and (c(3, 0) == 2) and   //
              (c(0, 1) == 2) and (c(1, 1) == 2) and (c(2, 1) == 2) and (c(3, 1) == 2) and   //
              (c(0, 2) == 2) and (c(1, 2) == 2) and (c(2, 2) == 2) and (c(3, 2) == 2)));
+
+    Table<int> d{a.nbRows(), a.nbColumns()};
+    copy_to(a, d);
+
+    REQUIRE(((a(0, 0) == 0) and (a(1, 0) == 2) and (a(2, 0) == 4) and (a(3, 0) == 6) and   //
+             (a(0, 1) == 1) and (a(1, 1) == 3) and (a(2, 1) == 5) and (a(3, 1) == 7) and   //
+             (a(0, 2) == 2) and (a(1, 2) == 4) and (a(2, 2) == 6) and (a(3, 2) == 8)));
+
+    REQUIRE(((d(0, 0) == 0) and (d(1, 0) == 2) and (d(2, 0) == 4) and (d(3, 0) == 6) and   //
+             (d(0, 1) == 1) and (d(1, 1) == 3) and (d(2, 1) == 5) and (d(3, 1) == 7) and   //
+             (d(0, 2) == 2) and (d(1, 2) == 4) and (d(2, 2) == 6) and (d(3, 2) == 8)));
+
+    copy_to(c, d);
+
+    REQUIRE(((d(0, 0) == 2) and (d(1, 0) == 2) and (d(2, 0) == 2) and (d(3, 0) == 2) and   //
+             (d(0, 1) == 2) and (d(1, 1) == 2) and (d(2, 1) == 2) and (d(3, 1) == 2) and   //
+             (d(0, 2) == 2) and (d(1, 2) == 2) and (d(2, 2) == 2) and (d(3, 2) == 2)));
   }
 
   SECTION("checking for Kokkos::View encaspulation")
@@ -167,5 +184,20 @@ TEST_CASE("Table", "[utils]")
     REQUIRE_THROWS_AS(a(4, 0), AssertError);
     REQUIRE_THROWS_AS(a(0, 3), AssertError);
   }
+
+  SECTION("invalid copy_to")
+  {
+    SECTION("wrong row number")
+    {
+      Table<int> b{2 * a.nbRows(), a.nbColumns()};
+      REQUIRE_THROWS_AS(copy_to(a, b), AssertError);
+    }
+
+    SECTION("wrong column number")
+    {
+      Table<int> c{a.nbRows(), 2 * a.nbColumns()};
+      REQUIRE_THROWS_AS(copy_to(a, c), AssertError);
+    }
+  }
 #endif   // NDEBUG
 }
diff --git a/tests/test_UnaryExpressionProcessor.cpp b/tests/test_UnaryExpressionProcessor.cpp
index 84b3bc0c8da06d17dd429f7add7d72d036d17068..f63e45d1fa9912f1d007f83a2759bfc5b7316b42 100644
--- a/tests/test_UnaryExpressionProcessor.cpp
+++ b/tests/test_UnaryExpressionProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -18,6 +19,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
diff --git a/tests/test_WhileProcessor.cpp b/tests/test_WhileProcessor.cpp
index 33f1d9488a3007e8f1a4d2f8c9a733487c496d4b..e886b62137e4d9d67bdabdca4745bb6cf266537d 100644
--- a/tests/test_WhileProcessor.cpp
+++ b/tests/test_WhileProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -19,6 +20,9 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTModulesImporter{*ast};                                                 \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
+                                                                              \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \