diff --git a/src/language/modules/BinaryOperatorRegisterForVh.cpp b/src/language/modules/BinaryOperatorRegisterForVh.cpp
index 675d84772cc9764df42be29b8be9a03de77c6858..3eb83537323d91bbf6266cbefebf4d004d74c2be 100644
--- a/src/language/modules/BinaryOperatorRegisterForVh.cpp
+++ b/src/language/modules/BinaryOperatorRegisterForVh.cpp
@@ -4,9 +4,8 @@
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/DataHandler.hpp>
 #include <language/utils/DataVariant.hpp>
-#include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
+#include <language/utils/EmbeddedDiscreteFunctionOperators.hpp>
 #include <language/utils/OperatorRepository.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 
 void
 BinaryOperatorRegisterForVh::_register_plus()
@@ -14,89 +13,89 @@ 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>, bool,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    bool, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    int64_t, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    uint64_t, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>, double,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    double, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, bool>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyVector<1>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyVector<2>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyVector<2>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyVector<3>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyVector<3>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<1>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyMatrix<1>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyMatrix<2>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyMatrix<3>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::plus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::plus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, TinyMatrix<3>>>());
 }
 
 void
@@ -105,89 +104,89 @@ 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>, bool,
-                                                    std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    bool, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    int64_t, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    uint64_t, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    double, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, bool>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyVector<1>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyVector<2>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyVector<2>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyVector<3>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyVector<3>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<1>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyMatrix<1>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyMatrix<2>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    TinyMatrix<3>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::minus_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::minus_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>, TinyMatrix<3>>>());
 }
 
 void
@@ -196,77 +195,94 @@ 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<
+      language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+      std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    bool, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>, bool,
+                                     std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>, int64_t,
+                                     std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>, uint64_t,
+                                     std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>, double,
+                                     std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, bool>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     TinyMatrix<1>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<2>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     TinyMatrix<2>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    TinyMatrix<3>, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     TinyMatrix<3>, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::multiply_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    std::shared_ptr<const IDiscreteFunction>, TinyVector<1>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, 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>>>());
+    std::make_shared<
+      BinaryOperatorProcessorBuilder<language::multiply_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                     std::shared_ptr<const DiscreteFunctionVariant>, TinyMatrix<3>>>());
 }
 
 void
@@ -275,21 +291,21 @@ 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>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::divide_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    int64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    int64_t, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::divide_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    uint64_t, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    uint64_t, std::shared_ptr<const DiscreteFunctionVariant>>>());
 
   repository.addBinaryOperator<language::divide_op>(
-    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const IDiscreteFunction>,
-                                                    double, std::shared_ptr<const IDiscreteFunction>>>());
+    std::make_shared<BinaryOperatorProcessorBuilder<language::divide_op, std::shared_ptr<const DiscreteFunctionVariant>,
+                                                    double, std::shared_ptr<const DiscreteFunctionVariant>>>());
 }
 
 BinaryOperatorRegisterForVh::BinaryOperatorRegisterForVh()
diff --git a/src/language/modules/MathFunctionRegisterForVh.cpp b/src/language/modules/MathFunctionRegisterForVh.cpp
index 05d2ea703cfbce9d233fc285f2b38ebbb805ae14..38dc08b6e4bec82be8cab2666080563bd7cb9736 100644
--- a/src/language/modules/MathFunctionRegisterForVh.cpp
+++ b/src/language/modules/MathFunctionRegisterForVh.cpp
@@ -2,376 +2,387 @@
 
 #include <language/modules/SchemeModule.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
-#include <language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp>
-#include <scheme/IDiscreteFunction.hpp>
-#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 
 MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module)
 {
   scheme_module._addBuiltinFunction("sqrt", std::function(
 
-                                              [](std::shared_ptr<const IDiscreteFunction> a)
-                                                -> std::shared_ptr<const IDiscreteFunction> { return sqrt(a); }
+                                              [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                -> std::shared_ptr<const DiscreteFunctionVariant> { return sqrt(a); }
 
                                               ));
 
   scheme_module._addBuiltinFunction("abs", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return abs(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return abs(a); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("sin", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return sin(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return sin(a); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("cos", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return cos(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return cos(a); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("tan", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return tan(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return tan(a); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("asin", std::function(
 
-                                              [](std::shared_ptr<const IDiscreteFunction> a)
-                                                -> std::shared_ptr<const IDiscreteFunction> { return asin(a); }
+                                              [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                -> std::shared_ptr<const DiscreteFunctionVariant> { return asin(a); }
 
                                               ));
 
   scheme_module._addBuiltinFunction("acos", std::function(
 
-                                              [](std::shared_ptr<const IDiscreteFunction> a)
-                                                -> std::shared_ptr<const IDiscreteFunction> { return acos(a); }
+                                              [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                -> std::shared_ptr<const DiscreteFunctionVariant> { return acos(a); }
 
                                               ));
 
   scheme_module._addBuiltinFunction("atan", std::function(
 
-                                              [](std::shared_ptr<const IDiscreteFunction> a)
-                                                -> std::shared_ptr<const IDiscreteFunction> { return atan(a); }
+                                              [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                -> std::shared_ptr<const DiscreteFunctionVariant> { return atan(a); }
 
                                               ));
 
-  scheme_module._addBuiltinFunction("atan2", std::function(
-
-                                               [](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::function(
+  scheme_module._addBuiltinFunction("atan2",
+                                    std::function(
 
-                                               [](double a, std::shared_ptr<const IDiscreteFunction> b)
-                                                 -> std::shared_ptr<const IDiscreteFunction> { return atan2(a, b); }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a,
+                                         std::shared_ptr<const DiscreteFunctionVariant> b)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return atan2(a, b); }
 
-                                               ));
+                                      ));
 
   scheme_module._addBuiltinFunction("atan2",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a,
-                                         double b) -> std::shared_ptr<const IDiscreteFunction> { return atan2(a, b); }
+                                      [](double a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return atan2(a, b); }
 
                                       ));
 
+  scheme_module._addBuiltinFunction("atan2", std::function(
+
+                                               [](std::shared_ptr<const DiscreteFunctionVariant> a,
+                                                  double b) -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                                 return atan2(a, b);
+                                               }
+
+                                               ));
+
   scheme_module._addBuiltinFunction("sinh", std::function(
 
-                                              [](std::shared_ptr<const IDiscreteFunction> a)
-                                                -> std::shared_ptr<const IDiscreteFunction> { return sinh(a); }
+                                              [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                -> std::shared_ptr<const DiscreteFunctionVariant> { return sinh(a); }
 
                                               ));
 
-  scheme_module._addBuiltinFunction("std::function", std::function(
+  scheme_module._addBuiltinFunction("tanh", std::function(
 
-                                                       [](std::shared_ptr<const IDiscreteFunction> a)
-                                                         -> std::shared_ptr<const IDiscreteFunction> { return tanh(a); }
+                                              [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                -> std::shared_ptr<const DiscreteFunctionVariant> { return tanh(a); }
 
-                                                       ));
+                                              ));
 
   scheme_module._addBuiltinFunction("asinh", std::function(
 
-                                               [](std::shared_ptr<const IDiscreteFunction> a)
-                                                 -> std::shared_ptr<const IDiscreteFunction> { return asinh(a); }
+                                               [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                 -> std::shared_ptr<const DiscreteFunctionVariant> { return asinh(a); }
 
                                                ));
 
   scheme_module._addBuiltinFunction("acosh", std::function(
 
-                                               [](std::shared_ptr<const IDiscreteFunction> a)
-                                                 -> std::shared_ptr<const IDiscreteFunction> { return acosh(a); }
+                                               [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                 -> std::shared_ptr<const DiscreteFunctionVariant> { return acosh(a); }
 
                                                ));
 
   scheme_module._addBuiltinFunction("atanh", std::function(
 
-                                               [](std::shared_ptr<const IDiscreteFunction> a)
-                                                 -> std::shared_ptr<const IDiscreteFunction> { return atanh(a); }
+                                               [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                 -> std::shared_ptr<const DiscreteFunctionVariant> { return atanh(a); }
 
                                                ));
 
   scheme_module._addBuiltinFunction("exp", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return exp(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return exp(a); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("log", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return log(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return log(a); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("pow", std::function(
 
-                                             [](double a, std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return pow(a, b); }
+                                             [](double a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return pow(a, b); }
 
                                              ));
 
-  scheme_module._addBuiltinFunction("pow",
-                                    std::function(
+  scheme_module._addBuiltinFunction("pow", std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a,
-                                         double b) -> std::shared_ptr<const IDiscreteFunction> { return pow(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a, double b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return pow(a, b); }
 
-                                      ));
+                                             ));
 
   scheme_module._addBuiltinFunction("pow", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a,
-                                                std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return pow(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a,
+                                                std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return pow(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("dot", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a,
-                                                std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a,
+                                                std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("dot", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a, const TinyVector<1> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a, const TinyVector<1> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("dot", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a, const TinyVector<2> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a, const TinyVector<2> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
                                              ));
 
-  scheme_module._addBuiltinFunction("dot", std::function(
+  scheme_module._addBuiltinFunction("dot",
+                                    std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a, const TinyVector<3>& b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a, const TinyVector<3>& b)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
-                                             ));
+                                      ));
 
   scheme_module._addBuiltinFunction("dot", std::function(
 
-                                             [](const TinyVector<1> a, std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                             [](const TinyVector<1> a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("dot", std::function(
 
-                                             [](const TinyVector<2> a, std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                             [](const TinyVector<2> a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
                                              ));
 
-  scheme_module._addBuiltinFunction("dot", std::function(
+  scheme_module._addBuiltinFunction("dot",
+                                    std::function(
 
-                                             [](const TinyVector<3>& a, std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return dot(a, b); }
+                                      [](const TinyVector<3>& a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
 
-                                             ));
+                                      ));
 
   scheme_module._addBuiltinFunction("det", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> A)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return det(A); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> A)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return det(A); }
 
                                              ));
 
+  scheme_module._addBuiltinFunction("inverse",
+                                    std::function(
+
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> A)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return inverse(A); }
+
+                                      ));
+
   scheme_module._addBuiltinFunction("trace", std::function(
 
-                                               [](std::shared_ptr<const IDiscreteFunction> A)
-                                                 -> std::shared_ptr<const IDiscreteFunction> { return trace(A); }
+                                               [](std::shared_ptr<const DiscreteFunctionVariant> A)
+                                                 -> std::shared_ptr<const DiscreteFunctionVariant> { return trace(A); }
 
                                                ));
+  scheme_module._addBuiltinFunction("min",
+                                    std::function(
 
-  scheme_module._addBuiltinFunction("inverse", std::function(
-
-                                                 [](std::shared_ptr<const IDiscreteFunction> A)
-                                                   -> std::shared_ptr<const IDiscreteFunction> { return inverse(A); }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> double { return min(a); }
 
-                                                 ));
+                                      ));
 
   scheme_module._addBuiltinFunction("transpose",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> A)
-                                        -> std::shared_ptr<const IDiscreteFunction> { return transpose(A); }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> A)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return transpose(A); }
 
                                       ));
 
   scheme_module._addBuiltinFunction("min", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a) -> double { return min(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a,
+                                                std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return min(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("min", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a,
-                                                std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return min(a, b); }
+                                             [](double a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return min(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("min", std::function(
 
-                                             [](double a, std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return min(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a, double b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return min(a, b); }
 
                                              ));
 
-  scheme_module._addBuiltinFunction("min",
+  scheme_module._addBuiltinFunction("max",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a,
-                                         double b) -> std::shared_ptr<const IDiscreteFunction> { return min(a, b); }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> double { return max(a); }
 
                                       ));
 
   scheme_module._addBuiltinFunction("max", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a) -> double { return max(a); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a,
+                                                std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return max(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("max", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> a,
-                                                std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return max(a, b); }
+                                             [](double a, std::shared_ptr<const DiscreteFunctionVariant> b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return max(a, b); }
 
                                              ));
 
   scheme_module._addBuiltinFunction("max", std::function(
 
-                                             [](double a, std::shared_ptr<const IDiscreteFunction> b)
-                                               -> std::shared_ptr<const IDiscreteFunction> { return max(a, b); }
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> a, double b)
+                                               -> std::shared_ptr<const DiscreteFunctionVariant> { return max(a, b); }
 
                                              ));
 
-  scheme_module._addBuiltinFunction("max",
-                                    std::function(
-
-                                      [](std::shared_ptr<const IDiscreteFunction> a,
-                                         double b) -> std::shared_ptr<const IDiscreteFunction> { return max(a, b); }
-
-                                      ));
-
   scheme_module._addBuiltinFunction("sum_of_R", std::function(
 
-                                                  [](std::shared_ptr<const IDiscreteFunction> a) -> double {
+                                                  [](std::shared_ptr<const DiscreteFunctionVariant> a) -> double {
                                                     return sum_of<double>(a);
                                                   }
 
                                                   ));
 
-  scheme_module._addBuiltinFunction("sum_of_R1", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R1",
+                                    std::function(
 
-                                                   [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<1> {
-                                                     return sum_of<TinyVector<1>>(a);
-                                                   }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<1> {
+                                        return sum_of<TinyVector<1>>(a);
+                                      }
 
-                                                   ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R2", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R2",
+                                    std::function(
 
-                                                   [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<2> {
-                                                     return sum_of<TinyVector<2>>(a);
-                                                   }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<2> {
+                                        return sum_of<TinyVector<2>>(a);
+                                      }
 
-                                                   ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R3", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R3",
+                                    std::function(
 
-                                                   [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<3> {
-                                                     return sum_of<TinyVector<3>>(a);
-                                                   }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<3> {
+                                        return sum_of<TinyVector<3>>(a);
+                                      }
 
-                                                   ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R1x1", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R1x1",
+                                    std::function(
 
-                                                     [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<1> {
-                                                       return sum_of<TinyMatrix<1>>(a);
-                                                     }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<1> {
+                                        return sum_of<TinyMatrix<1>>(a);
+                                      }
 
-                                                     ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R2x2", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R2x2",
+                                    std::function(
 
-                                                     [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<2> {
-                                                       return sum_of<TinyMatrix<2>>(a);
-                                                     }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<2> {
+                                        return sum_of<TinyMatrix<2>>(a);
+                                      }
 
-                                                     ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R3x3", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R3x3",
+                                    std::function(
 
-                                                     [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<3> {
-                                                       return sum_of<TinyMatrix<3>>(a);
-                                                     }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<3> {
+                                        return sum_of<TinyMatrix<3>>(a);
+                                      }
 
-                                                     ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_Vh",
-                                    std::function(
+  scheme_module._addBuiltinFunction("sum_of_Vh", std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a)
-                                        -> std::shared_ptr<const IDiscreteFunction> { return sum_of_Vh_components(a); }
+                                                   [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                     -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                                     return sum_of_Vh_components(a);
+                                                   }
 
-                                      ));
+                                                   ));
 
-  scheme_module
-    ._addBuiltinFunction("vectorize",
-                         std::function(
+  scheme_module._addBuiltinFunction("vectorize",
+                                    std::function(
 
-                           [](const std::vector<std::shared_ptr<const IDiscreteFunction>>& discrete_function_list)
-                             -> std::shared_ptr<const IDiscreteFunction> { return vectorize(discrete_function_list); }
+                                      [](const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>&
+                                           discrete_function_list) -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                        return vectorize(discrete_function_list);
+                                      }
 
-                           ));
+                                      ));
 
   scheme_module._addBuiltinFunction("integral_of_R", std::function(
 
-                                                       [](std::shared_ptr<const IDiscreteFunction> a) -> double {
+                                                       [](std::shared_ptr<const DiscreteFunctionVariant> a) -> double {
                                                          return integral_of<double>(a);
                                                        }
 
@@ -380,7 +391,7 @@ MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module
   scheme_module._addBuiltinFunction("integral_of_R1",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<1> {
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<1> {
                                         return integral_of<TinyVector<1>>(a);
                                       }
 
@@ -389,7 +400,7 @@ MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module
   scheme_module._addBuiltinFunction("integral_of_R2",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<2> {
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<2> {
                                         return integral_of<TinyVector<2>>(a);
                                       }
 
@@ -398,7 +409,7 @@ MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module
   scheme_module._addBuiltinFunction("integral_of_R3",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<3> {
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<3> {
                                         return integral_of<TinyVector<3>>(a);
                                       }
 
@@ -407,7 +418,7 @@ MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module
   scheme_module._addBuiltinFunction("integral_of_R1x1",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<1> {
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<1> {
                                         return integral_of<TinyMatrix<1>>(a);
                                       }
 
@@ -416,7 +427,7 @@ MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module
   scheme_module._addBuiltinFunction("integral_of_R2x2",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<2> {
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<2> {
                                         return integral_of<TinyMatrix<2>>(a);
                                       }
 
@@ -425,7 +436,7 @@ MathFunctionRegisterForVh::MathFunctionRegisterForVh(SchemeModule& scheme_module
   scheme_module._addBuiltinFunction("integral_of_R3x3",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<3> {
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<3> {
                                         return integral_of<TinyMatrix<3>>(a);
                                       }
 
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index e3c6f1b96bf5ae660f7e3648ab70858edd4c20a3..bdae18265ed7ffc231c5c1feebf8de9d5402325d 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -25,6 +25,7 @@
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/DiscreteFunctionVectorIntegrator.hpp>
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
 #include <scheme/ExternalBoundaryConditionDescriptor.hpp>
@@ -32,7 +33,6 @@
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
 #include <scheme/FreeBoundaryConditionDescriptor.hpp>
 #include <scheme/IBoundaryConditionDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <scheme/NeumannBoundaryConditionDescriptor.hpp>
 #include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
@@ -42,7 +42,7 @@
 
 SchemeModule::SchemeModule()
 {
-  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunction>>);
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const DiscreteFunctionVariant>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>>);
 
@@ -96,11 +96,11 @@ SchemeModule::SchemeModule()
                                  std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
                                  std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
                                  const std::vector<FunctionSymbolId>& function_id_list)
-                                -> std::shared_ptr<const IDiscreteFunction> {
-                                return DiscreteFunctionVectorIntegrator{mesh, integration_zone_list,
-                                                                        quadrature_descriptor,
-                                                                        discrete_function_descriptor, function_id_list}
-                                  .integrate();
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                return std::make_shared<DiscreteFunctionVariant>(
+                                  DiscreteFunctionVectorIntegrator{mesh, integration_zone_list, quadrature_descriptor,
+                                                                   discrete_function_descriptor, function_id_list}
+                                    .integrate());
                               }
 
                               ));
@@ -112,34 +112,40 @@ SchemeModule::SchemeModule()
                                  std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
                                  std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
                                  const std::vector<FunctionSymbolId>& function_id_list)
-                                -> std::shared_ptr<const IDiscreteFunction> {
-                                return DiscreteFunctionVectorIntegrator{mesh, quadrature_descriptor,
-                                                                        discrete_function_descriptor, function_id_list}
-                                  .integrate();
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                return std::make_shared<DiscreteFunctionVariant>(
+                                  DiscreteFunctionVectorIntegrator{mesh, quadrature_descriptor,
+                                                                   discrete_function_descriptor, function_id_list}
+                                    .integrate());
                               }
 
                               ));
 
-  this->_addBuiltinFunction(
-    "integrate",
-    std::function(
+  this->_addBuiltinFunction("integrate",
+                            std::function(
 
-      [](std::shared_ptr<const IMesh> mesh,
-         const std::vector<std::shared_ptr<const IZoneDescriptor>>& integration_zone_list,
-         std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
-         const FunctionSymbolId& function_id) -> std::shared_ptr<const IDiscreteFunction> {
-        return DiscreteFunctionIntegrator{mesh, integration_zone_list, quadrature_descriptor, function_id}.integrate();
-      }
+                              [](std::shared_ptr<const IMesh> mesh,
+                                 const std::vector<std::shared_ptr<const IZoneDescriptor>>& integration_zone_list,
+                                 std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
+                                 const FunctionSymbolId& function_id)
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                return std::make_shared<DiscreteFunctionVariant>(
+                                  DiscreteFunctionIntegrator{mesh, integration_zone_list, quadrature_descriptor,
+                                                             function_id}
+                                    .integrate());
+                              }
 
-      ));
+                              ));
 
   this->_addBuiltinFunction("integrate",
                             std::function(
 
                               [](std::shared_ptr<const IMesh> mesh,
                                  std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
-                                 const FunctionSymbolId& function_id) -> std::shared_ptr<const IDiscreteFunction> {
-                                return DiscreteFunctionIntegrator{mesh, quadrature_descriptor, function_id}.integrate();
+                                 const FunctionSymbolId& function_id)
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                return std::make_shared<DiscreteFunctionVariant>(
+                                  DiscreteFunctionIntegrator{mesh, quadrature_descriptor, function_id}.integrate());
                               }
 
                               ));
@@ -151,21 +157,22 @@ SchemeModule::SchemeModule()
                                  const std::vector<std::shared_ptr<const IZoneDescriptor>>& interpolation_zone_list,
                                  std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
                                  const std::vector<FunctionSymbolId>& function_id_list)
-                                -> std::shared_ptr<const IDiscreteFunction> {
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
                                 switch (discrete_function_descriptor->type()) {
                                 case DiscreteFunctionType::P0: {
                                   if (function_id_list.size() != 1) {
                                     throw NormalError("invalid function descriptor type");
                                   }
-                                  return DiscreteFunctionInterpoler{mesh, interpolation_zone_list,
-                                                                    discrete_function_descriptor, function_id_list[0]}
-                                    .interpolate();
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionInterpoler{mesh, interpolation_zone_list,
+                                                               discrete_function_descriptor, function_id_list[0]}
+                                      .interpolate());
                                 }
                                 case DiscreteFunctionType::P0Vector: {
-                                  return DiscreteFunctionVectorInterpoler{mesh, interpolation_zone_list,
-                                                                          discrete_function_descriptor,
-                                                                          function_id_list}
-                                    .interpolate();
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionVectorInterpoler{mesh, interpolation_zone_list,
+                                                                     discrete_function_descriptor, function_id_list}
+                                      .interpolate());
                                 }
                                 default: {
                                   throw NormalError("invalid function descriptor type");
@@ -175,30 +182,35 @@ SchemeModule::SchemeModule()
 
                               ));
 
-  this->_addBuiltinFunction(
-    "interpolate",
-    std::function(
-
-      [](std::shared_ptr<const IMesh> mesh,
-         std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
-         const std::vector<FunctionSymbolId>& function_id_list) -> std::shared_ptr<const IDiscreteFunction> {
-        switch (discrete_function_descriptor->type()) {
-        case DiscreteFunctionType::P0: {
-          if (function_id_list.size() != 1) {
-            throw NormalError("invalid function descriptor type");
-          }
-          return DiscreteFunctionInterpoler{mesh, discrete_function_descriptor, function_id_list[0]}.interpolate();
-        }
-        case DiscreteFunctionType::P0Vector: {
-          return DiscreteFunctionVectorInterpoler{mesh, discrete_function_descriptor, function_id_list}.interpolate();
-        }
-        default: {
-          throw NormalError("invalid function descriptor type");
-        }
-        }
-      }
-
-      ));
+  this->_addBuiltinFunction("interpolate",
+                            std::function(
+
+                              [](std::shared_ptr<const IMesh> mesh,
+                                 std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
+                                 const std::vector<FunctionSymbolId>& function_id_list)
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                switch (discrete_function_descriptor->type()) {
+                                case DiscreteFunctionType::P0: {
+                                  if (function_id_list.size() != 1) {
+                                    throw NormalError("invalid function descriptor type");
+                                  }
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionInterpoler{mesh, discrete_function_descriptor, function_id_list[0]}
+                                      .interpolate());
+                                }
+                                case DiscreteFunctionType::P0Vector: {
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionVectorInterpoler{mesh, discrete_function_descriptor,
+                                                                     function_id_list}
+                                      .interpolate());
+                                }
+                                default: {
+                                  throw NormalError("invalid function descriptor type");
+                                }
+                                }
+                              }
+
+                              ));
 
   this->_addBuiltinFunction("randomizeMesh",
                             std::function(
@@ -290,10 +302,10 @@ SchemeModule::SchemeModule()
 
   this->_addBuiltinFunction("glace_fluxes", std::function(
 
-                                              [](const std::shared_ptr<const IDiscreteFunction>& rho,
-                                                 const std::shared_ptr<const IDiscreteFunction>& u,
-                                                 const std::shared_ptr<const IDiscreteFunction>& c,
-                                                 const std::shared_ptr<const IDiscreteFunction>& p,
+                                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                                 const std::shared_ptr<const DiscreteFunctionVariant>& c,
+                                                 const std::shared_ptr<const DiscreteFunctionVariant>& p,
                                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                                    bc_descriptor_list)
                                                 -> std::tuple<std::shared_ptr<const ItemValueVariant>,
@@ -309,17 +321,17 @@ SchemeModule::SchemeModule()
   this->_addBuiltinFunction("glace_solver",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction>& rho,
-                                 const std::shared_ptr<const IDiscreteFunction>& u,
-                                 const std::shared_ptr<const IDiscreteFunction>& E,
-                                 const std::shared_ptr<const IDiscreteFunction>& c,
-                                 const std::shared_ptr<const IDiscreteFunction>& p,
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& c,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& p,
                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                    bc_descriptor_list,
-                                 const double& dt)
-                                -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
-                                              std::shared_ptr<const IDiscreteFunction>,
-                                              std::shared_ptr<const IDiscreteFunction>> {
+                                 const double& dt) -> std::tuple<std::shared_ptr<const IMesh>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
                                 return AcousticSolverHandler{getCommonMesh({rho, u, E, c, p})}
                                   .solver()
                                   .apply(AcousticSolverHandler::SolverType::Glace, dt, rho, u, E, c, p,
@@ -331,10 +343,10 @@ SchemeModule::SchemeModule()
   this->_addBuiltinFunction("eucclhyd_fluxes",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction>& rho,
-                                 const std::shared_ptr<const IDiscreteFunction>& u,
-                                 const std::shared_ptr<const IDiscreteFunction>& c,
-                                 const std::shared_ptr<const IDiscreteFunction>& p,
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& c,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& p,
                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                    bc_descriptor_list)
                                 -> std::tuple<std::shared_ptr<const ItemValueVariant>,
@@ -350,17 +362,17 @@ SchemeModule::SchemeModule()
   this->_addBuiltinFunction("eucclhyd_solver",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction>& rho,
-                                 const std::shared_ptr<const IDiscreteFunction>& u,
-                                 const std::shared_ptr<const IDiscreteFunction>& E,
-                                 const std::shared_ptr<const IDiscreteFunction>& c,
-                                 const std::shared_ptr<const IDiscreteFunction>& p,
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& c,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& p,
                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                    bc_descriptor_list,
-                                 const double& dt)
-                                -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
-                                              std::shared_ptr<const IDiscreteFunction>,
-                                              std::shared_ptr<const IDiscreteFunction>> {
+                                 const double& dt) -> std::tuple<std::shared_ptr<const IMesh>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
                                 return AcousticSolverHandler{getCommonMesh({rho, u, E, c, p})}
                                   .solver()
                                   .apply(AcousticSolverHandler::SolverType::Eucclhyd, dt, rho, u, E, c, p,
@@ -372,15 +384,15 @@ SchemeModule::SchemeModule()
   this->_addBuiltinFunction("apply_acoustic_fluxes",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction>& rho,            //
-                                 const std::shared_ptr<const IDiscreteFunction>& u,              //
-                                 const std::shared_ptr<const IDiscreteFunction>& E,              //
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
                                  const std::shared_ptr<const ItemValueVariant>& ur,              //
                                  const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
-                                 const double& dt)
-                                -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
-                                              std::shared_ptr<const IDiscreteFunction>,
-                                              std::shared_ptr<const IDiscreteFunction>> {
+                                 const double& dt) -> std::tuple<std::shared_ptr<const IMesh>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
                                 return AcousticSolverHandler{getCommonMesh({rho, u, E})}   //
                                   .solver()
                                   .apply_fluxes(dt, rho, u, E, ur, Fjr);
@@ -392,58 +404,59 @@ SchemeModule::SchemeModule()
                             std::function(
 
                               [](const std::shared_ptr<const IMesh>& mesh,
-                                 const std::shared_ptr<const IDiscreteFunction>& v)
-                                -> std::shared_ptr<const IDiscreteFunction> { return shallowCopy(mesh, v); }
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& v)
+                                -> std::shared_ptr<const DiscreteFunctionVariant> { return shallowCopy(mesh, v); }
 
                               ));
 
-  this->_addBuiltinFunction("acoustic_dt",
-                            std::function(
-
-                              [](const std::shared_ptr<const IDiscreteFunction>& c) -> double { return acoustic_dt(c); }
+  this->_addBuiltinFunction("acoustic_dt", std::function(
 
-                              ));
+                                             [](const std::shared_ptr<const DiscreteFunctionVariant>& c) -> double {
+                                               return acoustic_dt(c);
+                                             }
 
-  this
-    ->_addBuiltinFunction("cell_volume",
-                          std::function(
+                                             ));
 
-                            [](const std::shared_ptr<const IMesh>& i_mesh) -> std::shared_ptr<const IDiscreteFunction> {
-                              switch (i_mesh->dimension()) {
-                              case 1: {
-                                constexpr size_t Dimension = 1;
-                                using MeshType             = Mesh<Connectivity<Dimension>>;
-                                std::shared_ptr<const MeshType> mesh =
-                                  std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(i_mesh);
+  this->_addBuiltinFunction("cell_volume",
+                            std::function(
 
-                                return std::make_shared<const DiscreteFunctionP0<
-                                  Dimension, double>>(mesh, copy(MeshDataManager::instance().getMeshData(*mesh).Vj()));
-                              }
-                              case 2: {
-                                constexpr size_t Dimension = 2;
-                                using MeshType             = Mesh<Connectivity<Dimension>>;
-                                std::shared_ptr<const MeshType> mesh =
-                                  std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(i_mesh);
-
-                                return std::make_shared<const DiscreteFunctionP0<
-                                  Dimension, double>>(mesh, copy(MeshDataManager::instance().getMeshData(*mesh).Vj()));
-                              }
-                              case 3: {
-                                constexpr size_t Dimension = 3;
-                                using MeshType             = Mesh<Connectivity<Dimension>>;
-                                std::shared_ptr<const MeshType> mesh =
-                                  std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(i_mesh);
-
-                                return std::make_shared<const DiscreteFunctionP0<
-                                  Dimension, double>>(mesh, copy(MeshDataManager::instance().getMeshData(*mesh).Vj()));
-                              }
-                              default: {
-                                throw UnexpectedError("invalid mesh dimension");
-                              }
+                              [](const std::shared_ptr<const IMesh>& i_mesh)
+                                -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                switch (i_mesh->dimension()) {
+                                case 1: {
+                                  constexpr size_t Dimension = 1;
+                                  using MeshType             = Mesh<Connectivity<Dimension>>;
+                                  std::shared_ptr<const MeshType> mesh =
+                                    std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(i_mesh);
+
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionP0(mesh, MeshDataManager::instance().getMeshData(*mesh).Vj()));
+                                }
+                                case 2: {
+                                  constexpr size_t Dimension = 2;
+                                  using MeshType             = Mesh<Connectivity<Dimension>>;
+                                  std::shared_ptr<const MeshType> mesh =
+                                    std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(i_mesh);
+
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionP0(mesh, MeshDataManager::instance().getMeshData(*mesh).Vj()));
+                                }
+                                case 3: {
+                                  constexpr size_t Dimension = 3;
+                                  using MeshType             = Mesh<Connectivity<Dimension>>;
+                                  std::shared_ptr<const MeshType> mesh =
+                                    std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(i_mesh);
+
+                                  return std::make_shared<DiscreteFunctionVariant>(
+                                    DiscreteFunctionP0(mesh, MeshDataManager::instance().getMeshData(*mesh).Vj()));
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
                               }
-                            }
 
-                            ));
+                              ));
 
   MathFunctionRegisterForVh{*this};
 }
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index 6c1f0324aa2858437ec8d7f49434c2d8ac718a45..d56a5ec74069bd23d8169f25ff8a829c7c4a53ff 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -10,9 +10,9 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary_condition");
 
-class IDiscreteFunction;
+class DiscreteFunctionVariant;
 template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IDiscreteFunction>> =
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const DiscreteFunctionVariant>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh");
 
 class IDiscreteFunctionDescriptor;
diff --git a/src/language/modules/UnaryOperatorRegisterForVh.cpp b/src/language/modules/UnaryOperatorRegisterForVh.cpp
index b1dc85aa9dd11be2f5a6d159d8f1a70254d68c7b..e2e9c2db9b72a9a687806d24dee836cfd37a22e8 100644
--- a/src/language/modules/UnaryOperatorRegisterForVh.cpp
+++ b/src/language/modules/UnaryOperatorRegisterForVh.cpp
@@ -3,10 +3,9 @@
 #include <language/modules/SchemeModule.hpp>
 #include <language/utils/DataHandler.hpp>
 #include <language/utils/DataVariant.hpp>
-#include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
+#include <language/utils/EmbeddedDiscreteFunctionOperators.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <language/utils/UnaryOperatorProcessorBuilder.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 
 void
 UnaryOperatorRegisterForVh::_register_unary_minus()
@@ -14,8 +13,9 @@ 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>>>());
+    std::make_shared<
+      UnaryOperatorProcessorBuilder<language::unary_minus, std::shared_ptr<const DiscreteFunctionVariant>,
+                                    std::shared_ptr<const DiscreteFunctionVariant>>>());
 }
 
 UnaryOperatorRegisterForVh::UnaryOperatorRegisterForVh()
diff --git a/src/language/modules/WriterModule.cpp b/src/language/modules/WriterModule.cpp
index 6523d56a93d4fe0b4f953fb6065cd4a15be7330c..aa479c0eb94005d59000d98dd460d8fedb89ab21 100644
--- a/src/language/modules/WriterModule.cpp
+++ b/src/language/modules/WriterModule.cpp
@@ -12,9 +12,7 @@
 #include <output/NamedDiscreteFunction.hpp>
 #include <output/NamedItemValueVariant.hpp>
 #include <output/VTKWriter.hpp>
-#include <scheme/DiscreteFunctionP0.hpp>
-#include <scheme/IDiscreteFunction.hpp>
-#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 
 WriterModule::WriterModule()
 {
@@ -75,7 +73,7 @@ WriterModule::WriterModule()
 
   this->_addBuiltinFunction("name_output", std::function(
 
-                                             [](std::shared_ptr<const IDiscreteFunction> discrete_function,
+                                             [](std::shared_ptr<const DiscreteFunctionVariant> discrete_function,
                                                 const std::string& name) -> std::shared_ptr<const INamedDiscreteData> {
                                                return std::make_shared<const NamedDiscreteFunction>(discrete_function,
                                                                                                     name);
diff --git a/src/language/modules/WriterModule.hpp b/src/language/modules/WriterModule.hpp
index f61eafb0e4847c248e9def011bf269b3b9a4022e..97bef5a78ac6f90cff1af303bed901c4c46c665a 100644
--- a/src/language/modules/WriterModule.hpp
+++ b/src/language/modules/WriterModule.hpp
@@ -7,7 +7,6 @@
 
 class OutputNamedItemValueSet;
 class INamedDiscreteData;
-class IDiscreteFunction;
 
 #include <string>
 
diff --git a/src/language/utils/BuiltinFunctionEmbedder.hpp b/src/language/utils/BuiltinFunctionEmbedder.hpp
index c3f015c5b61fbdfda66ee78bbf928fe3a24f3de0..460ec3e866be479633e5bf9a9a416c1f84fc09e6 100644
--- a/src/language/utils/BuiltinFunctionEmbedder.hpp
+++ b/src/language/utils/BuiltinFunctionEmbedder.hpp
@@ -40,19 +40,25 @@ template <typename FX, typename... Args>
 class BuiltinFunctionEmbedderBase<FX(Args...)> : public IBuiltinFunctionEmbedder
 {
  protected:
-  template <size_t I>
-  PUGS_INLINE void constexpr _check_value() const
+  template <typename ValueT>
+  PUGS_INLINE void constexpr _check_value_type() const
   {
-    using ValueN_T = std::tuple_element_t<I, FX>;
-    if constexpr (std::is_lvalue_reference_v<ValueN_T>) {
-      static_assert(std::is_const_v<std::remove_reference_t<ValueN_T>>,
+    if constexpr (std::is_lvalue_reference_v<ValueT>) {
+      static_assert(std::is_const_v<std::remove_reference_t<ValueT>>,
                     "builtin function return values are non mutable use 'const' when passing references");
     }
 
-    if constexpr (is_std_ptr_v<ValueN_T>) {
-      static_assert(std::is_const_v<typename ValueN_T::element_type>,
+    if constexpr (is_std_ptr_v<ValueT>) {
+      static_assert(std::is_const_v<typename ValueT::element_type>,
                     "builtin function return values are non mutable. For instance use std::shared_ptr<const T>");
     }
+  }
+
+  template <size_t I>
+  PUGS_INLINE void constexpr _check_value() const
+  {
+    using ValueN_T = std::tuple_element_t<I, FX>;
+    _check_value_type<ValueN_T>();
 
     if (ast_node_data_type_from<std::remove_cv_t<std::remove_reference_t<ValueN_T>>> == ASTNodeDataType::undefined_t) {
       throw std::invalid_argument(std::string{"cannot bind C++ to language.\nnote: return value number "} +
@@ -79,6 +85,7 @@ class BuiltinFunctionEmbedderBase<FX(Args...)> : public IBuiltinFunctionEmbedder
           std::string{"cannot bind C++ to language.\nnote: return value has no associated language type: "} +
           demangle<FX>());
       }
+      _check_value_type<FX>();
     }
   }
 
@@ -99,8 +106,7 @@ class BuiltinFunctionEmbedderBase<FX(Args...)> : public IBuiltinFunctionEmbedder
   }
 
   template <size_t... I>
-  PUGS_INLINE std::vector<std::shared_ptr<const ASTNodeDataType>>
-  _getCompoundDataTypes(std::index_sequence<I...>) const
+  PUGS_INLINE std::vector<std::shared_ptr<const ASTNodeDataType>> _getCompoundDataTypes(std::index_sequence<I...>) const
   {
     std::vector<std::shared_ptr<const ASTNodeDataType>> compound_type_list;
     (compound_type_list.push_back(std::make_shared<ASTNodeDataType>(this->_getOneElementDataType<FX, I>())), ...);
@@ -266,8 +272,7 @@ class BuiltinFunctionEmbedder<FX(Args...)> : public BuiltinFunctionEmbedderBase<
   }
 
   template <size_t... I>
-  PUGS_INLINE std::vector<ASTNodeDataType>
-  _getParameterDataTypes(std::index_sequence<I...>) const
+  PUGS_INLINE std::vector<ASTNodeDataType> _getParameterDataTypes(std::index_sequence<I...>) const
   {
     std::vector<ASTNodeDataType> parameter_type_list;
     (parameter_type_list.push_back(this->template _getOneElementDataType<ArgsTuple, I>()), ...);
diff --git a/src/language/utils/CMakeLists.txt b/src/language/utils/CMakeLists.txt
index 0fe1c798ba77d98b4453ded4d4c3334c0d2199e5..a81ffa8aecdf7af0085296b181f2b3ec4d63238f 100644
--- a/src/language/utils/CMakeLists.txt
+++ b/src/language/utils/CMakeLists.txt
@@ -23,9 +23,8 @@ add_library(PugsLanguageUtils
   BuiltinFunctionEmbedderUtils.cpp
   DataVariant.cpp
   EmbeddedData.cpp
-  EmbeddedIDiscreteFunctionMathFunctions.cpp
-  EmbeddedIDiscreteFunctionOperators.cpp
-  EmbeddedIDiscreteFunctionUtils.cpp
+  EmbeddedDiscreteFunctionMathFunctions.cpp
+  EmbeddedDiscreteFunctionOperators.cpp
   FunctionSymbolId.cpp
   IncDecOperatorRegisterForN.cpp
   IncDecOperatorRegisterForZ.cpp
diff --git a/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp b/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3d820c561f623d01967c6a4d947eaa91a531e183
--- /dev/null
+++ b/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.cpp
@@ -0,0 +1,647 @@
+#include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
+
+#include <language/utils/EmbeddedDiscreteFunctionUtils.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+
+#include <utils/Demangle.hpp>
+
+#define DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(FUNCTION, ARG)                                      \
+  return std::visit(                                                                             \
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {                  \
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;                    \
+      if constexpr (std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<1, const double>> or \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<2, const double>> or \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<3, const double>>) { \
+        return std::make_shared<DiscreteFunctionVariant>(FUNCTION(discrete_function));           \
+      } else {                                                                                   \
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(ARG));               \
+      }                                                                                          \
+    },                                                                                           \
+    ARG->discreteFunction());
+
+#define DISCRETE_VH_TO_R_CALL(FUNCTION, ARG)                                                     \
+  return std::visit(                                                                             \
+    [&](auto&& discrete_function) -> double {                                                    \
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;                    \
+      if constexpr (std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<1, const double>> or \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<2, const double>> or \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<3, const double>>) { \
+        return FUNCTION(discrete_function);                                                      \
+      } else {                                                                                   \
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(ARG));               \
+      }                                                                                          \
+    },                                                                                           \
+    ARG->discreteFunction());
+
+#define DISCRETE_VH_VH_TO_VH_REAL_FUNCTION_CALL(FUNCTION, ARG0, ARG1)                       \
+  if (not hasSameMesh({ARG0, ARG1})) {                                                      \
+    throw NormalError("operands are defined on different meshes");                          \
+  }                                                                                         \
+  return std::visit(                                                                        \
+    [&](auto&& f, auto&& g) -> std::shared_ptr<DiscreteFunctionVariant> {                   \
+      using TypeOfF = std::decay_t<decltype(f)>;                                            \
+      using TypeOfG = std::decay_t<decltype(g)>;                                            \
+      if constexpr (std::is_same_v<TypeOfF, DiscreteFunctionP0<1, const double>> or         \
+                    std::is_same_v<TypeOfF, DiscreteFunctionP0<2, const double>> or         \
+                    std::is_same_v<TypeOfF, DiscreteFunctionP0<3, const double>>) {         \
+        if constexpr (std::is_same_v<TypeOfF, TypeOfG>) {                                   \
+          return std::make_shared<DiscreteFunctionVariant>(FUNCTION(f, g));                 \
+        } else {                                                                            \
+          throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g)); \
+        }                                                                                   \
+      } else {                                                                              \
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));   \
+      }                                                                                     \
+    },                                                                                      \
+    ARG0->discreteFunction(), ARG1->discreteFunction());
+
+#define DISCRETE_R_VH_TO_VH_REAL_FUNCTION_CALL(FUNCTION, ARG0, ARG1)                                         \
+  return std::visit(                                                                                         \
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {                              \
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;                                \
+      if constexpr (std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<1, const double>> or             \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<2, const double>> or             \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<3, const double>>) {             \
+        return std::make_shared<DiscreteFunctionVariant>(FUNCTION(ARG0, discrete_function));                 \
+      } else {                                                                                               \
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(ARG0, discrete_function)); \
+      }                                                                                                      \
+    },                                                                                                       \
+    ARG1->discreteFunction());
+
+#define DISCRETE_VH_R_TO_VH_REAL_FUNCTION_CALL(FUNCTION, ARG0, ARG1)                                         \
+  return std::visit(                                                                                         \
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {                              \
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;                                \
+      if constexpr (std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<1, const double>> or             \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<2, const double>> or             \
+                    std::is_same_v<DiscreteFunctionType, DiscreteFunctionP0<3, const double>>) {             \
+        return std::make_shared<DiscreteFunctionVariant>(FUNCTION(discrete_function, ARG1));                 \
+      } else {                                                                                               \
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(discrete_function, ARG1)); \
+      }                                                                                                      \
+    },                                                                                                       \
+    ARG0->discreteFunction());
+
+std::shared_ptr<const DiscreteFunctionVariant>
+sqrt(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(sqrt, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+abs(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(abs, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+sin(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(sin, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+cos(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(cos, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+tan(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(tan, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+asin(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(asin, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+acos(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(acos, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+atan(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(atan, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+atan2(const std::shared_ptr<const DiscreteFunctionVariant>& f_v,
+      const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  DISCRETE_VH_VH_TO_VH_REAL_FUNCTION_CALL(atan2, f_v, g_v);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+atan2(const double a, const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_R_VH_TO_VH_REAL_FUNCTION_CALL(atan2, a, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+atan2(const std::shared_ptr<const DiscreteFunctionVariant>& f, const double a)
+{
+  DISCRETE_VH_R_TO_VH_REAL_FUNCTION_CALL(atan2, f, a);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+sinh(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(sinh, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+cosh(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(cosh, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+tanh(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(tanh, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+asinh(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(asinh, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+acosh(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(acosh, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+atanh(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(atanh, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+exp(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(exp, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+log(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(log, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+pow(const std::shared_ptr<const DiscreteFunctionVariant>& f, const std::shared_ptr<const DiscreteFunctionVariant>& g)
+{
+  DISCRETE_VH_VH_TO_VH_REAL_FUNCTION_CALL(pow, f, g);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+pow(const double a, const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_R_VH_TO_VH_REAL_FUNCTION_CALL(pow, a, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+pow(const std::shared_ptr<const DiscreteFunctionVariant>& f, const double a)
+{
+  DISCRETE_VH_R_TO_VH_REAL_FUNCTION_CALL(pow, f, a);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+dot(const std::shared_ptr<const DiscreteFunctionVariant>& f_v,
+    const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  if (not hasSameMesh({f_v, g_v})) {
+    throw NormalError("operands are defined on different meshes");
+  }
+
+  return std::visit(
+    [&](auto&& f, auto&& g) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using TypeOfF = std::decay_t<decltype(f)>;
+      using TypeOfG = std::decay_t<decltype(g)>;
+      if constexpr (not std::is_same_v<TypeOfF, TypeOfG>) {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
+      } else {
+        using DataType = std::decay_t<typename TypeOfF::data_type>;
+        if constexpr (is_discrete_function_P0_v<TypeOfF>) {
+          if constexpr (is_tiny_vector_v<DataType>) {
+            return std::make_shared<DiscreteFunctionVariant>(dot(f, g));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+          }
+        } else if constexpr (is_discrete_function_P0_vector_v<TypeOfF>) {
+          if (f.size() == g.size()) {
+            return std::make_shared<DiscreteFunctionVariant>(dot(f, g));
+          } else {
+            throw NormalError("operands have different dimension");
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+        }
+      }
+    },
+    f_v->discreteFunction(), g_v->discreteFunction());
+}
+
+template <size_t VectorDimension>
+std::shared_ptr<const DiscreteFunctionVariant>
+dot(const std::shared_ptr<const DiscreteFunctionVariant>& f, const TinyVector<VectorDimension>& a)
+{
+  return std::visit(
+    [&](auto&& discrete_function0) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using DiscreteFunction0Type = std::decay_t<decltype(discrete_function0)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunction0Type>) {
+        using DataType = std::decay_t<typename DiscreteFunction0Type::data_type>;
+        if constexpr (is_tiny_vector_v<DataType>) {
+          if constexpr (std::is_same_v<DataType, TinyVector<VectorDimension>>) {
+            return std::make_shared<DiscreteFunctionVariant>(dot(discrete_function0, a));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+      }
+    },
+    f->discreteFunction());
+}
+
+template <size_t VectorDimension>
+std::shared_ptr<const DiscreteFunctionVariant>
+dot(const TinyVector<VectorDimension>& a, const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  return std::visit(
+    [&](auto&& discrete_function0) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using DiscreteFunction0Type = std::decay_t<decltype(discrete_function0)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunction0Type>) {
+        using DataType = std::decay_t<typename DiscreteFunction0Type::data_type>;
+        if constexpr (is_tiny_vector_v<DataType>) {
+          if constexpr (std::is_same_v<DataType, TinyVector<VectorDimension>>) {
+            return std::make_shared<DiscreteFunctionVariant>(dot(a, discrete_function0));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+      }
+    },
+    f->discreteFunction());
+}
+
+template std::shared_ptr<const DiscreteFunctionVariant> dot(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                            const TinyVector<1>&);
+
+template std::shared_ptr<const DiscreteFunctionVariant> dot(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                            const TinyVector<2>&);
+
+template std::shared_ptr<const DiscreteFunctionVariant> dot(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                            const TinyVector<3>&);
+
+template std::shared_ptr<const DiscreteFunctionVariant> dot(const TinyVector<1>&,
+                                                            const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template std::shared_ptr<const DiscreteFunctionVariant> dot(const TinyVector<2>&,
+                                                            const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template std::shared_ptr<const DiscreteFunctionVariant> dot(const TinyVector<3>&,
+                                                            const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant>
+det(const std::shared_ptr<const DiscreteFunctionVariant>& A)
+{
+  return std::visit(
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+        if constexpr (is_tiny_matrix_v<std::decay_t<typename DiscreteFunctionType::data_type>>) {
+          if constexpr (DiscreteFunctionType::data_type::NumberOfRows ==
+                        DiscreteFunctionType::data_type::NumberOfColumns) {
+            return std::make_shared<DiscreteFunctionVariant>(det(discrete_function));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+      }
+    },
+    A->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+trace(const std::shared_ptr<const DiscreteFunctionVariant>& A)
+{
+  return std::visit(
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+        if constexpr (is_tiny_matrix_v<std::decay_t<typename DiscreteFunctionType::data_type>>) {
+          if constexpr (DiscreteFunctionType::data_type::NumberOfRows ==
+                        DiscreteFunctionType::data_type::NumberOfColumns) {
+            return std::make_shared<DiscreteFunctionVariant>(trace(discrete_function));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+      }
+    },
+    A->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+inverse(const std::shared_ptr<const DiscreteFunctionVariant>& A)
+{
+  return std::visit(
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+        if constexpr (is_tiny_matrix_v<std::decay_t<typename DiscreteFunctionType::data_type>>) {
+          if constexpr (DiscreteFunctionType::data_type::NumberOfRows ==
+                        DiscreteFunctionType::data_type::NumberOfColumns) {
+            return std::make_shared<DiscreteFunctionVariant>(inverse(discrete_function));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+      }
+    },
+    A->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+transpose(const std::shared_ptr<const DiscreteFunctionVariant>& A)
+{
+  return std::visit(
+    [&](auto&& discrete_function) -> std::shared_ptr<DiscreteFunctionVariant> {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+        if constexpr (is_tiny_matrix_v<std::decay_t<typename DiscreteFunctionType::data_type>>) {
+          if constexpr (DiscreteFunctionType::data_type::NumberOfRows ==
+                        DiscreteFunctionType::data_type::NumberOfColumns) {
+            return std::make_shared<DiscreteFunctionVariant>(transpose(discrete_function));
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+          }
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(A));
+      }
+    },
+    A->discreteFunction());
+}
+
+double
+min(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_R_CALL(min, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+min(const std::shared_ptr<const DiscreteFunctionVariant>& f, const std::shared_ptr<const DiscreteFunctionVariant>& g)
+{
+  DISCRETE_VH_VH_TO_VH_REAL_FUNCTION_CALL(min, f, g);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+min(const double a, const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_R_VH_TO_VH_REAL_FUNCTION_CALL(min, a, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+min(const std::shared_ptr<const DiscreteFunctionVariant>& f, const double a)
+{
+  DISCRETE_VH_R_TO_VH_REAL_FUNCTION_CALL(min, f, a);
+}
+
+double
+max(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_VH_TO_R_CALL(max, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+max(const std::shared_ptr<const DiscreteFunctionVariant>& f, const std::shared_ptr<const DiscreteFunctionVariant>& g)
+{
+  DISCRETE_VH_VH_TO_VH_REAL_FUNCTION_CALL(max, f, g);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+max(const double a, const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  DISCRETE_R_VH_TO_VH_REAL_FUNCTION_CALL(max, a, f);
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+max(const std::shared_ptr<const DiscreteFunctionVariant>& f, const double a)
+{
+  DISCRETE_VH_R_TO_VH_REAL_FUNCTION_CALL(max, f, a);
+}
+
+template <typename ValueT>
+ValueT
+sum_of(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  ValueT value;
+  std::visit(
+    [&](auto&& discrete_function) {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+        using DataType = std::decay_t<typename DiscreteFunctionType::data_type>;
+        if constexpr (std::is_same_v<ValueT, DataType>) {
+          value = sum(discrete_function);
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+      }
+    },
+    f->discreteFunction());
+
+  return value;
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+sum_of_Vh_components(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  return std::visit(
+    [&](auto&& discrete_function) -> std::shared_ptr<const DiscreteFunctionVariant> {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionType>) {
+        using DataType = std::decay_t<typename DiscreteFunctionType::data_type>;
+        static_assert(std::is_same_v<DataType, double>);
+        return std::make_shared<DiscreteFunctionVariant>(sumOfComponents(discrete_function));
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+      }
+    },
+    f->discreteFunction());
+}
+
+template <size_t Dimension>
+void
+vectorize_to(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_list,
+             const Mesh<Connectivity<Dimension>>& mesh,
+             DiscreteFunctionP0Vector<Dimension, double>& discrete_vector_function)
+{
+  if (hasSameMesh(discrete_function_list)) {
+    for (size_t i_discrete_function = 0; i_discrete_function < discrete_function_list.size(); ++i_discrete_function) {
+      std::visit(
+        [&](auto&& discrete_function) {
+          using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+          if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+            using DataType = std::remove_const_t<typename DiscreteFunctionType::data_type>;
+            if constexpr (std::is_same_v<DataType, double> and
+                          (Dimension == DiscreteFunctionType::MeshType::Dimension)) {
+              const auto& connectivity = mesh.connectivity();
+              parallel_for(
+                connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                  discrete_vector_function[cell_id][i_discrete_function] = discrete_function[cell_id];
+                });
+            } else {
+              throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(discrete_function));
+            }
+          } else {
+            throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(discrete_function));
+          }
+        },
+        discrete_function_list[i_discrete_function]->discreteFunction());
+    }
+
+  } else {
+    throw NormalError("discrete functions are not defined on the same mesh");
+  }
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+vectorize(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_list)
+{
+  if (hasSameMesh(discrete_function_list)) {
+    std::shared_ptr p_i_mesh = getCommonMesh(discrete_function_list);
+    Assert(p_i_mesh.use_count() > 0);
+
+    switch (p_i_mesh->dimension()) {
+    case 1: {
+      constexpr size_t Dimension       = 1;
+      using DiscreteFunctionVectorType = DiscreteFunctionP0Vector<Dimension, double>;
+      std::shared_ptr<const Mesh<Connectivity<Dimension>>> p_mesh =
+        std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(p_i_mesh);
+
+      DiscreteFunctionVectorType vector_function(p_mesh, discrete_function_list.size());
+      vectorize_to(discrete_function_list, *p_mesh, vector_function);
+
+      return std::make_shared<DiscreteFunctionVariant>(vector_function);
+    }
+    case 2: {
+      constexpr size_t Dimension       = 2;
+      using DiscreteFunctionVectorType = DiscreteFunctionP0Vector<Dimension, double>;
+      std::shared_ptr<const Mesh<Connectivity<Dimension>>> p_mesh =
+        std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(p_i_mesh);
+
+      DiscreteFunctionVectorType vector_function(p_mesh, discrete_function_list.size());
+      vectorize_to(discrete_function_list, *p_mesh, vector_function);
+
+      return std::make_shared<DiscreteFunctionVariant>(vector_function);
+    }
+    case 3: {
+      constexpr size_t Dimension       = 3;
+      using DiscreteFunctionVectorType = DiscreteFunctionP0Vector<Dimension, double>;
+      std::shared_ptr<const Mesh<Connectivity<Dimension>>> p_mesh =
+        std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(p_i_mesh);
+
+      DiscreteFunctionVectorType vector_function(p_mesh, discrete_function_list.size());
+      vectorize_to(discrete_function_list, *p_mesh, vector_function);
+
+      return std::make_shared<DiscreteFunctionVariant>(vector_function);
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("invalid mesh dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+  } else {
+    throw NormalError("discrete functions are not defined on the same mesh");
+  }
+}
+
+template double sum_of<double>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyVector<1> sum_of<TinyVector<1>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyVector<2> sum_of<TinyVector<2>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyVector<3> sum_of<TinyVector<3>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyMatrix<1> sum_of<TinyMatrix<1>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyMatrix<2> sum_of<TinyMatrix<2>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyMatrix<3> sum_of<TinyMatrix<3>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template <typename ValueT>
+ValueT
+integral_of(const std::shared_ptr<const DiscreteFunctionVariant>& f)
+{
+  return std::visit(
+    [&](auto&& discrete_function) -> ValueT {
+      using DiscreteFunctionType = std::decay_t<decltype(discrete_function)>;
+      if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
+        using DataType = std::decay_t<typename DiscreteFunctionType::data_type>;
+        if constexpr (std::is_same_v<ValueT, DataType>) {
+          return integrate(discrete_function);
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::invalidOperandType(f));
+      }
+    },
+    f->discreteFunction());
+}
+
+template double integral_of<double>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyVector<1> integral_of<TinyVector<1>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyVector<2> integral_of<TinyVector<2>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyVector<3> integral_of<TinyVector<3>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyMatrix<1> integral_of<TinyMatrix<1>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyMatrix<2> integral_of<TinyMatrix<2>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template TinyMatrix<3> integral_of<TinyMatrix<3>>(const std::shared_ptr<const DiscreteFunctionVariant>&);
diff --git a/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp b/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..66b784921da372cc7dfa2d616b94d6fbfe403ee3
--- /dev/null
+++ b/src/language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp
@@ -0,0 +1,110 @@
+#ifndef EMBEDDED_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
+#define EMBEDDED_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <memory>
+
+class DiscreteFunctionVariant;
+
+std::shared_ptr<const DiscreteFunctionVariant> sqrt(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> sqrt(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> abs(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> sin(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> cos(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> tan(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> asin(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> acos(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> atan(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> atan2(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                     const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> atan2(const double,
+                                                     const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> atan2(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                     const double);
+
+std::shared_ptr<const DiscreteFunctionVariant> sinh(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> cosh(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> tanh(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> asinh(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> acosh(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> atanh(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> exp(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> log(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> pow(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                   const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> pow(const double, const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> pow(const std::shared_ptr<const DiscreteFunctionVariant>&, const double);
+
+std::shared_ptr<const DiscreteFunctionVariant> dot(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                   const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template <size_t VectorDimension>
+std::shared_ptr<const DiscreteFunctionVariant> dot(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                   const TinyVector<VectorDimension>&);
+
+template <size_t VectorDimension>
+std::shared_ptr<const DiscreteFunctionVariant> dot(const TinyVector<VectorDimension>&,
+                                                   const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> det(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> trace(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> inverse(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> transpose(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> sum_of_Vh_components(
+  const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> vectorize(
+  const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_list);
+
+double min(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> min(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                   const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> min(const double, const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> min(const std::shared_ptr<const DiscreteFunctionVariant>&, const double);
+
+double max(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> max(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                   const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> max(const double, const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> max(const std::shared_ptr<const DiscreteFunctionVariant>&, const double);
+
+template <typename ValueT>
+ValueT sum_of(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+template <typename ValueT>
+ValueT integral_of(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+#endif   // EMBEDDED_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
diff --git a/src/language/utils/EmbeddedDiscreteFunctionOperators.cpp b/src/language/utils/EmbeddedDiscreteFunctionOperators.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0b47153cf0660abd807774d477e7fcd8ae712b62
--- /dev/null
+++ b/src/language/utils/EmbeddedDiscreteFunctionOperators.cpp
@@ -0,0 +1,671 @@
+#include <language/utils/EmbeddedDiscreteFunctionOperators.hpp>
+
+#include <language/node_processor/BinaryExpressionProcessor.hpp>
+#include <language/node_processor/UnaryExpressionProcessor.hpp>
+#include <language/utils/EmbeddedDiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <utils/Exceptions.hpp>
+
+// unary operators
+
+template <typename UnaryOperatorT, typename DiscreteFunctionT>
+std::shared_ptr<const DiscreteFunctionVariant>
+applyUnaryOperation(const DiscreteFunctionT& f)
+{
+  return std::make_shared<DiscreteFunctionVariant>(UnaryOp<UnaryOperatorT>{}.eval(f));
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyUnaryOperation<language::unary_minus>(f);
+    },
+    f_v->discreteFunction());
+}
+
+// binary operators
+
+template <typename DiscreteFunctionT, typename BinOperatorT>
+std::shared_ptr<const DiscreteFunctionVariant>
+innerCompositionLaw(const DiscreteFunctionT& f, const DiscreteFunctionT& g)
+{
+  using data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
+  if constexpr ((std::is_same_v<language::multiply_op, BinOperatorT> and is_tiny_vector_v<data_type>) or
+                (std::is_same_v<language::divide_op, BinOperatorT> and not std::is_arithmetic_v<data_type>)) {
+    throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
+  } else {
+    if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) {
+      if (f.size() != g.size()) {
+        std::ostringstream error_msg;
+        error_msg << EmbeddedDiscreteFunctionUtils::getOperandTypeName(f) << " spaces have different sizes";
+        throw NormalError(error_msg.str());
+      } else {
+        return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, g));
+      }
+    } else {
+      return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, g));
+    }
+  }
+}
+
+template <typename DiscreteFunctionT1, typename DiscreteFunctionT2, typename BinOperatorT>
+std::shared_ptr<const DiscreteFunctionVariant>
+applyBinaryOperation(const DiscreteFunctionT1& f, const DiscreteFunctionT2& g)
+{
+  using f_data_type = std::decay_t<typename DiscreteFunctionT1::data_type>;
+  using g_data_type = std::decay_t<typename DiscreteFunctionT2::data_type>;
+  if constexpr (std::is_same_v<language::multiply_op, BinOperatorT>) {
+    if constexpr (is_discrete_function_P0_v<DiscreteFunctionT1> and
+                  is_discrete_function_P0_vector_v<DiscreteFunctionT2> and std::is_same_v<double, f_data_type>) {
+      return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, g));
+    } else if constexpr (is_discrete_function_P0_v<DiscreteFunctionT1> and
+                         is_discrete_function_P0_v<DiscreteFunctionT2>) {
+      if constexpr (std::is_same_v<double, f_data_type> and
+                    (is_tiny_vector_v<g_data_type> or is_tiny_matrix_v<g_data_type>)) {
+        return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, g));
+      } else if constexpr (is_tiny_matrix_v<f_data_type> and is_tiny_vector_v<g_data_type>) {
+        if constexpr (f_data_type::NumberOfColumns == g_data_type::Dimension) {
+          return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, g));
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
+      }
+    } else {
+      throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
+    }
+  } else {
+    throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
+  }
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v,
+          const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  if (not hasSameMesh({f_v, g_v})) {
+    throw NormalError("operands are defined on different meshes");
+  }
+
+  return std::visit(
+    [&](auto&& f, auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {
+      using TypeOfF = std::decay_t<decltype(f)>;
+      using TypeOfG = std::decay_t<decltype(g)>;
+      if constexpr (std::is_same_v<TypeOfF, TypeOfG>) {
+        return innerCompositionLaw<TypeOfF, language::plus_op>(f, g);
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f_v, g_v));
+      }
+    },
+    f_v->discreteFunction(), g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v,
+          const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  if (not hasSameMesh({f_v, g_v})) {
+    throw NormalError("operands are defined on different meshes");
+  }
+
+  return std::visit(
+    [&](auto&& f, auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {
+      using TypeOfF = std::decay_t<decltype(f)>;
+      using TypeOfG = std::decay_t<decltype(g)>;
+      if constexpr (std::is_same_v<TypeOfF, TypeOfG>) {
+        return innerCompositionLaw<TypeOfF, language::minus_op>(f, g);
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f_v, g_v));
+      }
+    },
+    f_v->discreteFunction(), g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v,
+          const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  if (not hasSameMesh({f_v, g_v})) {
+    throw NormalError("operands are defined on different meshes");
+  }
+
+  return std::visit(
+    [&](auto&& f, auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {
+      using TypeOfF = std::decay_t<decltype(f)>;
+      using TypeOfG = std::decay_t<decltype(g)>;
+      if constexpr (std::is_same_v<TypeOfF, TypeOfG> and not is_discrete_function_P0_vector_v<TypeOfF>) {
+        return innerCompositionLaw<TypeOfF, language::multiply_op>(f, g);
+      } else {
+        if constexpr (std::is_same_v<typename TypeOfF::MeshType, typename TypeOfG::MeshType>) {
+          return applyBinaryOperation<TypeOfF, TypeOfG, language::multiply_op>(f, g);
+        } else {
+          // LCOV_EXCL_START
+          throw UnexpectedError("operands are defined on different meshes");
+          // LCOV_EXCL_STOP
+        }
+      }
+    },
+    f_v->discreteFunction(), g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator/(const std::shared_ptr<const DiscreteFunctionVariant>& f_v,
+          const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  if (not hasSameMesh({f_v, g_v})) {
+    throw NormalError("operands are defined on different meshes");
+  }
+
+  return std::visit(
+    [&](auto&& f, auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {
+      using TypeOfF = std::decay_t<decltype(f)>;
+      using TypeOfG = std::decay_t<decltype(g)>;
+      if constexpr (std::is_same_v<TypeOfF, TypeOfG> and not is_discrete_function_P0_vector_v<TypeOfF>) {
+        return innerCompositionLaw<TypeOfF, language::divide_op>(f, g);
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f_v, g_v));
+      }
+    },
+    f_v->discreteFunction(), g_v->discreteFunction());
+}
+
+template <typename BinOperatorT, typename DataType, typename DiscreteFunctionT>
+std::shared_ptr<const DiscreteFunctionVariant>
+applyBinaryOperationWithLeftConstant(const DataType& a, const DiscreteFunctionT& f)
+{
+  if constexpr (is_discrete_function_P0_v<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<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(a, f));
+      } else if constexpr (is_tiny_matrix_v<lhs_data_type> and std::is_same_v<lhs_data_type, rhs_data_type>) {
+        return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(a, f));
+      } else if constexpr (is_tiny_matrix_v<lhs_data_type> and is_tiny_vector_v<rhs_data_type>) {
+        if constexpr (lhs_data_type::NumberOfColumns == rhs_data_type::Dimension) {
+          return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(a, f));
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
+        }
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(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_same_v<rhs_data_type, double>) {
+        return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(a, f));
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(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<rhs_data_type, lhs_data_type>) or
+                    (std::is_arithmetic_v<rhs_data_type> and std::is_arithmetic_v<lhs_data_type>)) {
+        return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(a, f));
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
+      }
+    } else {
+      throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
+    }
+  } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT> and
+                       std::is_same_v<language::multiply_op, BinOperatorT>) {
+    using lhs_data_type = std::decay_t<DataType>;
+    if constexpr (std::is_same_v<lhs_data_type, double>) {
+      return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(a, f));
+    } else {
+      throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
+    }
+  } else {
+    throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
+  }
+}
+
+template <typename BinOperatorT, typename DataType, typename DiscreteFunctionT>
+std::shared_ptr<const DiscreteFunctionVariant>
+applyBinaryOperationWithRightConstant(const DiscreteFunctionT& f, const DataType& a)
+{
+  if constexpr (is_discrete_function_P0_v<DiscreteFunctionT>) {
+    using lhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
+    using rhs_data_type = std::decay_t<DataType>;
+
+    if constexpr (std::is_same_v<language::multiply_op, BinOperatorT>) {
+      if constexpr (is_tiny_matrix_v<lhs_data_type> and is_tiny_matrix_v<rhs_data_type>) {
+        if constexpr (lhs_data_type::NumberOfColumns == rhs_data_type::NumberOfRows) {
+          return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, a));
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
+        }
+      } else if constexpr (is_tiny_matrix_v<lhs_data_type> and is_tiny_vector_v<rhs_data_type>) {
+        if constexpr (lhs_data_type::NumberOfColumns == rhs_data_type::Dimension) {
+          return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, a));
+        } else {
+          throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(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> or
+                            std::is_arithmetic_v<rhs_data_type>)) {
+        return std::make_shared<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, a));
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(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<DiscreteFunctionVariant>(BinOp<BinOperatorT>{}.eval(f, a));
+      } else {
+        throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
+      }
+    } else {
+      throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
+    }
+  } else {
+    throw NormalError(EmbeddedDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
+  }
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const double& f, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(f, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const TinyVector<1>& v, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(v, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const TinyVector<2>& v, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(v, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const TinyVector<3>& v, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(v, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const TinyMatrix<1>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const TinyMatrix<2>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const TinyMatrix<3>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::plus_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const double& g)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, g);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<1>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<2>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<3>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<1>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<2>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator+(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<3>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::plus_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const double& f, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(f, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const TinyVector<1>& v, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(v, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const TinyVector<2>& v, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(v, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const TinyVector<3>& v, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(v, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const TinyMatrix<1>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const TinyMatrix<2>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const TinyMatrix<3>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::minus_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const double& g)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, g);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<1>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<2>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<3>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<1>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<2>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator-(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<3>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::minus_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const double& f, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::multiply_op>(f, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const TinyMatrix<1>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::multiply_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const TinyMatrix<2>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::multiply_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const TinyMatrix<3>& A, const std::shared_ptr<const DiscreteFunctionVariant>& g_v)
+{
+  return std::visit(
+    [&](auto&& g) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::multiply_op>(A, g);
+    },
+    g_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const double& g)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, g);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<1>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<2>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyVector<3>& v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, v);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<1>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<2>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator*(const std::shared_ptr<const DiscreteFunctionVariant>& f_v, const TinyMatrix<3>& A)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithRightConstant<language::multiply_op>(f, A);
+    },
+    f_v->discreteFunction());
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+operator/(const double& a, const std::shared_ptr<const DiscreteFunctionVariant>& f_v)
+{
+  return std::visit(
+    [&](auto&& f) -> std::shared_ptr<const DiscreteFunctionVariant> {   //
+      return applyBinaryOperationWithLeftConstant<language::divide_op>(a, f);
+    },
+    f_v->discreteFunction());
+}
diff --git a/src/language/utils/EmbeddedDiscreteFunctionOperators.hpp b/src/language/utils/EmbeddedDiscreteFunctionOperators.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a23ea91a0929a10148d703dd4ec13d703f48e2bf
--- /dev/null
+++ b/src/language/utils/EmbeddedDiscreteFunctionOperators.hpp
@@ -0,0 +1,150 @@
+#ifndef EMBEDDED_DISCRETE_FUNCTION_OPERATORS_HPP
+#define EMBEDDED_DISCRETE_FUNCTION_OPERATORS_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <memory>
+
+class DiscreteFunctionVariant;
+
+// unary minus
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+// sum
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const double&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const double&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const TinyVector<1>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const TinyVector<2>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const TinyVector<3>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const TinyMatrix<1>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const TinyMatrix<2>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const TinyMatrix<3>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<1>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<2>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<3>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<1>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<2>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator+(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<3>&);
+
+// difference
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const double&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const double&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const TinyVector<1>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const TinyVector<2>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const TinyVector<3>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const TinyMatrix<1>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const TinyMatrix<2>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const TinyMatrix<3>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<1>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<2>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<3>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<1>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<2>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator-(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<3>&);
+
+// product
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const double&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const double&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const TinyMatrix<1>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const TinyMatrix<2>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const TinyMatrix<3>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<1>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<2>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyVector<3>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<1>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<2>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator*(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const TinyMatrix<3>&);
+
+// ratio
+std::shared_ptr<const DiscreteFunctionVariant> operator/(const double&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+std::shared_ptr<const DiscreteFunctionVariant> operator/(const std::shared_ptr<const DiscreteFunctionVariant>&,
+                                                         const std::shared_ptr<const DiscreteFunctionVariant>&);
+
+#endif   // EMBEDDED_DISCRETE_FUNCTION_OPERATORS_HPP
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp b/src/language/utils/EmbeddedDiscreteFunctionUtils.hpp
similarity index 53%
rename from src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
rename to src/language/utils/EmbeddedDiscreteFunctionUtils.hpp
index 1e61f917dfa448add18b562dd403a70101dbec3b..bca3b6772b7913be46ebee9a7731aa26fb7f7a6d 100644
--- a/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
+++ b/src/language/utils/EmbeddedDiscreteFunctionUtils.hpp
@@ -1,13 +1,15 @@
-#ifndef EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP
-#define EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP
+#ifndef EMBEDDED_DISCRETE_FUNCTION_UTILS_HPP
+#define EMBEDDED_DISCRETE_FUNCTION_UTILS_HPP
 
 #include <language/utils/ASTNodeDataType.hpp>
-#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 
+#include <utils/Demangle.hpp>
+
 #include <string>
 
-struct EmbeddedIDiscreteFunctionUtils
+struct EmbeddedDiscreteFunctionUtils
 {
   template <typename T>
   static PUGS_INLINE std::string
@@ -16,22 +18,16 @@ struct EmbeddedIDiscreteFunctionUtils
     if constexpr (is_shared_ptr_v<T>) {
       Assert(t.use_count() > 0, "dangling shared_ptr");
       return getOperandTypeName(*t);
-    } else if constexpr (std::is_base_of_v<IDiscreteFunction, std::decay_t<T>>) {
-      return "Vh(" + name(t.descriptor().type()) + ':' + dataTypeName(t.dataType()) + ')';
+    } else if constexpr (std::is_same_v<DiscreteFunctionVariant, std::decay_t<T>>) {
+      return std::visit([](auto&& v) -> std::string { return getOperandTypeName(v); }, t.discreteFunction());
+    } else if constexpr (is_discrete_function_v<std::decay_t<T>>) {
+      std::string type_name = "Vh(" + name(t.descriptor().type()) + ':' + dataTypeName(t.dataType()) + ')';
+      return type_name;
     } else {
       return dataTypeName(ast_node_data_type_from<T>);
     }
   }
 
-  static bool isSameDiscretization(const IDiscreteFunction& f, const IDiscreteFunction& g);
-
-  static PUGS_INLINE bool
-  isSameDiscretization(const std::shared_ptr<const IDiscreteFunction>& f,
-                       const std::shared_ptr<const IDiscreteFunction>& g)
-  {
-    return isSameDiscretization(*f, *g);
-  }
-
   template <typename T1, typename T2>
   PUGS_INLINE static std::string
   incompatibleOperandTypes(const T1& t1, const T2& t2)
@@ -47,4 +43,4 @@ struct EmbeddedIDiscreteFunctionUtils
   }
 };
 
-#endif   // EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP
+#endif   // EMBEDDED_DISCRETE_FUNCTION_UTILS_HPP
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
deleted file mode 100644
index 67622f4a7c2ebbc7284f7305be98aeb3beb8768f..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
+++ /dev/null
@@ -1,1157 +0,0 @@
-#include <language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp>
-
-#include <language/utils/EmbeddedIDiscreteFunctionUtils.hpp>
-#include <mesh/IMesh.hpp>
-#include <scheme/DiscreteFunctionP0.hpp>
-#include <scheme/DiscreteFunctionP0Vector.hpp>
-#include <scheme/DiscreteFunctionUtils.hpp>
-#include <scheme/IDiscreteFunction.hpp>
-#include <scheme/IDiscreteFunctionDescriptor.hpp>
-
-#define DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(FUNCTION, ARG)                                                           \
-  if (ARG->dataType() == ASTNodeDataType::double_t and ARG->descriptor().type() == DiscreteFunctionType::P0) {        \
-    switch (ARG->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 NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(ARG));                                       \
-  }
-
-std::shared_ptr<const IDiscreteFunction>
-sqrt(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(sqrt, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-abs(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(abs, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-sin(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(sin, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-cos(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(cos, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-tan(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(tan, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-asin(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(asin, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-acos(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(acos, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-atan(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_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
-      (g->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)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-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)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, 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));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-sinh(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(sinh, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-cosh(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(cosh, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-tanh(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(tanh, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-asinh(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(asinh, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-acosh(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(acosh, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-atanh(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(atanh, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-exp(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_FUNCTION_CALL(exp, f);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-log(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  DISCRETE_VH_TO_VH_REAL_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
-      (g->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)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-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)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, 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));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-}
-
-template <size_t Dimension>
-std::shared_ptr<const IDiscreteFunction>
-dot(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  Assert(((f->descriptor().type() == DiscreteFunctionType::P0Vector) and
-          (g->descriptor().type() == DiscreteFunctionType::P0Vector)) or
-         ((f->dataType() == ASTNodeDataType::vector_t and f->descriptor().type() == DiscreteFunctionType::P0) and
-          (g->dataType() == ASTNodeDataType::vector_t and g->descriptor().type() == DiscreteFunctionType::P0) and
-          (f->dataType().dimension() == g->dataType().dimension())));
-
-  if ((f->descriptor().type() == DiscreteFunctionType::P0Vector) and
-      (g->descriptor().type() == DiscreteFunctionType::P0Vector)) {
-    using DiscreteFunctionResultType = DiscreteFunctionP0<Dimension, double>;
-    using DiscreteFunctionType       = DiscreteFunctionP0Vector<Dimension, double>;
-
-    const DiscreteFunctionType& f_vector = dynamic_cast<const DiscreteFunctionType&>(*f);
-    const DiscreteFunctionType& g_vector = dynamic_cast<const DiscreteFunctionType&>(*g);
-
-    if (f_vector.size() != g_vector.size()) {
-      throw NormalError("operands have different dimension");
-    } else {
-      return std::make_shared<const DiscreteFunctionResultType>(dot(f_vector, g_vector));
-    }
-
-  } else {
-    using DiscreteFunctionResultType = DiscreteFunctionP0<Dimension, double>;
-
-    switch (f->dataType().dimension()) {
-    case 1: {
-      using Rd                   = TinyVector<1>;
-      using DiscreteFunctionType = DiscreteFunctionP0<Dimension, Rd>;
-      return std::make_shared<const DiscreteFunctionResultType>(
-        dot(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-    case 2: {
-      using Rd                   = TinyVector<2>;
-      using DiscreteFunctionType = DiscreteFunctionP0<Dimension, Rd>;
-      return std::make_shared<const DiscreteFunctionResultType>(
-        dot(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-    case 3: {
-      using Rd                   = TinyVector<3>;
-      using DiscreteFunctionType = DiscreteFunctionP0<Dimension, Rd>;
-      return std::make_shared<const DiscreteFunctionResultType>(
-        dot(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-    }
-      // LCOV_EXCL_STOP
-    }
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-dot(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  if (((f->descriptor().type() == DiscreteFunctionType::P0Vector) and
-       (g->descriptor().type() == DiscreteFunctionType::P0Vector)) or
-      ((f->dataType() == ASTNodeDataType::vector_t and f->descriptor().type() == DiscreteFunctionType::P0) and
-       (g->dataType() == ASTNodeDataType::vector_t and g->descriptor().type() == DiscreteFunctionType::P0) and
-       (f->dataType().dimension() == g->dataType().dimension()))) {
-    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: {
-      return dot<1>(f, g);
-    }
-    case 2: {
-      return dot<2>(f, g);
-    }
-    case 3: {
-      return dot<3>(f, g);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-template <size_t Dimension, size_t VectorDimension>
-std::shared_ptr<const IDiscreteFunction>
-dot(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<VectorDimension>& a)
-{
-  Assert((f->dataType() == ASTNodeDataType::vector_t and f->descriptor().type() == DiscreteFunctionType::P0) and
-         (f->dataType().dimension() == a.dimension()));
-
-  using DiscreteFunctionResultType = DiscreteFunctionP0<Dimension, double>;
-  using DiscreteFunctionType       = DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>>;
-
-  return std::make_shared<const DiscreteFunctionResultType>(dot(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-}
-
-template <size_t VectorDimension>
-std::shared_ptr<const IDiscreteFunction>
-dot(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<VectorDimension>& a)
-{
-  if ((f->dataType() == ASTNodeDataType::vector_t and f->descriptor().type() == DiscreteFunctionType::P0) and
-      (f->dataType().dimension() == a.dimension())) {
-    switch (f->mesh()->dimension()) {
-    case 1: {
-      return dot<1, VectorDimension>(f, a);
-    }
-    case 2: {
-      return dot<2, VectorDimension>(f, a);
-    }
-    case 3: {
-      return dot<3, VectorDimension>(f, a);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-}
-
-template <size_t Dimension, size_t VectorDimension>
-std::shared_ptr<const IDiscreteFunction>
-dot(const TinyVector<VectorDimension>& a, const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  Assert((f->dataType() == ASTNodeDataType::vector_t and f->descriptor().type() == DiscreteFunctionType::P0) and
-         (f->dataType().dimension() == a.dimension()));
-
-  using DiscreteFunctionResultType = DiscreteFunctionP0<Dimension, double>;
-  using DiscreteFunctionType       = DiscreteFunctionP0<Dimension, TinyVector<VectorDimension>>;
-
-  return std::make_shared<const DiscreteFunctionResultType>(dot(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-}
-
-template <size_t VectorDimension>
-std::shared_ptr<const IDiscreteFunction>
-dot(const TinyVector<VectorDimension>& a, const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  if ((f->dataType() == ASTNodeDataType::vector_t and f->descriptor().type() == DiscreteFunctionType::P0) and
-      (f->dataType().dimension() == a.dimension())) {
-    switch (f->mesh()->dimension()) {
-    case 1: {
-      return dot<1, VectorDimension>(a, f);
-    }
-    case 2: {
-      return dot<2, VectorDimension>(a, f);
-    }
-    case 3: {
-      return dot<3, VectorDimension>(a, f);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-  }
-}
-
-template std::shared_ptr<const IDiscreteFunction> dot(const std::shared_ptr<const IDiscreteFunction>&,
-                                                      const TinyVector<1>&);
-
-template std::shared_ptr<const IDiscreteFunction> dot(const std::shared_ptr<const IDiscreteFunction>&,
-                                                      const TinyVector<2>&);
-
-template std::shared_ptr<const IDiscreteFunction> dot(const std::shared_ptr<const IDiscreteFunction>&,
-                                                      const TinyVector<3>&);
-
-template std::shared_ptr<const IDiscreteFunction> dot(const TinyVector<1>&,
-                                                      const std::shared_ptr<const IDiscreteFunction>&);
-
-template std::shared_ptr<const IDiscreteFunction> dot(const TinyVector<2>&,
-                                                      const std::shared_ptr<const IDiscreteFunction>&);
-
-template std::shared_ptr<const IDiscreteFunction> dot(const TinyVector<3>&,
-                                                      const std::shared_ptr<const IDiscreteFunction>&);
-
-template <typename MatrixType>
-std::shared_ptr<const IDiscreteFunction>
-det(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  switch (A->mesh()->dimension()) {
-  case 1: {
-    using DiscreteFunctionType = DiscreteFunctionP0<1, MatrixType>;
-    return std::make_shared<const DiscreteFunctionP0<1, double>>(det(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 2: {
-    using DiscreteFunctionType = DiscreteFunctionP0<2, MatrixType>;
-    return std::make_shared<const DiscreteFunctionP0<2, double>>(det(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 3: {
-    using DiscreteFunctionType = DiscreteFunctionP0<3, MatrixType>;
-    return std::make_shared<const DiscreteFunctionP0<3, double>>(det(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-det(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  if (A->dataType() == ASTNodeDataType::matrix_t and A->descriptor().type() == DiscreteFunctionType::P0) {
-    if (A->dataType().numberOfRows() != A->dataType().numberOfColumns()) {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-    }
-    switch (A->dataType().numberOfRows()) {
-    case 1: {
-      return det<TinyMatrix<1>>(A);
-    }
-    case 2: {
-      return det<TinyMatrix<2>>(A);
-    }
-    case 3: {
-      return det<TinyMatrix<3>>(A);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid matrix type");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-  }
-}
-
-template <typename MatrixType>
-std::shared_ptr<const IDiscreteFunction>
-trace(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  switch (A->mesh()->dimension()) {
-  case 1: {
-    using DiscreteFunctionType = DiscreteFunctionP0<1, MatrixType>;
-    return std::make_shared<const DiscreteFunctionP0<1, double>>(trace(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 2: {
-    using DiscreteFunctionType = DiscreteFunctionP0<2, MatrixType>;
-    return std::make_shared<const DiscreteFunctionP0<2, double>>(trace(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 3: {
-    using DiscreteFunctionType = DiscreteFunctionP0<3, MatrixType>;
-    return std::make_shared<const DiscreteFunctionP0<3, double>>(trace(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-trace(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  if (A->dataType() == ASTNodeDataType::matrix_t and A->descriptor().type() == DiscreteFunctionType::P0) {
-    if (A->dataType().numberOfRows() != A->dataType().numberOfColumns()) {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-    }
-    switch (A->dataType().numberOfRows()) {
-    case 1: {
-      return trace<TinyMatrix<1>>(A);
-    }
-    case 2: {
-      return trace<TinyMatrix<2>>(A);
-    }
-    case 3: {
-      return trace<TinyMatrix<3>>(A);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid matrix type");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-  }
-}
-
-template <typename MatrixType>
-std::shared_ptr<const IDiscreteFunction>
-inverse(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  switch (A->mesh()->dimension()) {
-  case 1: {
-    using DiscreteFunctionType = DiscreteFunctionP0<1, MatrixType>;
-    return std::make_shared<const DiscreteFunctionType>(inverse(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 2: {
-    using DiscreteFunctionType = DiscreteFunctionP0<2, MatrixType>;
-    return std::make_shared<const DiscreteFunctionType>(inverse(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 3: {
-    using DiscreteFunctionType = DiscreteFunctionP0<3, MatrixType>;
-    return std::make_shared<const DiscreteFunctionType>(inverse(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-inverse(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  if (A->dataType() == ASTNodeDataType::matrix_t and A->descriptor().type() == DiscreteFunctionType::P0) {
-    if (A->dataType().numberOfRows() != A->dataType().numberOfColumns()) {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-    }
-    switch (A->dataType().numberOfRows()) {
-    case 1: {
-      return inverse<TinyMatrix<1>>(A);
-    }
-    case 2: {
-      return inverse<TinyMatrix<2>>(A);
-    }
-    case 3: {
-      return inverse<TinyMatrix<3>>(A);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid matrix type");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-  }
-}
-
-template <typename MatrixType>
-std::shared_ptr<const IDiscreteFunction>
-transpose(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  switch (A->mesh()->dimension()) {
-  case 1: {
-    using DiscreteFunctionType = DiscreteFunctionP0<1, MatrixType>;
-    return std::make_shared<const DiscreteFunctionType>(transpose(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 2: {
-    using DiscreteFunctionType = DiscreteFunctionP0<2, MatrixType>;
-    return std::make_shared<const DiscreteFunctionType>(transpose(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  case 3: {
-    using DiscreteFunctionType = DiscreteFunctionP0<3, MatrixType>;
-    return std::make_shared<const DiscreteFunctionType>(transpose(dynamic_cast<const DiscreteFunctionType&>(*A)));
-  }
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-transpose(const std::shared_ptr<const IDiscreteFunction>& A)
-{
-  if (A->dataType() == ASTNodeDataType::matrix_t and A->descriptor().type() == DiscreteFunctionType::P0) {
-    if (A->dataType().numberOfRows() != A->dataType().numberOfColumns()) {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-    }
-    switch (A->dataType().numberOfRows()) {
-    case 1: {
-      return transpose<TinyMatrix<1>>(A);
-    }
-    case 2: {
-      return transpose<TinyMatrix<2>>(A);
-    }
-    case 3: {
-      return transpose<TinyMatrix<3>>(A);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid matrix type");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(A));
-  }
-}
-
-double
-min(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 min(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return min(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return min(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-min(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
-      (g->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>(
-        min(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return std::make_shared<const DiscreteFunctionType>(
-        min(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return std::make_shared<const DiscreteFunctionType>(
-        min(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-min(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>(min(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return std::make_shared<const DiscreteFunctionType>(min(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return std::make_shared<const DiscreteFunctionType>(min(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-min(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>(min(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return std::make_shared<const DiscreteFunctionType>(min(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return std::make_shared<const DiscreteFunctionType>(min(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-}
-
-double
-max(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 max(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return max(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return max(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-max(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
-      (g->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>(
-        max(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return std::make_shared<const DiscreteFunctionType>(
-        max(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return std::make_shared<const DiscreteFunctionType>(
-        max(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-max(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>(max(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return std::make_shared<const DiscreteFunctionType>(max(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return std::make_shared<const DiscreteFunctionType>(max(a, dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-max(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>(max(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, double>;
-      return std::make_shared<const DiscreteFunctionType>(max(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, double>;
-      return std::make_shared<const DiscreteFunctionType>(max(dynamic_cast<const DiscreteFunctionType&>(*f), a));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-}
-
-template <typename ValueT>
-ValueT
-sum_of(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  if (f->dataType() == ast_node_data_type_from<ValueT> and f->descriptor().type() == DiscreteFunctionType::P0) {
-    switch (f->mesh()->dimension()) {
-    case 1: {
-      using DiscreteFunctionType = DiscreteFunctionP0<1, ValueT>;
-      return sum(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, ValueT>;
-      return sum(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, ValueT>;
-      return sum(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-sum_of_Vh_components(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  if (f->descriptor().type() == DiscreteFunctionType::P0Vector) {
-    switch (f->mesh()->dimension()) {
-    case 1: {
-      constexpr size_t Dimension = 1;
-      using DiscreteFunctionType = DiscreteFunctionP0Vector<Dimension, double>;
-      return std::make_shared<const DiscreteFunctionP0<Dimension, double>>(
-        sumOfComponents(dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-    case 2: {
-      constexpr size_t Dimension = 2;
-      using DiscreteFunctionType = DiscreteFunctionP0Vector<Dimension, double>;
-      return std::make_shared<const DiscreteFunctionP0<Dimension, double>>(
-        sumOfComponents(dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-    case 3: {
-      constexpr size_t Dimension = 3;
-      using DiscreteFunctionType = DiscreteFunctionP0Vector<Dimension, double>;
-      return std::make_shared<const DiscreteFunctionP0<Dimension, double>>(
-        sumOfComponents(dynamic_cast<const DiscreteFunctionType&>(*f)));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-vectorize(const std::vector<std::shared_ptr<const IDiscreteFunction>>& discrete_function_list)
-{
-  Assert(discrete_function_list.size() > 0);
-  std::shared_ptr p_i_mesh = [&] {
-    auto i = discrete_function_list.begin();
-
-    std::shared_ptr<const IMesh> i_mesh = (*i)->mesh();
-    ++i;
-    for (; i != discrete_function_list.end(); ++i) {
-      if ((*i)->mesh() != i_mesh) {
-        throw NormalError("discrete functions are not defined on the same mesh");
-      }
-    }
-
-    return i_mesh;
-  }();
-
-  for (auto&& i_discrete_function : discrete_function_list) {
-    if ((i_discrete_function->descriptor().type() != DiscreteFunctionType::P0) or
-        (i_discrete_function->dataType() != ASTNodeDataType::double_t)) {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(i_discrete_function));
-    }
-  }
-
-  switch (p_i_mesh->dimension()) {
-  case 1: {
-    constexpr size_t Dimension       = 1;
-    using DiscreteFunctionVectorType = DiscreteFunctionP0Vector<Dimension, double>;
-    using DiscreteFunctionType       = DiscreteFunctionP0<Dimension, double>;
-    std::shared_ptr<const Mesh<Connectivity<Dimension>>> p_mesh =
-      std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(p_i_mesh);
-
-    DiscreteFunctionVectorType vector_function(p_mesh, discrete_function_list.size());
-    for (size_t i = 0; i < discrete_function_list.size(); ++i) {
-      const DiscreteFunctionType& f = dynamic_cast<const DiscreteFunctionType&>(*discrete_function_list[i]);
-      for (CellId cell_id = 0; cell_id < p_mesh->numberOfCells(); ++cell_id) {
-        vector_function[cell_id][i] = f[cell_id];
-      }
-    }
-
-    return std::make_shared<DiscreteFunctionVectorType>(vector_function);
-  }
-  case 2: {
-    constexpr size_t Dimension       = 2;
-    using DiscreteFunctionVectorType = DiscreteFunctionP0Vector<Dimension, double>;
-    using DiscreteFunctionType       = DiscreteFunctionP0<Dimension, double>;
-    std::shared_ptr<const Mesh<Connectivity<Dimension>>> p_mesh =
-      std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(p_i_mesh);
-
-    DiscreteFunctionVectorType vector_function(p_mesh, discrete_function_list.size());
-    for (size_t i = 0; i < discrete_function_list.size(); ++i) {
-      const DiscreteFunctionType& f = dynamic_cast<const DiscreteFunctionType&>(*discrete_function_list[i]);
-      for (CellId cell_id = 0; cell_id < p_mesh->numberOfCells(); ++cell_id) {
-        vector_function[cell_id][i] = f[cell_id];
-      }
-    }
-
-    return std::make_shared<DiscreteFunctionVectorType>(vector_function);
-  }
-  case 3: {
-    constexpr size_t Dimension       = 3;
-    using DiscreteFunctionVectorType = DiscreteFunctionP0Vector<Dimension, double>;
-    using DiscreteFunctionType       = DiscreteFunctionP0<Dimension, double>;
-    std::shared_ptr<const Mesh<Connectivity<Dimension>>> p_mesh =
-      std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(p_i_mesh);
-
-    DiscreteFunctionVectorType vector_function(p_mesh, discrete_function_list.size());
-    for (size_t i = 0; i < discrete_function_list.size(); ++i) {
-      const DiscreteFunctionType& f = dynamic_cast<const DiscreteFunctionType&>(*discrete_function_list[i]);
-      for (CellId cell_id = 0; cell_id < p_mesh->numberOfCells(); ++cell_id) {
-        vector_function[cell_id][i] = f[cell_id];
-      }
-    }
-
-    return std::make_shared<DiscreteFunctionVectorType>(vector_function);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template double sum_of<double>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyVector<1> sum_of<TinyVector<1>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyVector<2> sum_of<TinyVector<2>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyVector<3> sum_of<TinyVector<3>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyMatrix<1> sum_of<TinyMatrix<1>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyMatrix<2> sum_of<TinyMatrix<2>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyMatrix<3> sum_of<TinyMatrix<3>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template <typename ValueT>
-ValueT
-integral_of(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  if (f->dataType() == ast_node_data_type_from<ValueT> and f->descriptor().type() == DiscreteFunctionType::P0) {
-    switch (f->mesh()->dimension()) {
-    case 1: {
-      using DiscreteFunctionType = DiscreteFunctionP0<1, ValueT>;
-      return integrate(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 2: {
-      using DiscreteFunctionType = DiscreteFunctionP0<2, ValueT>;
-      return integrate(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-    case 3: {
-      using DiscreteFunctionType = DiscreteFunctionP0<3, ValueT>;
-      return integrate(dynamic_cast<const DiscreteFunctionType&>(*f));
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid mesh dimension");
-    }
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-}
-
-template double integral_of<double>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyVector<1> integral_of<TinyVector<1>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyVector<2> integral_of<TinyVector<2>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyVector<3> integral_of<TinyVector<3>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyMatrix<1> integral_of<TinyMatrix<1>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyMatrix<2> integral_of<TinyMatrix<2>>(const std::shared_ptr<const IDiscreteFunction>&);
-
-template TinyMatrix<3> integral_of<TinyMatrix<3>>(const std::shared_ptr<const IDiscreteFunction>&);
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp
deleted file mode 100644
index 24994c743428d98befb8d34cc18c2cbbf12a2ba4..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp
+++ /dev/null
@@ -1,105 +0,0 @@
-#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);
-
-std::shared_ptr<const IDiscreteFunction> dot(const std::shared_ptr<const IDiscreteFunction>&,
-                                             const std::shared_ptr<const IDiscreteFunction>&);
-
-template <size_t VectorDimension>
-std::shared_ptr<const IDiscreteFunction> dot(const std::shared_ptr<const IDiscreteFunction>&,
-                                             const TinyVector<VectorDimension>&);
-
-template <size_t VectorDimension>
-std::shared_ptr<const IDiscreteFunction> dot(const TinyVector<VectorDimension>&,
-                                             const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> det(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> trace(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> inverse(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> transpose(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> sum_of_Vh_components(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> vectorize(
-  const std::vector<std::shared_ptr<const IDiscreteFunction>>& discrete_function_list);
-
-double min(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> min(const std::shared_ptr<const IDiscreteFunction>&,
-                                             const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> min(const double, const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> min(const std::shared_ptr<const IDiscreteFunction>&, const double);
-
-double max(const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> max(const std::shared_ptr<const IDiscreteFunction>&,
-                                             const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> max(const double, const std::shared_ptr<const IDiscreteFunction>&);
-
-std::shared_ptr<const IDiscreteFunction> max(const std::shared_ptr<const IDiscreteFunction>&, const double);
-
-template <typename ValueT>
-ValueT sum_of(const std::shared_ptr<const IDiscreteFunction>&);
-
-template <typename ValueT>
-ValueT integral_of(const std::shared_ptr<const IDiscreteFunction>&);
-
-#endif   // EMBEDDED_I_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp b/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
deleted file mode 100644
index 656164d6fc012ef2469f846d3e54f32a9c1d4927..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
+++ /dev/null
@@ -1,1097 +0,0 @@
-#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>
-
-// unary operators
-template <typename UnaryOperatorT, typename DiscreteFunctionT>
-std::shared_ptr<const IDiscreteFunction>
-applyUnaryOperation(const DiscreteFunctionT& f)
-{
-  return std::make_shared<decltype(UnaryOp<UnaryOperatorT>{}.eval(f))>(UnaryOp<UnaryOperatorT>{}.eval(f));
-}
-
-template <typename UnaryOperatorT, size_t Dimension>
-std::shared_ptr<const IDiscreteFunction>
-applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  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);
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    }
-    case ASTNodeDataType::matrix_t: {
-      Assert(f->dataType().numberOfRows() == f->dataType().numberOfColumns());
-      switch (f->dataType().numberOfRows()) {
-      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);
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-    }
-      // LCOV_EXCL_STOP
-    }
-    break;
-  }
-  case DiscreteFunctionType::P0Vector: {
-    switch (f->dataType()) {
-    case ASTNodeDataType::double_t: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*f);
-      return applyUnaryOperation<UnaryOperatorT>(fh);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-    }
-      // LCOV_EXCL_STOP
-    }
-    break;
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template <typename UnaryOperatorT>
-std::shared_ptr<const IDiscreteFunction>
-applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  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);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator-(const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  return applyUnaryOperation<language::unary_minus>(f);
-}
-
-// binary operators
-
-template <typename BinOperatorT, typename DiscreteFunctionT>
-std::shared_ptr<const IDiscreteFunction>
-innerCompositionLaw(const DiscreteFunctionT& lhs, const DiscreteFunctionT& rhs)
-{
-  Assert(lhs.mesh() == rhs.mesh());
-  using data_type = typename DiscreteFunctionT::data_type;
-  if constexpr ((std::is_same_v<language::multiply_op, BinOperatorT> and is_tiny_vector_v<data_type>) or
-                (std::is_same_v<language::divide_op, BinOperatorT> and not std::is_arithmetic_v<data_type>)) {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(lhs, rhs));
-  } else {
-    return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(lhs, rhs))>(BinOp<BinOperatorT>{}.eval(lhs, rhs));
-  }
-}
-
-template <typename BinOperatorT, size_t Dimension>
-std::shared_ptr<const IDiscreteFunction>
-innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
-                    const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  Assert(f->mesh() == g->mesh());
-  Assert(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g));
-
-  switch (f->dataType()) {
-  case ASTNodeDataType::double_t: {
-    if (f->descriptor().type() == DiscreteFunctionType::P0) {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-
-    } else if (f->descriptor().type() == DiscreteFunctionType::P0Vector) {
-      if constexpr (std::is_same_v<BinOperatorT, language::plus_op> or
-                    std::is_same_v<BinOperatorT, language::minus_op>) {
-        auto fh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*f);
-        auto gh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*g);
-
-        if (fh.size() != gh.size()) {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) + " spaces have different sizes");
-        }
-
-        return innerCompositionLaw<BinOperatorT>(fh, gh);
-      } else {
-        throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-      }
-    } else {
-      // LCOV_EXCL_START
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-      // LCOV_EXCL_STOP
-    }
-  }
-  case ASTNodeDataType::vector_t: {
-    Assert(f->descriptor().type() == DiscreteFunctionType::P0);
-    switch (f->dataType().dimension()) {
-    case 1: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-    }
-    case 2: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-    }
-    case 3: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-    }
-      // LCOV_EXCL_STOP
-    }
-  }
-  case ASTNodeDataType::matrix_t: {
-    Assert(f->descriptor().type() == DiscreteFunctionType::P0);
-    Assert(f->dataType().numberOfRows() == f->dataType().numberOfColumns());
-    switch (f->dataType().numberOfRows()) {
-    case 1: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-    }
-    case 2: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-    }
-    case 3: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*f);
-      auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*g);
-
-      return innerCompositionLaw<BinOperatorT>(fh, gh);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-    }
-      // LCOV_EXCL_STOP
-    }
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::invalidOperandType(f));
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template <typename BinOperatorT>
-std::shared_ptr<const IDiscreteFunction>
-innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
-                    const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  std::shared_ptr mesh = getCommonMesh({f, g});
-  if (mesh.use_count() == 0) {
-    throw NormalError("operands are defined on different meshes");
-  }
-
-  Assert(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g));
-
-  switch (mesh->dimension()) {
-  case 1: {
-    return innerCompositionLaw<BinOperatorT, 1>(f, g);
-  }
-  case 2: {
-    return innerCompositionLaw<BinOperatorT, 2>(f, g);
-  }
-  case 3: {
-    return innerCompositionLaw<BinOperatorT, 3>(f, g);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template <typename BinOperatorT, typename LeftDiscreteFunctionT, typename RightDiscreteFunctionT>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperation(const LeftDiscreteFunctionT& lhs, const RightDiscreteFunctionT& rhs)
-{
-  Assert(lhs.mesh() == rhs.mesh());
-
-  static_assert(not std::is_same_v<LeftDiscreteFunctionT, RightDiscreteFunctionT>,
-                "use innerCompositionLaw when data types are the same");
-
-  return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(lhs, rhs))>(BinOp<BinOperatorT>{}.eval(lhs, rhs));
-}
-
-template <typename BinOperatorT, size_t Dimension, typename DiscreteFunctionT>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  Assert(fh.mesh() == g->mesh());
-  Assert(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(fh, *g));
-  using lhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
-
-  switch (g->dataType()) {
-  case ASTNodeDataType::double_t: {
-    if constexpr (std::is_same_v<BinOperatorT, language::multiply_op> and
-                  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 {
-        // LCOV_EXCL_START
-        throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-        // LCOV_EXCL_STOP
-      }
-    } else {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-    }
-  }
-  case ASTNodeDataType::vector_t: {
-    if constexpr (std::is_same_v<language::multiply_op, BinOperatorT>) {
-      switch (g->dataType().dimension()) {
-      case 1: {
-        if constexpr (not is_tiny_vector_v<lhs_data_type> and
-                      (std::is_same_v<lhs_data_type, TinyMatrix<1>> or std::is_same_v<lhs_data_type, double>)) {
-          auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*g);
-
-          return applyBinaryOperation<BinOperatorT>(fh, gh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-        }
-      }
-      case 2: {
-        if constexpr (not is_tiny_vector_v<lhs_data_type> and
-                      (std::is_same_v<lhs_data_type, TinyMatrix<2>> or std::is_same_v<lhs_data_type, double>)) {
-          auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*g);
-
-          return applyBinaryOperation<BinOperatorT>(fh, gh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-        }
-      }
-      case 3: {
-        if constexpr (not is_tiny_vector_v<lhs_data_type> and
-                      (std::is_same_v<lhs_data_type, TinyMatrix<3>> or std::is_same_v<lhs_data_type, double>)) {
-          auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*g);
-
-          return applyBinaryOperation<BinOperatorT>(fh, gh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-        }
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid rhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g));
-      }
-        // LCOV_EXCL_STOP
-      }
-    } else {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-    }
-  }
-  case ASTNodeDataType::matrix_t: {
-    Assert(g->dataType().numberOfRows() == g->dataType().numberOfColumns());
-    if constexpr (std::is_same_v<lhs_data_type, double> and std::is_same_v<language::multiply_op, BinOperatorT>) {
-      switch (g->dataType().numberOfRows()) {
-      case 1: {
-        auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*g);
-
-        return applyBinaryOperation<BinOperatorT>(fh, gh);
-      }
-      case 2: {
-        auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*g);
-
-        return applyBinaryOperation<BinOperatorT>(fh, gh);
-      }
-      case 3: {
-        auto gh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*g);
-
-        return applyBinaryOperation<BinOperatorT>(fh, gh);
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid rhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g));
-      }
-        // LCOV_EXCL_STOP
-      }
-    } else {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(fh, g));
-    }
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid rhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g));
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template <typename BinOperatorT, size_t Dimension>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
-                     const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  Assert(f->mesh() == g->mesh());
-  Assert(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g));
-
-  if (f->descriptor().type() == DiscreteFunctionType::P0) {
-    switch (f->dataType()) {
-    case ASTNodeDataType::double_t: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*f);
-      return applyBinaryOperation<BinOperatorT, Dimension>(fh, g);
-    }
-    case ASTNodeDataType::matrix_t: {
-      Assert(f->dataType().numberOfRows() == f->dataType().numberOfColumns());
-      switch (f->dataType().numberOfRows()) {
-      case 1: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*f);
-
-        return applyBinaryOperation<BinOperatorT, Dimension>(fh, g);
-      }
-      case 2: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*f);
-
-        return applyBinaryOperation<BinOperatorT, Dimension>(fh, g);
-      }
-      case 3: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*f);
-
-        return applyBinaryOperation<BinOperatorT, Dimension>(fh, g);
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    }
-    default: {
-      throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-    }
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-template <typename BinOperatorT>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
-                     const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  std::shared_ptr mesh = getCommonMesh({f, g});
-  if (mesh.use_count() == 0) {
-    throw NormalError("operands are defined on different meshes");
-  }
-
-  Assert(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g), "should call inner composition instead");
-
-  switch (mesh->dimension()) {
-  case 1: {
-    return applyBinaryOperation<BinOperatorT, 1>(f, g);
-  }
-  case 2: {
-    return applyBinaryOperation<BinOperatorT, 2>(f, g);
-  }
-  case 3: {
-    return applyBinaryOperation<BinOperatorT, 3>(f, g);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator+(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
-    return innerCompositionLaw<language::plus_op>(f, g);
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator-(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
-    return innerCompositionLaw<language::minus_op>(f, g);
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, g));
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
-    return innerCompositionLaw<language::multiply_op>(f, g);
-  } else {
-    return applyBinaryOperation<language::multiply_op>(f, g);
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator/(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
-    return innerCompositionLaw<language::divide_op>(f, g);
-  } else {
-    return applyBinaryOperation<language::divide_op>(f, g);
-  }
-}
-
-template <typename BinOperatorT, typename DataType, typename DiscreteFunctionT>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperationWithLeftConstant(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));
-    } 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(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(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(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(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 {
-      // LCOV_EXCL_START
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-      // LCOV_EXCL_STOP
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(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));
-    } 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(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-  }
-}
-
-template <typename BinOperatorT, size_t Dimension, typename DataType>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  switch (f->dataType()) {
-  case ASTNodeDataType::bool_t:
-  case ASTNodeDataType::unsigned_int_t:
-  case ASTNodeDataType::int_t:
-  case ASTNodeDataType::double_t: {
-    if (f->descriptor().type() == DiscreteFunctionType::P0) {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*f);
-      return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-    } else if (f->descriptor().type() == DiscreteFunctionType::P0Vector) {
-      auto fh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*f);
-      return applyBinaryOperationToVectorWithLeftConstant<BinOperatorT>(a, fh);
-    } else {
-      // LCOV_EXCL_START
-      throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-      // LCOV_EXCL_STOP
-    }
-  }
-  case ASTNodeDataType::vector_t: {
-    if constexpr (is_tiny_matrix_v<DataType>) {
-      switch (f->dataType().dimension()) {
-      case 1: {
-        if constexpr (std::is_same_v<DataType, TinyMatrix<1>>) {
-          auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*f);
-          return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-        }
-      }
-      case 2: {
-        if constexpr (std::is_same_v<DataType, TinyMatrix<2>>) {
-          auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*f);
-          return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-        }
-      }
-      case 3: {
-        if constexpr (std::is_same_v<DataType, TinyMatrix<3>>) {
-          auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*f);
-          return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-        }
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    } else {
-      switch (f->dataType().dimension()) {
-      case 1: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*f);
-        return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-      }
-      case 2: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*f);
-        return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-      }
-      case 3: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*f);
-        return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    }
-  }
-  case ASTNodeDataType::matrix_t: {
-    Assert(f->dataType().numberOfRows() == f->dataType().numberOfColumns());
-    if constexpr (is_tiny_matrix_v<DataType>) {
-      switch (f->dataType().numberOfRows()) {
-      case 1: {
-        if constexpr (std::is_same_v<DataType, TinyMatrix<1>>) {
-          auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*f);
-          return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-        }
-      }
-      case 2: {
-        if constexpr (std::is_same_v<DataType, TinyMatrix<2>>) {
-          auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*f);
-          return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-        }
-      }
-      case 3: {
-        if constexpr (std::is_same_v<DataType, TinyMatrix<3>>) {
-          auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*f);
-          return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-        } else {
-          throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-        }
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    } else {
-      switch (f->dataType().numberOfRows()) {
-      case 1: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*f);
-        return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-      }
-      case 2: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*f);
-        return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-      }
-      case 3: {
-        auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*f);
-        return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-      }
-        // LCOV_EXCL_STOP
-      }
-    }
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(a, f));
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template <typename BinOperatorT, typename DataType>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<const IDiscreteFunction>& f)
-{
-  switch (f->mesh()->dimension()) {
-  case 1: {
-    return applyBinaryOperationWithLeftConstant<BinOperatorT, 1>(a, f);
-  }
-  case 2: {
-    return applyBinaryOperationWithLeftConstant<BinOperatorT, 2>(a, f);
-  }
-  case 3: {
-    return applyBinaryOperationWithLeftConstant<BinOperatorT, 3>(a, f);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-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);
-
-  using lhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
-  using rhs_data_type = std::decay_t<DataType>;
-
-  if constexpr (std::is_same_v<language::multiply_op, BinOperatorT>) {
-    if constexpr (is_tiny_matrix_v<lhs_data_type> and is_tiny_matrix_v<rhs_data_type>) {
-      if constexpr (lhs_data_type::NumberOfColumns == rhs_data_type::NumberOfRows) {
-        return std::make_shared<decltype(BinOp<BinOperatorT>{}.eval(f, a))>(BinOp<BinOperatorT>{}.eval(f, a));
-      } else {
-        throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(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> 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(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(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(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-    }
-  } else {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-}
-
-template <typename BinOperatorT, size_t Dimension, typename DataType>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunction>& f, const DataType& a)
-{
-  if (f->descriptor().type() != DiscreteFunctionType::P0) {
-    throw NormalError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-
-  switch (f->dataType()) {
-  case ASTNodeDataType::bool_t:
-  case ASTNodeDataType::unsigned_int_t:
-  case ASTNodeDataType::int_t:
-  case ASTNodeDataType::double_t: {
-    auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*f);
-    return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-  }
-  case ASTNodeDataType::vector_t: {
-    switch (f->dataType().dimension()) {
-    case 1: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>&>(*f);
-      return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-    }
-    case 2: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>&>(*f);
-      return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-    }
-    case 3: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>&>(*f);
-      return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-    }
-      // LCOV_EXCL_STOP
-    }
-  }
-  case ASTNodeDataType::matrix_t: {
-    Assert(f->dataType().numberOfRows() == f->dataType().numberOfColumns());
-    switch (f->dataType().numberOfRows()) {
-    case 1: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*f);
-      return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-    }
-    case 2: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>&>(*f);
-      return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-    }
-    case 3: {
-      auto fh = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>&>(*f);
-      return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
-    }
-      // LCOV_EXCL_STOP
-    }
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError(EmbeddedIDiscreteFunctionUtils::incompatibleOperandTypes(f, a));
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-template <typename BinOperatorT, typename DataType>
-std::shared_ptr<const IDiscreteFunction>
-applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunction>& f, const DataType& a)
-{
-  switch (f->mesh()->dimension()) {
-  case 1: {
-    return applyBinaryOperationWithRightConstant<BinOperatorT, 1>(f, a);
-  }
-  case 2: {
-    return applyBinaryOperationWithRightConstant<BinOperatorT, 2>(f, a);
-  }
-  case 3: {
-    return applyBinaryOperationWithRightConstant<BinOperatorT, 3>(f, a);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-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)
-{
-  return applyBinaryOperationWithLeftConstant<language::multiply_op>(A, B);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const TinyMatrix<2>& A, const std::shared_ptr<const IDiscreteFunction>& B)
-{
-  return applyBinaryOperationWithLeftConstant<language::multiply_op>(A, B);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const TinyMatrix<3>& A, const std::shared_ptr<const IDiscreteFunction>& B)
-{
-  return applyBinaryOperationWithLeftConstant<language::multiply_op>(A, B);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyVector<1>& u)
-{
-  return applyBinaryOperationWithRightConstant<language::multiply_op>(a, u);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyVector<2>& u)
-{
-  return applyBinaryOperationWithRightConstant<language::multiply_op>(a, u);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyVector<3>& u)
-{
-  return applyBinaryOperationWithRightConstant<language::multiply_op>(a, u);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyMatrix<1>& A)
-{
-  return applyBinaryOperationWithRightConstant<language::multiply_op>(a, A);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyMatrix<2>& A)
-{
-  return applyBinaryOperationWithRightConstant<language::multiply_op>(a, A);
-}
-
-std::shared_ptr<const IDiscreteFunction>
-operator*(const std::shared_ptr<const IDiscreteFunction>& a, const TinyMatrix<3>& A)
-{
-  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
deleted file mode 100644
index f797a95d03e19a796d9af965d81bbd3970c6cddd..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionOperators.hpp
+++ /dev/null
@@ -1,143 +0,0 @@
-#ifndef EMBEDDED_I_DISCRETE_FUNCTION_OPERATORS_HPP
-#define EMBEDDED_I_DISCRETE_FUNCTION_OPERATORS_HPP
-
-#include <algebra/TinyMatrix.hpp>
-#include <algebra/TinyVector.hpp>
-
-#include <memory>
-
-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 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>&);
-
-// 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>&);
-
-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>&);
-
-// 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.cpp b/src/language/utils/EmbeddedIDiscreteFunctionUtils.cpp
deleted file mode 100644
index 286988b9b2849fb34dc84948be05414ebbad6d5b..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionUtils.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include <language/utils/EmbeddedIDiscreteFunctionUtils.hpp>
-
-#include <utils/Exceptions.hpp>
-
-bool
-EmbeddedIDiscreteFunctionUtils::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().numberOfRows() == g.dataType().numberOfRows()) and
-             (f.dataType().numberOfColumns() == g.dataType().numberOfColumns());
-    }
-    default: {
-      throw UnexpectedError("invalid data type " + getOperandTypeName(f));
-    }
-    }
-  } else {
-    return false;
-  }
-}
diff --git a/src/mesh/DiamondDualConnectivityBuilder.cpp b/src/mesh/DiamondDualConnectivityBuilder.cpp
index 792e972071fc388ef13e239e559741c5c22a1d03..c6a8fd78a9774dc63cbc5e5398ce721c5cfb9acb 100644
--- a/src/mesh/DiamondDualConnectivityBuilder.cpp
+++ b/src/mesh/DiamondDualConnectivityBuilder.cpp
@@ -276,7 +276,7 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
         }
       }
 
-      return {};
+      return std::nullopt;
     };
 
     std::vector<unsigned int> face_node_list;
@@ -352,7 +352,7 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
         }
       }
 
-      return {};
+      return std::nullopt;
     };
 
     for (size_t i_edge_list = 0; i_edge_list < primal_connectivity.template numberOfRefItemList<ItemType::edge>();
diff --git a/src/output/NamedDiscreteFunction.hpp b/src/output/NamedDiscreteFunction.hpp
index d56d44369bb2e5492c078a35a0cf0a01e5524d42..305db7be68d5a2ca6a4790c1d4bde525297e8999 100644
--- a/src/output/NamedDiscreteFunction.hpp
+++ b/src/output/NamedDiscreteFunction.hpp
@@ -6,12 +6,12 @@
 #include <memory>
 #include <string>
 
-class IDiscreteFunction;
+class DiscreteFunctionVariant;
 
 class NamedDiscreteFunction final : public INamedDiscreteData
 {
  private:
-  std::shared_ptr<const IDiscreteFunction> m_discrete_function;
+  std::shared_ptr<const DiscreteFunctionVariant> m_discrete_function_variant;
   std::string m_name;
 
  public:
@@ -27,14 +27,15 @@ class NamedDiscreteFunction final : public INamedDiscreteData
     return m_name;
   }
 
-  const std::shared_ptr<const IDiscreteFunction>
-  discreteFunction() const
+  const std::shared_ptr<const DiscreteFunctionVariant>
+  discreteFunctionVariant() const
   {
-    return m_discrete_function;
+    return m_discrete_function_variant;
   }
 
-  NamedDiscreteFunction(const std::shared_ptr<const IDiscreteFunction>& discrete_function, const std::string& name)
-    : m_discrete_function{discrete_function}, m_name{name}
+  NamedDiscreteFunction(const std::shared_ptr<const DiscreteFunctionVariant>& discrete_function,
+                        const std::string& name)
+    : m_discrete_function_variant{discrete_function}, m_name{name}
   {}
 
   NamedDiscreteFunction(const NamedDiscreteFunction&) = default;
diff --git a/src/output/WriterBase.cpp b/src/output/WriterBase.cpp
index fcb2cf78f21f3bebc878b0343826c3abab4636c1..83d890c2ab8bf31e60d7ed09e4e2da7770ebbfb8 100644
--- a/src/output/WriterBase.cpp
+++ b/src/output/WriterBase.cpp
@@ -7,7 +7,7 @@
 #include <output/OutputNamedItemValueSet.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
-#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <utils/Exceptions.hpp>
 
@@ -17,142 +17,14 @@ WriterBase::_registerDiscreteFunction(const std::string& name,
                                       const DiscreteFunctionType& discrete_function,
                                       OutputNamedItemDataSet& named_item_data_set)
 {
-  if constexpr (DiscreteFunctionType::handled_data_type == IDiscreteFunction::HandledItemDataType::value) {
+  if constexpr (is_discrete_function_P0_v<DiscreteFunctionType>) {
     named_item_data_set.add(NamedItemData{name, discrete_function.cellValues()});
   } else {
+    static_assert(is_discrete_function_P0_vector_v<DiscreteFunctionType>, "unexpected discrete function type");
     named_item_data_set.add(NamedItemData{name, discrete_function.cellArrays()});
   }
 }
 
-template <size_t Dimension, template <size_t DimensionT, typename DataTypeT> typename DiscreteFunctionType>
-void
-WriterBase::_registerDiscreteFunction(const std::string& name,
-                                      const IDiscreteFunction& i_discrete_function,
-                                      OutputNamedItemDataSet& named_item_data_set)
-{
-  const ASTNodeDataType& data_type = i_discrete_function.dataType();
-  switch (data_type) {
-  case ASTNodeDataType::bool_t: {
-    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, bool>&>(i_discrete_function),
-                              named_item_data_set);
-    break;
-  }
-  case ASTNodeDataType::unsigned_int_t: {
-    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, uint64_t>&>(i_discrete_function),
-                              named_item_data_set);
-    break;
-  }
-  case ASTNodeDataType::int_t: {
-    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, int64_t>&>(i_discrete_function),
-                              named_item_data_set);
-    break;
-  }
-  case ASTNodeDataType::double_t: {
-    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, double>&>(i_discrete_function),
-                              named_item_data_set);
-    break;
-  }
-  case ASTNodeDataType::vector_t: {
-    if constexpr (DiscreteFunctionType<Dimension, double>::handled_data_type ==
-                  IDiscreteFunction::HandledItemDataType::vector) {
-      throw UnexpectedError("invalid data type for vector data");
-    } else {
-      switch (data_type.dimension()) {
-      case 1: {
-        _registerDiscreteFunction(name,
-                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<1, double>>&>(
-                                    i_discrete_function),
-                                  named_item_data_set);
-        break;
-      }
-      case 2: {
-        _registerDiscreteFunction(name,
-                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<2, double>>&>(
-                                    i_discrete_function),
-                                  named_item_data_set);
-        break;
-      }
-      case 3: {
-        _registerDiscreteFunction(name,
-                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<3, double>>&>(
-                                    i_discrete_function),
-                                  named_item_data_set);
-        break;
-      }
-      default: {
-        throw UnexpectedError("invalid vector dimension");
-      }
-      }
-    }
-    break;
-  }
-  case ASTNodeDataType::matrix_t: {
-    if constexpr (DiscreteFunctionType<Dimension, double>::handled_data_type ==
-                  IDiscreteFunction::HandledItemDataType::vector) {
-      throw UnexpectedError("invalid data type for vector data");
-    } else {
-      Assert(data_type.numberOfRows() == data_type.numberOfColumns(), "invalid matrix dimensions");
-      switch (data_type.numberOfRows()) {
-      case 1: {
-        _registerDiscreteFunction(name,
-                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<1, 1, double>>&>(
-                                    i_discrete_function),
-                                  named_item_data_set);
-        break;
-      }
-      case 2: {
-        _registerDiscreteFunction(name,
-                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<2, 2, double>>&>(
-                                    i_discrete_function),
-                                  named_item_data_set);
-        break;
-      }
-      case 3: {
-        _registerDiscreteFunction(name,
-                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<3, 3, double>>&>(
-                                    i_discrete_function),
-                                  named_item_data_set);
-        break;
-      }
-      default: {
-        throw UnexpectedError("invalid matrix dimension");
-      }
-      }
-    }
-    break;
-  }
-  default: {
-    throw UnexpectedError("invalid data type " + dataTypeName(data_type));
-  }
-  }
-}
-
-template <template <size_t Dimension, typename DataType> typename DiscreteFunctionType>
-void
-WriterBase::_registerDiscreteFunction(const NamedDiscreteFunction& named_discrete_function,
-                                      OutputNamedItemDataSet& named_item_data_set)
-{
-  const IDiscreteFunction& i_discrete_function = *named_discrete_function.discreteFunction();
-  const std::string& name                      = named_discrete_function.name();
-  switch (i_discrete_function.mesh()->dimension()) {
-  case 1: {
-    _registerDiscreteFunction<1, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
-    break;
-  }
-  case 2: {
-    _registerDiscreteFunction<2, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
-    break;
-  }
-  case 3: {
-    _registerDiscreteFunction<3, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
-    break;
-  }
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-  }
-}
-
 void
 WriterBase::_checkConnectivity(
   const std::shared_ptr<const IMesh>& mesh,
@@ -211,7 +83,11 @@ WriterBase::_checkMesh(const std::shared_ptr<const IMesh>& mesh,
       const NamedDiscreteFunction& named_discrete_function =
         dynamic_cast<const NamedDiscreteFunction&>(*named_discrete_data);
 
-      if (mesh != named_discrete_function.discreteFunction()->mesh()) {
+      std::shared_ptr<const IMesh> discrete_function_mesh =
+        std::visit([](auto&& f) { return f.mesh(); },
+                   named_discrete_function.discreteFunctionVariant()->discreteFunction());
+
+      if (mesh != discrete_function_mesh) {
         std::ostringstream error_msg;
         error_msg << "The variable " << rang::fgB::yellow << named_discrete_function.name() << rang::fg::reset
                   << " is not defined on the provided mesh\n";
@@ -237,7 +113,8 @@ WriterBase::_getMesh(const std::vector<std::shared_ptr<const INamedDiscreteData>
       const NamedDiscreteFunction& named_discrete_function =
         dynamic_cast<const NamedDiscreteFunction&>(*named_discrete_data);
 
-      std::shared_ptr mesh = named_discrete_function.discreteFunction()->mesh();
+      std::shared_ptr mesh = std::visit([&](auto&& f) { return f.mesh(); },
+                                        named_discrete_function.discreteFunctionVariant()->discreteFunction());
       mesh_set[mesh]       = named_discrete_function.name();
 
       switch (mesh->dimension()) {
@@ -317,24 +194,12 @@ WriterBase::_getOutputNamedItemDataSet(
       const NamedDiscreteFunction& named_discrete_function =
         dynamic_cast<const NamedDiscreteFunction&>(*named_discrete_data);
 
-      const IDiscreteFunction& i_discrete_function = *named_discrete_function.discreteFunction();
+      const std::string& name = named_discrete_function.name();
 
-      switch (i_discrete_function.descriptor().type()) {
-      case DiscreteFunctionType::P0: {
-        WriterBase::_registerDiscreteFunction<DiscreteFunctionP0>(named_discrete_function, named_item_data_set);
-        break;
-      }
-      case DiscreteFunctionType::P0Vector: {
-        WriterBase::_registerDiscreteFunction<DiscreteFunctionP0Vector>(named_discrete_function, named_item_data_set);
-        break;
-      }
-      default: {
-        std::ostringstream error_msg;
-        error_msg << "the type of discrete function of " << rang::fgB::blue << named_discrete_data->name()
-                  << rang::style::reset << " is not supported";
-        throw NormalError(error_msg.str());
-      }
-      }
+      const DiscreteFunctionVariant& discrete_function_variant = *named_discrete_function.discreteFunctionVariant();
+
+      std::visit([&](auto&& f) { WriterBase::_registerDiscreteFunction(name, f, named_item_data_set); },
+                 discrete_function_variant.discreteFunction());
       break;
     }
     case INamedDiscreteData::Type::item_value: {
diff --git a/src/output/WriterBase.hpp b/src/output/WriterBase.hpp
index 339cd7645c8cbdc903357cee6000d1c6ab45dda3..7fae994d78ed0e97be8f10d63097464280c739fc 100644
--- a/src/output/WriterBase.hpp
+++ b/src/output/WriterBase.hpp
@@ -10,7 +10,6 @@
 
 class IMesh;
 class OutputNamedItemDataSet;
-class IDiscreteFunction;
 class NamedDiscreteFunction;
 
 class WriterBase : public IWriter
@@ -90,12 +89,6 @@ class WriterBase : public IWriter
   template <typename DiscreteFunctionType>
   static void _registerDiscreteFunction(const std::string& name, const DiscreteFunctionType&, OutputNamedItemDataSet&);
 
-  template <size_t Dimension, template <size_t DimensionT, typename DataTypeT> typename DiscreteFunctionType>
-  static void _registerDiscreteFunction(const std::string& name, const IDiscreteFunction&, OutputNamedItemDataSet&);
-
-  template <template <size_t DimensionT, typename DataTypeT> typename DiscreteFunctionType>
-  static void _registerDiscreteFunction(const NamedDiscreteFunction&, OutputNamedItemDataSet&);
-
  protected:
   void _checkConnectivity(const std::shared_ptr<const IMesh>& mesh,
                           const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;
diff --git a/src/scheme/AcousticSolver.cpp b/src/scheme/AcousticSolver.cpp
index 589b1d380d6ba1fbac10a61290552fe85ab1d193..98e26a68959865636e73356fa442e79357dc8251 100644
--- a/src/scheme/AcousticSolver.cpp
+++ b/src/scheme/AcousticSolver.cpp
@@ -13,7 +13,6 @@
 #include <scheme/ExternalBoundaryConditionDescriptor.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/IBoundaryConditionDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
 #include <utils/Socket.hpp>
@@ -23,7 +22,7 @@
 
 template <size_t Dimension>
 double
-acoustic_dt(const DiscreteFunctionP0<Dimension, double>& c)
+acoustic_dt(const DiscreteFunctionP0<Dimension, const double>& c)
 {
   const Mesh<Connectivity<Dimension>>& mesh = dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*c.mesh());
 
@@ -38,23 +37,19 @@ acoustic_dt(const DiscreteFunctionP0<Dimension, double>& c)
 }
 
 double
-acoustic_dt(const std::shared_ptr<const IDiscreteFunction>& c)
+acoustic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c)
 {
-  if ((c->descriptor().type() != DiscreteFunctionType::P0) or (c->dataType() != ASTNodeDataType::double_t)) {
-    throw NormalError("invalid discrete function type");
-  }
-
-  std::shared_ptr mesh = c->mesh();
+  std::shared_ptr mesh = getCommonMesh({c});
 
   switch (mesh->dimension()) {
   case 1: {
-    return acoustic_dt(dynamic_cast<const DiscreteFunctionP0<1, double>&>(*c));
+    return acoustic_dt(c->get<DiscreteFunctionP0<1, const double>>());
   }
   case 2: {
-    return acoustic_dt(dynamic_cast<const DiscreteFunctionP0<2, double>&>(*c));
+    return acoustic_dt(c->get<DiscreteFunctionP0<2, const double>>());
   }
   case 3: {
-    return acoustic_dt(dynamic_cast<const DiscreteFunctionP0<3, double>&>(*c));
+    return acoustic_dt(c->get<DiscreteFunctionP0<2, const double>>());
   }
   default: {
     throw UnexpectedError("invalid mesh dimension");
@@ -72,8 +67,8 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
   using MeshType     = Mesh<Connectivity<Dimension>>;
   using MeshDataType = MeshData<Dimension>;
 
-  using DiscreteScalarFunction = DiscreteFunctionP0<Dimension, double>;
-  using DiscreteVectorFunction = DiscreteFunctionP0<Dimension, Rd>;
+  using DiscreteScalarFunction = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteVectorFunction = DiscreteFunctionP0<Dimension, const Rd>;
 
   class FixedBoundaryCondition;
   class PressureBoundaryCondition;
@@ -381,26 +376,26 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
  public:
   std::tuple<const std::shared_ptr<const ItemValueVariant>, const std::shared_ptr<const SubItemValuePerItemVariant>>
   compute_fluxes(const SolverType& solver_type,
-                 const std::shared_ptr<const IDiscreteFunction>& i_rho,
-                 const std::shared_ptr<const IDiscreteFunction>& i_c,
-                 const std::shared_ptr<const IDiscreteFunction>& i_u,
-                 const std::shared_ptr<const IDiscreteFunction>& i_p,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& rho_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& c_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& p_v,
                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
   {
-    std::shared_ptr i_mesh = getCommonMesh({i_rho, i_c, i_u, i_p});
+    std::shared_ptr i_mesh = getCommonMesh({rho_v, c_v, u_v, p_v});
     if (not i_mesh) {
       throw NormalError("discrete functions are not defined on the same mesh");
     }
 
-    if (not checkDiscretizationType({i_rho, i_c, i_u, i_p}, DiscreteFunctionType::P0)) {
+    if (not checkDiscretizationType({rho_v, c_v, u_v, p_v}, DiscreteFunctionType::P0)) {
       throw NormalError("acoustic solver expects P0 functions");
     }
 
     const MeshType& mesh              = dynamic_cast<const MeshType&>(*i_mesh);
-    const DiscreteScalarFunction& rho = dynamic_cast<const DiscreteScalarFunction&>(*i_rho);
-    const DiscreteScalarFunction& c   = dynamic_cast<const DiscreteScalarFunction&>(*i_c);
-    const DiscreteVectorFunction& u   = dynamic_cast<const DiscreteVectorFunction&>(*i_u);
-    const DiscreteScalarFunction& p   = dynamic_cast<const DiscreteScalarFunction&>(*i_p);
+    const DiscreteScalarFunction& rho = rho_v->get<DiscreteScalarFunction>();
+    const DiscreteScalarFunction& c   = c_v->get<DiscreteScalarFunction>();
+    const DiscreteVectorFunction& u   = u_v->get<DiscreteVectorFunction>();
+    const DiscreteScalarFunction& p   = p_v->get<DiscreteScalarFunction>();
 
     NodeValuePerCell<const Rdxd> Ajr = this->_computeAjr(solver_type, mesh, rho * c);
 
@@ -422,14 +417,14 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
   }
 
   std::tuple<std::shared_ptr<const IMesh>,
-             std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>,
-             std::shared_ptr<const DiscreteFunctionP0<Dimension, Rd>>,
-             std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>>
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
   apply_fluxes(const double& dt,
                const MeshType& mesh,
-               const DiscreteFunctionP0<Dimension, double>& rho,
-               const DiscreteFunctionP0<Dimension, Rd>& u,
-               const DiscreteFunctionP0<Dimension, double>& E,
+               const DiscreteScalarFunction& rho,
+               const DiscreteVectorFunction& u,
+               const DiscreteScalarFunction& E,
                const NodeValue<const Rd>& ur,
                const NodeValuePerCell<const Rd>& Fjr) const
   {
@@ -473,51 +468,51 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
     parallel_for(
       mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
 
-    return {new_mesh, std::make_shared<DiscreteScalarFunction>(new_mesh, new_rho),
-            std::make_shared<DiscreteVectorFunction>(new_mesh, new_u),
-            std::make_shared<DiscreteScalarFunction>(new_mesh, new_E)};
+    return {new_mesh, std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E))};
   }
 
   std::tuple<std::shared_ptr<const IMesh>,
-             std::shared_ptr<const IDiscreteFunction>,
-             std::shared_ptr<const IDiscreteFunction>,
-             std::shared_ptr<const IDiscreteFunction>>
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
   apply_fluxes(const double& dt,
-               const std::shared_ptr<const IDiscreteFunction>& rho,
-               const std::shared_ptr<const IDiscreteFunction>& u,
-               const std::shared_ptr<const IDiscreteFunction>& E,
+               const std::shared_ptr<const DiscreteFunctionVariant>& rho_v,
+               const std::shared_ptr<const DiscreteFunctionVariant>& u_v,
+               const std::shared_ptr<const DiscreteFunctionVariant>& E_v,
                const std::shared_ptr<const ItemValueVariant>& ur,
                const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr) const
   {
-    std::shared_ptr i_mesh = getCommonMesh({rho, u, E});
+    std::shared_ptr i_mesh = getCommonMesh({rho_v, u_v, E_v});
     if (not i_mesh) {
       throw NormalError("discrete functions are not defined on the same mesh");
     }
 
-    if (not checkDiscretizationType({rho, u, E}, DiscreteFunctionType::P0)) {
+    if (not checkDiscretizationType({rho_v, u_v, E_v}, DiscreteFunctionType::P0)) {
       throw NormalError("acoustic solver expects P0 functions");
     }
 
-    return this->apply_fluxes(dt,                                                  //
-                              dynamic_cast<const MeshType&>(*i_mesh),              //
-                              dynamic_cast<const DiscreteScalarFunction&>(*rho),   //
-                              dynamic_cast<const DiscreteVectorFunction&>(*u),     //
-                              dynamic_cast<const DiscreteScalarFunction&>(*E),     //
-                              ur->get<NodeValue<const Rd>>(),                      //
+    return this->apply_fluxes(dt,                                       //
+                              dynamic_cast<const MeshType&>(*i_mesh),   //
+                              rho_v->get<DiscreteScalarFunction>(),     //
+                              u_v->get<DiscreteVectorFunction>(),       //
+                              E_v->get<DiscreteScalarFunction>(),       //
+                              ur->get<NodeValue<const Rd>>(),           //
                               Fjr->get<NodeValuePerCell<const Rd>>());
   }
 
   std::tuple<std::shared_ptr<const IMesh>,
-             std::shared_ptr<const IDiscreteFunction>,
-             std::shared_ptr<const IDiscreteFunction>,
-             std::shared_ptr<const IDiscreteFunction>>
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
   apply(const SolverType& solver_type,
         const double& dt,
-        const std::shared_ptr<const IDiscreteFunction>& rho,
-        const std::shared_ptr<const IDiscreteFunction>& u,
-        const std::shared_ptr<const IDiscreteFunction>& E,
-        const std::shared_ptr<const IDiscreteFunction>& c,
-        const std::shared_ptr<const IDiscreteFunction>& p,
+        const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+        const std::shared_ptr<const DiscreteFunctionVariant>& u,
+        const std::shared_ptr<const DiscreteFunctionVariant>& E,
+        const std::shared_ptr<const DiscreteFunctionVariant>& c,
+        const std::shared_ptr<const DiscreteFunctionVariant>& p,
         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
   {
     auto [ur, Fjr] = compute_fluxes(solver_type, rho, c, u, p, bc_descriptor_list);
diff --git a/src/scheme/AcousticSolver.hpp b/src/scheme/AcousticSolver.hpp
index 489b8e795d843f9677daf562214bd0266f803baa..36b62a32a1442b18cbe014a9e90ee28ffad08c8a 100644
--- a/src/scheme/AcousticSolver.hpp
+++ b/src/scheme/AcousticSolver.hpp
@@ -5,13 +5,13 @@
 #include <tuple>
 #include <vector>
 
-class IDiscreteFunction;
+class DiscreteFunctionVariant;
 class IBoundaryConditionDescriptor;
 class IMesh;
 class ItemValueVariant;
 class SubItemValuePerItemVariant;
 
-double acoustic_dt(const std::shared_ptr<const IDiscreteFunction>& c);
+double acoustic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c);
 
 class AcousticSolverHandler
 {
@@ -29,34 +29,34 @@ class AcousticSolverHandler
                        const std::shared_ptr<const SubItemValuePerItemVariant>>
     compute_fluxes(
       const SolverType& solver_type,
-      const std::shared_ptr<const IDiscreteFunction>& rho,
-      const std::shared_ptr<const IDiscreteFunction>& c,
-      const std::shared_ptr<const IDiscreteFunction>& u,
-      const std::shared_ptr<const IDiscreteFunction>& p,
+      const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+      const std::shared_ptr<const DiscreteFunctionVariant>& c,
+      const std::shared_ptr<const DiscreteFunctionVariant>& u,
+      const std::shared_ptr<const DiscreteFunctionVariant>& p,
       const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
 
     virtual std::tuple<std::shared_ptr<const IMesh>,
-                       std::shared_ptr<const IDiscreteFunction>,
-                       std::shared_ptr<const IDiscreteFunction>,
-                       std::shared_ptr<const IDiscreteFunction>>
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
     apply_fluxes(const double& dt,
-                 const std::shared_ptr<const IDiscreteFunction>& rho,
-                 const std::shared_ptr<const IDiscreteFunction>& u,
-                 const std::shared_ptr<const IDiscreteFunction>& E,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
                  const std::shared_ptr<const ItemValueVariant>& ur,
                  const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr) const = 0;
 
     virtual std::tuple<std::shared_ptr<const IMesh>,
-                       std::shared_ptr<const IDiscreteFunction>,
-                       std::shared_ptr<const IDiscreteFunction>,
-                       std::shared_ptr<const IDiscreteFunction>>
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
     apply(const SolverType& solver_type,
           const double& dt,
-          const std::shared_ptr<const IDiscreteFunction>& rho,
-          const std::shared_ptr<const IDiscreteFunction>& u,
-          const std::shared_ptr<const IDiscreteFunction>& E,
-          const std::shared_ptr<const IDiscreteFunction>& c,
-          const std::shared_ptr<const IDiscreteFunction>& p,
+          const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+          const std::shared_ptr<const DiscreteFunctionVariant>& u,
+          const std::shared_ptr<const DiscreteFunctionVariant>& E,
+          const std::shared_ptr<const DiscreteFunctionVariant>& c,
+          const std::shared_ptr<const DiscreteFunctionVariant>& p,
           const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
 
     IAcousticSolver()                  = default;
diff --git a/src/scheme/DiscreteFunctionIntegrator.cpp b/src/scheme/DiscreteFunctionIntegrator.cpp
index 93c836461ce14ae05b51b879cf90525f73a793eb..c77f7a2c87ece74da4f48902855e9f3a5f6742bc 100644
--- a/src/scheme/DiscreteFunctionIntegrator.cpp
+++ b/src/scheme/DiscreteFunctionIntegrator.cpp
@@ -3,10 +3,11 @@
 #include <language/utils/IntegrateCellValue.hpp>
 #include <mesh/MeshCellZone.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <utils/Exceptions.hpp>
 
 template <size_t Dimension, typename DataType, typename ValueType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionIntegrator::_integrateOnZoneList() const
 {
   static_assert(std::is_convertible_v<DataType, ValueType>);
@@ -61,11 +62,11 @@ DiscreteFunctionIntegrator::_integrateOnZoneList() const
   parallel_for(
     zone_cell_list.size(), PUGS_LAMBDA(const size_t i_cell) { cell_value[zone_cell_list[i_cell]] = array[i_cell]; });
 
-  return std::make_shared<DiscreteFunctionP0<Dimension, ValueType>>(p_mesh, cell_value);
+  return DiscreteFunctionP0<Dimension, ValueType>(p_mesh, cell_value);
 }
 
 template <size_t Dimension, typename DataType, typename ValueType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionIntegrator::_integrateGlobally() const
 {
   Assert(m_zone_list.size() == 0, "invalid call when zones are defined");
@@ -75,14 +76,13 @@ DiscreteFunctionIntegrator::_integrateGlobally() const
 
   static_assert(std::is_convertible_v<DataType, ValueType>);
 
-  return std::make_shared<
-    DiscreteFunctionP0<Dimension, ValueType>>(mesh,
-                                              IntegrateCellValue<ValueType(TinyVector<Dimension>)>::template integrate<
-                                                MeshType>(m_function_id, *m_quadrature_descriptor, *mesh));
+  return DiscreteFunctionP0<Dimension,
+                            ValueType>(mesh, IntegrateCellValue<ValueType(TinyVector<Dimension>)>::template integrate<
+                                               MeshType>(m_function_id, *m_quadrature_descriptor, *mesh));
 }
 
 template <size_t Dimension, typename DataType, typename ValueType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionIntegrator::_integrate() const
 {
   if (m_zone_list.size() == 0) {
@@ -93,7 +93,7 @@ DiscreteFunctionIntegrator::_integrate() const
 }
 
 template <size_t Dimension>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionIntegrator::_integrate() const
 {
   const auto& function_descriptor = m_function_id.descriptor();
@@ -168,10 +168,9 @@ DiscreteFunctionIntegrator::_integrate() const
   }
 }
 
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionIntegrator::integrate() const
 {
-  std::shared_ptr<IDiscreteFunction> discrete_function;
   switch (m_mesh->dimension()) {
   case 1: {
     return this->_integrate<1>();
diff --git a/src/scheme/DiscreteFunctionIntegrator.hpp b/src/scheme/DiscreteFunctionIntegrator.hpp
index 21a461d54561d67f1f745d3257ec76ee514d264a..e21beabf704b6ed1a7cd3ef4277f751036d274da 100644
--- a/src/scheme/DiscreteFunctionIntegrator.hpp
+++ b/src/scheme/DiscreteFunctionIntegrator.hpp
@@ -5,10 +5,11 @@
 #include <language/utils/FunctionSymbolId.hpp>
 #include <mesh/IMesh.hpp>
 #include <mesh/IZoneDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 
 #include <memory>
 
+class DiscreteFunctionVariant;
+
 class DiscreteFunctionIntegrator
 {
  private:
@@ -18,19 +19,19 @@ class DiscreteFunctionIntegrator
   const FunctionSymbolId m_function_id;
 
   template <size_t Dimension, typename DataType, typename ValueType = DataType>
-  std::shared_ptr<IDiscreteFunction> _integrateOnZoneList() const;
+  DiscreteFunctionVariant _integrateOnZoneList() const;
 
   template <size_t Dimension, typename DataType, typename ValueType = DataType>
-  std::shared_ptr<IDiscreteFunction> _integrateGlobally() const;
+  DiscreteFunctionVariant _integrateGlobally() const;
 
   template <size_t Dimension, typename DataType, typename ValueType = DataType>
-  std::shared_ptr<IDiscreteFunction> _integrate() const;
+  DiscreteFunctionVariant _integrate() const;
 
   template <size_t Dimension>
-  std::shared_ptr<IDiscreteFunction> _integrate() const;
+  DiscreteFunctionVariant _integrate() const;
 
  public:
-  std::shared_ptr<IDiscreteFunction> integrate() const;
+  DiscreteFunctionVariant integrate() const;
 
   DiscreteFunctionIntegrator(const std::shared_ptr<const IMesh>& mesh,
                              const std::shared_ptr<const IQuadratureDescriptor>& quadrature_descriptor,
diff --git a/src/scheme/DiscreteFunctionInterpoler.cpp b/src/scheme/DiscreteFunctionInterpoler.cpp
index e19073f1e93bd70d649f66f7ca0bd2f49ef8a102..c570e67e5ae9b417a60096e5ccb34a025e7a94a4 100644
--- a/src/scheme/DiscreteFunctionInterpoler.cpp
+++ b/src/scheme/DiscreteFunctionInterpoler.cpp
@@ -3,10 +3,11 @@
 #include <language/utils/InterpolateItemValue.hpp>
 #include <mesh/MeshCellZone.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <utils/Exceptions.hpp>
 
 template <size_t Dimension, typename DataType, typename ValueType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionInterpoler::_interpolateOnZoneList() const
 {
   static_assert(std::is_convertible_v<DataType, ValueType>);
@@ -61,11 +62,11 @@ DiscreteFunctionInterpoler::_interpolateOnZoneList() const
   parallel_for(
     zone_cell_list.size(), PUGS_LAMBDA(const size_t i_cell) { cell_value[zone_cell_list[i_cell]] = array[i_cell]; });
 
-  return std::make_shared<DiscreteFunctionP0<Dimension, ValueType>>(p_mesh, cell_value);
+  return DiscreteFunctionP0<Dimension, ValueType>(p_mesh, cell_value);
 }
 
 template <size_t Dimension, typename DataType, typename ValueType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionInterpoler::_interpolateGlobally() const
 {
   Assert(m_zone_list.size() == 0, "invalid call when zones are defined");
@@ -75,10 +76,9 @@ DiscreteFunctionInterpoler::_interpolateGlobally() const
   MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*p_mesh);
 
   if constexpr (std::is_same_v<DataType, ValueType>) {
-    return std::make_shared<
-      DiscreteFunctionP0<Dimension, ValueType>>(p_mesh,
-                                                InterpolateItemValue<DataType(TinyVector<Dimension>)>::
-                                                  template interpolate<ItemType::cell>(m_function_id, mesh_data.xj()));
+    return DiscreteFunctionP0<Dimension, ValueType>(p_mesh, InterpolateItemValue<DataType(TinyVector<Dimension>)>::
+                                                              template interpolate<ItemType::cell>(m_function_id,
+                                                                                                   mesh_data.xj()));
   } else {
     static_assert(std::is_convertible_v<DataType, ValueType>);
 
@@ -91,12 +91,12 @@ DiscreteFunctionInterpoler::_interpolateGlobally() const
     parallel_for(
       p_mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_data[cell_id]; });
 
-    return std::make_shared<DiscreteFunctionP0<Dimension, ValueType>>(p_mesh, cell_value);
+    return DiscreteFunctionP0<Dimension, ValueType>(p_mesh, cell_value);
   }
 }
 
 template <size_t Dimension, typename DataType, typename ValueType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionInterpoler::_interpolate() const
 {
   if (m_zone_list.size() == 0) {
@@ -107,7 +107,7 @@ DiscreteFunctionInterpoler::_interpolate() const
 }
 
 template <size_t Dimension>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionInterpoler::_interpolate() const
 {
   const auto& function_descriptor = m_function_id.descriptor();
@@ -182,7 +182,7 @@ DiscreteFunctionInterpoler::_interpolate() const
   }
 }
 
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionInterpoler::interpolate() const
 {
   switch (m_mesh->dimension()) {
diff --git a/src/scheme/DiscreteFunctionInterpoler.hpp b/src/scheme/DiscreteFunctionInterpoler.hpp
index 295c2fd9a540269bf484f485716131b1346964e1..0f436d43cf2c347fc437e05016d798bc28aeed77 100644
--- a/src/scheme/DiscreteFunctionInterpoler.hpp
+++ b/src/scheme/DiscreteFunctionInterpoler.hpp
@@ -4,9 +4,10 @@
 #include <language/utils/FunctionSymbolId.hpp>
 #include <mesh/IMesh.hpp>
 #include <mesh/IZoneDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 
+class DiscreteFunctionVariant;
+
 #include <memory>
 
 class DiscreteFunctionInterpoler
@@ -18,19 +19,19 @@ class DiscreteFunctionInterpoler
   const FunctionSymbolId m_function_id;
 
   template <size_t Dimension, typename DataType, typename ValueType = DataType>
-  std::shared_ptr<IDiscreteFunction> _interpolateOnZoneList() const;
+  DiscreteFunctionVariant _interpolateOnZoneList() const;
 
   template <size_t Dimension, typename DataType, typename ValueType = DataType>
-  std::shared_ptr<IDiscreteFunction> _interpolateGlobally() const;
+  DiscreteFunctionVariant _interpolateGlobally() const;
 
   template <size_t Dimension, typename DataType, typename ValueType = DataType>
-  std::shared_ptr<IDiscreteFunction> _interpolate() const;
+  DiscreteFunctionVariant _interpolate() const;
 
   template <size_t Dimension>
-  std::shared_ptr<IDiscreteFunction> _interpolate() const;
+  DiscreteFunctionVariant _interpolate() const;
 
  public:
-  std::shared_ptr<IDiscreteFunction> interpolate() const;
+  DiscreteFunctionVariant interpolate() const;
 
   DiscreteFunctionInterpoler(const std::shared_ptr<const IMesh>& mesh,
                              const std::shared_ptr<const IDiscreteFunctionDescriptor>& discrete_function_descriptor,
diff --git a/src/scheme/DiscreteFunctionP0.hpp b/src/scheme/DiscreteFunctionP0.hpp
index d1bd72c3394346db10898cd83f1c609974f17697..1fb2ce14cee3f42e6c27c262e3ba6deefb5b555d 100644
--- a/src/scheme/DiscreteFunctionP0.hpp
+++ b/src/scheme/DiscreteFunctionP0.hpp
@@ -1,7 +1,7 @@
 #ifndef DISCRETE_FUNCTION_P0_HPP
 #define DISCRETE_FUNCTION_P0_HPP
 
-#include <scheme/IDiscreteFunction.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
 
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemValueUtils.hpp>
@@ -11,14 +11,12 @@
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 
 template <size_t Dimension, typename DataType>
-class DiscreteFunctionP0 : public IDiscreteFunction
+class DiscreteFunctionP0
 {
  public:
   using data_type = DataType;
   using MeshType  = Mesh<Connectivity<Dimension>>;
 
-  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>>;
 
@@ -31,9 +29,9 @@ class DiscreteFunctionP0 : public IDiscreteFunction
  public:
   PUGS_INLINE
   ASTNodeDataType
-  dataType() const final
+  dataType() const
   {
-    return ast_node_data_type_from<DataType>;
+    return ast_node_data_type_from<std::remove_const_t<DataType>>;
   }
 
   PUGS_INLINE
@@ -52,7 +50,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
 
   PUGS_INLINE
   const IDiscreteFunctionDescriptor&
-  descriptor() const final
+  descriptor() const
   {
     return m_discrete_function_descriptor;
   }
@@ -450,7 +448,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   atan2(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -463,7 +461,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   atan2(const double a, const DiscreteFunctionP0& f)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -475,7 +473,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   atan2(const DiscreteFunctionP0& f, const double a)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -487,7 +485,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   pow(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -500,7 +498,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   pow(const double a, const DiscreteFunctionP0& f)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -512,7 +510,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   pow(const DiscreteFunctionP0& f, const double a)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -527,7 +525,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   det(const DiscreteFunctionP0& A)
   {
-    static_assert(is_tiny_matrix_v<DataType>);
+    static_assert(is_tiny_matrix_v<std::decay_t<DataType>>);
     Assert(A.m_cell_values.isBuilt());
     DiscreteFunctionP0<Dimension, double> result{A.m_mesh};
     parallel_for(
@@ -539,7 +537,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   trace(const DiscreteFunctionP0& A)
   {
-    static_assert(is_tiny_matrix_v<DataType>);
+    static_assert(is_tiny_matrix_v<std::decay_t<DataType>>);
     Assert(A.m_cell_values.isBuilt());
     DiscreteFunctionP0<Dimension, double> result{A.m_mesh};
     parallel_for(
@@ -548,26 +546,26 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   inverse(const DiscreteFunctionP0& A)
   {
-    static_assert(is_tiny_matrix_v<DataType>);
+    static_assert(is_tiny_matrix_v<std::decay_t<DataType>>);
     static_assert(DataType::NumberOfRows == DataType::NumberOfColumns, "cannot compute inverse of non square matrices");
     Assert(A.m_cell_values.isBuilt());
-    DiscreteFunctionP0 result{A.m_mesh};
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{A.m_mesh};
     parallel_for(
       A.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = inverse(A[cell_id]); });
 
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   transpose(const DiscreteFunctionP0& A)
   {
-    static_assert(is_tiny_matrix_v<DataType>);
+    static_assert(is_tiny_matrix_v<std::decay_t<DataType>>);
     static_assert(DataType::NumberOfRows == DataType::NumberOfColumns, "cannot compute inverse of non square matrices");
     Assert(A.m_cell_values.isBuilt());
-    DiscreteFunctionP0 result{A.m_mesh};
+    DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>> result{A.m_mesh};
     parallel_for(
       A.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = transpose(A[cell_id]); });
 
@@ -577,7 +575,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   dot(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
   {
-    static_assert(is_tiny_vector_v<DataType>);
+    static_assert(is_tiny_vector_v<std::decay_t<DataType>>);
     Assert(f.m_cell_values.isBuilt() and g.m_cell_values.isBuilt());
     Assert(f.m_mesh == g.m_mesh);
     DiscreteFunctionP0<Dimension, double> result{f.m_mesh};
@@ -590,7 +588,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   dot(const DiscreteFunctionP0& f, const DataType& a)
   {
-    static_assert(is_tiny_vector_v<DataType>);
+    static_assert(is_tiny_vector_v<std::decay_t<DataType>>);
     Assert(f.m_cell_values.isBuilt());
     DiscreteFunctionP0<Dimension, double> result{f.m_mesh};
     parallel_for(
@@ -602,7 +600,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
   PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   dot(const DataType& a, const DiscreteFunctionP0& f)
   {
-    static_assert(is_tiny_vector_v<DataType>);
+    static_assert(is_tiny_vector_v<std::decay_t<DataType>>);
     Assert(f.m_cell_values.isBuilt());
     DiscreteFunctionP0<Dimension, double> result{f.m_mesh};
     parallel_for(
@@ -620,7 +618,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return min(f.m_cell_values);
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   min(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -633,7 +631,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   min(const double a, const DiscreteFunctionP0& f)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -645,7 +643,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   min(const DiscreteFunctionP0& f, const double a)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -666,7 +664,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return max(f.m_cell_values);
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   max(const DiscreteFunctionP0& f, const DiscreteFunctionP0& g)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -679,7 +677,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   max(const double a, const DiscreteFunctionP0& f)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -691,7 +689,7 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
-  PUGS_INLINE friend DiscreteFunctionP0
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
   max(const DiscreteFunctionP0& f, const double a)
   {
     static_assert(std::is_arithmetic_v<DataType>);
@@ -720,16 +718,23 @@ class DiscreteFunctionP0 : public IDiscreteFunction
 
    public:
     PUGS_INLINE
-    operator data_type()
+    operator std::decay_t<data_type>()
     {
-      data_type reduced_value;
+      std::decay_t<data_type> reduced_value;
+      if constexpr (std::is_arithmetic_v<std::decay_t<data_type>>) {
+        reduced_value = 0;
+      } else {
+        static_assert(is_tiny_vector_v<std::decay_t<data_type>> or is_tiny_matrix_v<std::decay_t<data_type>>,
+                      "invalid data type");
+        reduced_value = zero;
+      }
       parallel_reduce(m_cell_volume.numberOfItems(), *this, reduced_value);
       return reduced_value;
     }
 
     PUGS_INLINE
     void
-    operator()(const CellId& cell_id, data_type& data) const
+    operator()(const CellId& cell_id, std::decay_t<data_type>& data) const
     {
       if (m_cell_is_owned[cell_id]) {
         data += m_cell_volume[cell_id] * m_function[cell_id];
@@ -738,19 +743,20 @@ class DiscreteFunctionP0 : public IDiscreteFunction
 
     PUGS_INLINE
     void
-    join(volatile data_type& dst, const volatile data_type& src) const
+    join(volatile std::decay_t<data_type>& dst, const volatile std::decay_t<data_type>& src) const
     {
       dst += src;
     }
 
     PUGS_INLINE
     void
-    init(data_type& value) const
+    init(std::decay_t<data_type>& value) const
     {
       if constexpr (std::is_arithmetic_v<data_type>) {
         value = 0;
       } else {
-        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
+        static_assert(is_tiny_vector_v<std::decay_t<data_type>> or is_tiny_matrix_v<std::decay_t<data_type>>,
+                      "invalid data type");
         value = zero;
       }
     }
diff --git a/src/scheme/DiscreteFunctionP0Vector.hpp b/src/scheme/DiscreteFunctionP0Vector.hpp
index 97593e1b3c5a9a599fc0c17f7edca7a839f2b5ef..7bd833f344ffd29f6f47f6fa0d0bff13a2407339 100644
--- a/src/scheme/DiscreteFunctionP0Vector.hpp
+++ b/src/scheme/DiscreteFunctionP0Vector.hpp
@@ -1,8 +1,6 @@
 #ifndef DISCRETE_FUNCTION_P0_VECTOR_HPP
 #define DISCRETE_FUNCTION_P0_VECTOR_HPP
 
-#include <scheme/IDiscreteFunction.hpp>
-
 #include <algebra/Vector.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemArray.hpp>
@@ -14,14 +12,12 @@
 #include <utils/Exceptions.hpp>
 
 template <size_t Dimension, typename DataType>
-class DiscreteFunctionP0Vector : public IDiscreteFunction
+class DiscreteFunctionP0Vector
 {
  public:
   using data_type = DataType;
   using MeshType  = Mesh<Connectivity<Dimension>>;
 
-  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>>;
 
@@ -36,9 +32,9 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
  public:
   PUGS_INLINE
   ASTNodeDataType
-  dataType() const final
+  dataType() const
   {
-    return ast_node_data_type_from<data_type>;
+    return ast_node_data_type_from<std::remove_const_t<data_type>>;
   }
 
   PUGS_INLINE
@@ -64,7 +60,7 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
 
   PUGS_INLINE
   const IDiscreteFunctionDescriptor&
-  descriptor() const final
+  descriptor() const
   {
     return m_discrete_function_descriptor;
   }
diff --git a/src/scheme/DiscreteFunctionUtils.cpp b/src/scheme/DiscreteFunctionUtils.cpp
index fe861c0868b6bf620696cba09d8ac72bcf6aa948..ba2416a9ab08e03e75bb0824f32930c105cd366c 100644
--- a/src/scheme/DiscreteFunctionUtils.cpp
+++ b/src/scheme/DiscreteFunctionUtils.cpp
@@ -4,128 +4,106 @@
 #include <mesh/IMesh.hpp>
 #include <mesh/Mesh.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <utils/Stringify.hpp>
 
-template <size_t Dimension, typename DataType>
-std::shared_ptr<const IDiscreteFunction>
-shallowCopy(const std::shared_ptr<const Mesh<Connectivity<Dimension>>>& mesh,
-            const std::shared_ptr<const DiscreteFunctionP0<Dimension, DataType>>& discrete_function)
+std::shared_ptr<const IMesh>
+getCommonMesh(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list)
 {
-  Assert(mesh->shared_connectivity() ==
-           dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*discrete_function->mesh()).shared_connectivity(),
-         "connectivities should be the same");
+  std::shared_ptr<const IMesh> i_mesh;
+  bool is_same_mesh = true;
+  for (const auto& discrete_function_variant : discrete_function_variant_list) {
+    std::visit(
+      [&](auto&& discrete_function) {
+        if (not i_mesh.use_count()) {
+          i_mesh = discrete_function.mesh();
+        } else {
+          if (i_mesh != discrete_function.mesh()) {
+            is_same_mesh = false;
+          }
+        }
+      },
+      discrete_function_variant->discreteFunction());
+  }
+  if (not is_same_mesh) {
+    i_mesh.reset();
+  }
+  return i_mesh;
+}
+
+bool
+hasSameMesh(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list)
+{
+  std::shared_ptr<const IMesh> i_mesh;
+  bool is_same_mesh = true;
+  for (const auto& discrete_function_variant : discrete_function_variant_list) {
+    std::visit(
+      [&](auto&& discrete_function) {
+        if (not i_mesh.use_count()) {
+          i_mesh = discrete_function.mesh();
+        } else {
+          if (i_mesh != discrete_function.mesh()) {
+            is_same_mesh = false;
+          }
+        }
+      },
+      discrete_function_variant->discreteFunction());
+  }
 
-  return std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh, discrete_function->cellValues());
+  return is_same_mesh;
 }
 
-template <size_t Dimension>
-std::shared_ptr<const IDiscreteFunction>
-shallowCopy(const std::shared_ptr<const Mesh<Connectivity<Dimension>>>& mesh,
-            const std::shared_ptr<const IDiscreteFunction>& discrete_function)
+template <typename MeshType, typename DiscreteFunctionT>
+std::shared_ptr<const DiscreteFunctionVariant>
+shallowCopy(const std::shared_ptr<const MeshType>& mesh, const DiscreteFunctionT& f)
 {
-  const std::shared_ptr function_mesh =
-    std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(discrete_function->mesh());
+  const std::shared_ptr function_mesh = std::dynamic_pointer_cast<const MeshType>(f.mesh());
 
   if (mesh->shared_connectivity() != function_mesh->shared_connectivity()) {
     throw NormalError("cannot shallow copy when connectivity changes");
   }
 
-  switch (discrete_function->descriptor().type()) {
-  case DiscreteFunctionType::P0: {
-    switch (discrete_function->dataType()) {
-    case ASTNodeDataType::double_t: {
-      return shallowCopy(mesh,
-                         std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, double>>(discrete_function));
+  if constexpr (std::is_same_v<MeshType, typename DiscreteFunctionT::MeshType>) {
+    if constexpr (is_discrete_function_P0_v<DiscreteFunctionT>) {
+      return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionT(mesh, f.cellValues()));
+    } else if constexpr (is_discrete_function_P0_vector_v<DiscreteFunctionT>) {
+      return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionT(mesh, f.cellArrays()));
+    } else {
+      throw UnexpectedError("invalid discrete function type");
     }
-    case ASTNodeDataType::vector_t: {
-      switch (discrete_function->dataType().dimension()) {
-      case 1: {
-        return shallowCopy(mesh, std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(
-                                   discrete_function));
-      }
-      case 2: {
-        return shallowCopy(mesh, std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(
-                                   discrete_function));
-      }
-      case 3: {
-        return shallowCopy(mesh, std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(
-                                   discrete_function));
-      }
-        // LCOV_EXCL_START
-      default: {
-        throw UnexpectedError("invalid data vector dimension: " + stringify(discrete_function->dataType().dimension()));
-      }
-        // LCOV_EXCL_STOP
-      }
-    }
-    case ASTNodeDataType::matrix_t: {
-      if (discrete_function->dataType().numberOfRows() != discrete_function->dataType().numberOfColumns()) {
-        // LCOV_EXCL_START
-        throw UnexpectedError(
-          "invalid data matrix dimensions: " + stringify(discrete_function->dataType().numberOfRows()) + "x" +
-          stringify(discrete_function->dataType().numberOfColumns()));
-        // LCOV_EXCL_STOP
+  } else {
+    throw UnexpectedError("invalid mesh types");
+  }
+}
+
+std::shared_ptr<const DiscreteFunctionVariant>
+shallowCopy(const std::shared_ptr<const IMesh>& mesh,
+            const std::shared_ptr<const DiscreteFunctionVariant>& discrete_function_variant)
+{
+  return std::visit(
+    [&](auto&& f) {
+      if (mesh == f.mesh()) {
+        return discrete_function_variant;
+      } else if (mesh->dimension() != f.mesh()->dimension()) {
+        throw NormalError("incompatible mesh dimensions");
       }
-      switch (discrete_function->dataType().numberOfRows()) {
+
+      switch (mesh->dimension()) {
       case 1: {
-        return shallowCopy(mesh, std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(
-                                   discrete_function));
+        return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<1>>>(mesh), f);
       }
       case 2: {
-        return shallowCopy(mesh, std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(
-                                   discrete_function));
+        return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<2>>>(mesh), f);
       }
       case 3: {
-        return shallowCopy(mesh, std::dynamic_pointer_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(
-                                   discrete_function));
+        return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<3>>>(mesh), f);
       }
         // LCOV_EXCL_START
       default: {
-        throw UnexpectedError(
-          "invalid data matrix dimensions: " + stringify(discrete_function->dataType().numberOfRows()) + "x" +
-          stringify(discrete_function->dataType().numberOfColumns()));
+        throw UnexpectedError("invalid mesh dimension");
       }
         // LCOV_EXCL_STOP
       }
-    }
-      // LCOV_EXCL_START
-    default: {
-      throw UnexpectedError("invalid kind of P0 function: invalid data type");
-    }
-      // LCOV_EXCL_STOP
-    }
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid discretization type");
-  }
-    // LCOV_EXCL_STOP
-  }
-}
-
-std::shared_ptr<const IDiscreteFunction>
-shallowCopy(const std::shared_ptr<const IMesh>& mesh, const std::shared_ptr<const IDiscreteFunction>& discrete_function)
-{
-  if (mesh == discrete_function->mesh()) {
-    return discrete_function;
-  } else if (mesh->dimension() != discrete_function->mesh()->dimension()) {
-    throw NormalError("incompatible mesh dimensions");
-  }
-
-  switch (mesh->dimension()) {
-  case 1: {
-    return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<1>>>(mesh), discrete_function);
-  }
-  case 2: {
-    return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<2>>>(mesh), discrete_function);
-  }
-  case 3: {
-    return shallowCopy(std::dynamic_pointer_cast<const Mesh<Connectivity<3>>>(mesh), discrete_function);
-  }
-    // LCOV_EXCL_START
-  default: {
-    throw UnexpectedError("invalid mesh dimension");
-  }
-    // LCOV_EXCL_STOP
-  }
+    },
+    discrete_function_variant->discreteFunction());
 }
diff --git a/src/scheme/DiscreteFunctionUtils.hpp b/src/scheme/DiscreteFunctionUtils.hpp
index e01437f2b0e0d1c7dc9468bea6f63aa0b14aae74..91acbccb7c1444e2adfbc55333b1ee2a0e89d3b9 100644
--- a/src/scheme/DiscreteFunctionUtils.hpp
+++ b/src/scheme/DiscreteFunctionUtils.hpp
@@ -2,42 +2,32 @@
 #define DISCRETE_FUNCTION_UTILS_HPP
 
 #include <scheme/DiscreteFunctionType.hpp>
-#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 
 #include <vector>
 
 PUGS_INLINE
 bool
-checkDiscretizationType(const std::vector<std::shared_ptr<const IDiscreteFunction>>& discrete_function_list,
+checkDiscretizationType(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_list,
                         const DiscreteFunctionType& discrete_function_type)
 {
-  for (const auto& discrete_function : discrete_function_list) {
-    if (discrete_function->descriptor().type() != discrete_function_type) {
+  for (const auto& discrete_function_variant : discrete_function_list) {
+    if (not std::visit([&](auto&& f) { return f.descriptor().type() == discrete_function_type; },
+                       discrete_function_variant->discreteFunction())) {
       return false;
     }
   }
   return true;
 }
 
-PUGS_INLINE
-std::shared_ptr<const IMesh>
-getCommonMesh(const std::vector<std::shared_ptr<const IDiscreteFunction>>& discrete_function_list)
-{
-  std::shared_ptr<const IMesh> i_mesh;
-  for (const auto& discrete_function : discrete_function_list) {
-    if (not i_mesh.use_count()) {
-      i_mesh = discrete_function->mesh();
-    } else {
-      if (i_mesh != discrete_function->mesh()) {
-        return {};
-      }
-    }
-  }
-  return i_mesh;
-}
+std::shared_ptr<const IMesh> getCommonMesh(
+  const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list);
+
+bool hasSameMesh(const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list);
 
-std::shared_ptr<const IDiscreteFunction> shallowCopy(const std::shared_ptr<const IMesh>& mesh,
-                                                     const std::shared_ptr<const IDiscreteFunction>& discrete_function);
+std::shared_ptr<const DiscreteFunctionVariant> shallowCopy(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::shared_ptr<const DiscreteFunctionVariant>& discrete_function);
 
 #endif   // DISCRETE_FUNCTION_UTILS_HPP
diff --git a/src/scheme/DiscreteFunctionVariant.hpp b/src/scheme/DiscreteFunctionVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6feb86bcbc564898ae7801a0f3ca2bab8d370097
--- /dev/null
+++ b/src/scheme/DiscreteFunctionVariant.hpp
@@ -0,0 +1,105 @@
+#ifndef DISCRETE_FUNCTION_VARIANT_HPP
+#define DISCRETE_FUNCTION_VARIANT_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <utils/Demangle.hpp>
+#include <utils/Exceptions.hpp>
+
+class DiscreteFunctionVariant
+{
+ private:
+  using Variant = std::variant<DiscreteFunctionP0<1, const double>,
+                               DiscreteFunctionP0<1, const TinyVector<1>>,
+                               DiscreteFunctionP0<1, const TinyVector<2>>,
+                               DiscreteFunctionP0<1, const TinyVector<3>>,
+                               DiscreteFunctionP0<1, const TinyMatrix<1>>,
+                               DiscreteFunctionP0<1, const TinyMatrix<2>>,
+                               DiscreteFunctionP0<1, const TinyMatrix<3>>,
+
+                               DiscreteFunctionP0<2, const double>,
+                               DiscreteFunctionP0<2, const TinyVector<1>>,
+                               DiscreteFunctionP0<2, const TinyVector<2>>,
+                               DiscreteFunctionP0<2, const TinyVector<3>>,
+                               DiscreteFunctionP0<2, const TinyMatrix<1>>,
+                               DiscreteFunctionP0<2, const TinyMatrix<2>>,
+                               DiscreteFunctionP0<2, const TinyMatrix<3>>,
+
+                               DiscreteFunctionP0<3, const double>,
+                               DiscreteFunctionP0<3, const TinyVector<1>>,
+                               DiscreteFunctionP0<3, const TinyVector<2>>,
+                               DiscreteFunctionP0<3, const TinyVector<3>>,
+                               DiscreteFunctionP0<3, const TinyMatrix<1>>,
+                               DiscreteFunctionP0<3, const TinyMatrix<2>>,
+                               DiscreteFunctionP0<3, const TinyMatrix<3>>,
+
+                               DiscreteFunctionP0Vector<1, const double>,
+                               DiscreteFunctionP0Vector<2, const double>,
+                               DiscreteFunctionP0Vector<3, const double>>;
+
+  Variant m_discrete_function;
+
+ public:
+  PUGS_INLINE
+  const Variant&
+  discreteFunction() const
+  {
+    return m_discrete_function;
+  }
+
+  template <typename DiscreteFunctionT>
+  PUGS_INLINE auto
+  get() const
+  {
+    static_assert(is_discrete_function_v<DiscreteFunctionT>, "invalid template argument");
+    using DataType = typename DiscreteFunctionT::data_type;
+    static_assert(std::is_const_v<DataType>, "data type of extracted discrete function must be const");
+
+    if (not std::holds_alternative<DiscreteFunctionT>(this->m_discrete_function)) {
+      std::ostringstream error_msg;
+      error_msg << "invalid discrete function type\n";
+      error_msg << "- required " << rang::fgB::red << demangle<DiscreteFunctionT>() << rang::fg::reset << '\n';
+      error_msg << "- contains " << rang::fgB::yellow
+                << std::visit([](auto&& f) -> std::string { return demangle<decltype(f)>(); },
+                              this->m_discrete_function)
+                << rang::fg::reset;
+      throw NormalError(error_msg.str());
+    }
+    return std::get<DiscreteFunctionT>(this->discreteFunction());
+  }
+
+  template <size_t Dimension, typename DataType>
+  DiscreteFunctionVariant(const DiscreteFunctionP0<Dimension, DataType>& discrete_function)
+    : m_discrete_function{DiscreteFunctionP0<Dimension, const DataType>{discrete_function}}
+  {
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, double> or                       //
+                    std::is_same_v<std::remove_const_t<DataType>, TinyVector<1, double>> or      //
+                    std::is_same_v<std::remove_const_t<DataType>, TinyVector<2, double>> or      //
+                    std::is_same_v<std::remove_const_t<DataType>, TinyVector<3, double>> or      //
+                    std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<1, 1, double>> or   //
+                    std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<2, 2, double>> or   //
+                    std::is_same_v<std::remove_const_t<DataType>, TinyMatrix<3, 3, double>>,
+                  "DiscreteFunctionP0 with this DataType is not allowed in variant");
+  }
+
+  template <size_t Dimension, typename DataType>
+  DiscreteFunctionVariant(const DiscreteFunctionP0Vector<Dimension, DataType>& discrete_function)
+    : m_discrete_function{DiscreteFunctionP0Vector<Dimension, const DataType>{discrete_function}}
+  {
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, double>,
+                  "DiscreteFunctionP0Vector with this DataType is not allowed in variant");
+  }
+
+  DiscreteFunctionVariant& operator=(DiscreteFunctionVariant&&) = default;
+  DiscreteFunctionVariant& operator=(const DiscreteFunctionVariant&) = default;
+
+  DiscreteFunctionVariant(const DiscreteFunctionVariant&) = default;
+  DiscreteFunctionVariant(DiscreteFunctionVariant&&)      = default;
+
+  DiscreteFunctionVariant()  = delete;
+  ~DiscreteFunctionVariant() = default;
+};
+
+#endif   // DISCRETE_FUNCTION_VARIANT_HPP
diff --git a/src/scheme/DiscreteFunctionVectorIntegrator.cpp b/src/scheme/DiscreteFunctionVectorIntegrator.cpp
index cfa65b1787090a06c6de0ac6bf774b3771a4a1eb..80b9e711ebfe9e45297e1a01a1d63ad37da72914 100644
--- a/src/scheme/DiscreteFunctionVectorIntegrator.cpp
+++ b/src/scheme/DiscreteFunctionVectorIntegrator.cpp
@@ -3,10 +3,11 @@
 #include <language/utils/IntegrateCellArray.hpp>
 #include <mesh/MeshCellZone.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <utils/Exceptions.hpp>
 
 template <size_t Dimension, typename DataType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorIntegrator::_integrateOnZoneList() const
 {
   Assert(m_zone_list.size() > 0, "no zone list provided");
@@ -62,25 +63,24 @@ DiscreteFunctionVectorIntegrator::_integrateOnZoneList() const
       }
     });
 
-  return std::make_shared<DiscreteFunctionP0Vector<Dimension, DataType>>(p_mesh, cell_array);
+  return DiscreteFunctionP0Vector<Dimension, DataType>(p_mesh, cell_array);
 }
 
 template <size_t Dimension, typename DataType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorIntegrator::_integrateGlobally() const
 {
   Assert(m_zone_list.size() == 0, "invalid call when zones are defined");
 
   std::shared_ptr mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
 
-  return std::make_shared<
-    DiscreteFunctionP0Vector<Dimension, DataType>>(mesh, IntegrateCellArray<DataType(TinyVector<Dimension>)>::
-                                                           template integrate(m_function_id_list,
-                                                                              *m_quadrature_descriptor, *mesh));
+  return DiscreteFunctionP0Vector<Dimension, DataType>(mesh, IntegrateCellArray<DataType(TinyVector<Dimension>)>::
+                                                               template integrate(m_function_id_list,
+                                                                                  *m_quadrature_descriptor, *mesh));
 }
 
 template <size_t Dimension, typename DataType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorIntegrator::_integrate() const
 {
   if (m_zone_list.size() == 0) {
@@ -91,7 +91,7 @@ DiscreteFunctionVectorIntegrator::_integrate() const
 }
 
 template <size_t Dimension>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorIntegrator::_integrate() const
 {
   for (const auto& function_id : m_function_id_list) {
@@ -117,7 +117,7 @@ DiscreteFunctionVectorIntegrator::_integrate() const
   return this->_integrate<Dimension, double>();
 }
 
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorIntegrator::integrate() const
 {
   if (m_discrete_function_descriptor->type() != DiscreteFunctionType::P0Vector) {
diff --git a/src/scheme/DiscreteFunctionVectorIntegrator.hpp b/src/scheme/DiscreteFunctionVectorIntegrator.hpp
index 55f5fe3572cbb237e28b9bf86ff7bda19db20a98..44b192bc985888325a691db1433737d5c5a03ca0 100644
--- a/src/scheme/DiscreteFunctionVectorIntegrator.hpp
+++ b/src/scheme/DiscreteFunctionVectorIntegrator.hpp
@@ -5,12 +5,13 @@
 #include <language/utils/FunctionSymbolId.hpp>
 #include <mesh/IMesh.hpp>
 #include <mesh/IZoneDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 
 #include <memory>
 #include <vector>
 
+class DiscreteFunctionVariant;
+
 class DiscreteFunctionVectorIntegrator
 {
  private:
@@ -21,19 +22,19 @@ class DiscreteFunctionVectorIntegrator
   const std::vector<FunctionSymbolId> m_function_id_list;
 
   template <size_t Dimension, typename DataType>
-  std::shared_ptr<IDiscreteFunction> _integrateOnZoneList() const;
+  DiscreteFunctionVariant _integrateOnZoneList() const;
 
   template <size_t Dimension, typename DataType>
-  std::shared_ptr<IDiscreteFunction> _integrateGlobally() const;
+  DiscreteFunctionVariant _integrateGlobally() const;
 
   template <size_t Dimension, typename DataType>
-  std::shared_ptr<IDiscreteFunction> _integrate() const;
+  DiscreteFunctionVariant _integrate() const;
 
   template <size_t Dimension>
-  std::shared_ptr<IDiscreteFunction> _integrate() const;
+  DiscreteFunctionVariant _integrate() const;
 
  public:
-  std::shared_ptr<IDiscreteFunction> integrate() const;
+  DiscreteFunctionVariant integrate() const;
 
   DiscreteFunctionVectorIntegrator(
     const std::shared_ptr<const IMesh>& mesh,
diff --git a/src/scheme/DiscreteFunctionVectorInterpoler.cpp b/src/scheme/DiscreteFunctionVectorInterpoler.cpp
index 128c735e4ae8e158f3dc42bf3d5ed47b17ac3fb5..b416400c65a719614be50a53f167a44d859c9497 100644
--- a/src/scheme/DiscreteFunctionVectorInterpoler.cpp
+++ b/src/scheme/DiscreteFunctionVectorInterpoler.cpp
@@ -3,10 +3,11 @@
 #include <language/utils/InterpolateItemArray.hpp>
 #include <mesh/MeshCellZone.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <utils/Exceptions.hpp>
 
 template <size_t Dimension, typename DataType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorInterpoler::_interpolateOnZoneList() const
 {
   Assert(m_zone_list.size() > 0, "no zone list provided");
@@ -60,11 +61,11 @@ DiscreteFunctionVectorInterpoler::_interpolateOnZoneList() const
       }
     });
 
-  return std::make_shared<DiscreteFunctionP0Vector<Dimension, DataType>>(p_mesh, cell_array);
+  return DiscreteFunctionP0Vector<Dimension, DataType>(p_mesh, cell_array);
 }
 
 template <size_t Dimension, typename DataType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorInterpoler::_interpolateGlobally() const
 {
   Assert(m_zone_list.size() == 0, "invalid call when zones are defined");
@@ -74,14 +75,14 @@ DiscreteFunctionVectorInterpoler::_interpolateGlobally() const
   using MeshDataType      = MeshData<Dimension>;
   MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*p_mesh);
 
-  return std::make_shared<
-    DiscreteFunctionP0Vector<Dimension, DataType>>(p_mesh, InterpolateItemArray<DataType(TinyVector<Dimension>)>::
-                                                             template interpolate<ItemType::cell>(m_function_id_list,
-                                                                                                  mesh_data.xj()));
+  return DiscreteFunctionP0Vector<Dimension, DataType>(p_mesh,
+                                                       InterpolateItemArray<DataType(TinyVector<Dimension>)>::
+                                                         template interpolate<ItemType::cell>(m_function_id_list,
+                                                                                              mesh_data.xj()));
 }
 
 template <size_t Dimension, typename DataType>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorInterpoler::_interpolate() const
 {
   if (m_zone_list.size() == 0) {
@@ -92,7 +93,7 @@ DiscreteFunctionVectorInterpoler::_interpolate() const
 }
 
 template <size_t Dimension>
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorInterpoler::_interpolate() const
 {
   for (const auto& function_id : m_function_id_list) {
@@ -118,7 +119,7 @@ DiscreteFunctionVectorInterpoler::_interpolate() const
   return this->_interpolate<Dimension, double>();
 }
 
-std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVariant
 DiscreteFunctionVectorInterpoler::interpolate() const
 {
   if (m_discrete_function_descriptor->type() != DiscreteFunctionType::P0Vector) {
diff --git a/src/scheme/DiscreteFunctionVectorInterpoler.hpp b/src/scheme/DiscreteFunctionVectorInterpoler.hpp
index 4bd917264fcfdb8992d6096db824bc17951c2603..8cec9d09e7f9ce0fd03e98dbdaed0edf6afee900 100644
--- a/src/scheme/DiscreteFunctionVectorInterpoler.hpp
+++ b/src/scheme/DiscreteFunctionVectorInterpoler.hpp
@@ -4,9 +4,10 @@
 #include <language/utils/FunctionSymbolId.hpp>
 #include <mesh/IMesh.hpp>
 #include <mesh/IZoneDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 
+class DiscreteFunctionVariant;
+
 #include <memory>
 #include <vector>
 
@@ -19,19 +20,19 @@ class DiscreteFunctionVectorInterpoler
   const std::vector<FunctionSymbolId> m_function_id_list;
 
   template <size_t Dimension, typename DataType>
-  std::shared_ptr<IDiscreteFunction> _interpolateOnZoneList() const;
+  DiscreteFunctionVariant _interpolateOnZoneList() const;
 
   template <size_t Dimension, typename DataType>
-  std::shared_ptr<IDiscreteFunction> _interpolateGlobally() const;
+  DiscreteFunctionVariant _interpolateGlobally() const;
 
   template <size_t Dimension, typename DataType>
-  std::shared_ptr<IDiscreteFunction> _interpolate() const;
+  DiscreteFunctionVariant _interpolate() const;
 
   template <size_t Dimension>
-  std::shared_ptr<IDiscreteFunction> _interpolate() const;
+  DiscreteFunctionVariant _interpolate() const;
 
  public:
-  std::shared_ptr<IDiscreteFunction> interpolate() const;
+  DiscreteFunctionVariant interpolate() const;
 
   DiscreteFunctionVectorInterpoler(
     const std::shared_ptr<const IMesh>& mesh,
diff --git a/src/scheme/IDiscreteFunction.hpp b/src/scheme/IDiscreteFunction.hpp
deleted file mode 100644
index be6ca66278de95265e9a301ebc8f2fe0fa7ee5db..0000000000000000000000000000000000000000
--- a/src/scheme/IDiscreteFunction.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef I_DISCRETE_FUNCTION_HPP
-#define I_DISCRETE_FUNCTION_HPP
-
-class IMesh;
-class IDiscreteFunctionDescriptor;
-
-#include <language/utils/ASTNodeDataTypeTraits.hpp>
-#include <memory>
-
-class IDiscreteFunction
-{
- public:
-  enum class HandledItemDataType
-  {
-    value,
-    vector,
-  };
-
-  virtual std::shared_ptr<const IMesh> mesh() const             = 0;
-  virtual const IDiscreteFunctionDescriptor& descriptor() const = 0;
-  virtual ASTNodeDataType dataType() const                      = 0;
-
-  IDiscreteFunction() = default;
-
-  IDiscreteFunction(const IDiscreteFunction&) = default;
-
-  IDiscreteFunction(IDiscreteFunction&&) noexcept = default;
-
-  virtual ~IDiscreteFunction() noexcept = default;
-};
-
-#endif   // I_DISCRETE_FUNCTION_HPP
diff --git a/src/utils/PugsTraits.hpp b/src/utils/PugsTraits.hpp
index 46a2b1d6dc5923d8ee12668d88484875f404944c..285d7978d8a8f05e784534d0fb475bc01da75042 100644
--- a/src/utils/PugsTraits.hpp
+++ b/src/utils/PugsTraits.hpp
@@ -20,6 +20,12 @@ class ItemValue;
 template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 class ItemArray;
 
+template <size_t Dimension, typename DataType>
+class DiscreteFunctionP0;
+
+template <size_t Dimension, typename DataType>
+class DiscreteFunctionP0Vector;
+
 // Traits is_trivially_castable
 
 template <typename T>
@@ -116,7 +122,7 @@ constexpr inline bool is_item_value_v = false;
 template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 constexpr inline bool is_item_value_v<ItemValue<DataType, item_type, ConnectivityPtr>> = true;
 
-// Trais is ItemValue
+// Trais is ItemArray
 
 template <typename T>
 constexpr inline bool is_item_array_v = false;
@@ -124,6 +130,27 @@ constexpr inline bool is_item_array_v = false;
 template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 constexpr inline bool is_item_array_v<ItemArray<DataType, item_type, ConnectivityPtr>> = true;
 
+// Trais is DiscreteFunctionP0
+
+template <typename T>
+constexpr inline bool is_discrete_function_P0_v = false;
+
+template <size_t Dimension, typename DataType>
+constexpr inline bool is_discrete_function_P0_v<DiscreteFunctionP0<Dimension, DataType>> = true;
+
+// Trais is DiscreteFunctionP0Vector
+
+template <typename T>
+constexpr inline bool is_discrete_function_P0_vector_v = false;
+
+template <size_t Dimension, typename DataType>
+constexpr inline bool is_discrete_function_P0_vector_v<DiscreteFunctionP0Vector<Dimension, DataType>> = true;
+
+// Trais is DiscreteFunction
+
+template <typename T>
+constexpr inline bool is_discrete_function_v = is_discrete_function_P0_v<T> or is_discrete_function_P0_vector_v<T>;
+
 // helper to check if a type is part of a variant
 
 template <typename T, typename V>
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3f174398186c2b5fd0bcc56aef017f40936f9704..0e585acac5ba1e5b7656ffa29411567e0f0d54f4 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -82,7 +82,7 @@ add_executable (unit_tests
   test_EdgeIntegrator.cpp
   test_EigenvalueSolver.cpp
   test_EmbeddedData.cpp
-  test_EmbeddedIDiscreteFunctionUtils.cpp
+  test_EmbeddedDiscreteFunctionUtils.cpp
   test_EscapedString.cpp
   test_Exceptions.cpp
   test_ExecutionPolicy.cpp
@@ -167,8 +167,12 @@ add_executable (mpi_unit_tests
   test_DiscreteFunctionVectorIntegratorByZone.cpp
   test_DiscreteFunctionVectorInterpoler.cpp
   test_DiscreteFunctionVectorInterpolerByZone.cpp
-  test_EmbeddedIDiscreteFunctionMathFunctions.cpp
-  test_EmbeddedIDiscreteFunctionOperators.cpp
+  test_EmbeddedDiscreteFunctionMathFunctions1D.cpp
+  test_EmbeddedDiscreteFunctionMathFunctions2D.cpp
+  test_EmbeddedDiscreteFunctionMathFunctions3D.cpp
+  test_EmbeddedDiscreteFunctionOperators1D.cpp
+  test_EmbeddedDiscreteFunctionOperators2D.cpp
+  test_EmbeddedDiscreteFunctionOperators3D.cpp
   test_InterpolateItemArray.cpp
   test_InterpolateItemValue.cpp
   test_ItemArray.cpp
diff --git a/tests/test_BuiltinFunctionEmbedder.cpp b/tests/test_BuiltinFunctionEmbedder.cpp
index 7fd481e163a77f73b32a0d08fba3f49224cdc130..2e56fe228bc4b5d0ae863ae177b3633efbfa6e90 100644
--- a/tests/test_BuiltinFunctionEmbedder.cpp
+++ b/tests/test_BuiltinFunctionEmbedder.cpp
@@ -10,12 +10,8 @@ inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const double>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("shared_const_double");
 
 template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<double>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("shared_double");
-
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<uint64_t>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("shared_uint64_t");
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const uint64_t>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("shared_const_uint64_t");
 
 TEST_CASE("BuiltinFunctionEmbedder", "[language]")
 {
@@ -177,7 +173,7 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
     REQUIRE(*data.data_ptr() == (2.3 + 4ul));
   }
 
-  SECTION("double(std::shared_ptr<double>) BuiltinFunctionEmbedder")
+  SECTION("double(std::shared_ptr<const double>) BuiltinFunctionEmbedder")
   {
     std::function abs = [&](std::shared_ptr<const double> x) -> double { return std::abs(*x); };
 
@@ -237,7 +233,7 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
 
   SECTION("uint64_t(std::vector<EmbeddedData>) BuiltinFunctionEmbedder")
   {
-    std::function sum = [&](const std::vector<std::shared_ptr<uint64_t>>& x) -> uint64_t {
+    std::function sum = [&](const std::vector<std::shared_ptr<const uint64_t>>& x) -> uint64_t {
       uint64_t sum = 0;
       for (size_t i = 0; i < x.size(); ++i) {
         sum += *x[i];
@@ -246,7 +242,7 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
     };
 
     std::unique_ptr<IBuiltinFunctionEmbedder> i_embedded_c =
-      std::make_unique<BuiltinFunctionEmbedder<uint64_t(const std::vector<std::shared_ptr<uint64_t>>&)>>(sum);
+      std::make_unique<BuiltinFunctionEmbedder<uint64_t(const std::vector<std::shared_ptr<const uint64_t>>&)>>(sum);
 
     REQUIRE(i_embedded_c->numberOfParameters() == 1);
     REQUIRE(i_embedded_c->getParameterDataTypes().size() == 1);
@@ -255,21 +251,21 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
 
     std::vector<EmbeddedData> embedded_data;
     REQUIRE(std::get<uint64_t>(i_embedded_c->apply({embedded_data})) == 0);
-    embedded_data.emplace_back(std::make_shared<DataHandler<uint64_t>>(std::make_shared<uint64_t>(1)));
-    embedded_data.emplace_back(std::make_shared<DataHandler<uint64_t>>(std::make_shared<uint64_t>(2)));
-    embedded_data.emplace_back(std::make_shared<DataHandler<uint64_t>>(std::make_shared<uint64_t>(3)));
+    embedded_data.emplace_back(std::make_shared<DataHandler<const uint64_t>>(std::make_shared<const uint64_t>(1)));
+    embedded_data.emplace_back(std::make_shared<DataHandler<const uint64_t>>(std::make_shared<const uint64_t>(2)));
+    embedded_data.emplace_back(std::make_shared<DataHandler<const uint64_t>>(std::make_shared<const uint64_t>(3)));
 
     REQUIRE(std::get<uint64_t>(i_embedded_c->apply({embedded_data})) == 6);
 
-    embedded_data.emplace_back(std::make_shared<DataHandler<double>>(std::make_shared<double>(4)));
+    embedded_data.emplace_back(std::make_shared<DataHandler<const double>>(std::make_shared<const double>(4)));
     REQUIRE_THROWS_WITH(i_embedded_c->apply({embedded_data}),
                         "unexpected error: unexpected argument types while casting: invalid"
                         " EmbeddedData type, expecting " +
-                          demangle<DataHandler<uint64_t>>());
+                          demangle<DataHandler<const uint64_t>>());
 
     REQUIRE_THROWS_WITH(i_embedded_c->apply({TinyVector<1>{13}}),
                         "unexpected error: unexpected argument types while casting \"" + demangle<TinyVector<1>>() +
-                          "\" to \"" + demangle<std::vector<std::shared_ptr<uint64_t>>>() + '"');
+                          "\" to \"" + demangle<std::vector<std::shared_ptr<const uint64_t>>>() + '"');
   }
 
   SECTION("double(void) BuiltinFunctionEmbedder")
@@ -286,9 +282,9 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
     REQUIRE(i_embedded_c->getReturnDataType() == ASTNodeDataType::double_t);
   }
 
-  SECTION("R*R -> R*R^2*shared_double BuiltinFunctionEmbedder")
+  SECTION("R*R -> R*R^2*shared_const_double BuiltinFunctionEmbedder")
   {
-    std::function c = [](double a, double b) -> std::tuple<double, TinyVector<2>, std::shared_ptr<double>> {
+    std::function c = [](double a, double b) -> std::tuple<double, TinyVector<2>, std::shared_ptr<const double>> {
       return std::make_tuple(a + b, TinyVector<2>{b, -a}, std::make_shared<double>(a - b));
     };
 
@@ -311,15 +307,15 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
 
     REQUIRE(*data_type.contentTypeList()[0] == ASTNodeDataType::double_t);
     REQUIRE(*data_type.contentTypeList()[1] == ASTNodeDataType::build<ASTNodeDataType::vector_t>(2));
-    REQUIRE(*data_type.contentTypeList()[2] == ast_node_data_type_from<std::shared_ptr<double>>);
+    REQUIRE(*data_type.contentTypeList()[2] == ast_node_data_type_from<std::shared_ptr<const double>>);
   }
 
-  SECTION("void -> N*R*shared_double BuiltinFunctionEmbedder")
+  SECTION("void -> N*R*shared_const_double BuiltinFunctionEmbedder")
   {
-    std::function c = [](void) -> std::tuple<uint64_t, double, std::shared_ptr<double>> {
+    std::function c = [](void) -> std::tuple<uint64_t, double, std::shared_ptr<const double>> {
       uint64_t a = 1;
       double b   = 3.5;
-      return std::make_tuple(a, b, std::make_shared<double>(a + b));
+      return std::make_tuple(a, b, std::make_shared<const double>(a + b));
     };
 
     std::unique_ptr<IBuiltinFunctionEmbedder> i_embedded_c =
@@ -357,19 +353,19 @@ TEST_CASE("BuiltinFunctionEmbedder", "[language]")
 
   SECTION("EmbeddedData(void) BuiltinFunctionEmbedder")
   {
-    std::function c = [](void) -> std::shared_ptr<double> { return std::make_shared<double>(1.5); };
+    std::function c = [](void) -> std::shared_ptr<const double> { return std::make_shared<const double>(1.5); };
 
     std::unique_ptr<IBuiltinFunctionEmbedder> i_embedded_c =
-      std::make_unique<BuiltinFunctionEmbedder<std::shared_ptr<double>(void)>>(c);
+      std::make_unique<BuiltinFunctionEmbedder<std::shared_ptr<const double>(void)>>(c);
 
     REQUIRE(i_embedded_c->numberOfParameters() == 0);
     REQUIRE(i_embedded_c->getParameterDataTypes().size() == 0);
 
-    REQUIRE(i_embedded_c->getReturnDataType() == ast_node_data_type_from<std::shared_ptr<double>>);
+    REQUIRE(i_embedded_c->getReturnDataType() == ast_node_data_type_from<std::shared_ptr<const double>>);
 
-    const auto embedded_data         = std::get<EmbeddedData>(i_embedded_c->apply(std::vector<DataVariant>{}));
-    const IDataHandler& handled_data = embedded_data.get();
-    const DataHandler<double>& data  = dynamic_cast<const DataHandler<double>&>(handled_data);
+    const auto embedded_data              = std::get<EmbeddedData>(i_embedded_c->apply(std::vector<DataVariant>{}));
+    const IDataHandler& handled_data      = embedded_data.get();
+    const DataHandler<const double>& data = dynamic_cast<const DataHandler<const double>&>(handled_data);
     REQUIRE(*data.data_ptr() == 1.5);
   }
 
diff --git a/tests/test_DiscreteFunctionIntegrator.cpp b/tests/test_DiscreteFunctionIntegrator.cpp
index 250d8010244aac6eb3a908f4da5c60099ab5ae79..b3ba2efba4ae8fe3dc90b8b54f6df6032d622e8c 100644
--- a/tests/test_DiscreteFunctionIntegrator.cpp
+++ b/tests/test_DiscreteFunctionIntegrator.cpp
@@ -23,6 +23,7 @@
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 #include <scheme/DiscreteFunctionIntegrator.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -98,10 +99,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("N_scalar_non_linear_1d")
@@ -116,10 +116,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("Z_scalar_non_linear_1d")
@@ -134,10 +133,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R_scalar_non_linear_1d")
@@ -152,10 +150,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R1_non_linear_1d")
@@ -173,10 +170,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
                                                                    *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2_non_linear_1d")
@@ -194,10 +190,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
                                                                    *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3_non_linear_1d")
@@ -215,10 +210,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
                                                                    *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R1x1_non_linear_1d")
@@ -236,10 +230,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
                                                                    *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2x2_non_linear_1d")
@@ -257,10 +250,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
                                                                    *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3x3_non_linear_1d")
@@ -278,10 +270,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
                                                                    *mesh_1d);
 
           DiscreteFunctionIntegrator integrator(mesh_1d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
       }
     }
@@ -344,10 +335,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("N_scalar_non_linear_2d")
@@ -362,10 +352,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("Z_scalar_non_linear_2d")
@@ -380,10 +369,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R_scalar_non_linear_2d")
@@ -398,10 +386,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R1_non_linear_2d")
@@ -419,10 +406,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2_non_linear_2d")
@@ -440,10 +426,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3_non_linear_2d")
@@ -461,10 +446,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R1x1_non_linear_2d")
@@ -482,10 +466,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2x2_non_linear_2d")
@@ -503,10 +486,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3x3_non_linear_2d")
@@ -524,10 +506,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_2d);
 
           DiscreteFunctionIntegrator integrator(mesh_2d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
       }
     }
@@ -590,10 +571,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("N_scalar_non_linear_3d")
@@ -608,10 +588,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("Z_scalar_non_linear_3d")
@@ -626,10 +605,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R_scalar_non_linear_3d")
@@ -644,10 +622,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R1_non_linear_3d")
@@ -665,10 +642,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2_non_linear_3d")
@@ -686,10 +662,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3_non_linear_3d")
@@ -707,10 +682,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R1x1_non_linear_3d")
@@ -728,10 +702,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2x2_non_linear_3d")
@@ -749,10 +722,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3x3_non_linear_3d")
@@ -770,10 +742,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
                                                                    *mesh_3d);
 
           DiscreteFunctionIntegrator integrator(mesh_3d, quadrature_descriptor, function_symbol_id);
-          std::shared_ptr discrete_function = integrator.integrate();
+          DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
       }
     }
diff --git a/tests/test_DiscreteFunctionIntegratorByZone.cpp b/tests/test_DiscreteFunctionIntegratorByZone.cpp
index 19c0ac87c1d941e4c5f0026e154087be305c55f2..217c15b38d17195f2e3baa56d0a698f0b2c9c2a4 100644
--- a/tests/test_DiscreteFunctionIntegratorByZone.cpp
+++ b/tests/test_DiscreteFunctionIntegratorByZone.cpp
@@ -25,6 +25,7 @@
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 #include <scheme/DiscreteFunctionIntegrator.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -111,10 +112,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("N_scalar_non_linear_1d")
@@ -139,10 +139,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("Z_scalar_non_linear_1d")
@@ -167,10 +166,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R_scalar_non_linear_1d")
@@ -195,10 +193,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R1_non_linear_1d")
@@ -225,10 +222,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2_non_linear_1d")
@@ -255,10 +251,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3_non_linear_1d")
@@ -285,10 +280,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R1x1_non_linear_1d")
@@ -315,10 +309,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2x2_non_linear_1d")
@@ -345,10 +338,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3x3_non_linear_1d")
@@ -375,10 +367,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
   }
 
@@ -450,10 +441,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("N_scalar_non_linear_2d")
@@ -478,10 +468,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("Z_scalar_non_linear_2d")
@@ -506,10 +495,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R_scalar_non_linear_2d")
@@ -534,10 +522,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R1_non_linear_2d")
@@ -564,10 +551,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2_non_linear_2d")
@@ -594,10 +580,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3_non_linear_2d")
@@ -624,10 +609,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R1x1_non_linear_2d")
@@ -654,10 +638,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2x2_non_linear_2d")
@@ -684,10 +667,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3x3_non_linear_2d")
@@ -714,10 +696,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
   }
 
@@ -789,10 +770,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("N_scalar_non_linear_3d")
@@ -817,10 +797,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("Z_scalar_non_linear_3d")
@@ -845,10 +824,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R_scalar_non_linear_3d")
@@ -873,10 +851,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R1_non_linear_3d")
@@ -903,10 +880,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2_non_linear_3d")
@@ -933,10 +909,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3_non_linear_3d")
@@ -963,10 +938,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R1x1_non_linear_3d")
@@ -993,10 +967,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2x2_non_linear_3d")
@@ -1023,10 +996,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3x3_non_linear_3d")
@@ -1053,10 +1025,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
         });
 
       DiscreteFunctionIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor, function_symbol_id);
-      std::shared_ptr discrete_function = integrator.integrate();
+      DiscreteFunctionVariant discrete_function = integrator.integrate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
   }
 }
diff --git a/tests/test_DiscreteFunctionInterpoler.cpp b/tests/test_DiscreteFunctionInterpoler.cpp
index 431e5a55939dfc67a71a258f004afe92fdbe5a58..0287d97c89ef966f5a4d68ecd091875b3e19e14b 100644
--- a/tests/test_DiscreteFunctionInterpoler.cpp
+++ b/tests/test_DiscreteFunctionInterpoler.cpp
@@ -21,6 +21,7 @@
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -101,10 +102,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("N_scalar_non_linear_1d")
@@ -124,10 +124,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("Z_scalar_non_linear_1d")
@@ -147,10 +146,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R_scalar_non_linear_1d")
@@ -170,10 +168,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R1_non_linear_1d")
@@ -195,10 +192,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2_non_linear_1d")
@@ -220,10 +216,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3_non_linear_1d")
@@ -245,10 +240,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R1x1_non_linear_1d")
@@ -270,10 +264,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2x2_non_linear_1d")
@@ -296,10 +289,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3x3_non_linear_1d")
@@ -329,10 +321,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
       }
     }
@@ -400,10 +391,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("N_scalar_non_linear_2d")
@@ -423,10 +413,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("Z_scalar_non_linear_2d")
@@ -446,10 +435,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R_scalar_non_linear_2d")
@@ -469,10 +457,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R1_non_linear_2d")
@@ -494,10 +481,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2_non_linear_2d")
@@ -519,10 +505,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3_non_linear_2d")
@@ -544,10 +529,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R1x1_non_linear_2d")
@@ -569,10 +553,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2x2_non_linear_2d")
@@ -595,10 +578,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3x3_non_linear_2d")
@@ -629,10 +611,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
       }
     }
@@ -700,10 +681,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("N_scalar_non_linear_3d")
@@ -723,10 +703,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("Z_scalar_non_linear_3d")
@@ -746,10 +725,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R_scalar_non_linear_3d")
@@ -769,10 +747,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
         }
 
         SECTION("R1_non_linear_3d")
@@ -794,10 +771,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2_non_linear_3d")
@@ -819,10 +795,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3_non_linear_3d")
@@ -844,10 +819,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R1x1_non_linear_3d")
@@ -869,10 +843,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R2x2_non_linear_3d")
@@ -895,10 +868,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
 
         SECTION("R3x3_non_linear_3d")
@@ -929,10 +901,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           DiscreteFunctionInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                                 function_symbol_id);
-          std::shared_ptr discrete_function = interpoler.interpolate();
+          DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-          REQUIRE(same_cell_value(cell_value,
-                                  dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
         }
       }
     }
diff --git a/tests/test_DiscreteFunctionInterpolerByZone.cpp b/tests/test_DiscreteFunctionInterpolerByZone.cpp
index dce3b645a7e98ee91b41f8cd5f158edb019d94ed..c4e6f0a00a15a8921615bb0540adf3c3668e7300 100644
--- a/tests/test_DiscreteFunctionInterpolerByZone.cpp
+++ b/tests/test_DiscreteFunctionInterpolerByZone.cpp
@@ -23,6 +23,7 @@
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -113,10 +114,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("N_scalar_non_linear_1d")
@@ -140,10 +140,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("Z_scalar_non_linear_1d")
@@ -167,10 +166,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R_scalar_non_linear_1d")
@@ -194,10 +192,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R1_non_linear_1d")
@@ -223,10 +220,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2_non_linear_1d")
@@ -252,10 +248,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3_non_linear_1d")
@@ -281,10 +276,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R1x1_non_linear_1d")
@@ -310,10 +304,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2x2_non_linear_1d")
@@ -340,10 +333,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3x3_non_linear_1d")
@@ -377,10 +369,9 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_1d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
   }
 
@@ -456,10 +447,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("N_scalar_non_linear_2d")
@@ -483,10 +473,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("Z_scalar_non_linear_2d")
@@ -510,10 +499,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R_scalar_non_linear_2d")
@@ -537,10 +525,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R1_non_linear_2d")
@@ -566,10 +553,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2_non_linear_2d")
@@ -595,10 +581,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3_non_linear_2d")
@@ -624,10 +609,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R1x1_non_linear_2d")
@@ -653,10 +637,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2x2_non_linear_2d")
@@ -683,10 +666,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3x3_non_linear_2d")
@@ -721,10 +703,9 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_2d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
   }
 
@@ -800,10 +781,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("N_scalar_non_linear_3d")
@@ -827,10 +807,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("Z_scalar_non_linear_3d")
@@ -854,10 +833,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R_scalar_non_linear_3d")
@@ -881,10 +859,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const double>>()));
     }
 
     SECTION("R1_non_linear_3d")
@@ -910,10 +887,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2_non_linear_3d")
@@ -939,10 +915,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3_non_linear_3d")
@@ -968,10 +943,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R1x1_non_linear_3d")
@@ -997,10 +971,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R2x2_non_linear_3d")
@@ -1027,10 +1000,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
 
     SECTION("R3x3_non_linear_3d")
@@ -1065,10 +1037,9 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       DiscreteFunctionInterpoler interpoler(mesh_3d, zone_list, std::make_shared<DiscreteFunctionDescriptorP0>(),
                                             function_symbol_id);
-      std::shared_ptr discrete_function = interpoler.interpolate();
+      DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
-      REQUIRE(
-        same_cell_value(cell_value, dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*discrete_function)));
+      REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<Dimension, const DataType>>()));
     }
   }
 }
diff --git a/tests/test_DiscreteFunctionUtils.cpp b/tests/test_DiscreteFunctionUtils.cpp
index c1cb07afea956f0ae9f70f6497d08bfda06edc71..85c916c4cf559ca4c2ceef612d1da03db779715e 100644
--- a/tests/test_DiscreteFunctionUtils.cpp
+++ b/tests/test_DiscreteFunctionUtils.cpp
@@ -28,39 +28,50 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
         SECTION("common mesh")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
+          DiscreteFunctionP0<Dimension, double> uh(mesh);
+          DiscreteFunctionP0<Dimension, double> vh(mesh);
+          DiscreteFunctionP0<Dimension, TinyVector<2>> wh(mesh);
 
-          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
+          DiscreteFunctionP0<Dimension, double> qh(mesh_copy);
 
-          REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
-          REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
+          std::shared_ptr uh_v = std::make_shared<DiscreteFunctionVariant>(uh);
+          std::shared_ptr vh_v = std::make_shared<DiscreteFunctionVariant>(vh);
+          std::shared_ptr wh_v = std::make_shared<DiscreteFunctionVariant>(wh);
+          std::shared_ptr qh_v = std::make_shared<DiscreteFunctionVariant>(qh);
+
+          REQUIRE(getCommonMesh({uh_v, vh_v, wh_v}).get() == mesh.get());
+          REQUIRE(getCommonMesh({uh_v, vh_v, wh_v, qh_v}).use_count() == 0);
         }
 
         SECTION("check discretization type")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-
-          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
-
-          std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-          std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-
-          REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
-          REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
-          REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
-          REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
+          DiscreteFunctionP0<Dimension, double> uh(mesh);
+          DiscreteFunctionP0<Dimension, double> vh(mesh);
+          DiscreteFunctionP0<Dimension, double> qh(mesh_copy);
+
+          DiscreteFunctionP0Vector<Dimension, double> Uh(mesh, 3);
+          DiscreteFunctionP0Vector<Dimension, double> Vh(mesh, 3);
+
+          auto uh_v = std::make_shared<DiscreteFunctionVariant>(uh);
+          auto vh_v = std::make_shared<DiscreteFunctionVariant>(vh);
+          auto qh_v = std::make_shared<DiscreteFunctionVariant>(qh);
+          auto Uh_v = std::make_shared<DiscreteFunctionVariant>(Uh);
+          auto Vh_v = std::make_shared<DiscreteFunctionVariant>(Vh);
+
+          REQUIRE(checkDiscretizationType({uh_v}, DiscreteFunctionType::P0));
+          REQUIRE(checkDiscretizationType({uh_v, vh_v, qh_v}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({uh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({uh_v, vh_v, qh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh_v, Vh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({Uh_v, Vh_v}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({Uh_v}, DiscreteFunctionType::P0));
         }
 
         SECTION("scalar function shallow copy")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const double>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, double>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -68,14 +79,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^1 function shallow copy")
         {
-          using DataType     = TinyVector<1>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<1>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -83,14 +95,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^2 function shallow copy")
         {
-          using DataType     = TinyVector<2>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<2>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -98,14 +111,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^3 function shallow copy")
         {
-          using DataType     = TinyVector<3>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<3>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -113,14 +127,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^1x1 function shallow copy")
         {
-          using DataType     = TinyMatrix<1>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<1>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -128,14 +143,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^2x2 function shallow copy")
         {
-          using DataType     = TinyMatrix<2>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<2>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -143,14 +159,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^3x3 function shallow copy")
         {
-          using DataType     = TinyMatrix<3>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<3>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -158,8 +175,24 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
+        }
+
+        SECTION("P0Vector function shallow copy")
+        {
+          using DiscreteFunctionT = DiscreteFunctionP0Vector<Dimension, const double>;
+          std::shared_ptr uh =
+            std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0Vector<Dimension, double>(mesh, 2));
+          std::shared_ptr vh = shallowCopy(mesh, uh);
+
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellArrays()[CellId{0}][0]) ==
+                  &(wh->get<DiscreteFunctionT>().cellArrays()[CellId{0}][0]));
         }
       }
     }
@@ -181,39 +214,50 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
         SECTION("common mesh")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
+          DiscreteFunctionP0<Dimension, double> uh(mesh);
+          DiscreteFunctionP0<Dimension, double> vh(mesh);
+          DiscreteFunctionP0<Dimension, TinyVector<2>> wh(mesh);
 
-          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
+          DiscreteFunctionP0<Dimension, double> qh(mesh_copy);
 
-          REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
-          REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
+          std::shared_ptr uh_v = std::make_shared<DiscreteFunctionVariant>(uh);
+          std::shared_ptr vh_v = std::make_shared<DiscreteFunctionVariant>(vh);
+          std::shared_ptr wh_v = std::make_shared<DiscreteFunctionVariant>(wh);
+          std::shared_ptr qh_v = std::make_shared<DiscreteFunctionVariant>(qh);
+
+          REQUIRE(getCommonMesh({uh_v, vh_v, wh_v}).get() == mesh.get());
+          REQUIRE(getCommonMesh({uh_v, vh_v, wh_v, qh_v}).use_count() == 0);
         }
 
         SECTION("check discretization type")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-
-          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
-
-          std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-          std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-
-          REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
-          REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
-          REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
-          REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
+          DiscreteFunctionP0<Dimension, double> uh(mesh);
+          DiscreteFunctionP0<Dimension, double> vh(mesh);
+          DiscreteFunctionP0<Dimension, double> qh(mesh_copy);
+
+          DiscreteFunctionP0Vector<Dimension, double> Uh(mesh, 3);
+          DiscreteFunctionP0Vector<Dimension, double> Vh(mesh, 3);
+
+          auto uh_v = std::make_shared<DiscreteFunctionVariant>(uh);
+          auto vh_v = std::make_shared<DiscreteFunctionVariant>(vh);
+          auto qh_v = std::make_shared<DiscreteFunctionVariant>(qh);
+          auto Uh_v = std::make_shared<DiscreteFunctionVariant>(Uh);
+          auto Vh_v = std::make_shared<DiscreteFunctionVariant>(Vh);
+
+          REQUIRE(checkDiscretizationType({uh_v}, DiscreteFunctionType::P0));
+          REQUIRE(checkDiscretizationType({uh_v, vh_v, qh_v}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({uh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({uh_v, vh_v, qh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh_v, Vh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({Uh_v, Vh_v}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({Uh_v}, DiscreteFunctionType::P0));
         }
 
         SECTION("scalar function shallow copy")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const double>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, double>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -221,14 +265,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^1 function shallow copy")
         {
-          using DataType     = TinyVector<1>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<1>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -236,14 +281,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^2 function shallow copy")
         {
-          using DataType     = TinyVector<2>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<2>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -251,14 +297,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^3 function shallow copy")
         {
-          using DataType     = TinyVector<3>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<3>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -266,14 +313,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^1x1 function shallow copy")
         {
-          using DataType     = TinyMatrix<1>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<1>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -281,14 +329,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^2x2 function shallow copy")
         {
-          using DataType     = TinyMatrix<2>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<2>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -296,14 +345,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^3x3 function shallow copy")
         {
-          using DataType     = TinyMatrix<3>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<3>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -311,8 +361,24 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
+        }
+
+        SECTION("P0Vector function shallow copy")
+        {
+          using DiscreteFunctionT = DiscreteFunctionP0Vector<Dimension, const double>;
+          std::shared_ptr uh =
+            std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0Vector<Dimension, double>(mesh, 2));
+          std::shared_ptr vh = shallowCopy(mesh, uh);
+
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellArrays()[CellId{0}][0]) ==
+                  &(wh->get<DiscreteFunctionT>().cellArrays()[CellId{0}][0]));
         }
       }
     }
@@ -334,39 +400,50 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
         SECTION("common mesh")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr wh = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh);
+          DiscreteFunctionP0<Dimension, double> uh(mesh);
+          DiscreteFunctionP0<Dimension, double> vh(mesh);
+          DiscreteFunctionP0<Dimension, TinyVector<2>> wh(mesh);
+
+          DiscreteFunctionP0<Dimension, double> qh(mesh_copy);
 
-          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
+          std::shared_ptr uh_v = std::make_shared<DiscreteFunctionVariant>(uh);
+          std::shared_ptr vh_v = std::make_shared<DiscreteFunctionVariant>(vh);
+          std::shared_ptr wh_v = std::make_shared<DiscreteFunctionVariant>(wh);
+          std::shared_ptr qh_v = std::make_shared<DiscreteFunctionVariant>(qh);
 
-          REQUIRE(getCommonMesh({uh, vh, wh}).get() == mesh.get());
-          REQUIRE(getCommonMesh({uh, vh, wh, qh}).use_count() == 0);
+          REQUIRE(getCommonMesh({uh_v, vh_v, wh_v}).get() == mesh.get());
+          REQUIRE(getCommonMesh({uh_v, vh_v, wh_v, qh_v}).use_count() == 0);
         }
 
         SECTION("check discretization type")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-          std::shared_ptr vh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-
-          std::shared_ptr qh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_copy);
-
-          std::shared_ptr Uh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-          std::shared_ptr Vh = std::make_shared<DiscreteFunctionP0Vector<Dimension, double>>(mesh, 3);
-
-          REQUIRE(checkDiscretizationType({uh}, DiscreteFunctionType::P0));
-          REQUIRE(checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0));
-          REQUIRE(not checkDiscretizationType({uh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(not checkDiscretizationType({uh, vh, qh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(checkDiscretizationType({Uh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0Vector));
-          REQUIRE(not checkDiscretizationType({Uh, Vh}, DiscreteFunctionType::P0));
-          REQUIRE(not checkDiscretizationType({Uh}, DiscreteFunctionType::P0));
+          DiscreteFunctionP0<Dimension, double> uh(mesh);
+          DiscreteFunctionP0<Dimension, double> vh(mesh);
+          DiscreteFunctionP0<Dimension, double> qh(mesh_copy);
+
+          DiscreteFunctionP0Vector<Dimension, double> Uh(mesh, 3);
+          DiscreteFunctionP0Vector<Dimension, double> Vh(mesh, 3);
+
+          auto uh_v = std::make_shared<DiscreteFunctionVariant>(uh);
+          auto vh_v = std::make_shared<DiscreteFunctionVariant>(vh);
+          auto qh_v = std::make_shared<DiscreteFunctionVariant>(qh);
+          auto Uh_v = std::make_shared<DiscreteFunctionVariant>(Uh);
+          auto Vh_v = std::make_shared<DiscreteFunctionVariant>(Vh);
+
+          REQUIRE(checkDiscretizationType({uh_v}, DiscreteFunctionType::P0));
+          REQUIRE(checkDiscretizationType({uh_v, vh_v, qh_v}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({uh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({uh_v, vh_v, qh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(checkDiscretizationType({Uh_v, Vh_v}, DiscreteFunctionType::P0Vector));
+          REQUIRE(not checkDiscretizationType({Uh_v, Vh_v}, DiscreteFunctionType::P0));
+          REQUIRE(not checkDiscretizationType({Uh_v}, DiscreteFunctionType::P0));
         }
 
         SECTION("scalar function shallow copy")
         {
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const double>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, double>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -374,14 +451,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^1 function shallow copy")
         {
-          using DataType     = TinyVector<1>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<1>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -389,14 +467,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^2 function shallow copy")
         {
-          using DataType     = TinyVector<2>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<2>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -404,14 +483,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^3 function shallow copy")
         {
-          using DataType     = TinyVector<3>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyVector<3>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -419,14 +499,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^1x1 function shallow copy")
         {
-          using DataType     = TinyMatrix<1>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<1>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -434,14 +515,15 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^2x2 function shallow copy")
         {
-          using DataType     = TinyMatrix<2>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<2>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -449,14 +531,31 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
         }
 
         SECTION("R^3x3 function shallow copy")
         {
-          using DataType     = TinyMatrix<3>;
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, DataType>>(mesh);
+          using DataType          = TinyMatrix<3>;
+          using DiscreteFunctionT = DiscreteFunctionP0<Dimension, const DataType>;
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, DataType>(mesh));
+          std::shared_ptr vh = shallowCopy(mesh, uh);
+
+          REQUIRE(uh == vh);
+
+          std::shared_ptr wh = shallowCopy(mesh_copy, uh);
+
+          REQUIRE(uh != wh);
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellValues()[CellId{0}]) ==
+                  &(wh->get<DiscreteFunctionT>().cellValues()[CellId{0}]));
+        }
+
+        SECTION("P0Vector function shallow copy")
+        {
+          using DiscreteFunctionT = DiscreteFunctionP0Vector<Dimension, const double>;
+          std::shared_ptr uh =
+            std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0Vector<Dimension, double>(mesh, 2));
           std::shared_ptr vh = shallowCopy(mesh, uh);
 
           REQUIRE(uh == vh);
@@ -464,8 +563,8 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr wh = shallowCopy(mesh_copy, uh);
 
           REQUIRE(uh != wh);
-          REQUIRE(&(uh->cellValues()[CellId{0}]) ==
-                  &(dynamic_cast<const DiscreteFunctionP0<Dimension, DataType>&>(*wh).cellValues()[CellId{0}]));
+          REQUIRE(&(uh->get<DiscreteFunctionT>().cellArrays()[CellId{0}][0]) ==
+                  &(wh->get<DiscreteFunctionT>().cellArrays()[CellId{0}][0]));
         }
       }
     }
@@ -487,7 +586,7 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
           std::shared_ptr other_mesh =
             CartesianMeshBuilder{TinyVector<1>{-1}, TinyVector<1>{3}, TinyVector<1, size_t>{19}}.mesh();
 
-          std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+          std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, double>(mesh));
 
           REQUIRE_THROWS_WITH(shallowCopy(other_mesh, uh), "error: cannot shallow copy when connectivity changes");
         }
@@ -501,7 +600,7 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
       std::shared_ptr mesh_1d = MeshDataBaseForTests::get().cartesian1DMesh();
       std::shared_ptr mesh_2d = MeshDataBaseForTests::get().cartesian2DMesh();
 
-      std::shared_ptr uh = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh_1d);
+      std::shared_ptr uh = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionP0<Dimension, double>(mesh_1d));
 
       REQUIRE_THROWS_WITH(shallowCopy(mesh_2d, uh), "error: incompatible mesh dimensions");
     }
diff --git a/tests/test_DiscreteFunctionVectorIntegrator.cpp b/tests/test_DiscreteFunctionVectorIntegrator.cpp
index 848db0dfdb6af1d9efaa166411454aee626e67bb..4c510542ba9b244d505b7531db8d49f0c106bc2d 100644
--- a/tests/test_DiscreteFunctionVectorIntegrator.cpp
+++ b/tests/test_DiscreteFunctionVectorIntegrator.cpp
@@ -23,6 +23,7 @@
 
 #include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/DiscreteFunctionVectorIntegrator.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -100,7 +101,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
         DiscreteFunctionVectorIntegrator integrator(mesh_1d, quadrature_descriptor,
                                                     std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                     function_id_list);
-        std::shared_ptr discrete_function = integrator.integrate();
+        DiscreteFunctionVariant discrete_function = integrator.integrate();
 
         size_t i = 0;
 
@@ -108,36 +109,32 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_1d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_1d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_1d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<1>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_1d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         REQUIRE(i == function_id_list.size());
@@ -193,7 +190,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
         DiscreteFunctionVectorIntegrator integrator(mesh_2d, quadrature_descriptor,
                                                     std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                     function_id_list);
-        std::shared_ptr discrete_function = integrator.integrate();
+        DiscreteFunctionVariant discrete_function = integrator.integrate();
 
         size_t i = 0;
 
@@ -201,36 +198,32 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_2d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_2d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_2d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<2>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_2d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         REQUIRE(i == function_id_list.size());
@@ -286,7 +279,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
         DiscreteFunctionVectorIntegrator integrator(mesh_3d, quadrature_descriptor,
                                                     std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                     function_id_list);
-        std::shared_ptr discrete_function = integrator.integrate();
+        DiscreteFunctionVariant discrete_function = integrator.integrate();
 
         size_t i = 0;
 
@@ -294,36 +287,32 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_3d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_3d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_3d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
           CellValue<double> cell_value =
             IntegrateCellValue<double(TinyVector<3>)>::integrate(function_id_list[i], *quadrature_descriptor, *mesh_3d);
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         REQUIRE(i == function_id_list.size());
diff --git a/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp b/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp
index 98111d4c6b7f1dd4788745db480e72c06c083945..ac516d3ff9b7d20e3cedd152f522c7f7263e8acd 100644
--- a/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp
+++ b/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp
@@ -25,6 +25,7 @@
 
 #include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/DiscreteFunctionVectorIntegrator.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -103,7 +104,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
     DiscreteFunctionVectorIntegrator integrator(mesh_1d, zone_list, quadrature_descriptor,
                                                 std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                 function_id_list);
-    std::shared_ptr discrete_function = integrator.integrate();
+    DiscreteFunctionVariant discrete_function = integrator.integrate();
 
     size_t i = 0;
 
@@ -121,8 +122,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -139,8 +140,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -157,8 +158,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -175,8 +176,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     REQUIRE(i == function_id_list.size());
@@ -231,7 +232,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
     DiscreteFunctionVectorIntegrator integrator(mesh_2d, zone_list, quadrature_descriptor,
                                                 std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                 function_id_list);
-    std::shared_ptr discrete_function = integrator.integrate();
+    DiscreteFunctionVariant discrete_function = integrator.integrate();
 
     size_t i = 0;
 
@@ -249,8 +250,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -267,8 +268,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -285,8 +286,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -303,8 +304,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     REQUIRE(i == function_id_list.size());
@@ -359,7 +360,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
     DiscreteFunctionVectorIntegrator integrator(mesh_3d, zone_list, quadrature_descriptor,
                                                 std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                 function_id_list);
-    std::shared_ptr discrete_function = integrator.integrate();
+    DiscreteFunctionVariant discrete_function = integrator.integrate();
 
     size_t i = 0;
 
@@ -377,8 +378,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -395,8 +396,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -413,8 +414,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -431,8 +432,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           cell_value[cell_id]  = array[j];
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     REQUIRE(i == function_id_list.size());
diff --git a/tests/test_DiscreteFunctionVectorInterpoler.cpp b/tests/test_DiscreteFunctionVectorInterpoler.cpp
index a52e0e7c84f9174e761a4953db9c98acdf2db496..e47b8754d7ff7fe46d8f90d903af2efaed06be51 100644
--- a/tests/test_DiscreteFunctionVectorInterpoler.cpp
+++ b/tests/test_DiscreteFunctionVectorInterpoler.cpp
@@ -20,6 +20,7 @@
 
 #include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -96,7 +97,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
         DiscreteFunctionVectorInterpoler interpoler(mesh_1d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                     function_id_list);
-        std::shared_ptr discrete_function = interpoler.interpolate();
+        DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
         size_t i = 0;
 
@@ -108,9 +109,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -121,9 +121,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               cell_value[cell_id]            = std::floor(3 * x[0] * x[0] + 2);
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -134,9 +133,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 1);
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -147,9 +145,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               cell_value[cell_id]            = 2 * std::exp(x[0]) + 3;
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         REQUIRE(i == function_id_list.size());
@@ -204,7 +201,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
 
         DiscreteFunctionVectorInterpoler interpoler(mesh_2d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                     function_id_list);
-        std::shared_ptr discrete_function = interpoler.interpolate();
+        DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
         size_t i = 0;
 
@@ -216,9 +213,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
               cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -229,9 +225,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
               cell_value[cell_id]            = std::floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -242,9 +237,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
               cell_value[cell_id]            = std::floor(std::exp(2 * x[1]) - 1);
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -255,9 +249,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
               cell_value[cell_id]            = 2 * std::exp(x[0] + x[1]) + 3;
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         REQUIRE(i == function_id_list.size());
@@ -312,7 +305,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
 
         DiscreteFunctionVectorInterpoler interpoler(mesh_3d, std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                     function_id_list);
-        std::shared_ptr discrete_function = interpoler.interpolate();
+        DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
         size_t i = 0;
 
@@ -324,9 +317,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
               cell_value[cell_id]            = std::exp(2 * x[0] + x[2]) + 3 > 4;
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -337,9 +329,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
               cell_value[cell_id]            = std::floor(3 * (x[0] * x[1]) * (x[0] * x[1]) + 2);
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -350,9 +341,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
               cell_value[cell_id]            = std::floor(std::exp(2 * x[1]) - x[2]);
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         {
@@ -363,9 +353,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
               cell_value[cell_id]            = 2 * std::exp(x[0] + x[1]) + 3 * x[2];
             });
 
-          REQUIRE(
-            same_cell_value(cell_value, i++,
-                            dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+          REQUIRE(same_cell_value(cell_value, i++,
+                                  discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
         }
 
         REQUIRE(i == function_id_list.size());
diff --git a/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp b/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
index 5bbe87d84f4204b9dd9a4dda985ded8f39aaf59c..2fe9849341741d0483202621f40cb45dbc4acc81 100644
--- a/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
+++ b/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
@@ -22,6 +22,7 @@
 
 #include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -105,7 +106,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
     DiscreteFunctionVectorInterpoler interpoler(mesh_1d, zone_list,
                                                 std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                 function_id_list);
-    std::shared_ptr discrete_function = interpoler.interpolate();
+    DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
     size_t i = 0;
 
@@ -121,8 +122,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -137,8 +138,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -153,8 +154,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -169,8 +170,8 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     REQUIRE(i == function_id_list.size());
@@ -230,7 +231,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
     DiscreteFunctionVectorInterpoler interpoler(mesh_2d, zone_list,
                                                 std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                 function_id_list);
-    std::shared_ptr discrete_function = interpoler.interpolate();
+    DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
     size_t i = 0;
 
@@ -246,8 +247,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -262,8 +263,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -278,8 +279,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -294,8 +295,8 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     REQUIRE(i == function_id_list.size());
@@ -355,7 +356,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
     DiscreteFunctionVectorInterpoler interpoler(mesh_3d, zone_list,
                                                 std::make_shared<DiscreteFunctionDescriptorP0Vector>(),
                                                 function_id_list);
-    std::shared_ptr discrete_function = interpoler.interpolate();
+    DiscreteFunctionVariant discrete_function = interpoler.interpolate();
 
     size_t i = 0;
 
@@ -371,8 +372,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -387,8 +388,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -403,8 +404,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     {
@@ -419,8 +420,8 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
           }
         });
 
-      REQUIRE(same_cell_value(cell_value, i++,
-                              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*discrete_function)));
+      REQUIRE(
+        same_cell_value(cell_value, i++, discrete_function.get<DiscreteFunctionP0Vector<Dimension, const double>>()));
     }
 
     REQUIRE(i == function_id_list.size());
diff --git a/tests/test_EmbeddedDiscreteFunctionMathFunctions.hpp b/tests/test_EmbeddedDiscreteFunctionMathFunctions.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bfa17ffc7609a8a304ee5dd36b3574782fbba464
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionMathFunctions.hpp
@@ -0,0 +1,93 @@
+#ifndef TEST_EMBEDDED_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
+#define TEST_EMBEDDED_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
+
+#define CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(P_U, FCT, U_TYPE, FU_TYPE) \
+  {                                                                            \
+    std::shared_ptr p_fu = ::FCT(P_U);                                         \
+                                                                               \
+    REQUIRE(p_fu.use_count() > 0);                                             \
+                                                                               \
+    const U_TYPE& u   = P_U->get<U_TYPE>();                                    \
+    const FU_TYPE& fu = p_fu->get<FU_TYPE>();                                  \
+                                                                               \
+    bool is_same = true;                                                       \
+    auto values  = u.cellValues();                                             \
+    for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {    \
+      using namespace std;                                                     \
+      if (fu[cell_id] != FCT(values[cell_id])) {                               \
+        is_same = false;                                                       \
+        break;                                                                 \
+      }                                                                        \
+    }                                                                          \
+                                                                               \
+    REQUIRE(is_same);                                                          \
+  }
+
+#define CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(P_U, P_V, FCT, U_TYPE, V_TYPE, FUV_TYPE) \
+  {                                                                                           \
+    std::shared_ptr p_fuv = ::FCT(P_U, P_V);                                                  \
+                                                                                              \
+    REQUIRE(p_fuv.use_count() > 0);                                                           \
+                                                                                              \
+    const U_TYPE& u     = P_U->get<U_TYPE>();                                                 \
+    const V_TYPE& v     = P_V->get<V_TYPE>();                                                 \
+    const FUV_TYPE& fuv = p_fuv->get<FUV_TYPE>();                                             \
+                                                                                              \
+    bool is_same  = true;                                                                     \
+    auto u_values = u.cellValues();                                                           \
+    auto v_values = v.cellValues();                                                           \
+    for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {                   \
+      using namespace std;                                                                    \
+      if (fuv[cell_id] != FCT(u_values[cell_id], v_values[cell_id])) {                        \
+        is_same = false;                                                                      \
+        break;                                                                                \
+      }                                                                                       \
+    }                                                                                         \
+                                                                                              \
+    REQUIRE(is_same);                                                                         \
+  }
+
+#define CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(P_U, V, FCT, U_TYPE, FUV_TYPE) \
+  {                                                                                  \
+    std::shared_ptr p_fuv = ::FCT(P_U, V);                                           \
+                                                                                     \
+    REQUIRE(p_fuv.use_count() > 0);                                                  \
+    const U_TYPE& u     = P_U->get<U_TYPE>();                                        \
+    const FUV_TYPE& fuv = p_fuv->get<FUV_TYPE>();                                    \
+                                                                                     \
+    bool is_same  = true;                                                            \
+    auto u_values = u.cellValues();                                                  \
+    for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {          \
+      using namespace std;                                                           \
+      if (fuv[cell_id] != FCT(u_values[cell_id], V)) {                               \
+        is_same = false;                                                             \
+        break;                                                                       \
+      }                                                                              \
+    }                                                                                \
+                                                                                     \
+    REQUIRE(is_same);                                                                \
+  }
+
+#define CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(U, P_V, FCT, V_TYPE, FUV_TYPE) \
+  {                                                                                  \
+    std::shared_ptr p_fuv = ::FCT(U, P_V);                                           \
+                                                                                     \
+    REQUIRE(p_fuv.use_count() > 0);                                                  \
+                                                                                     \
+    const V_TYPE& v     = P_V->get<V_TYPE>();                                        \
+    const FUV_TYPE& fuv = p_fuv->get<FUV_TYPE>();                                    \
+                                                                                     \
+    bool is_same  = true;                                                            \
+    auto v_values = v.cellValues();                                                  \
+    for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {          \
+      using namespace std;                                                           \
+      if (fuv[cell_id] != FCT(U, v_values[cell_id])) {                               \
+        is_same = false;                                                             \
+        break;                                                                       \
+      }                                                                              \
+    }                                                                                \
+                                                                                     \
+    REQUIRE(is_same);                                                                \
+  }
+
+#endif   // TEST_EMBEDDED_DISCRETE_FUNCTION_MATH_FUNCTIONS_HPP
diff --git a/tests/test_EmbeddedDiscreteFunctionMathFunctions1D.cpp b/tests/test_EmbeddedDiscreteFunctionMathFunctions1D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed0331dde90ba7270c0050a633a84982545a5e7b
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionMathFunctions1D.cpp
@@ -0,0 +1,715 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <scheme/DiscreteFunctionP0.hpp>
+
+#include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+
+#include <test_EmbeddedDiscreteFunctionMathFunctions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("EmbeddedDiscreteFunctionVariantMathFunctions1D", "[scheme]")
+{
+  constexpr size_t Dimension = 1;
+
+  using Rd = TinyVector<Dimension>;
+
+  std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+  using DiscreteFunctionR    = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteFunctionR1   = DiscreteFunctionP0<Dimension, const TinyVector<1>>;
+  using DiscreteFunctionR2   = DiscreteFunctionP0<Dimension, const TinyVector<2>>;
+  using DiscreteFunctionR3   = DiscreteFunctionP0<Dimension, const TinyVector<3>>;
+  using DiscreteFunctionR1x1 = DiscreteFunctionP0<Dimension, const TinyMatrix<1>>;
+  using DiscreteFunctionR2x2 = DiscreteFunctionP0<Dimension, const TinyMatrix<2>>;
+  using DiscreteFunctionR3x3 = DiscreteFunctionP0<Dimension, const TinyMatrix<3>>;
+
+  using DiscreteFunctionVector = DiscreteFunctionP0Vector<Dimension, const double>;
+
+  for (const auto& named_mesh : mesh_list) {
+    SECTION(named_mesh.name())
+    {
+      auto mesh = named_mesh.mesh();
+
+      std::shared_ptr other_mesh =
+        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      CellValue<double> values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> positive_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> bounded_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      std::shared_ptr p_u = std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, values));
+      std::shared_ptr p_other_mesh_u =
+        std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(other_mesh, values));
+      std::shared_ptr p_positive_u =
+        std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, positive_values));
+      std::shared_ptr p_bounded_u =
+        std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, bounded_values));
+
+      std::shared_ptr p_R1_u = [=] {
+        CellValue<TinyVector<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R1_v = [=] {
+        CellValue<TinyVector<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1_u = std::make_shared<const DiscreteFunctionVariant>(
+        DiscreteFunctionR1(other_mesh, p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+
+      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<2>{x[0], 1 + x[0] * x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<2>{x[0], x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<2>{x[0], x[1] + x[2]};
+        }
+      };
+
+      std::shared_ptr p_R2_u = [=] {
+        CellValue<TinyVector<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2_v = [=] {
+        CellValue<TinyVector<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2_u = std::make_shared<const DiscreteFunctionVariant>(
+        DiscreteFunctionR2(other_mesh, p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+
+      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<3>{x[0], x[1], x[0] + x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<3>{x[0], x[1], x[2]};
+        }
+      };
+
+      std::shared_ptr p_R3_u = [=] {
+        CellValue<TinyVector<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3_v = [=] {
+        CellValue<TinyVector<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3_u = std::make_shared<const DiscreteFunctionVariant>(
+        DiscreteFunctionR3(other_mesh, p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+
+      std::shared_ptr p_R1x1_u = [=] {
+        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2x2_u = [=] {
+        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
+                                        2 * x[1], -x[0]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3x3_u = [=] {
+        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
+                                        2 * x[1],        -x[0],           x[0] - x[1],   //
+                                        3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, uj));
+      }();
+
+      std::shared_ptr p_Vector3_u = [=] {
+        CellArray<double> uj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj_vector[cell_id][0] = 2 * x[0] + 1;
+            uj_vector[cell_id][1] = 1 - x[1] * x[2];
+            uj_vector[cell_id][2] = x[0] + x[2];
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, uj_vector));
+      }();
+
+      std::shared_ptr p_Vector3_v = [=] {
+        CellArray<double> vj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj_vector[cell_id][0] = x[0] * x[1] + 1;
+            vj_vector[cell_id][1] = 2 * x[1];
+            vj_vector[cell_id][2] = x[2] * x[0];
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, vj_vector));
+      }();
+
+      std::shared_ptr p_Vector2_w = [=] {
+        CellArray<double> wj_vector{mesh->connectivity(), 2};
+        parallel_for(
+          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            wj_vector[cell_id][0] = x[0] + x[1] * 2;
+            wj_vector[cell_id][1] = x[0] * x[1];
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, wj_vector));
+      }();
+
+      SECTION("sqrt Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, sqrt,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("abs Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, abs,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("sin Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, sin,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("cos Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, cos,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("tan Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, tan,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("asin Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, asin,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("acos Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, acos,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("atan Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, atan,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("sinh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, sinh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("cosh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, cosh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("tanh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, tanh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("asinh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, asinh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("acosh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, acosh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("atanh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, atanh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("exp Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, exp,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("log Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, log,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("atan2 Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+      }
+
+      SECTION("atan2 Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("atan2 R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("min Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+      }
+
+      SECTION("min Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("min R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("min Vh -> R")
+      {
+        REQUIRE(min(p_u) == min(p_u->get<DiscreteFunctionR>().cellValues()));
+        REQUIRE_THROWS_WITH(min(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("max Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+      }
+
+      SECTION("max Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("max Vh -> R")
+      {
+        REQUIRE(max(p_u) == max(p_u->get<DiscreteFunctionR>().cellValues()));
+        REQUIRE_THROWS_WITH(max(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("max R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("pow Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+      }
+
+      SECTION("pow Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("pow R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("dot Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot,   //
+                                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR);
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot,   //
+                                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR);
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot,   //
+                                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR);
+
+        {
+          std::shared_ptr p_fuv = ::dot(p_Vector3_u, p_Vector3_v);
+
+          REQUIRE(p_fuv.use_count() > 0);
+
+          const DiscreteFunctionVector& u = p_Vector3_u->get<DiscreteFunctionVector>();
+          const DiscreteFunctionVector& v = p_Vector3_v->get<DiscreteFunctionVector>();
+          const DiscreteFunctionR& fuv    = p_fuv->get<DiscreteFunctionR>();
+
+          bool is_same  = true;
+          auto u_arrays = u.cellArrays();
+          auto v_arrays = v.cellArrays();
+          for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
+            using namespace std;
+            double dot_u_v = [&](auto&& a, auto&& b) {
+              double sum = 0;
+              for (size_t i = 0; i < a.size(); ++i) {
+                sum += a[i] * b[i];
+              }
+              return sum;
+            }(u_arrays[cell_id], v_arrays[cell_id]);
+            if (fuv[cell_id] != dot_u_v) {
+              is_same = false;
+              break;
+            }
+          }
+
+          REQUIRE(is_same);
+        }
+
+        REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
+        REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
+      }
+
+      SECTION("det Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, det,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, det,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, det,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR);
+
+        REQUIRE_THROWS_WITH(det(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(det(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(det(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(det(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("trace Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, trace,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, trace,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, trace,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR);
+
+        REQUIRE_THROWS_WITH(trace(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(trace(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(trace(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(trace(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("inverse Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, inverse,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, inverse,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, inverse,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+        REQUIRE_THROWS_WITH(inverse(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(inverse(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(inverse(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(inverse(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("transpose Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, transpose,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, transpose,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, transpose,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+        REQUIRE_THROWS_WITH(transpose(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(transpose(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(transpose(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(transpose(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("sum_of_Vh Vh -> Vh")
+      {
+        {
+          auto p_sum_components = sum_of_Vh_components(p_Vector3_u);
+          REQUIRE(p_sum_components.use_count() == 1);
+
+          const DiscreteFunctionR& sum_components = p_sum_components->get<DiscreteFunctionR>();
+          const DiscreteFunctionVector& vector3_u = p_Vector3_u->get<DiscreteFunctionVector>();
+          DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            double sum = 0;
+            for (size_t i = 0; i < vector3_u.size(); ++i) {
+              sum += vector3_u[cell_id][i];
+            }
+
+            direct_sum[cell_id] = sum;
+          }
+
+          bool is_same = true;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            if (sum_components[cell_id] != direct_sum[cell_id]) {
+              is_same = false;
+              break;
+            }
+          }
+
+          REQUIRE(is_same);
+        }
+
+        REQUIRE_THROWS_WITH(sum_of_Vh_components(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("vectorize (Vh) -> Vh")
+      {
+        {
+          std::shared_ptr p_vector3 = vectorize(std::vector{p_u, p_positive_u, p_bounded_u});
+          REQUIRE(p_vector3.use_count() == 1);
+
+          const DiscreteFunctionVector vector3 = p_vector3->get<DiscreteFunctionVector>();
+
+          const DiscreteFunctionR& u          = p_u->get<DiscreteFunctionR>();
+          const DiscreteFunctionR& positive_u = p_positive_u->get<DiscreteFunctionR>();
+          const DiscreteFunctionR& bounded_u  = p_bounded_u->get<DiscreteFunctionR>();
+
+          REQUIRE(vector3.size() == 3);
+
+          bool is_same = true;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            is_same &= (u[cell_id] == vector3[cell_id][0]);
+            is_same &= (positive_u[cell_id] == vector3[cell_id][1]);
+            is_same &= (bounded_u[cell_id] == vector3[cell_id][2]);
+          }
+          REQUIRE(is_same);
+        }
+
+        REQUIRE_THROWS_WITH(vectorize(std::vector{p_u, p_other_mesh_u}),
+                            "error: discrete functions are not defined on the same mesh");
+        REQUIRE_THROWS_WITH(vectorize(std::vector{p_R1_u}), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("dot Vh*Rd -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot,   //
+                                                      DiscreteFunctionR1, DiscreteFunctionR);
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot,   //
+                                                      DiscreteFunctionR2, DiscreteFunctionR);
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot,   //
+                                                      DiscreteFunctionR3, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
+                            "error: incompatible operand types Vh(P0:R^1) and R^2");
+        REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
+                            "error: incompatible operand types Vh(P0:R^2) and R^3");
+        REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
+      }
+
+      SECTION("dot Rd*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot,   //
+                                                      DiscreteFunctionR1, DiscreteFunctionR);
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot,   //
+                                                      DiscreteFunctionR2, DiscreteFunctionR);
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot,   //
+                                                      DiscreteFunctionR3, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
+                            "error: incompatible operand types R^2 and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
+                            "error: incompatible operand types R^3 and Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
+      }
+
+      SECTION("sum_of_R* Vh -> R*")
+      {
+        REQUIRE(sum_of<double>(p_u) == sum(p_u->get<DiscreteFunctionR>().cellValues()));
+        REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+        REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+        REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+        REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues()));
+        REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues()));
+        REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues()));
+
+        REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+      }
+
+      SECTION("integral_of_R* Vh -> R*")
+      {
+        auto integrate_locally = [&](const auto& cell_values) {
+          const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+          using DataType = decltype(double{} * cell_values[CellId{0}]);
+          CellValue<DataType> local_integral{mesh->connectivity()};
+          parallel_for(
+            local_integral.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
+          return local_integral;
+        };
+
+        REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->get<DiscreteFunctionR>().cellValues())));
+        REQUIRE(integral_of<TinyVector<1>>(p_R1_u) ==
+                sum(integrate_locally(p_R1_u->get<DiscreteFunctionR1>().cellValues())));
+        REQUIRE(integral_of<TinyVector<2>>(p_R2_u) ==
+                sum(integrate_locally(p_R2_u->get<DiscreteFunctionR2>().cellValues())));
+        REQUIRE(integral_of<TinyVector<3>>(p_R3_u) ==
+                sum(integrate_locally(p_R3_u->get<DiscreteFunctionR3>().cellValues())));
+        REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) ==
+                sum(integrate_locally(p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues())));
+        REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) ==
+                sum(integrate_locally(p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues())));
+        REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) ==
+                sum(integrate_locally(p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues())));
+
+        REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+      }
+    }
+  }
+}
diff --git a/tests/test_EmbeddedDiscreteFunctionMathFunctions2D.cpp b/tests/test_EmbeddedDiscreteFunctionMathFunctions2D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dfbdb01500b08f73dd9804d4d4581aafe4c4ba63
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionMathFunctions2D.cpp
@@ -0,0 +1,718 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <scheme/DiscreteFunctionP0.hpp>
+
+#include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+
+#include <test_EmbeddedDiscreteFunctionMathFunctions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("EmbeddedDiscreteFunctionVariantMathFunctions2D", "[scheme]")
+{
+  SECTION("2D")
+  {
+    constexpr size_t Dimension = 2;
+
+    using Rd = TinyVector<Dimension>;
+
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    using DiscreteFunctionR    = DiscreteFunctionP0<Dimension, const double>;
+    using DiscreteFunctionR1   = DiscreteFunctionP0<Dimension, const TinyVector<1>>;
+    using DiscreteFunctionR2   = DiscreteFunctionP0<Dimension, const TinyVector<2>>;
+    using DiscreteFunctionR3   = DiscreteFunctionP0<Dimension, const TinyVector<3>>;
+    using DiscreteFunctionR1x1 = DiscreteFunctionP0<Dimension, const TinyMatrix<1>>;
+    using DiscreteFunctionR2x2 = DiscreteFunctionP0<Dimension, const TinyMatrix<2>>;
+    using DiscreteFunctionR3x3 = DiscreteFunctionP0<Dimension, const TinyMatrix<3>>;
+
+    using DiscreteFunctionVector = DiscreteFunctionP0Vector<Dimension, const double>;
+
+    for (const auto& named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh = named_mesh.mesh();
+
+        std::shared_ptr other_mesh =
+          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+        CellValue<double> values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> positive_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        CellValue<double> bounded_values = [=] {
+          CellValue<double> build_values{mesh->connectivity()};
+          parallel_for(
+            build_values.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
+          return build_values;
+        }();
+
+        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, values));
+        std::shared_ptr p_other_mesh_u =
+          std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(other_mesh, values));
+        std::shared_ptr p_positive_u =
+          std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, positive_values));
+        std::shared_ptr p_bounded_u =
+          std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, bounded_values));
+
+        std::shared_ptr p_R1_u = [=] {
+          CellValue<TinyVector<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, uj));
+        }();
+
+        std::shared_ptr p_R1_v = [=] {
+          CellValue<TinyVector<1>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, vj));
+        }();
+
+        std::shared_ptr p_other_mesh_R1_u = std::make_shared<const DiscreteFunctionVariant>(
+          DiscreteFunctionR1(other_mesh, p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+
+        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+          if constexpr (Dimension == 1) {
+            return TinyVector<2>{x[0], 1 + x[0] * x[0]};
+          } else if constexpr (Dimension == 2) {
+            return TinyVector<2>{x[0], x[1]};
+          } else if constexpr (Dimension == 3) {
+            return TinyVector<2>{x[0], x[1] + x[2]};
+          }
+        };
+
+        std::shared_ptr p_R2_u = [=] {
+          CellValue<TinyVector<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, uj));
+        }();
+
+        std::shared_ptr p_R2_v = [=] {
+          CellValue<TinyVector<2>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+              vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, vj));
+        }();
+
+        std::shared_ptr p_other_mesh_R2_u = std::make_shared<const DiscreteFunctionVariant>(
+          DiscreteFunctionR2(other_mesh, p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+
+        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+          if constexpr (Dimension == 1) {
+            return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
+          } else if constexpr (Dimension == 2) {
+            return TinyVector<3>{x[0], x[1], x[0] + x[1]};
+          } else if constexpr (Dimension == 3) {
+            return TinyVector<3>{x[0], x[1], x[2]};
+          }
+        };
+
+        std::shared_ptr p_R3_u = [=] {
+          CellValue<TinyVector<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, uj));
+        }();
+
+        std::shared_ptr p_R3_v = [=] {
+          CellValue<TinyVector<3>> vj{mesh->connectivity()};
+          parallel_for(
+            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, vj));
+        }();
+
+        std::shared_ptr p_other_mesh_R3_u = std::make_shared<const DiscreteFunctionVariant>(
+          DiscreteFunctionR3(other_mesh, p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+
+        std::shared_ptr p_R1x1_u = [=] {
+          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, uj));
+        }();
+
+        std::shared_ptr p_R2x2_u = [=] {
+          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<2> x = to_2d(xj[cell_id]);
+
+              uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
+                                          2 * x[1], -x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, uj));
+        }();
+
+        std::shared_ptr p_R3x3_u = [=] {
+          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+          parallel_for(
+            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+
+              uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
+                                          2 * x[1],        -x[0],           x[0] - x[1],   //
+                                          3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, uj));
+        }();
+
+        std::shared_ptr p_Vector3_u = [=] {
+          CellArray<double> uj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              uj_vector[cell_id][0] = 2 * x[0] + 1;
+              uj_vector[cell_id][1] = 1 - x[1] * x[2];
+              uj_vector[cell_id][2] = x[0] + x[2];
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, uj_vector));
+        }();
+
+        std::shared_ptr p_Vector3_v = [=] {
+          CellArray<double> vj_vector{mesh->connectivity(), 3};
+          parallel_for(
+            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              vj_vector[cell_id][0] = x[0] * x[1] + 1;
+              vj_vector[cell_id][1] = 2 * x[1];
+              vj_vector[cell_id][2] = x[2] * x[0];
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, vj_vector));
+        }();
+
+        std::shared_ptr p_Vector2_w = [=] {
+          CellArray<double> wj_vector{mesh->connectivity(), 2};
+          parallel_for(
+            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<3> x = to_3d(xj[cell_id]);
+              wj_vector[cell_id][0] = x[0] + x[1] * 2;
+              wj_vector[cell_id][1] = x[0] * x[1];
+            });
+
+          return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, wj_vector));
+        }();
+
+        SECTION("sqrt Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, sqrt,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("abs Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, abs,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("sin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, sin,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("cos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, cos,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("tan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, tan,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("asin Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, asin,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("acos Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, acos,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("atan Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, atan,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("sinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, sinh,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("cosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, cosh,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("tanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, tanh,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("asinh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, asinh,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("acosh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, acosh,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("atanh Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, atanh,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("exp Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, exp,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("log Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, log,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("atan2 Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2,   //
+                                                       DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        }
+
+        SECTION("atan2 Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
+
+        SECTION("atan2 R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
+
+        SECTION("min Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min,   //
+                                                       DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
+
+        SECTION("min Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
+
+        SECTION("min R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
+
+        SECTION("min Vh -> R")
+        {
+          REQUIRE(min(p_u) == min(p_u->get<DiscreteFunctionR>().cellValues()));
+          REQUIRE_THROWS_WITH(min(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("max Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max,   //
+                                                       DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
+
+        SECTION("max Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
+
+        SECTION("max Vh -> R")
+        {
+          REQUIRE(max(p_u) == max(p_u->get<DiscreteFunctionR>().cellValues()));
+          REQUIRE_THROWS_WITH(max(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("max R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
+
+        SECTION("pow Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow,   //
+                                                       DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        }
+
+        SECTION("pow Vh*R -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
+        }
+
+        SECTION("pow R*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow,   //
+                                                        DiscreteFunctionR, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+        }
+
+        SECTION("dot Vh*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot,   //
+                                                       DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot,   //
+                                                       DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR);
+          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot,   //
+                                                       DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR);
+
+          {
+            std::shared_ptr p_fuv = ::dot(p_Vector3_u, p_Vector3_v);
+
+            REQUIRE(p_fuv.use_count() > 0);
+
+            const DiscreteFunctionVector& u = p_Vector3_u->get<DiscreteFunctionVector>();
+            const DiscreteFunctionVector& v = p_Vector3_v->get<DiscreteFunctionVector>();
+            const DiscreteFunctionR& fuv    = p_fuv->get<DiscreteFunctionR>();
+
+            bool is_same  = true;
+            auto u_arrays = u.cellArrays();
+            auto v_arrays = v.cellArrays();
+            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
+              using namespace std;
+              double dot_u_v = [&](auto&& a, auto&& b) {
+                double sum = 0;
+                for (size_t i = 0; i < a.size(); ++i) {
+                  sum += a[i] * b[i];
+                }
+                return sum;
+              }(u_arrays[cell_id], v_arrays[cell_id]);
+              if (fuv[cell_id] != dot_u_v) {
+                is_same = false;
+                break;
+              }
+            }
+
+            REQUIRE(is_same);
+          }
+
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
+          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
+        }
+
+        SECTION("det Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, det,   //
+                                                      DiscreteFunctionR1x1, DiscreteFunctionR);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, det,   //
+                                                      DiscreteFunctionR2x2, DiscreteFunctionR);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, det,   //
+                                                      DiscreteFunctionR3x3, DiscreteFunctionR);
+
+          REQUIRE_THROWS_WITH(det(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(det(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(det(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(det(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        }
+
+        SECTION("trace Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, trace,   //
+                                                      DiscreteFunctionR1x1, DiscreteFunctionR);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, trace,   //
+                                                      DiscreteFunctionR2x2, DiscreteFunctionR);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, trace,   //
+                                                      DiscreteFunctionR3x3, DiscreteFunctionR);
+
+          REQUIRE_THROWS_WITH(trace(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(trace(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(trace(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(trace(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        }
+
+        SECTION("inverse Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, inverse,   //
+                                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, inverse,   //
+                                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, inverse,   //
+                                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+          REQUIRE_THROWS_WITH(inverse(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(inverse(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(inverse(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(inverse(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        }
+
+        SECTION("transpose Vh -> Vh")
+        {
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, transpose,   //
+                                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, transpose,   //
+                                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+          CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, transpose,   //
+                                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+          REQUIRE_THROWS_WITH(transpose(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(transpose(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(transpose(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(transpose(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        }
+
+        SECTION("sum_of_Vh Vh -> Vh")
+        {
+          {
+            auto p_sum_components = sum_of_Vh_components(p_Vector3_u);
+            REQUIRE(p_sum_components.use_count() == 1);
+
+            const DiscreteFunctionR& sum_components = p_sum_components->get<DiscreteFunctionR>();
+            const DiscreteFunctionVector& vector3_u = p_Vector3_u->get<DiscreteFunctionVector>();
+            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              double sum = 0;
+              for (size_t i = 0; i < vector3_u.size(); ++i) {
+                sum += vector3_u[cell_id][i];
+              }
+
+              direct_sum[cell_id] = sum;
+            }
+
+            bool is_same = true;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              if (sum_components[cell_id] != direct_sum[cell_id]) {
+                is_same = false;
+                break;
+              }
+            }
+
+            REQUIRE(is_same);
+          }
+
+          REQUIRE_THROWS_WITH(sum_of_Vh_components(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        }
+
+        SECTION("vectorize (Vh) -> Vh")
+        {
+          {
+            std::shared_ptr p_vector3 = vectorize(std::vector{p_u, p_positive_u, p_bounded_u});
+            REQUIRE(p_vector3.use_count() == 1);
+
+            const DiscreteFunctionVector vector3 = p_vector3->get<DiscreteFunctionVector>();
+
+            const DiscreteFunctionR& u          = p_u->get<DiscreteFunctionR>();
+            const DiscreteFunctionR& positive_u = p_positive_u->get<DiscreteFunctionR>();
+            const DiscreteFunctionR& bounded_u  = p_bounded_u->get<DiscreteFunctionR>();
+
+            REQUIRE(vector3.size() == 3);
+
+            bool is_same = true;
+            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+              is_same &= (u[cell_id] == vector3[cell_id][0]);
+              is_same &= (positive_u[cell_id] == vector3[cell_id][1]);
+              is_same &= (bounded_u[cell_id] == vector3[cell_id][2]);
+            }
+            REQUIRE(is_same);
+          }
+
+          REQUIRE_THROWS_WITH(vectorize(std::vector{p_u, p_other_mesh_u}),
+                              "error: discrete functions are not defined on the same mesh");
+          REQUIRE_THROWS_WITH(vectorize(std::vector{p_R1_u}), "error: invalid operand type Vh(P0:R^1)");
+        }
+
+        SECTION("dot Vh*Rd -> Vh")
+        {
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot,   //
+                                                        DiscreteFunctionR1, DiscreteFunctionR);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot,   //
+                                                        DiscreteFunctionR2, DiscreteFunctionR);
+          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot,   //
+                                                        DiscreteFunctionR3, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
+                              "error: incompatible operand types Vh(P0:R^1) and R^2");
+          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
+                              "error: incompatible operand types Vh(P0:R^2) and R^3");
+          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
+        }
+
+        SECTION("dot Rd*Vh -> Vh")
+        {
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot,   //
+                                                        DiscreteFunctionR1, DiscreteFunctionR);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot,   //
+                                                        DiscreteFunctionR2, DiscreteFunctionR);
+          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot,   //
+                                                        DiscreteFunctionR3, DiscreteFunctionR);
+          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
+                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
+                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
+        }
+
+        SECTION("sum_of_R* Vh -> R*")
+        {
+          REQUIRE(sum_of<double>(p_u) == sum(p_u->get<DiscreteFunctionR>().cellValues()));
+          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues()));
+          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues()));
+          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues()));
+
+          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+
+        SECTION("integral_of_R* Vh -> R*")
+        {
+          auto integrate_locally = [&](const auto& cell_values) {
+            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+            using DataType = decltype(double{} * cell_values[CellId{0}]);
+            CellValue<DataType> local_integral{mesh->connectivity()};
+            parallel_for(
+              local_integral.numberOfItems(),
+              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
+            return local_integral;
+          };
+
+          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->get<DiscreteFunctionR>().cellValues())));
+          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) ==
+                  sum(integrate_locally(p_R1_u->get<DiscreteFunctionR1>().cellValues())));
+          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) ==
+                  sum(integrate_locally(p_R2_u->get<DiscreteFunctionR2>().cellValues())));
+          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) ==
+                  sum(integrate_locally(p_R3_u->get<DiscreteFunctionR3>().cellValues())));
+          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) ==
+                  sum(integrate_locally(p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues())));
+          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) ==
+                  sum(integrate_locally(p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues())));
+          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) ==
+                  sum(integrate_locally(p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues())));
+
+          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_EmbeddedDiscreteFunctionMathFunctions3D.cpp b/tests/test_EmbeddedDiscreteFunctionMathFunctions3D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5f9896ad64df5601f54f9d701fc5bfe94f4d51b9
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionMathFunctions3D.cpp
@@ -0,0 +1,715 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <scheme/DiscreteFunctionP0.hpp>
+
+#include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+
+#include <test_EmbeddedDiscreteFunctionMathFunctions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("EmbeddedDiscreteFunctionVariantMathFunctions3D", "[scheme]")
+{
+  constexpr size_t Dimension = 3;
+
+  using Rd = TinyVector<Dimension>;
+
+  std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+  using DiscreteFunctionR    = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteFunctionR1   = DiscreteFunctionP0<Dimension, const TinyVector<1>>;
+  using DiscreteFunctionR2   = DiscreteFunctionP0<Dimension, const TinyVector<2>>;
+  using DiscreteFunctionR3   = DiscreteFunctionP0<Dimension, const TinyVector<3>>;
+  using DiscreteFunctionR1x1 = DiscreteFunctionP0<Dimension, const TinyMatrix<1>>;
+  using DiscreteFunctionR2x2 = DiscreteFunctionP0<Dimension, const TinyMatrix<2>>;
+  using DiscreteFunctionR3x3 = DiscreteFunctionP0<Dimension, const TinyMatrix<3>>;
+
+  using DiscreteFunctionVector = DiscreteFunctionP0Vector<Dimension, const double>;
+
+  for (const auto& named_mesh : mesh_list) {
+    SECTION(named_mesh.name())
+    {
+      auto mesh = named_mesh.mesh();
+
+      std::shared_ptr other_mesh =
+        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      CellValue<double> values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> positive_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> bounded_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      std::shared_ptr p_u = std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, values));
+      std::shared_ptr p_other_mesh_u =
+        std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(other_mesh, values));
+      std::shared_ptr p_positive_u =
+        std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, positive_values));
+      std::shared_ptr p_bounded_u =
+        std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR(mesh, bounded_values));
+
+      std::shared_ptr p_R1_u = [=] {
+        CellValue<TinyVector<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R1_v = [=] {
+        CellValue<TinyVector<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1_u = std::make_shared<const DiscreteFunctionVariant>(
+        DiscreteFunctionR1(other_mesh, p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+
+      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<2>{x[0], 1 + x[0] * x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<2>{x[0], x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<2>{x[0], x[1] + x[2]};
+        }
+      };
+
+      std::shared_ptr p_R2_u = [=] {
+        CellValue<TinyVector<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2_v = [=] {
+        CellValue<TinyVector<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2_u = std::make_shared<const DiscreteFunctionVariant>(
+        DiscreteFunctionR2(other_mesh, p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+
+      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<3>{x[0], x[1], x[0] + x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<3>{x[0], x[1], x[2]};
+        }
+      };
+
+      std::shared_ptr p_R3_u = [=] {
+        CellValue<TinyVector<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3_v = [=] {
+        CellValue<TinyVector<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3_u = std::make_shared<const DiscreteFunctionVariant>(
+        DiscreteFunctionR3(other_mesh, p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+
+      std::shared_ptr p_R1x1_u = [=] {
+        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2x2_u = [=] {
+        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
+                                        2 * x[1], -x[0]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3x3_u = [=] {
+        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
+                                        2 * x[1],        -x[0],           x[0] - x[1],   //
+                                        3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, uj));
+      }();
+
+      std::shared_ptr p_Vector3_u = [=] {
+        CellArray<double> uj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj_vector[cell_id][0] = 2 * x[0] + 1;
+            uj_vector[cell_id][1] = 1 - x[1] * x[2];
+            uj_vector[cell_id][2] = x[0] + x[2];
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, uj_vector));
+      }();
+
+      std::shared_ptr p_Vector3_v = [=] {
+        CellArray<double> vj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj_vector[cell_id][0] = x[0] * x[1] + 1;
+            vj_vector[cell_id][1] = 2 * x[1];
+            vj_vector[cell_id][2] = x[2] * x[0];
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, vj_vector));
+      }();
+
+      std::shared_ptr p_Vector2_w = [=] {
+        CellArray<double> wj_vector{mesh->connectivity(), 2};
+        parallel_for(
+          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            wj_vector[cell_id][0] = x[0] + x[1] * 2;
+            wj_vector[cell_id][1] = x[0] * x[1];
+          });
+
+        return std::make_shared<const DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, wj_vector));
+      }();
+
+      SECTION("sqrt Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, sqrt,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("abs Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, abs,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("sin Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, sin,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("cos Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, cos,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("tan Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, tan,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("asin Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, asin,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("acos Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, acos,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("atan Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, atan,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("sinh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, sinh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("cosh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, cosh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("tanh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, tanh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("asinh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, asinh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("acosh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, acosh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("atanh Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_bounded_u, atanh,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("exp Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_u, exp,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("log Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_positive_u, log,   //
+                                                    DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("atan2 Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+        REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+      }
+
+      SECTION("atan2 Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("atan2 R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("min Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+      }
+
+      SECTION("min Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("min R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("min Vh -> R")
+      {
+        REQUIRE(min(p_u) == min(p_u->get<DiscreteFunctionR>().cellValues()));
+        REQUIRE_THROWS_WITH(min(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("max Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+      }
+
+      SECTION("max Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("max Vh -> R")
+      {
+        REQUIRE(max(p_u) == max(p_u->get<DiscreteFunctionR>().cellValues()));
+        REQUIRE_THROWS_WITH(max(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("max R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("pow Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow,   //
+                                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
+      }
+
+      SECTION("pow Vh*R -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
+      }
+
+      SECTION("pow R*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow,   //
+                                                      DiscreteFunctionR, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
+      }
+
+      SECTION("dot Vh*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot,   //
+                                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR);
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot,   //
+                                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR);
+        CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot,   //
+                                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR);
+
+        {
+          std::shared_ptr p_fuv = ::dot(p_Vector3_u, p_Vector3_v);
+
+          REQUIRE(p_fuv.use_count() > 0);
+
+          const DiscreteFunctionVector& u = p_Vector3_u->get<DiscreteFunctionVector>();
+          const DiscreteFunctionVector& v = p_Vector3_v->get<DiscreteFunctionVector>();
+          const DiscreteFunctionR& fuv    = p_fuv->get<DiscreteFunctionR>();
+
+          bool is_same  = true;
+          auto u_arrays = u.cellArrays();
+          auto v_arrays = v.cellArrays();
+          for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
+            using namespace std;
+            double dot_u_v = [&](auto&& a, auto&& b) {
+              double sum = 0;
+              for (size_t i = 0; i < a.size(); ++i) {
+                sum += a[i] * b[i];
+              }
+              return sum;
+            }(u_arrays[cell_id], v_arrays[cell_id]);
+            if (fuv[cell_id] != dot_u_v) {
+              is_same = false;
+              break;
+            }
+          }
+
+          REQUIRE(is_same);
+        }
+
+        REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
+        REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
+        REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
+      }
+
+      SECTION("det Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, det,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, det,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, det,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR);
+
+        REQUIRE_THROWS_WITH(det(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(det(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(det(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(det(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("trace Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, trace,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, trace,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, trace,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR);
+
+        REQUIRE_THROWS_WITH(trace(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(trace(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(trace(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(trace(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("inverse Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, inverse,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, inverse,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, inverse,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+        REQUIRE_THROWS_WITH(inverse(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(inverse(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(inverse(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(inverse(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("transpose Vh -> Vh")
+      {
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R1x1_u, transpose,   //
+                                                    DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R2x2_u, transpose,   //
+                                                    DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+        CHECK_EMBEDDED_VH_TO_VH_FUNCTION_EVALUATION(p_R3x3_u, transpose,   //
+                                                    DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+        REQUIRE_THROWS_WITH(transpose(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(transpose(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(transpose(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(transpose(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("sum_of_Vh Vh -> Vh")
+      {
+        {
+          auto p_sum_components = sum_of_Vh_components(p_Vector3_u);
+          REQUIRE(p_sum_components.use_count() == 1);
+
+          const DiscreteFunctionR& sum_components = p_sum_components->get<DiscreteFunctionR>();
+          const DiscreteFunctionVector& vector3_u = p_Vector3_u->get<DiscreteFunctionVector>();
+          DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            double sum = 0;
+            for (size_t i = 0; i < vector3_u.size(); ++i) {
+              sum += vector3_u[cell_id][i];
+            }
+
+            direct_sum[cell_id] = sum;
+          }
+
+          bool is_same = true;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            if (sum_components[cell_id] != direct_sum[cell_id]) {
+              is_same = false;
+              break;
+            }
+          }
+
+          REQUIRE(is_same);
+        }
+
+        REQUIRE_THROWS_WITH(sum_of_Vh_components(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+      }
+
+      SECTION("vectorize (Vh) -> Vh")
+      {
+        {
+          std::shared_ptr p_vector3 = vectorize(std::vector{p_u, p_positive_u, p_bounded_u});
+          REQUIRE(p_vector3.use_count() == 1);
+
+          const DiscreteFunctionVector vector3 = p_vector3->get<DiscreteFunctionVector>();
+
+          const DiscreteFunctionR& u          = p_u->get<DiscreteFunctionR>();
+          const DiscreteFunctionR& positive_u = p_positive_u->get<DiscreteFunctionR>();
+          const DiscreteFunctionR& bounded_u  = p_bounded_u->get<DiscreteFunctionR>();
+
+          REQUIRE(vector3.size() == 3);
+
+          bool is_same = true;
+          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+            is_same &= (u[cell_id] == vector3[cell_id][0]);
+            is_same &= (positive_u[cell_id] == vector3[cell_id][1]);
+            is_same &= (bounded_u[cell_id] == vector3[cell_id][2]);
+          }
+          REQUIRE(is_same);
+        }
+
+        REQUIRE_THROWS_WITH(vectorize(std::vector{p_u, p_other_mesh_u}),
+                            "error: discrete functions are not defined on the same mesh");
+        REQUIRE_THROWS_WITH(vectorize(std::vector{p_R1_u}), "error: invalid operand type Vh(P0:R^1)");
+      }
+
+      SECTION("dot Vh*Rd -> Vh")
+      {
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot,   //
+                                                      DiscreteFunctionR1, DiscreteFunctionR);
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot,   //
+                                                      DiscreteFunctionR2, DiscreteFunctionR);
+        CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot,   //
+                                                      DiscreteFunctionR3, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
+                            "error: incompatible operand types Vh(P0:R^1) and R^2");
+        REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
+                            "error: incompatible operand types Vh(P0:R^2) and R^3");
+        REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
+      }
+
+      SECTION("dot Rd*Vh -> Vh")
+      {
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot,   //
+                                                      DiscreteFunctionR1, DiscreteFunctionR);
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot,   //
+                                                      DiscreteFunctionR2, DiscreteFunctionR);
+        CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot,   //
+                                                      DiscreteFunctionR3, DiscreteFunctionR);
+        REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
+                            "error: incompatible operand types R^2 and Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
+                            "error: incompatible operand types R^3 and Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
+      }
+
+      SECTION("sum_of_R* Vh -> R*")
+      {
+        REQUIRE(sum_of<double>(p_u) == sum(p_u->get<DiscreteFunctionR>().cellValues()));
+        REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+        REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+        REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+        REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues()));
+        REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues()));
+        REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues()));
+
+        REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+        REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+      }
+
+      SECTION("integral_of_R* Vh -> R*")
+      {
+        auto integrate_locally = [&](const auto& cell_values) {
+          const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+          using DataType = decltype(double{} * cell_values[CellId{0}]);
+          CellValue<DataType> local_integral{mesh->connectivity()};
+          parallel_for(
+            local_integral.numberOfItems(),
+            PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
+          return local_integral;
+        };
+
+        REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->get<DiscreteFunctionR>().cellValues())));
+        REQUIRE(integral_of<TinyVector<1>>(p_R1_u) ==
+                sum(integrate_locally(p_R1_u->get<DiscreteFunctionR1>().cellValues())));
+        REQUIRE(integral_of<TinyVector<2>>(p_R2_u) ==
+                sum(integrate_locally(p_R2_u->get<DiscreteFunctionR2>().cellValues())));
+        REQUIRE(integral_of<TinyVector<3>>(p_R3_u) ==
+                sum(integrate_locally(p_R3_u->get<DiscreteFunctionR3>().cellValues())));
+        REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) ==
+                sum(integrate_locally(p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues())));
+        REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) ==
+                sum(integrate_locally(p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues())));
+        REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) ==
+                sum(integrate_locally(p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues())));
+
+        REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
+        REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
+      }
+    }
+  }
+}
diff --git a/tests/test_EmbeddedDiscreteFunctionOperators.hpp b/tests/test_EmbeddedDiscreteFunctionOperators.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..93faf56eb06d16469a89bd54206620ee98e0e74d
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionOperators.hpp
@@ -0,0 +1,177 @@
+#ifndef TEST_EMBEDDED_DISCRETE_FUNCTION_OPERATORS_HPP
+#define TEST_EMBEDDED_DISCRETE_FUNCTION_OPERATORS_HPP
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <language/utils/EmbeddedDiscreteFunctionOperators.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+#define CHECK_EMBEDDED_VH2_TO_VH(P_U, OPERATOR, P_V, U_TYPE, V_TYPE, U_OP_V_TYPE) \
+  {                                                                               \
+    std::shared_ptr p_u_op_v = P_U OPERATOR P_V;                                  \
+                                                                                  \
+    REQUIRE(p_u_op_v.use_count() > 0);                                            \
+                                                                                  \
+    auto u_op_v = p_u_op_v->get<U_OP_V_TYPE>().cellValues();                      \
+                                                                                  \
+    auto u_values = P_U->get<U_TYPE>().cellValues();                              \
+    auto v_values = P_V->get<V_TYPE>().cellValues();                              \
+    bool is_same  = true;                                                         \
+    for (CellId cell_id = 0; cell_id < u_values.numberOfItems(); ++cell_id) {     \
+      if (u_op_v[cell_id] != (u_values[cell_id] OPERATOR v_values[cell_id])) {    \
+        is_same = false;                                                          \
+        break;                                                                    \
+      }                                                                           \
+    }                                                                             \
+                                                                                  \
+    REQUIRE(is_same);                                                             \
+  }
+
+#define CHECK_EMBEDDED_VECTOR_VH2_TO_VH(P_U, OPERATOR, P_V, TYPE)                                \
+  {                                                                                              \
+    std::shared_ptr p_u_op_v = P_U OPERATOR P_V;                                                 \
+                                                                                                 \
+    REQUIRE(p_u_op_v.use_count() > 0);                                                           \
+                                                                                                 \
+    auto u_op_v_arrays = p_u_op_v->get<TYPE>().cellArrays();                                     \
+                                                                                                 \
+    auto u_arrays = P_U->get<TYPE>().cellArrays();                                               \
+    auto v_arrays = P_V->get<TYPE>().cellArrays();                                               \
+                                                                                                 \
+    REQUIRE(u_arrays.sizeOfArrays() > 0);                                                        \
+    REQUIRE(u_arrays.sizeOfArrays() == v_arrays.sizeOfArrays());                                 \
+    REQUIRE(u_arrays.sizeOfArrays() == u_op_v_arrays.sizeOfArrays());                            \
+                                                                                                 \
+    bool is_same = true;                                                                         \
+    for (CellId cell_id = 0; cell_id < u_arrays.numberOfItems(); ++cell_id) {                    \
+      for (size_t i = 0; i < u_arrays.sizeOfArrays(); ++i) {                                     \
+        if (u_op_v_arrays[cell_id][i] != (u_arrays[cell_id][i] OPERATOR v_arrays[cell_id][i])) { \
+          is_same = false;                                                                       \
+          break;                                                                                 \
+        }                                                                                        \
+      }                                                                                          \
+    }                                                                                            \
+                                                                                                 \
+    REQUIRE(is_same);                                                                            \
+  }
+
+#define CHECK_EMBEDDED_VHxX_TO_VH(P_U, OPERATOR, V, U_TYPE, U_OP_V_TYPE)      \
+  {                                                                           \
+    std::shared_ptr p_u_op_v = P_U OPERATOR V;                                \
+                                                                              \
+    REQUIRE(p_u_op_v.use_count() > 0);                                        \
+                                                                              \
+    auto u_op_v = p_u_op_v->get<U_OP_V_TYPE>().cellValues();                  \
+                                                                              \
+    auto u_values = P_U->get<U_TYPE>().cellValues();                          \
+    bool is_same  = true;                                                     \
+    for (CellId cell_id = 0; cell_id < u_values.numberOfItems(); ++cell_id) { \
+      if (u_op_v[cell_id] != (u_values[cell_id] OPERATOR V)) {                \
+        is_same = false;                                                      \
+        break;                                                                \
+      }                                                                       \
+    }                                                                         \
+                                                                              \
+    REQUIRE(is_same);                                                         \
+  }
+
+#define CHECK_EMBEDDED_XxVH_TO_VH(U, OPERATOR, P_V, V_TYPE, U_OP_V_TYPE)      \
+  {                                                                           \
+    std::shared_ptr p_u_op_v = U OPERATOR P_V;                                \
+                                                                              \
+    REQUIRE(p_u_op_v.use_count() > 0);                                        \
+                                                                              \
+    auto u_op_v = p_u_op_v->get<U_OP_V_TYPE>().cellValues();                  \
+                                                                              \
+    auto v_values = P_V->get<V_TYPE>().cellValues();                          \
+    bool is_same  = true;                                                     \
+    for (CellId cell_id = 0; cell_id < v_values.numberOfItems(); ++cell_id) { \
+      if (u_op_v[cell_id] != (U OPERATOR v_values[cell_id])) {                \
+        is_same = false;                                                      \
+        break;                                                                \
+      }                                                                       \
+    }                                                                         \
+                                                                              \
+    REQUIRE(is_same);                                                         \
+  }
+
+#define CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(U, OPERATOR, P_V, TYPE)                                 \
+  {                                                                                              \
+    std::shared_ptr p_u_op_v = U OPERATOR P_V;                                                   \
+                                                                                                 \
+    REQUIRE(p_u_op_v.use_count() > 0);                                                           \
+                                                                                                 \
+    auto u_op_v = p_u_op_v->get<TYPE>().cellArrays();                                            \
+                                                                                                 \
+    auto v_arrays = P_V->get<TYPE>().cellArrays();                                               \
+                                                                                                 \
+    REQUIRE(v_arrays.numberOfItems() == u_op_v.numberOfItems());                                 \
+    REQUIRE(v_arrays.sizeOfArrays() == u_op_v.sizeOfArrays());                                   \
+                                                                                                 \
+    bool is_same = true;                                                                         \
+    for (CellId cell_id = 0; cell_id < v_arrays.numberOfItems(); ++cell_id) {                    \
+      for (size_t i = 0; i < v_arrays.sizeOfArrays(); ++i) {                                     \
+        if (u_op_v[cell_id][i] != (U OPERATOR v_arrays[cell_id][i])) {                           \
+          is_same = false;                                                                       \
+          std::clog << u_op_v[cell_id][i] << " !=" << (U OPERATOR v_arrays[cell_id][i]) << '\n'; \
+        }                                                                                        \
+      }                                                                                          \
+    }                                                                                            \
+                                                                                                 \
+    REQUIRE(is_same);                                                                            \
+  }
+
+#define CHECK_EMBEDDED_VH_TO_VH(OPERATOR, P_U, U_TYPE)                        \
+  {                                                                           \
+    std::shared_ptr p_op_u = OPERATOR P_U;                                    \
+                                                                              \
+    REQUIRE(p_op_u.use_count() > 0);                                          \
+                                                                              \
+    auto op_u = p_op_u->get<U_TYPE>().cellValues();                           \
+                                                                              \
+    auto u_values = P_U->get<U_TYPE>().cellValues();                          \
+    bool is_same  = true;                                                     \
+    for (CellId cell_id = 0; cell_id < u_values.numberOfItems(); ++cell_id) { \
+      if (op_u[cell_id] != (OPERATOR u_values[cell_id])) {                    \
+        is_same = false;                                                      \
+        break;                                                                \
+      }                                                                       \
+    }                                                                         \
+                                                                              \
+    REQUIRE(is_same);                                                         \
+  }
+
+#define CHECK_EMBEDDED_VECTOR_VH_TO_VH(OPERATOR, P_U, TYPE)                   \
+  {                                                                           \
+    std::shared_ptr p_op_u = OPERATOR P_U;                                    \
+                                                                              \
+    REQUIRE(p_op_u.use_count() > 0);                                          \
+                                                                              \
+    auto op_u_arrays = p_op_u->get<TYPE>().cellArrays();                      \
+                                                                              \
+    auto u_arrays = P_U->get<TYPE>().cellArrays();                            \
+                                                                              \
+    REQUIRE(u_arrays.sizeOfArrays() == op_u_arrays.sizeOfArrays());           \
+    REQUIRE(u_arrays.numberOfItems() == op_u_arrays.numberOfItems());         \
+                                                                              \
+    bool is_same = true;                                                      \
+    for (CellId cell_id = 0; cell_id < u_arrays.numberOfItems(); ++cell_id) { \
+      for (size_t i = 0; i < u_arrays.sizeOfArrays(); ++i) {                  \
+        if (op_u_arrays[cell_id][i] != (OPERATOR u_arrays[cell_id][i])) {     \
+          is_same = false;                                                    \
+          break;                                                              \
+        }                                                                     \
+      }                                                                       \
+    }                                                                         \
+                                                                              \
+    REQUIRE(is_same);                                                         \
+  }
+
+#endif   // TEST_EMBEDDED_DISCRETE_FUNCTION_OPERATORS_HPP
diff --git a/tests/test_EmbeddedDiscreteFunctionOperators1D.cpp b/tests/test_EmbeddedDiscreteFunctionOperators1D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5538543de393bd2fb8009e3d2cbd04d57107ead3
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionOperators1D.cpp
@@ -0,0 +1,862 @@
+#include <test_EmbeddedDiscreteFunctionOperators.hpp>
+
+#ifdef __clang__
+#pragma clang optimize off
+#endif   // __clang__
+
+TEST_CASE("EmbeddedDiscreteFunctionOperators1D", "[scheme]")
+{
+  constexpr size_t Dimension = 1;
+
+  using Rd = TinyVector<Dimension>;
+
+  std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+  using DiscreteFunctionR    = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteFunctionR1   = DiscreteFunctionP0<Dimension, const TinyVector<1>>;
+  using DiscreteFunctionR2   = DiscreteFunctionP0<Dimension, const TinyVector<2>>;
+  using DiscreteFunctionR3   = DiscreteFunctionP0<Dimension, const TinyVector<3>>;
+  using DiscreteFunctionR1x1 = DiscreteFunctionP0<Dimension, const TinyMatrix<1>>;
+  using DiscreteFunctionR2x2 = DiscreteFunctionP0<Dimension, const TinyMatrix<2>>;
+  using DiscreteFunctionR3x3 = DiscreteFunctionP0<Dimension, const TinyMatrix<3>>;
+
+  using DiscreteFunctionVector = DiscreteFunctionP0Vector<Dimension, const double>;
+
+  for (const auto& named_mesh : mesh_list) {
+    SECTION(named_mesh.name())
+    {
+      auto mesh = named_mesh.mesh();
+
+      std::shared_ptr other_mesh =
+        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      CellValue<double> u_R_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> v_R_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      std::shared_ptr p_R_u = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(mesh, u_R_values));
+      std::shared_ptr p_other_mesh_R_u =
+        std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(other_mesh, u_R_values));
+      std::shared_ptr p_R_v = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(mesh, v_R_values));
+
+      std::shared_ptr p_R1_u = [=] {
+        CellValue<TinyVector<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R1_v = [=] {
+        CellValue<TinyVector<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR1(other_mesh, p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+
+      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<2>{x[0], 1 + x[0] * x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<2>{x[0], x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<2>{x[0], x[1] + x[2]};
+        }
+      };
+
+      std::shared_ptr p_R2_u = [=] {
+        CellValue<TinyVector<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2_v = [=] {
+        CellValue<TinyVector<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR2(other_mesh, p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+
+      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<3>{x[0], x[1], x[0] + x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<3>{x[0], x[1], x[2]};
+        }
+      };
+
+      std::shared_ptr p_R3_u = [=] {
+        CellValue<TinyVector<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3_v = [=] {
+        CellValue<TinyVector<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR3(other_mesh, p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+
+      std::shared_ptr p_R1x1_u = [=] {
+        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1x1_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR1x1(other_mesh, p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues()));
+
+      std::shared_ptr p_R1x1_v = [=] {
+        CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = TinyMatrix<1>{0.3 - xj[cell_id][0]}; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, vj));
+      }();
+
+      std::shared_ptr p_R2x2_u = [=] {
+        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
+                                        2 * x[1], -x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2x2_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR2x2(other_mesh, p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues()));
+
+      std::shared_ptr p_R2x2_v = [=] {
+        CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            vj[cell_id] = TinyMatrix<2>{x[0] + 0.3, 1 - x[1] - x[0],   //
+                                        2 * x[1] + x[0], x[1] - x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, vj));
+      }();
+
+      std::shared_ptr p_R3x3_u = [=] {
+        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
+                                        2 * x[1],        -x[0],           x[0] - x[1],   //
+                                        3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3x3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR3x3(other_mesh, p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues()));
+
+      std::shared_ptr p_R3x3_v = [=] {
+        CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            vj[cell_id] = TinyMatrix<3>{0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
+                                        2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
+                                        2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, vj));
+      }();
+
+      std::shared_ptr p_Vector3_u = [=] {
+        CellArray<double> uj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj_vector[cell_id][0] = 2 * x[0] + 1;
+            uj_vector[cell_id][1] = 1 - x[1] * x[2];
+            uj_vector[cell_id][2] = x[0] + x[2];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, uj_vector));
+      }();
+
+      std::shared_ptr p_other_mesh_Vector3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionVector(other_mesh, p_Vector3_u->get<DiscreteFunctionVector>().cellArrays()));
+
+      std::shared_ptr p_Vector3_v = [=] {
+        CellArray<double> vj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj_vector[cell_id][0] = x[0] * x[1] + 1;
+            vj_vector[cell_id][1] = 2 * x[1];
+            vj_vector[cell_id][2] = x[2] * x[0];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, vj_vector));
+      }();
+
+      std::shared_ptr p_Vector2_w = [=] {
+        CellArray<double> wj_vector{mesh->connectivity(), 2};
+        parallel_for(
+          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            wj_vector[cell_id][0] = x[0] + x[1] * 2;
+            wj_vector[cell_id][1] = x[0] * x[1];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, wj_vector));
+      }();
+
+      SECTION("binary operators")
+      {
+        SECTION("sum")
+        {
+          SECTION("Vh + Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, +, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1_u, +, p_R1_v,   //
+                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2_u, +, p_R2_v,   //
+                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3_u, +, p_R3_v,   //
+                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+            REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
+                                "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh + X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, +,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^1");
+            REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^2");
+          }
+
+          SECTION("X + Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      +, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
+                                "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
+                                "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("difference")
+        {
+          SECTION("Vh - Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, -, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1_u, -, p_R1_v,   //
+                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2_u, -, p_R2_v,   //
+                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3_u, -, p_R3_v,   //
+                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+            REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
+                                "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh - X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, -,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^1");
+            REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^2");
+          }
+
+          SECTION("X - Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      -, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
+                                "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
+                                "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("product")
+        {
+          SECTION("Vh * Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R1_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R2_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R3_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R1x1_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R2x2_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R3x3_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, *, p_R1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, *, p_R2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, *, p_R3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            {
+              std::shared_ptr p_u_op_v = p_R_u * p_Vector3_v;
+
+              REQUIRE(p_u_op_v.use_count() > 0);
+              DiscreteFunctionVector u_op_v = p_u_op_v->get<DiscreteFunctionVector>();
+
+              auto u_values = p_R_u->get<DiscreteFunctionR>().cellValues();
+              auto v_arrays = p_Vector3_v->get<DiscreteFunctionVector>().cellArrays();
+              bool is_same  = true;
+              for (CellId cell_id = 0; cell_id < u_values.numberOfItems(); ++cell_id) {
+                for (size_t i = 0; i < u_op_v.size(); ++i) {
+                  if (u_op_v[cell_id][i] != (u_values[cell_id] * v_arrays[cell_id][i])) {
+                    is_same = false;
+                    break;
+                  }
+                }
+              }
+
+              REQUIRE(is_same);
+            }
+
+            REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
+
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
+
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
+
+            REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
+
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
+
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh * X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, *,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, *, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, *, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, *, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3);
+
+            REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
+            REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R^2) and R^2");
+            REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
+                                "error: incompatible operand types Vh(P0:R^3) and R^3");
+            REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R^1) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R^2) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R^3) and R^3x3");
+            REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
+                                "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
+            REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
+          }
+
+          SECTION("X * Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+						       4.7, 2.3, 7.1,   //
+						       9.7, 3.2, 6.8}), *, p_R3_u,   //
+                                        DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+						       4.7, 2.3, 7.1,   //
+						       9.7, 3.2, 6.8}), *, p_R3x3_u,   //
+	      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u, "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
+                                "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
+                                "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("ratio")
+        {
+          SECTION("Vh / Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, /, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+
+            REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
+          }
+
+          SECTION("X / Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+          }
+        }
+      }
+
+      SECTION("unary operators")
+      {
+        SECTION("unary minus")
+        {
+          SECTION("- Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R_u, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R1_u, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R2_u, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R3_u, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R1x1_u, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R2x2_u, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R3x3_u, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH_TO_VH(-, p_Vector3_u, DiscreteFunctionVector);
+          }
+        }
+      }
+    }
+  }
+}
+
+#ifdef __clang__
+#pragma clang optimize on
+#endif   // __clang__
diff --git a/tests/test_EmbeddedDiscreteFunctionOperators2D.cpp b/tests/test_EmbeddedDiscreteFunctionOperators2D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fbf114d085d04a76da6715d3493516c9ead57044
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionOperators2D.cpp
@@ -0,0 +1,864 @@
+#include <test_EmbeddedDiscreteFunctionOperators.hpp>
+
+#ifdef __clang__
+#pragma clang optimize off
+#endif   // __clang__
+
+TEST_CASE("EmbeddedDiscreteFunctionOperators2D", "[scheme]")
+{
+  constexpr size_t Dimension = 2;
+
+  using Rd = TinyVector<Dimension>;
+
+  std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+  using DiscreteFunctionR    = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteFunctionR1   = DiscreteFunctionP0<Dimension, const TinyVector<1>>;
+  using DiscreteFunctionR2   = DiscreteFunctionP0<Dimension, const TinyVector<2>>;
+  using DiscreteFunctionR3   = DiscreteFunctionP0<Dimension, const TinyVector<3>>;
+  using DiscreteFunctionR1x1 = DiscreteFunctionP0<Dimension, const TinyMatrix<1>>;
+  using DiscreteFunctionR2x2 = DiscreteFunctionP0<Dimension, const TinyMatrix<2>>;
+  using DiscreteFunctionR3x3 = DiscreteFunctionP0<Dimension, const TinyMatrix<3>>;
+
+  using DiscreteFunctionVector = DiscreteFunctionP0Vector<Dimension, const double>;
+
+  for (const auto& named_mesh : mesh_list) {
+    SECTION(named_mesh.name())
+    {
+      auto mesh = named_mesh.mesh();
+
+      std::shared_ptr other_mesh =
+        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      CellValue<double> u_R_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> v_R_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      std::shared_ptr p_R_u = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(mesh, u_R_values));
+      std::shared_ptr p_other_mesh_R_u =
+        std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(other_mesh, u_R_values));
+      std::shared_ptr p_R_v = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(mesh, v_R_values));
+
+      std::shared_ptr p_R1_u = [=] {
+        CellValue<TinyVector<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R1_v = [=] {
+        CellValue<TinyVector<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR1(other_mesh, p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+
+      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<2>{x[0], 1 + x[0] * x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<2>{x[0], x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<2>{x[0], x[1] + x[2]};
+        }
+      };
+
+      std::shared_ptr p_R2_u = [=] {
+        CellValue<TinyVector<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2_v = [=] {
+        CellValue<TinyVector<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR2(other_mesh, p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+
+      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<3>{x[0], x[1], x[0] + x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<3>{x[0], x[1], x[2]};
+        }
+      };
+
+      std::shared_ptr p_R3_u = [=] {
+        CellValue<TinyVector<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3_v = [=] {
+        CellValue<TinyVector<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR3(other_mesh, p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+
+      std::shared_ptr p_R1x1_u = [=] {
+        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1x1_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR1x1(other_mesh, p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues()));
+
+      std::shared_ptr p_R1x1_v = [=] {
+        CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = TinyMatrix<1>{0.3 - xj[cell_id][0]}; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, vj));
+      }();
+
+      std::shared_ptr p_R2x2_u = [=] {
+        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
+                                        2 * x[1], -x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2x2_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR2x2(other_mesh, p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues()));
+
+      std::shared_ptr p_R2x2_v = [=] {
+        CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            vj[cell_id] = TinyMatrix<2>{x[0] + 0.3, 1 - x[1] - x[0],   //
+                                        2 * x[1] + x[0], x[1] - x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, vj));
+      }();
+
+      std::shared_ptr p_R3x3_u = [=] {
+        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
+                                        2 * x[1],        -x[0],           x[0] - x[1],   //
+                                        3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3x3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR3x3(other_mesh, p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues()));
+
+      std::shared_ptr p_R3x3_v = [=] {
+        CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            vj[cell_id] = TinyMatrix<3>{0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
+                                        2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
+                                        2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, vj));
+      }();
+
+      std::shared_ptr p_Vector3_u = [=] {
+        CellArray<double> uj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj_vector[cell_id][0] = 2 * x[0] + 1;
+            uj_vector[cell_id][1] = 1 - x[1] * x[2];
+            uj_vector[cell_id][2] = x[0] + x[2];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, uj_vector));
+      }();
+
+      std::shared_ptr p_other_mesh_Vector3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionVector(other_mesh, p_Vector3_u->get<DiscreteFunctionVector>().cellArrays()));
+
+      std::shared_ptr p_Vector3_v = [=] {
+        CellArray<double> vj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj_vector[cell_id][0] = x[0] * x[1] + 1;
+            vj_vector[cell_id][1] = 2 * x[1];
+            vj_vector[cell_id][2] = x[2] * x[0];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, vj_vector));
+      }();
+
+      std::shared_ptr p_Vector2_w = [=] {
+        CellArray<double> wj_vector{mesh->connectivity(), 2};
+        parallel_for(
+          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            wj_vector[cell_id][0] = x[0] + x[1] * 2;
+            wj_vector[cell_id][1] = x[0] * x[1];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, wj_vector));
+      }();
+
+      SECTION("binary operators")
+      {
+        SECTION("sum")
+        {
+          SECTION("Vh + Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, +, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1_u, +, p_R1_v,   //
+                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2_u, +, p_R2_v,   //
+                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3_u, +, p_R3_v,   //
+                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+            REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
+                                "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh + X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, +,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^1");
+            REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^2");
+          }
+
+          SECTION("X + Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      +, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
+                                "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
+                                "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("difference")
+        {
+          SECTION("Vh - Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, -, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1_u, -, p_R1_v,   //
+                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2_u, -, p_R2_v,   //
+                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3_u, -, p_R3_v,   //
+                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+            REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
+                                "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh - X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, -,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^1");
+            REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^2");
+          }
+
+          SECTION("X - Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      -, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
+                                "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
+                                "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("product")
+        {
+          SECTION("Vh * Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R1_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R2_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R3_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R1x1_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R2x2_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R3x3_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, *, p_R1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, *, p_R2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, *, p_R3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            {
+              std::shared_ptr p_u_op_v = p_R_u * p_Vector3_v;
+
+              REQUIRE(p_u_op_v.use_count() > 0);
+              DiscreteFunctionVector u_op_v = p_u_op_v->get<DiscreteFunctionVector>();
+
+              auto u_values = p_R_u->get<DiscreteFunctionR>().cellValues();
+              auto v_arrays = p_Vector3_v->get<DiscreteFunctionVector>().cellArrays();
+              bool is_same  = true;
+              for (CellId cell_id = 0; cell_id < u_values.numberOfItems(); ++cell_id) {
+                for (size_t i = 0; i < u_op_v.size(); ++i) {
+                  if (u_op_v[cell_id][i] != (u_values[cell_id] * v_arrays[cell_id][i])) {
+                    is_same = false;
+                    break;
+                  }
+                }
+              }
+
+              REQUIRE(is_same);
+            }
+
+            REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
+
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
+
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
+
+            REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
+
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
+
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh * X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, *,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, *, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, *, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, *, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3);
+
+            REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
+            REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R^2) and R^2");
+            REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
+                                "error: incompatible operand types Vh(P0:R^3) and R^3");
+            REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R^1) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R^2) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R^3) and R^3x3");
+            REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
+                                "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
+            REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
+          }
+
+          SECTION("X * Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                                     4.7, 2.3, 7.1,   //
+                                                                     9.7, 3.2, 6.8}),
+                                                      *, p_R3_u,   //
+                                        DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                                     4.7, 2.3, 7.1,   //
+                                                                     9.7, 3.2, 6.8}),
+                                                      *, p_R3x3_u,   //
+                                        DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u, "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
+                                "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
+                                "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("ratio")
+        {
+          SECTION("Vh / Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, /, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+
+            REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
+          }
+
+          SECTION("X / Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+          }
+        }
+      }
+
+      SECTION("unary operators")
+      {
+        SECTION("unary minus")
+        {
+          SECTION("- Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R_u, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R1_u, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R2_u, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R3_u, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R1x1_u, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R2x2_u, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R3x3_u, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH_TO_VH(-, p_Vector3_u, DiscreteFunctionVector);
+          }
+        }
+      }
+    }
+  }
+}
+
+#ifdef __clang__
+#pragma clang optimize on
+#endif   // __clang__
diff --git a/tests/test_EmbeddedDiscreteFunctionOperators3D.cpp b/tests/test_EmbeddedDiscreteFunctionOperators3D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1b24d0a9128db6bcc8d4c3eff70a38a3c5fc9ee4
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionOperators3D.cpp
@@ -0,0 +1,864 @@
+#include <test_EmbeddedDiscreteFunctionOperators.hpp>
+
+#ifdef __clang__
+#pragma clang optimize off
+#endif   // __clang__
+
+TEST_CASE("EmbeddedDiscreteFunctionOperators3D", "[scheme]")
+{
+  constexpr size_t Dimension = 3;
+
+  using Rd = TinyVector<Dimension>;
+
+  std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+  using DiscreteFunctionR    = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteFunctionR1   = DiscreteFunctionP0<Dimension, const TinyVector<1>>;
+  using DiscreteFunctionR2   = DiscreteFunctionP0<Dimension, const TinyVector<2>>;
+  using DiscreteFunctionR3   = DiscreteFunctionP0<Dimension, const TinyVector<3>>;
+  using DiscreteFunctionR1x1 = DiscreteFunctionP0<Dimension, const TinyMatrix<1>>;
+  using DiscreteFunctionR2x2 = DiscreteFunctionP0<Dimension, const TinyMatrix<2>>;
+  using DiscreteFunctionR3x3 = DiscreteFunctionP0<Dimension, const TinyMatrix<3>>;
+
+  using DiscreteFunctionVector = DiscreteFunctionP0Vector<Dimension, const double>;
+
+  for (const auto& named_mesh : mesh_list) {
+    SECTION(named_mesh.name())
+    {
+      auto mesh = named_mesh.mesh();
+
+      std::shared_ptr other_mesh =
+        std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
+
+      CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+      CellValue<double> u_R_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      CellValue<double> v_R_values = [=] {
+        CellValue<double> build_values{mesh->connectivity()};
+        parallel_for(
+          build_values.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
+        return build_values;
+      }();
+
+      std::shared_ptr p_R_u = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(mesh, u_R_values));
+      std::shared_ptr p_other_mesh_R_u =
+        std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(other_mesh, u_R_values));
+      std::shared_ptr p_R_v = std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR(mesh, v_R_values));
+
+      std::shared_ptr p_R1_u = [=] {
+        CellValue<TinyVector<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, uj));
+      }();
+
+      std::shared_ptr p_R1_v = [=] {
+        CellValue<TinyVector<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR1(other_mesh, p_R1_u->get<DiscreteFunctionR1>().cellValues()));
+
+      constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<2>{x[0], 1 + x[0] * x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<2>{x[0], x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<2>{x[0], x[1] + x[2]};
+        }
+      };
+
+      std::shared_ptr p_R2_u = [=] {
+        CellValue<TinyVector<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, uj));
+      }();
+
+      std::shared_ptr p_R2_v = [=] {
+        CellValue<TinyVector<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR2(other_mesh, p_R2_u->get<DiscreteFunctionR2>().cellValues()));
+
+      constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
+        if constexpr (Dimension == 1) {
+          return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
+        } else if constexpr (Dimension == 2) {
+          return TinyVector<3>{x[0], x[1], x[0] + x[1]};
+        } else if constexpr (Dimension == 3) {
+          return TinyVector<3>{x[0], x[1], x[2]};
+        }
+      };
+
+      std::shared_ptr p_R3_u = [=] {
+        CellValue<TinyVector<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, uj));
+      }();
+
+      std::shared_ptr p_R3_v = [=] {
+        CellValue<TinyVector<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3(mesh, vj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR3(other_mesh, p_R3_u->get<DiscreteFunctionR3>().cellValues()));
+
+      std::shared_ptr p_R1x1_u = [=] {
+        CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(),
+          PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R1x1_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR1x1(other_mesh, p_R1x1_u->get<DiscreteFunctionR1x1>().cellValues()));
+
+      std::shared_ptr p_R1x1_v = [=] {
+        CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = TinyMatrix<1>{0.3 - xj[cell_id][0]}; });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR1x1(mesh, vj));
+      }();
+
+      std::shared_ptr p_R2x2_u = [=] {
+        CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
+                                        2 * x[1], -x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R2x2_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR2x2(other_mesh, p_R2x2_u->get<DiscreteFunctionR2x2>().cellValues()));
+
+      std::shared_ptr p_R2x2_v = [=] {
+        CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<2> x = to_2d(xj[cell_id]);
+
+            vj[cell_id] = TinyMatrix<2>{x[0] + 0.3, 1 - x[1] - x[0],   //
+                                        2 * x[1] + x[0], x[1] - x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR2x2(mesh, vj));
+      }();
+
+      std::shared_ptr p_R3x3_u = [=] {
+        CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
+        parallel_for(
+          uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
+                                        2 * x[1],        -x[0],           x[0] - x[1],   //
+                                        3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, uj));
+      }();
+
+      std::shared_ptr p_other_mesh_R3x3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionR3x3(other_mesh, p_R3x3_u->get<DiscreteFunctionR3x3>().cellValues()));
+
+      std::shared_ptr p_R3x3_v = [=] {
+        CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
+        parallel_for(
+          vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+
+            vj[cell_id] = TinyMatrix<3>{0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
+                                        2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
+                                        2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionR3x3(mesh, vj));
+      }();
+
+      std::shared_ptr p_Vector3_u = [=] {
+        CellArray<double> uj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            uj_vector[cell_id][0] = 2 * x[0] + 1;
+            uj_vector[cell_id][1] = 1 - x[1] * x[2];
+            uj_vector[cell_id][2] = x[0] + x[2];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, uj_vector));
+      }();
+
+      std::shared_ptr p_other_mesh_Vector3_u = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionVector(other_mesh, p_Vector3_u->get<DiscreteFunctionVector>().cellArrays()));
+
+      std::shared_ptr p_Vector3_v = [=] {
+        CellArray<double> vj_vector{mesh->connectivity(), 3};
+        parallel_for(
+          vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            vj_vector[cell_id][0] = x[0] * x[1] + 1;
+            vj_vector[cell_id][1] = 2 * x[1];
+            vj_vector[cell_id][2] = x[2] * x[0];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, vj_vector));
+      }();
+
+      std::shared_ptr p_Vector2_w = [=] {
+        CellArray<double> wj_vector{mesh->connectivity(), 2};
+        parallel_for(
+          wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+            const TinyVector<3> x = to_3d(xj[cell_id]);
+            wj_vector[cell_id][0] = x[0] + x[1] * 2;
+            wj_vector[cell_id][1] = x[0] * x[1];
+          });
+
+        return std::make_shared<DiscreteFunctionVariant>(DiscreteFunctionVector(mesh, wj_vector));
+      }();
+
+      SECTION("binary operators")
+      {
+        SECTION("sum")
+        {
+          SECTION("Vh + Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, +, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1_u, +, p_R1_v,   //
+                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2_u, +, p_R2_v,   //
+                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3_u, +, p_R3_v,   //
+                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+            REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
+                                "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh + X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, +, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, +,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^1");
+            REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^2");
+          }
+
+          SECTION("X + Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, +, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      +, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
+                                "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
+                                "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("difference")
+        {
+          SECTION("Vh - Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, -, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1_u, -, p_R1_v,   //
+                                     DiscreteFunctionR1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2_u, -, p_R2_v,   //
+                                     DiscreteFunctionR2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3_u, -, p_R3_v,   //
+                                     DiscreteFunctionR3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
+
+            REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
+                                "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh - X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, -, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, -,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}), "error: incompatible operand types Vh(P0:R) and R^3");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
+            REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^1");
+            REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^2");
+          }
+
+          SECTION("X - Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, -, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                     4.7, 2.3, 7.1,   //
+                                                     9.7, 3.2, 6.8}),
+                                      -, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u, "error: incompatible operand types R^3 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
+                                "error: incompatible operand types R^1 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
+                                "error: incompatible operand types R^2 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("product")
+        {
+          SECTION("Vh * Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R1_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R2_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R3_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R1x1_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R2x2_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, *, p_R3x3_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VH2_TO_VH(p_R1x1_u, *, p_R1_v,   //
+                                     DiscreteFunctionR1x1, DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R2x2_u, *, p_R2_v,   //
+                                     DiscreteFunctionR2x2, DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH2_TO_VH(p_R3x3_u, *, p_R3_v,   //
+                                     DiscreteFunctionR3x3, DiscreteFunctionR3, DiscreteFunctionR3);
+
+            {
+              std::shared_ptr p_u_op_v = p_R_u * p_Vector3_v;
+
+              REQUIRE(p_u_op_v.use_count() > 0);
+              DiscreteFunctionVector u_op_v = p_u_op_v->get<DiscreteFunctionVector>();
+
+              auto u_values = p_R_u->get<DiscreteFunctionR>().cellValues();
+              auto v_arrays = p_Vector3_v->get<DiscreteFunctionVector>().cellArrays();
+              bool is_same  = true;
+              for (CellId cell_id = 0; cell_id < u_values.numberOfItems(); ++cell_id) {
+                for (size_t i = 0; i < u_op_v.size(); ++i) {
+                  if (u_op_v[cell_id][i] != (u_values[cell_id] * v_arrays[cell_id][i])) {
+                    is_same = false;
+                    break;
+                  }
+                }
+              }
+
+              REQUIRE(is_same);
+            }
+
+            REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
+
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
+
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
+
+            REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
+
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
+                                "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
+
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
+            REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
+          }
+
+          SECTION("Vh * X -> Vh")
+          {
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, bool{true},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, uint64_t{1},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, int64_t{2},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R_u, *, double{1.3},   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, *,
+                                      (TinyMatrix<3>{3.2, 7.1, 5.2,     //
+                                                     4.7, 2.3, 7.1,     //
+                                                     9.7, 3.2, 6.8}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R1x1_u, *, (TinyVector<1>{1.3}),   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R2x2_u, *, (TinyVector<2>{1.2, 2.3}),   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VHxX_TO_VH(p_R3x3_u, *, (TinyVector<3>{3.2, 7.1, 5.2}),   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3);
+
+            REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
+            REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R^2) and R^2");
+            REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
+                                "error: incompatible operand types Vh(P0:R^3) and R^3");
+            REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R^1) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R^2) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R^3) and R^3x3");
+            REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
+                                "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
+            REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
+                                "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
+            REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
+
+            REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
+                                "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
+            REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
+          }
+
+          SECTION("X * Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R3_u,   //
+                                      DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, *, p_R3x3_u,   //
+                                      DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u,   //
+                                      DiscreteFunctionR1, DiscreteFunctionR1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u,   //
+                                      DiscreteFunctionR2, DiscreteFunctionR2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                                     4.7, 2.3, 7.1,   //
+                                                                     9.7, 3.2, 6.8}),
+                                                      *, p_R3_u,   //
+                                        DiscreteFunctionR3, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u,   //
+                                      DiscreteFunctionR1x1, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u,   //
+                                      DiscreteFunctionR2x2, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
+                                                                     4.7, 2.3, 7.1,   //
+                                                                     9.7, 3.2, 6.8}),
+                                                      *, p_R3x3_u,   //
+                                        DiscreteFunctionR3x3, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u, DiscreteFunctionVector);
+            CHECK_EMBEDDED_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u, DiscreteFunctionVector);
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u, "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
+                                "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
+            REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
+                                "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
+
+            REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
+                                "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
+            REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
+                                "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
+          }
+        }
+
+        SECTION("ratio")
+        {
+          SECTION("Vh / Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH2_TO_VH(p_R_u, /, p_R_v,   //
+                                     DiscreteFunctionR, DiscreteFunctionR, DiscreteFunctionR);
+
+            REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
+            REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
+            REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
+
+            REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
+          }
+
+          SECTION("X / Vh -> Vh")
+          {
+            CHECK_EMBEDDED_XxVH_TO_VH(bool{true}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(uint64_t{1}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(int64_t{2}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+            CHECK_EMBEDDED_XxVH_TO_VH(double{1.3}, /, p_R_u,   //
+                                      DiscreteFunctionR, DiscreteFunctionR);
+          }
+        }
+      }
+
+      SECTION("unary operators")
+      {
+        SECTION("unary minus")
+        {
+          SECTION("- Vh -> Vh")
+          {
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R_u, DiscreteFunctionR);
+
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R1_u, DiscreteFunctionR1);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R2_u, DiscreteFunctionR2);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R3_u, DiscreteFunctionR3);
+
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R1x1_u, DiscreteFunctionR1x1);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R2x2_u, DiscreteFunctionR2x2);
+            CHECK_EMBEDDED_VH_TO_VH(-, p_R3x3_u, DiscreteFunctionR3x3);
+
+            CHECK_EMBEDDED_VECTOR_VH_TO_VH(-, p_Vector3_u, DiscreteFunctionVector);
+          }
+        }
+      }
+    }
+  }
+}
+
+#ifdef __clang__
+#pragma clang optimize on
+#endif   // __clang__
diff --git a/tests/test_EmbeddedDiscreteFunctionUtils.cpp b/tests/test_EmbeddedDiscreteFunctionUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dd2fe97b69541189d1eb859bad6d952e9d705605
--- /dev/null
+++ b/tests/test_EmbeddedDiscreteFunctionUtils.cpp
@@ -0,0 +1,83 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/utils/EmbeddedDiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("EmbeddedDiscreteFunctionUtils", "[language]")
+{
+  using R1 = TinyVector<1, double>;
+  using R2 = TinyVector<2, double>;
+  using R3 = TinyVector<3, double>;
+
+  using R1x1 = TinyMatrix<1, 1, double>;
+  using R2x2 = TinyMatrix<2, 2, double>;
+  using R3x3 = TinyMatrix<3, 3, double>;
+
+  SECTION("operand type name")
+  {
+    SECTION("basic types")
+    {
+      REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(double{1}) == "R");
+      REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(std::make_shared<double>(1)) == "R");
+    }
+
+    SECTION("discrete P0 function")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, double>{mesh_1d}) ==
+                  "Vh(P0:R)");
+
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1>{mesh_1d}) ==
+                  "Vh(P0:R^1)");
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2>{mesh_1d}) ==
+                  "Vh(P0:R^2)");
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3>{mesh_1d}) ==
+                  "Vh(P0:R^3)");
+
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1x1>{mesh_1d}) ==
+                  "Vh(P0:R^1x1)");
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2x2>{mesh_1d}) ==
+                  "Vh(P0:R^2x2)");
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3x3>{mesh_1d}) ==
+                  "Vh(P0:R^3x3)");
+        }
+      }
+    }
+
+    SECTION("discrete P0Vector function")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          REQUIRE(EmbeddedDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0Vector<1, double>{mesh_1d, 2}) ==
+                  "Vh(P0Vector:R)");
+        }
+      }
+    }
+  }
+
+#ifndef NDEBUG
+  SECTION("errors")
+  {
+    REQUIRE_THROWS_WITH(EmbeddedDiscreteFunctionUtils::getOperandTypeName(std::shared_ptr<double>()),
+                        "dangling shared_ptr");
+  }
+
+#endif   // NDEBUG
+}
diff --git a/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp b/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp
deleted file mode 100644
index 6d00782f96379548ebb66927044782def35612e8..0000000000000000000000000000000000000000
--- a/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp
+++ /dev/null
@@ -1,2675 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/matchers/catch_matchers_all.hpp>
-
-#include <MeshDataBaseForTests.hpp>
-
-#include <language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp>
-#include <scheme/DiscreteFunctionP0.hpp>
-#include <scheme/DiscreteFunctionP0Vector.hpp>
-
-// clazy:excludeall=non-pod-global-static
-
-#define CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(P_U, FCT)          \
-  {                                                                         \
-    using DiscreteFunctionType = const std::decay_t<decltype(*P_U)>;        \
-    std::shared_ptr p_fu       = ::FCT(P_U);                                \
-                                                                            \
-    REQUIRE(p_fu.use_count() > 0);                                          \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fu));      \
-                                                                            \
-    const auto& fu = dynamic_cast<const DiscreteFunctionType&>(*p_fu);      \
-                                                                            \
-    auto values  = P_U->cellValues();                                       \
-    bool is_same = true;                                                    \
-    for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) { \
-      if (fu[cell_id] != std::FCT(values[cell_id])) {                       \
-        is_same = false;                                                    \
-        break;                                                              \
-      }                                                                     \
-    }                                                                       \
-                                                                            \
-    REQUIRE(is_same);                                                       \
-  }
-
-#define CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(P_LHS, P_RHS, FCT)             \
-  {                                                                                 \
-    using DiscreteFunctionType = const std::decay_t<decltype(FCT(*P_LHS, *P_RHS))>; \
-    std::shared_ptr p_fuv      = ::FCT(P_LHS, P_RHS);                               \
-                                                                                    \
-    REQUIRE(p_fuv.use_count() > 0);                                                 \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));             \
-                                                                                    \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);            \
-                                                                                    \
-    auto lhs_values = P_LHS->cellValues();                                          \
-    auto rhs_values = P_RHS->cellValues();                                          \
-    bool is_same    = true;                                                         \
-    for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {     \
-      using namespace std;                                                          \
-      if (fuv[cell_id] != FCT(lhs_values[cell_id], rhs_values[cell_id])) {          \
-        is_same = false;                                                            \
-        break;                                                                      \
-      }                                                                             \
-    }                                                                               \
-                                                                                    \
-    REQUIRE(is_same);                                                               \
-  }
-
-#define CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(P_LHS, RHS, FCT)           \
-  {                                                                              \
-    using DiscreteFunctionType = const std::decay_t<decltype(FCT(*P_LHS, RHS))>; \
-    std::shared_ptr p_fuv      = ::FCT(P_LHS, RHS);                              \
-                                                                                 \
-    REQUIRE(p_fuv.use_count() > 0);                                              \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));          \
-                                                                                 \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);         \
-                                                                                 \
-    auto lhs_values = P_LHS->cellValues();                                       \
-    bool is_same    = true;                                                      \
-    for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {  \
-      using namespace std;                                                       \
-      if (fuv[cell_id] != FCT(lhs_values[cell_id], RHS)) {                       \
-        is_same = false;                                                         \
-        break;                                                                   \
-      }                                                                          \
-    }                                                                            \
-                                                                                 \
-    REQUIRE(is_same);                                                            \
-  }
-
-#define CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(LHS, P_RHS, FCT)           \
-  {                                                                              \
-    using DiscreteFunctionType = const std::decay_t<decltype(FCT(LHS, *P_RHS))>; \
-    std::shared_ptr p_fuv      = ::FCT(LHS, P_RHS);                              \
-                                                                                 \
-    REQUIRE(p_fuv.use_count() > 0);                                              \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));          \
-                                                                                 \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);         \
-                                                                                 \
-    auto rhs_values = P_RHS->cellValues();                                       \
-    bool is_same    = true;                                                      \
-    for (CellId cell_id = 0; cell_id < rhs_values.numberOfItems(); ++cell_id) {  \
-      using namespace std;                                                       \
-      if (fuv[cell_id] != FCT(LHS, rhs_values[cell_id])) {                       \
-        is_same = false;                                                         \
-        break;                                                                   \
-      }                                                                          \
-    }                                                                            \
-                                                                                 \
-    REQUIRE(is_same);                                                            \
-  }
-
-TEST_CASE("EmbeddedIDiscreteFunctionMathFunctions", "[scheme]")
-{
-  SECTION("1D")
-  {
-    constexpr size_t Dimension = 1;
-
-    using Rd = TinyVector<Dimension>;
-
-    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-    for (const auto& named_mesh : mesh_list) {
-      SECTION(named_mesh.name())
-      {
-        auto mesh = named_mesh.mesh();
-
-        std::shared_ptr other_mesh =
-          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-        CellValue<double> values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        CellValue<double> positive_values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        CellValue<double> bounded_values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
-        std::shared_ptr p_other_mesh_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
-        std::shared_ptr p_positive_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
-        std::shared_ptr p_bounded_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
-
-        std::shared_ptr p_R1_u = [=] {
-          CellValue<TinyVector<1>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R1_v = [=] {
-          CellValue<TinyVector<1>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R1_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-          if constexpr (Dimension == 1) {
-            return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-          } else if constexpr (Dimension == 2) {
-            return TinyVector<2>{x[0], x[1]};
-          } else if constexpr (Dimension == 3) {
-            return TinyVector<2>{x[0], x[1] + x[2]};
-          }
-        };
-
-        std::shared_ptr p_R2_u = [=] {
-          CellValue<TinyVector<2>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-              uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R2_v = [=] {
-          CellValue<TinyVector<2>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-              vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R2_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-          if constexpr (Dimension == 1) {
-            return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-          } else if constexpr (Dimension == 2) {
-            return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-          } else if constexpr (Dimension == 3) {
-            return TinyVector<3>{x[0], x[1], x[2]};
-          }
-        };
-
-        std::shared_ptr p_R3_u = [=] {
-          CellValue<TinyVector<3>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R3_v = [=] {
-          CellValue<TinyVector<3>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R3_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-        std::shared_ptr p_R1x1_u = [=] {
-          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R2x2_u = [=] {
-          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-
-              uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                          2 * x[1], -x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R3x3_u = [=] {
-          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-
-              uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                          2 * x[1],        -x[0],           x[0] - x[1],   //
-                                          3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_Vector3_u = [=] {
-          CellArray<double> uj_vector{mesh->connectivity(), 3};
-          parallel_for(
-            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              uj_vector[cell_id][0] = 2 * x[0] + 1;
-              uj_vector[cell_id][1] = 1 - x[1] * x[2];
-              uj_vector[cell_id][2] = x[0] + x[2];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-        }();
-
-        std::shared_ptr p_Vector3_v = [=] {
-          CellArray<double> vj_vector{mesh->connectivity(), 3};
-          parallel_for(
-            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              vj_vector[cell_id][0] = x[0] * x[1] + 1;
-              vj_vector[cell_id][1] = 2 * x[1];
-              vj_vector[cell_id][2] = x[2] * x[0];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-        }();
-
-        std::shared_ptr p_Vector2_w = [=] {
-          CellArray<double> wj_vector{mesh->connectivity(), 2};
-          parallel_for(
-            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              wj_vector[cell_id][0] = x[0] + x[1] * 2;
-              wj_vector[cell_id][1] = x[0] * x[1];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-        }();
-
-        SECTION("sqrt Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
-          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("abs Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
-          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("sin Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
-          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("cos Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
-          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("tan Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
-          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("asin Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
-          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("acos Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
-          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atan Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
-          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("sinh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
-          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("cosh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
-          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("tanh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
-          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("asinh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
-          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("acosh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
-          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atanh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
-          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("exp Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
-          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("log Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
-          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atan2 Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
-          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-        }
-
-        SECTION("atan2 Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
-          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("atan2 R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
-          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("min Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
-          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("min Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
-          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("min R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
-          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("min Vh -> R")
-        {
-          REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
-          REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("max Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
-          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("max Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
-          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("max Vh -> R")
-        {
-          REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
-          REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("max R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
-          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("pow Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
-          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("pow Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
-          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("pow R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
-          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("dot Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
-
-          {
-            auto p_UV = dot(p_Vector3_u, p_Vector3_v);
-            REQUIRE(p_UV.use_count() == 1);
-
-            auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
-            auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
-
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              if (UV[cell_id] != direct_UV[cell_id]) {
-                is_same = false;
-                break;
-              }
-            }
-
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
-        }
-
-        SECTION("det Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = det(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = det(p_R2x2_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = det(p_R3x3_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(det(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(det(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(det(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(det(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("trace Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = trace(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = trace(p_R2x2_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = trace(p_R3x3_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(trace(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(trace(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(trace(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(trace(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("inverse Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = inverse(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id](0, 0) != inverse(values[cell_id])(0, 0)) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = inverse(p_R2x2_u);
-            constexpr size_t nb_row = 2;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = inverse(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = inverse(p_R3x3_u);
-            constexpr size_t nb_row = 3;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = inverse(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(inverse(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(inverse(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(inverse(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(inverse(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("transpose Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = transpose(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id](0, 0) != transpose(values[cell_id])(0, 0)) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = transpose(p_R2x2_u);
-            constexpr size_t nb_row = 2;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = transpose(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = transpose(p_R3x3_u);
-            constexpr size_t nb_row = 3;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = transpose(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(transpose(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(transpose(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(transpose(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(transpose(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("sum_of_Vh Vh -> Vh")
-        {
-          {
-            auto p_sum_components = sum_of_Vh_components(p_Vector3_u);
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            const DiscreteFunctionP0<Dimension, double>& sum_components =
-              dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_sum_components);
-
-            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              double sum = 0;
-              for (size_t i = 0; i < p_Vector3_u->size(); ++i) {
-                sum += (*p_Vector3_u)[cell_id][i];
-              }
-
-              direct_sum[cell_id] = sum;
-            }
-
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              if (sum_components[cell_id] != direct_sum[cell_id]) {
-                is_same = false;
-                break;
-              }
-            }
-
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(sum_of_Vh_components(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("vectorize (Vh) -> Vh")
-        {
-          {
-            std::shared_ptr p_sum_components =
-              vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_u, p_positive_u, p_bounded_u});
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            const DiscreteFunctionP0Vector<Dimension, double> sum_components =
-              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_sum_components);
-            REQUIRE(sum_components.size() == 3);
-
-            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              is_same &= ((*p_u)[cell_id] == sum_components[cell_id][0]);
-              is_same &= ((*p_positive_u)[cell_id] == sum_components[cell_id][1]);
-              is_same &= ((*p_bounded_u)[cell_id] == sum_components[cell_id][2]);
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_u, p_other_mesh_u}),
-                              "error: discrete functions are not defined on the same mesh");
-          REQUIRE_THROWS_WITH(vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("dot Vh*Rd -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
-          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
-                              "error: incompatible operand types Vh(P0:R^1) and R^2");
-          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
-                              "error: incompatible operand types Vh(P0:R^2) and R^3");
-          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
-        }
-
-        SECTION("dot Rd*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
-          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
-                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
-                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
-        }
-
-        SECTION("sum_of_R* Vh -> R*")
-        {
-          REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
-
-          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-        }
-
-        SECTION("integral_of_R* Vh -> R*")
-        {
-          auto integrate_locally = [&](const auto& cell_values) {
-            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
-            using DataType = decltype(double{} * cell_values[CellId{0}]);
-            CellValue<DataType> local_integral{mesh->connectivity()};
-            parallel_for(
-              local_integral.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
-            return local_integral;
-          };
-
-          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
-
-          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-        }
-      }
-    }
-  }
-
-  SECTION("2D")
-  {
-    constexpr size_t Dimension = 2;
-
-    using Rd = TinyVector<Dimension>;
-
-    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
-
-    for (const auto& named_mesh : mesh_list) {
-      SECTION(named_mesh.name())
-      {
-        auto mesh = named_mesh.mesh();
-
-        std::shared_ptr other_mesh =
-          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-        CellValue<double> values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        CellValue<double> positive_values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        CellValue<double> bounded_values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
-        std::shared_ptr p_other_mesh_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
-        std::shared_ptr p_positive_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
-        std::shared_ptr p_bounded_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
-
-        std::shared_ptr p_R1_u = [=] {
-          CellValue<TinyVector<1>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R1_v = [=] {
-          CellValue<TinyVector<1>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R1_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-          if constexpr (Dimension == 1) {
-            return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-          } else if constexpr (Dimension == 2) {
-            return TinyVector<2>{x[0], x[1]};
-          } else if constexpr (Dimension == 3) {
-            return TinyVector<2>{x[0], x[1] + x[2]};
-          }
-        };
-
-        std::shared_ptr p_R2_u = [=] {
-          CellValue<TinyVector<2>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-              uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R2_v = [=] {
-          CellValue<TinyVector<2>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-              vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R2_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-          if constexpr (Dimension == 1) {
-            return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-          } else if constexpr (Dimension == 2) {
-            return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-          } else if constexpr (Dimension == 3) {
-            return TinyVector<3>{x[0], x[1], x[2]};
-          }
-        };
-
-        std::shared_ptr p_R3_u = [=] {
-          CellValue<TinyVector<3>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R3_v = [=] {
-          CellValue<TinyVector<3>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R3_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-        std::shared_ptr p_R1x1_u = [=] {
-          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R2x2_u = [=] {
-          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-
-              uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                          2 * x[1], -x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R3x3_u = [=] {
-          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-
-              uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                          2 * x[1],        -x[0],           x[0] - x[1],   //
-                                          3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_Vector3_u = [=] {
-          CellArray<double> uj_vector{mesh->connectivity(), 3};
-          parallel_for(
-            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              uj_vector[cell_id][0] = 2 * x[0] + 1;
-              uj_vector[cell_id][1] = 1 - x[1] * x[2];
-              uj_vector[cell_id][2] = x[0] + x[2];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-        }();
-
-        std::shared_ptr p_Vector3_v = [=] {
-          CellArray<double> vj_vector{mesh->connectivity(), 3};
-          parallel_for(
-            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              vj_vector[cell_id][0] = x[0] * x[1] + 1;
-              vj_vector[cell_id][1] = 2 * x[1];
-              vj_vector[cell_id][2] = x[2] * x[0];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-        }();
-
-        std::shared_ptr p_Vector2_w = [=] {
-          CellArray<double> wj_vector{mesh->connectivity(), 2};
-          parallel_for(
-            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              wj_vector[cell_id][0] = x[0] + x[1] * 2;
-              wj_vector[cell_id][1] = x[0] * x[1];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-        }();
-
-        SECTION("sqrt Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
-          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("abs Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
-          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("sin Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
-          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("cos Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
-          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("tan Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
-          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("asin Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
-          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("acos Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
-          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atan Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
-          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("sinh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
-          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("cosh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
-          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("tanh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
-          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("asinh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
-          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("acosh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
-          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atanh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
-          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("exp Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
-          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("log Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
-          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atan2 Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
-          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-        }
-
-        SECTION("atan2 Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
-          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("atan2 R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
-          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("min Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
-          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("min Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
-          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("min R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
-          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("min Vh -> R")
-        {
-          REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
-          REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("max Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
-          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("max Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
-          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("max Vh -> R")
-        {
-          REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
-          REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("max R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
-          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("pow Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
-          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("pow Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
-          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("pow R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
-          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("dot Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
-
-          {
-            auto p_UV = dot(p_Vector3_u, p_Vector3_v);
-            REQUIRE(p_UV.use_count() == 1);
-
-            auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
-            auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
-
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              if (UV[cell_id] != direct_UV[cell_id]) {
-                is_same = false;
-                break;
-              }
-            }
-
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
-        }
-
-        SECTION("det Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = det(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = det(p_R2x2_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = det(p_R3x3_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(det(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(det(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(det(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(det(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("trace Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = trace(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = trace(p_R2x2_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = trace(p_R3x3_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(trace(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(trace(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(trace(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(trace(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("inverse Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = inverse(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id](0, 0) != inverse(values[cell_id])(0, 0)) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = inverse(p_R2x2_u);
-            constexpr size_t nb_row = 2;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = inverse(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = inverse(p_R3x3_u);
-            constexpr size_t nb_row = 3;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = inverse(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(inverse(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(inverse(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(inverse(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(inverse(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("transpose Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = transpose(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id](0, 0) != transpose(values[cell_id])(0, 0)) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = transpose(p_R2x2_u);
-            constexpr size_t nb_row = 2;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = transpose(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = transpose(p_R3x3_u);
-            constexpr size_t nb_row = 3;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              const TinyMatrix invA          = transpose(values[cell_id]);
-              const TinyMatrix<nb_row>& fu_i = fu[cell_id];
-              for (size_t i = 0; i < nb_row; ++i) {
-                for (size_t j = 0; j < nb_row; ++j) {
-                  if (fu_i(i, j) != invA(i, j)) {
-                    is_same = false;
-                  }
-                }
-              }
-              if (not is_same) {
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(transpose(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(transpose(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(transpose(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(transpose(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("sum_of_Vh Vh -> Vh")
-        {
-          {
-            auto p_sum_components = sum_of_Vh_components(p_Vector3_u);
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            const DiscreteFunctionP0<Dimension, double>& sum_components =
-              dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_sum_components);
-
-            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              double sum = 0;
-              for (size_t i = 0; i < p_Vector3_u->size(); ++i) {
-                sum += (*p_Vector3_u)[cell_id][i];
-              }
-
-              direct_sum[cell_id] = sum;
-            }
-
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              if (sum_components[cell_id] != direct_sum[cell_id]) {
-                is_same = false;
-                break;
-              }
-            }
-
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(sum_of_Vh_components(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("vectorize (Vh) -> Vh")
-        {
-          {
-            std::shared_ptr p_sum_components =
-              vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_u, p_positive_u, p_bounded_u});
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            const DiscreteFunctionP0Vector<Dimension, double> sum_components =
-              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_sum_components);
-            REQUIRE(sum_components.size() == 3);
-
-            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              is_same &= ((*p_u)[cell_id] == sum_components[cell_id][0]);
-              is_same &= ((*p_positive_u)[cell_id] == sum_components[cell_id][1]);
-              is_same &= ((*p_bounded_u)[cell_id] == sum_components[cell_id][2]);
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_u, p_other_mesh_u}),
-                              "error: discrete functions are not defined on the same mesh");
-          REQUIRE_THROWS_WITH(vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("dot Vh*Rd -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
-          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
-                              "error: incompatible operand types Vh(P0:R^1) and R^2");
-          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
-                              "error: incompatible operand types Vh(P0:R^2) and R^3");
-          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
-        }
-
-        SECTION("dot Rd*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
-          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
-                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
-                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
-        }
-
-        SECTION("sum_of_R* Vh -> R*")
-        {
-          REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
-
-          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-        }
-
-        SECTION("integral_of_R* Vh -> R*")
-        {
-          auto integrate_locally = [&](const auto& cell_values) {
-            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
-            using DataType = decltype(double{} * cell_values[CellId{0}]);
-            CellValue<DataType> local_integral{mesh->connectivity()};
-            parallel_for(
-              local_integral.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
-            return local_integral;
-          };
-
-          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
-
-          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-        }
-      }
-    }
-  }
-
-  SECTION("3D")
-  {
-    constexpr size_t Dimension = 3;
-
-    using Rd = TinyVector<Dimension>;
-
-    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
-
-    for (const auto& named_mesh : mesh_list) {
-      SECTION(named_mesh.name())
-      {
-        auto mesh = named_mesh.mesh();
-
-        std::shared_ptr other_mesh =
-          std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-        CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-        CellValue<double> values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        CellValue<double> positive_values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 2 + std::sin(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        CellValue<double> bounded_values = [=] {
-          CellValue<double> build_values{mesh->connectivity()};
-          parallel_for(
-            build_values.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.9 * std::sin(l2Norm(xj[cell_id])); });
-          return build_values;
-        }();
-
-        std::shared_ptr p_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, values);
-        std::shared_ptr p_other_mesh_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, values);
-        std::shared_ptr p_positive_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, positive_values);
-        std::shared_ptr p_bounded_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, bounded_values);
-
-        std::shared_ptr p_R1_u = [=] {
-          CellValue<TinyVector<1>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R1_v = [=] {
-          CellValue<TinyVector<1>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R1_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-        constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-          if constexpr (Dimension == 1) {
-            return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-          } else if constexpr (Dimension == 2) {
-            return TinyVector<2>{x[0], x[1]};
-          } else if constexpr (Dimension == 3) {
-            return TinyVector<2>{x[0], x[1] + x[2]};
-          }
-        };
-
-        std::shared_ptr p_R2_u = [=] {
-          CellValue<TinyVector<2>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-              uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R2_v = [=] {
-          CellValue<TinyVector<2>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-              vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R2_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-        constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-          if constexpr (Dimension == 1) {
-            return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-          } else if constexpr (Dimension == 2) {
-            return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-          } else if constexpr (Dimension == 3) {
-            return TinyVector<3>{x[0], x[1], x[2]};
-          }
-        };
-
-        std::shared_ptr p_R3_u = [=] {
-          CellValue<TinyVector<3>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R3_v = [=] {
-          CellValue<TinyVector<3>> vj{mesh->connectivity()};
-          parallel_for(
-            vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-        }();
-
-        std::shared_ptr p_other_mesh_R3_u =
-          std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-        std::shared_ptr p_R1x1_u = [=] {
-          CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(),
-            PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R2x2_u = [=] {
-          CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<2> x = to_2d(xj[cell_id]);
-
-              uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                          2 * x[1], -x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_R3x3_u = [=] {
-          CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-          parallel_for(
-            uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-
-              uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                          2 * x[1],        -x[0],           x[0] - x[1],   //
-                                          3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-            });
-
-          return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-        }();
-
-        std::shared_ptr p_Vector3_u = [=] {
-          CellArray<double> uj_vector{mesh->connectivity(), 3};
-          parallel_for(
-            uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              uj_vector[cell_id][0] = 2 * x[0] + 1;
-              uj_vector[cell_id][1] = 1 - x[1] * x[2];
-              uj_vector[cell_id][2] = x[0] + x[2];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-        }();
-
-        std::shared_ptr p_Vector3_v = [=] {
-          CellArray<double> vj_vector{mesh->connectivity(), 3};
-          parallel_for(
-            vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              vj_vector[cell_id][0] = x[0] * x[1] + 1;
-              vj_vector[cell_id][1] = 2 * x[1];
-              vj_vector[cell_id][2] = x[2] * x[0];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-        }();
-
-        std::shared_ptr p_Vector2_w = [=] {
-          CellArray<double> wj_vector{mesh->connectivity(), 2};
-          parallel_for(
-            wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-              const TinyVector<3> x = to_3d(xj[cell_id]);
-              wj_vector[cell_id][0] = x[0] + x[1] * 2;
-              wj_vector[cell_id][1] = x[0] * x[1];
-            });
-
-          return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-        }();
-
-        SECTION("sqrt Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, sqrt);
-          REQUIRE_THROWS_WITH(sqrt(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("abs Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, abs);
-          REQUIRE_THROWS_WITH(abs(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("sin Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sin);
-          REQUIRE_THROWS_WITH(sin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("cos Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cos);
-          REQUIRE_THROWS_WITH(cos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("tan Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tan);
-          REQUIRE_THROWS_WITH(tan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("asin Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, asin);
-          REQUIRE_THROWS_WITH(asin(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("acos Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, acos);
-          REQUIRE_THROWS_WITH(acos(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atan Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atan);
-          REQUIRE_THROWS_WITH(atan(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("sinh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, sinh);
-          REQUIRE_THROWS_WITH(sinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("cosh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, cosh);
-          REQUIRE_THROWS_WITH(cosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("tanh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, tanh);
-          REQUIRE_THROWS_WITH(tanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("asinh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, asinh);
-          REQUIRE_THROWS_WITH(asinh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("acosh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, acosh);
-          REQUIRE_THROWS_WITH(acosh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atanh Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_bounded_u, atanh);
-          REQUIRE_THROWS_WITH(atanh(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("exp Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_u, exp);
-          REQUIRE_THROWS_WITH(exp(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("log Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH_TO_VH_REAL_FUNCTION_EVALUATION(p_positive_u, log);
-          REQUIRE_THROWS_WITH(log(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("atan2 Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, atan2);
-          REQUIRE_THROWS_WITH(atan2(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(atan2(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-          REQUIRE_THROWS_WITH(atan2(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-        }
-
-        SECTION("atan2 Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 3.6, atan2);
-          REQUIRE_THROWS_WITH(atan2(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("atan2 R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.4, p_u, atan2);
-          REQUIRE_THROWS_WITH(atan2(2.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("min Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, min);
-          REQUIRE_THROWS_WITH(::min(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(::min(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(::min(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("min Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, min);
-          REQUIRE_THROWS_WITH(min(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("min R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, min);
-          REQUIRE_THROWS_WITH(min(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("min Vh -> R")
-        {
-          REQUIRE(min(std::shared_ptr<const IDiscreteFunction>{p_u}) == min(p_u->cellValues()));
-          REQUIRE_THROWS_WITH(min(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("max Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_u, p_bounded_u, max);
-          REQUIRE_THROWS_WITH(::max(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(::max(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(::max(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("max Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_u, 1.2, max);
-          REQUIRE_THROWS_WITH(max(p_R1_u, 2.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("max Vh -> R")
-        {
-          REQUIRE(max(std::shared_ptr<const IDiscreteFunction>{p_u}) == max(p_u->cellValues()));
-          REQUIRE_THROWS_WITH(max(std::shared_ptr<const IDiscreteFunction>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("max R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(0.4, p_u, max);
-          REQUIRE_THROWS_WITH(max(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("pow Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_positive_u, p_bounded_u, pow);
-          REQUIRE_THROWS_WITH(pow(p_u, p_other_mesh_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(pow(p_u, p_R1_u), "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(pow(p_R1_u, p_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R)");
-        }
-
-        SECTION("pow Vh*R -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_positive_u, 3.3, pow);
-          REQUIRE_THROWS_WITH(pow(p_R1_u, 3.1), "error: incompatible operand types Vh(P0:R^1) and R");
-        }
-
-        SECTION("pow R*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION(2.1, p_u, pow);
-          REQUIRE_THROWS_WITH(pow(3.1, p_R1_u), "error: incompatible operand types R and Vh(P0:R^1)");
-        }
-
-        SECTION("dot Vh*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R1_u, p_R1_v, dot);
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R2_u, p_R2_v, dot);
-          CHECK_EMBEDDED_VH2_TO_VH_FUNCTION_EVALUATION(p_R3_u, p_R3_v, dot);
-
-          {
-            auto p_UV = dot(p_Vector3_u, p_Vector3_v);
-            REQUIRE(p_UV.use_count() == 1);
-
-            auto UV        = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_UV);
-            auto direct_UV = dot(*p_Vector3_u, *p_Vector3_v);
-
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              if (UV[cell_id] != direct_UV[cell_id]) {
-                is_same = false;
-                break;
-              }
-            }
-
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(dot(p_R1_u, p_other_mesh_R1_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R2_u, p_other_mesh_R2_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R3_u, p_other_mesh_R3_u), "error: operands are defined on different meshes");
-          REQUIRE_THROWS_WITH(dot(p_R1_u, p_R3_u), "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(dot(p_Vector3_u, p_Vector2_w), "error: operands have different dimension");
-        }
-
-        SECTION("det Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = det(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = det(p_R2x2_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = det(p_R3x3_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != det(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(det(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(det(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(det(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(det(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("trace Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = trace(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = trace(p_R2x2_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu = trace(p_R3x3_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != trace(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(trace(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(trace(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(trace(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(trace(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("inverse Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = inverse(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != inverse(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = inverse(p_R2x2_u);
-            constexpr size_t nb_row = 2;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != inverse(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = inverse(p_R3x3_u);
-            constexpr size_t nb_row = 3;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != inverse(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(inverse(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(inverse(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(inverse(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(inverse(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("transpose Vh -> Vh")
-        {
-          {
-            std::shared_ptr p_fu = transpose(p_R1x1_u);
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>&>(*p_fu);
-
-            auto values  = p_R1x1_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != transpose(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = transpose(p_R2x2_u);
-            constexpr size_t nb_row = 2;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R2x2_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != transpose(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          {
-            std::shared_ptr p_fu    = transpose(p_R3x3_u);
-            constexpr size_t nb_row = 3;
-
-            REQUIRE(p_fu.use_count() > 0);
-            REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu));
-
-            const auto& fu = dynamic_cast<const DiscreteFunctionP0<Dimension, TinyMatrix<nb_row>>&>(*p_fu);
-
-            auto values  = p_R3x3_u->cellValues();
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < values.numberOfItems(); ++cell_id) {
-              if (fu[cell_id] != transpose(values[cell_id])) {
-                is_same = false;
-                break;
-              }
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(transpose(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(transpose(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(transpose(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(transpose(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("sum_of_Vh Vh -> Vh")
-        {
-          {
-            auto p_sum_components = sum_of_Vh_components(p_Vector3_u);
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            const DiscreteFunctionP0<Dimension, double>& sum_components =
-              dynamic_cast<const DiscreteFunctionP0<Dimension, double>&>(*p_sum_components);
-
-            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              double sum = 0;
-              for (size_t i = 0; i < p_Vector3_u->size(); ++i) {
-                sum += (*p_Vector3_u)[cell_id][i];
-              }
-
-              direct_sum[cell_id] = sum;
-            }
-
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              if (sum_components[cell_id] != direct_sum[cell_id]) {
-                is_same = false;
-                break;
-              }
-            }
-
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(sum_of_Vh_components(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-        }
-
-        SECTION("vectorize (Vh) -> Vh")
-        {
-          {
-            std::shared_ptr p_sum_components =
-              vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_u, p_positive_u, p_bounded_u});
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            REQUIRE(p_sum_components.use_count() == 1);
-
-            const DiscreteFunctionP0Vector<Dimension, double> sum_components =
-              dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_sum_components);
-            REQUIRE(sum_components.size() == 3);
-
-            DiscreteFunctionP0<Dimension, double> direct_sum(mesh);
-            bool is_same = true;
-            for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-              is_same &= ((*p_u)[cell_id] == sum_components[cell_id][0]);
-              is_same &= ((*p_positive_u)[cell_id] == sum_components[cell_id][1]);
-              is_same &= ((*p_bounded_u)[cell_id] == sum_components[cell_id][2]);
-            }
-            REQUIRE(is_same);
-          }
-
-          REQUIRE_THROWS_WITH(vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_u, p_other_mesh_u}),
-                              "error: discrete functions are not defined on the same mesh");
-          REQUIRE_THROWS_WITH(vectorize(std::vector<std::shared_ptr<const IDiscreteFunction>>{p_R1_u}),
-                              "error: invalid operand type Vh(P0:R^1)");
-        }
-
-        SECTION("dot Vh*Rd -> Vh")
-        {
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R1_u, (TinyVector<1>{3}), dot);
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R2_u, (TinyVector<2>{-6, 2}), dot);
-          CHECK_EMBEDDED_VHxW_TO_VH_FUNCTION_EVALUATION(p_R3_u, (TinyVector<3>{-1, 5, 2}), dot);
-          REQUIRE_THROWS_WITH(dot(p_R1_u, (TinyVector<2>{-6, 2})),
-                              "error: incompatible operand types Vh(P0:R^1) and R^2");
-          REQUIRE_THROWS_WITH(dot(p_R2_u, (TinyVector<3>{-1, 5, 2})),
-                              "error: incompatible operand types Vh(P0:R^2) and R^3");
-          REQUIRE_THROWS_WITH(dot(p_R3_u, (TinyVector<1>{-1})), "error: incompatible operand types Vh(P0:R^3) and R^1");
-        }
-
-        SECTION("dot Rd*Vh -> Vh")
-        {
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<1>{3}), p_R1_u, dot);
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<2>{-6, 2}), p_R2_u, dot);
-          CHECK_EMBEDDED_WxVH_TO_VH_FUNCTION_EVALUATION((TinyVector<3>{-1, 5, 2}), p_R3_u, dot);
-          REQUIRE_THROWS_WITH(dot((TinyVector<2>{-6, 2}), p_R1_u),
-                              "error: incompatible operand types R^2 and Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(dot((TinyVector<3>{-1, 5, 2}), p_R2_u),
-                              "error: incompatible operand types R^3 and Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(dot((TinyVector<1>{-1}), p_R3_u), "error: incompatible operand types R^1 and Vh(P0:R^3)");
-        }
-
-        SECTION("sum_of_R* Vh -> R*")
-        {
-          REQUIRE(sum_of<double>(p_u) == sum(p_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<1>>(p_R1_u) == sum(p_R1_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<2>>(p_R2_u) == sum(p_R2_u->cellValues()));
-          REQUIRE(sum_of<TinyVector<3>>(p_R3_u) == sum(p_R3_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<1>>(p_R1x1_u) == sum(p_R1x1_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<2>>(p_R2x2_u) == sum(p_R2x2_u->cellValues()));
-          REQUIRE(sum_of<TinyMatrix<3>>(p_R3x3_u) == sum(p_R3x3_u->cellValues()));
-
-          REQUIRE_THROWS_WITH(sum_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(sum_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-        }
-
-        SECTION("integral_of_R* Vh -> R*")
-        {
-          auto integrate_locally = [&](const auto& cell_values) {
-            const auto& Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
-            using DataType = decltype(double{} * cell_values[CellId{0}]);
-            CellValue<DataType> local_integral{mesh->connectivity()};
-            parallel_for(
-              local_integral.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { local_integral[cell_id] = Vj[cell_id] * cell_values[cell_id]; });
-            return local_integral;
-          };
-
-          REQUIRE(integral_of<double>(p_u) == sum(integrate_locally(p_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<1>>(p_R1_u) == sum(integrate_locally(p_R1_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<2>>(p_R2_u) == sum(integrate_locally(p_R2_u->cellValues())));
-          REQUIRE(integral_of<TinyVector<3>>(p_R3_u) == sum(integrate_locally(p_R3_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<1>>(p_R1x1_u) == sum(integrate_locally(p_R1x1_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<2>>(p_R2x2_u) == sum(integrate_locally(p_R2x2_u->cellValues())));
-          REQUIRE(integral_of<TinyMatrix<3>>(p_R3x3_u) == sum(integrate_locally(p_R3x3_u->cellValues())));
-
-          REQUIRE_THROWS_WITH(integral_of<TinyVector<1>>(p_u), "error: invalid operand type Vh(P0:R)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R1_u), "error: invalid operand type Vh(P0:R^1)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R2_u), "error: invalid operand type Vh(P0:R^2)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R3_u), "error: invalid operand type Vh(P0:R^3)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R1x1_u), "error: invalid operand type Vh(P0:R^1x1)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R2x2_u), "error: invalid operand type Vh(P0:R^2x2)");
-          REQUIRE_THROWS_WITH(integral_of<double>(p_R3x3_u), "error: invalid operand type Vh(P0:R^3x3)");
-        }
-      }
-    }
-  }
-}
diff --git a/tests/test_EmbeddedIDiscreteFunctionOperators.cpp b/tests/test_EmbeddedIDiscreteFunctionOperators.cpp
deleted file mode 100644
index 54e0e82f1ee25ea3166e51472e6be227e34a8080..0000000000000000000000000000000000000000
--- a/tests/test_EmbeddedIDiscreteFunctionOperators.cpp
+++ /dev/null
@@ -1,2735 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/matchers/catch_matchers_all.hpp>
-
-#include <MeshDataBaseForTests.hpp>
-
-#include <language/utils/EmbeddedIDiscreteFunctionOperators.hpp>
-#include <scheme/DiscreteFunctionP0.hpp>
-#include <scheme/DiscreteFunctionP0Vector.hpp>
-
-// clazy:excludeall=non-pod-global-static
-
-#define CHECK_SCALAR_VH2_TO_VH(P_LHS, OPERATOR, P_RHS)                                  \
-  {                                                                                     \
-    using DiscreteFunctionType = const std::decay_t<decltype(*P_LHS OPERATOR * P_RHS)>; \
-                                                                                        \
-    std::shared_ptr p_fuv = P_LHS OPERATOR P_RHS;                                       \
-                                                                                        \
-    REQUIRE(p_fuv.use_count() > 0);                                                     \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));                 \
-                                                                                        \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);                \
-                                                                                        \
-    auto lhs_values = P_LHS->cellValues();                                              \
-    auto rhs_values = P_RHS->cellValues();                                              \
-    bool is_same    = true;                                                             \
-    for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {         \
-      if (fuv[cell_id] != (lhs_values[cell_id] OPERATOR rhs_values[cell_id])) {         \
-        is_same = false;                                                                \
-        break;                                                                          \
-      }                                                                                 \
-    }                                                                                   \
-                                                                                        \
-    REQUIRE(is_same);                                                                   \
-  }
-
-#define CHECK_SCALAR_VHxX_TO_VH(P_LHS, OPERATOR, RHS)                               \
-  {                                                                                 \
-    using DiscreteFunctionType = const std::decay_t<decltype(*P_LHS OPERATOR RHS)>; \
-                                                                                    \
-    std::shared_ptr p_fuv = P_LHS OPERATOR RHS;                                     \
-                                                                                    \
-    REQUIRE(p_fuv.use_count() > 0);                                                 \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));             \
-                                                                                    \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);            \
-                                                                                    \
-    auto lhs_values = P_LHS->cellValues();                                          \
-    bool is_same    = true;                                                         \
-    for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {     \
-      if (fuv[cell_id] != (lhs_values[cell_id] OPERATOR RHS)) {                     \
-        is_same = false;                                                            \
-        break;                                                                      \
-      }                                                                             \
-    }                                                                               \
-                                                                                    \
-    REQUIRE(is_same);                                                               \
-  }
-
-#define CHECK_SCALAR_XxVH_TO_VH(LHS, OPERATOR, P_RHS)                                \
-  {                                                                                  \
-    using DiscreteFunctionType = const std::decay_t<decltype(LHS OPERATOR * P_RHS)>; \
-                                                                                     \
-    std::shared_ptr p_fuv = LHS OPERATOR P_RHS;                                      \
-                                                                                     \
-    REQUIRE(p_fuv.use_count() > 0);                                                  \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));              \
-                                                                                     \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);             \
-                                                                                     \
-    auto rhs_values = P_RHS->cellValues();                                           \
-    bool is_same    = true;                                                          \
-    for (CellId cell_id = 0; cell_id < rhs_values.numberOfItems(); ++cell_id) {      \
-      if (fuv[cell_id] != (LHS OPERATOR rhs_values[cell_id])) {                      \
-        is_same = false;                                                             \
-        break;                                                                       \
-      }                                                                              \
-    }                                                                                \
-                                                                                     \
-    REQUIRE(is_same);                                                                \
-  }
-
-#define CHECK_VECTOR_VH2_TO_VH(P_LHS, OPERATOR, P_RHS)                                     \
-  {                                                                                        \
-    using DiscreteFunctionType = const std::decay_t<decltype(*P_LHS OPERATOR * P_RHS)>;    \
-                                                                                           \
-    std::shared_ptr p_fuv = P_LHS OPERATOR P_RHS;                                          \
-                                                                                           \
-    REQUIRE(p_fuv.use_count() > 0);                                                        \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));                    \
-                                                                                           \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);                   \
-                                                                                           \
-    auto lhs_arrays = P_LHS->cellArrays();                                                 \
-    auto rhs_arrays = P_RHS->cellArrays();                                                 \
-    bool is_same    = true;                                                                \
-    REQUIRE(rhs_arrays.sizeOfArrays() > 0);                                                \
-    REQUIRE(lhs_arrays.sizeOfArrays() == rhs_arrays.sizeOfArrays());                       \
-    REQUIRE(lhs_arrays.sizeOfArrays() == fuv.size());                                      \
-    for (CellId cell_id = 0; cell_id < lhs_arrays.numberOfItems(); ++cell_id) {            \
-      for (size_t i = 0; i < fuv.size(); ++i) {                                            \
-        if (fuv[cell_id][i] != (lhs_arrays[cell_id][i] OPERATOR rhs_arrays[cell_id][i])) { \
-          is_same = false;                                                                 \
-          break;                                                                           \
-        }                                                                                  \
-      }                                                                                    \
-    }                                                                                      \
-                                                                                           \
-    REQUIRE(is_same);                                                                      \
-  }
-
-#define CHECK_VECTOR_XxVH_TO_VH(LHS, OPERATOR, P_RHS)                                \
-  {                                                                                  \
-    using DiscreteFunctionType = const std::decay_t<decltype(LHS OPERATOR * P_RHS)>; \
-                                                                                     \
-    std::shared_ptr p_fuv = LHS OPERATOR P_RHS;                                      \
-                                                                                     \
-    REQUIRE(p_fuv.use_count() > 0);                                                  \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fuv));              \
-                                                                                     \
-    const auto& fuv = dynamic_cast<const DiscreteFunctionType&>(*p_fuv);             \
-                                                                                     \
-    auto rhs_arrays = P_RHS->cellArrays();                                           \
-    bool is_same    = true;                                                          \
-    REQUIRE(rhs_arrays.sizeOfArrays() > 0);                                          \
-    REQUIRE(fuv.size() == rhs_arrays.sizeOfArrays());                                \
-    for (CellId cell_id = 0; cell_id < rhs_arrays.numberOfItems(); ++cell_id) {      \
-      for (size_t i = 0; i < fuv.size(); ++i) {                                      \
-        if (fuv[cell_id][i] != (LHS OPERATOR rhs_arrays[cell_id][i])) {              \
-          is_same = false;                                                           \
-          break;                                                                     \
-        }                                                                            \
-      }                                                                              \
-    }                                                                                \
-                                                                                     \
-    REQUIRE(is_same);                                                                \
-  }
-
-#define CHECK_SCALAR_VH_TO_VH(OPERATOR, P_RHS)                                   \
-  {                                                                              \
-    using DiscreteFunctionType = const std::decay_t<decltype(OPERATOR * P_RHS)>; \
-                                                                                 \
-    std::shared_ptr p_fu = OPERATOR P_RHS;                                       \
-                                                                                 \
-    REQUIRE(p_fu.use_count() > 0);                                               \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fu));           \
-                                                                                 \
-    const auto& fu = dynamic_cast<const DiscreteFunctionType&>(*p_fu);           \
-                                                                                 \
-    auto rhs_values = P_RHS->cellValues();                                       \
-    bool is_same    = true;                                                      \
-    for (CellId cell_id = 0; cell_id < rhs_values.numberOfItems(); ++cell_id) {  \
-      if (fu[cell_id] != (OPERATOR rhs_values[cell_id])) {                       \
-        is_same = false;                                                         \
-        break;                                                                   \
-      }                                                                          \
-    }                                                                            \
-                                                                                 \
-    REQUIRE(is_same);                                                            \
-  }
-
-#define CHECK_VECTOR_VH_TO_VH(OPERATOR, P_RHS)                                   \
-  {                                                                              \
-    using DiscreteFunctionType = const std::decay_t<decltype(OPERATOR * P_RHS)>; \
-                                                                                 \
-    std::shared_ptr p_fu = OPERATOR P_RHS;                                       \
-                                                                                 \
-    REQUIRE(p_fu.use_count() > 0);                                               \
-    REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionType&>(*p_fu));           \
-                                                                                 \
-    const auto& fu = dynamic_cast<const DiscreteFunctionType&>(*p_fu);           \
-                                                                                 \
-    auto rhs_arrays = P_RHS->cellArrays();                                       \
-    REQUIRE(rhs_arrays.sizeOfArrays() > 0);                                      \
-    REQUIRE(rhs_arrays.sizeOfArrays() == fu.size());                             \
-    bool is_same = true;                                                         \
-    for (CellId cell_id = 0; cell_id < rhs_arrays.numberOfItems(); ++cell_id) {  \
-      for (size_t i = 0; i < rhs_arrays.sizeOfArrays(); ++i) {                   \
-        if (fu[cell_id][i] != (OPERATOR rhs_arrays[cell_id][i])) {               \
-          is_same = false;                                                       \
-          break;                                                                 \
-        }                                                                        \
-      }                                                                          \
-    }                                                                            \
-                                                                                 \
-    REQUIRE(is_same);                                                            \
-  }
-
-#ifdef __clang__
-#pragma clang optimize off
-#endif   // __clang__
-
-TEST_CASE("EmbeddedIDiscreteFunctionOperators", "[scheme]")
-{
-  SECTION("binary operators")
-  {
-    SECTION("1D")
-    {
-      constexpr size_t Dimension = 1;
-
-      using Rd = TinyVector<Dimension>;
-
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh = named_mesh.mesh();
-
-          std::shared_ptr other_mesh =
-            std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-          CellValue<double> u_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          CellValue<double> v_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-          std::shared_ptr p_other_mesh_R_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
-          std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
-
-          std::shared_ptr p_R1_u = [=] {
-            CellValue<TinyVector<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R1_v = [=] {
-            CellValue<TinyVector<1>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R1_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<2>{x[0], x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<2>{x[0], x[1] + x[2]};
-            }
-          };
-
-          std::shared_ptr p_R2_u = [=] {
-            CellValue<TinyVector<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R2_v = [=] {
-            CellValue<TinyVector<2>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R2_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<3>{x[0], x[1], x[2]};
-            }
-          };
-
-          std::shared_ptr p_R3_u = [=] {
-            CellValue<TinyVector<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R3_v = [=] {
-            CellValue<TinyVector<3>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R3_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-          std::shared_ptr p_R1x1_u = [=] {
-            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R1x1_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
-
-          std::shared_ptr p_R1x1_v = [=] {
-            CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = TinyMatrix<1>{0.3 - xj[cell_id][0]}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_R2x2_u = [=] {
-            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                            2 * x[1], -x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R2x2_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
-
-          std::shared_ptr p_R2x2_v = [=] {
-            CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                vj[cell_id] = TinyMatrix<2>{x[0] + 0.3, 1 - x[1] - x[0],   //
-                                            2 * x[1] + x[0], x[1] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_R3x3_u = [=] {
-            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                            2 * x[1],        -x[0],           x[0] - x[1],   //
-                                            3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R3x3_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
-
-          std::shared_ptr p_R3x3_v = [=] {
-            CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                vj[cell_id] = TinyMatrix<3>{0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
-                                            2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
-                                            2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_Vector3_u = [=] {
-            CellArray<double> uj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj_vector[cell_id][0] = 2 * x[0] + 1;
-                uj_vector[cell_id][1] = 1 - x[1] * x[2];
-                uj_vector[cell_id][2] = x[0] + x[2];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-          }();
-
-          std::shared_ptr p_other_mesh_Vector3_u =
-            std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
-
-          std::shared_ptr p_Vector3_v = [=] {
-            CellArray<double> vj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                vj_vector[cell_id][0] = x[0] * x[1] + 1;
-                vj_vector[cell_id][1] = 2 * x[1];
-                vj_vector[cell_id][2] = x[2] * x[0];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-          }();
-
-          std::shared_ptr p_Vector2_w = [=] {
-            CellArray<double> wj_vector{mesh->connectivity(), 2};
-            parallel_for(
-              wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                wj_vector[cell_id][0] = x[0] + x[1] * 2;
-                wj_vector[cell_id][1] = x[0] * x[1];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-          }();
-
-          SECTION("sum")
-          {
-            SECTION("Vh + Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
-
-              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
-
-              REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-              REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
-                                  "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh + X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
-              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
-            }
-
-            SECTION("X + Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}),
-                                      +, p_R3x3_u);
-
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u,
-                                  "error: incompatible operand types R^3 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
-                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
-                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("difference")
-          {
-            SECTION("Vh - Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
-
-              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
-
-              REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-              REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
-                                  "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh - X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
-              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
-            }
-
-            SECTION("X - Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}),
-                                      -, p_R3x3_u);
-
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u,
-                                  "error: incompatible operand types R^3 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
-                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
-                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("product")
-          {
-            SECTION("Vh * Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
-
-              {
-                std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
-
-                REQUIRE(p_fuv.use_count() > 0);
-                REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
-
-                const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
-
-                auto lhs_values = p_R_u->cellValues();
-                auto rhs_arrays = p_Vector3_v->cellArrays();
-                bool is_same    = true;
-                for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
-                  for (size_t i = 0; i < fuv.size(); ++i) {
-                    if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
-                      is_same = false;
-                      break;
-                    }
-                  }
-                }
-
-                REQUIRE(is_same);
-              }
-
-              REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
-
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v,
-                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v,
-                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v,
-                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
-
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
-
-              REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
-
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
-
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh * X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
-              REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0:R^2) and R^2");
-              REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R^3) and R^3");
-              REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}),
-                                  "error: incompatible operand types Vh(P0:R^1) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R^2) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R^3) and R^3x3");
-              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
-                                  "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
-              REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
-            }
-
-            SECTION("X * Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                                     4.7, 2.3, 7.1,   //
-                                                                     9.7, 3.2, 6.8}),
-                                                      *, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                                     4.7, 2.3, 7.1,   //
-                                                                     9.7, 3.2, 6.8}),
-                                                      *, p_R3x3_u);
-
-              CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("ratio")
-          {
-            SECTION("Vh / Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
-
-              REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-
-              REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
-            }
-
-            SECTION("X / Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
-            }
-          }
-        }
-      }
-    }
-
-    SECTION("2D")
-    {
-      constexpr size_t Dimension = 2;
-
-      using Rd = TinyVector<Dimension>;
-
-      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh = named_mesh.mesh();
-
-          std::shared_ptr other_mesh =
-            std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-          CellValue<double> u_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          CellValue<double> v_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-          std::shared_ptr p_other_mesh_R_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
-          std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
-
-          std::shared_ptr p_R1_u = [=] {
-            CellValue<TinyVector<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R1_v = [=] {
-            CellValue<TinyVector<1>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R1_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<2>{x[0], x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<2>{x[0], x[1] + x[2]};
-            }
-          };
-
-          std::shared_ptr p_R2_u = [=] {
-            CellValue<TinyVector<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R2_v = [=] {
-            CellValue<TinyVector<2>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R2_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<3>{x[0], x[1], x[2]};
-            }
-          };
-
-          std::shared_ptr p_R3_u = [=] {
-            CellValue<TinyVector<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R3_v = [=] {
-            CellValue<TinyVector<3>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R3_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-          std::shared_ptr p_R1x1_u = [=] {
-            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R1x1_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
-
-          std::shared_ptr p_R1x1_v = [=] {
-            CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = TinyMatrix<1>{0.3 - xj[cell_id][0]}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_R2x2_u = [=] {
-            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                            2 * x[1], -x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R2x2_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
-
-          std::shared_ptr p_R2x2_v = [=] {
-            CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                vj[cell_id] = TinyMatrix<2>{x[0] + 0.3, 1 - x[1] - x[0],   //
-                                            2 * x[1] + x[0], x[1] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_R3x3_u = [=] {
-            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                            2 * x[1],        -x[0],           x[0] - x[1],   //
-                                            3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R3x3_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
-
-          std::shared_ptr p_R3x3_v = [=] {
-            CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                vj[cell_id] = TinyMatrix<3>{0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
-                                            2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
-                                            2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_Vector3_u = [=] {
-            CellArray<double> uj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj_vector[cell_id][0] = 2 * x[0] + 1;
-                uj_vector[cell_id][1] = 1 - x[1] * x[2];
-                uj_vector[cell_id][2] = x[0] + x[2];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-          }();
-
-          std::shared_ptr p_other_mesh_Vector3_u =
-            std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
-
-          std::shared_ptr p_Vector3_v = [=] {
-            CellArray<double> vj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                vj_vector[cell_id][0] = x[0] * x[1] + 1;
-                vj_vector[cell_id][1] = 2 * x[1];
-                vj_vector[cell_id][2] = x[2] * x[0];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-          }();
-
-          std::shared_ptr p_Vector2_w = [=] {
-            CellArray<double> wj_vector{mesh->connectivity(), 2};
-            parallel_for(
-              wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                wj_vector[cell_id][0] = x[0] + x[1] * 2;
-                wj_vector[cell_id][1] = x[0] * x[1];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-          }();
-
-          SECTION("sum")
-          {
-            SECTION("Vh + Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
-
-              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
-
-              REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-              REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
-                                  "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh + X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
-              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
-            }
-
-            SECTION("X + Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}),
-                                      +, p_R3x3_u);
-
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u,
-                                  "error: incompatible operand types R^3 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
-                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
-                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("difference")
-          {
-            SECTION("Vh - Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
-
-              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
-
-              REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-              REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
-                                  "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh - X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
-              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
-            }
-
-            SECTION("X - Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}),
-                                      -, p_R3x3_u);
-
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u,
-                                  "error: incompatible operand types R^3 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
-                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
-                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("product")
-          {
-            SECTION("Vh * Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
-
-              {
-                std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
-
-                REQUIRE(p_fuv.use_count() > 0);
-                REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
-
-                const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
-
-                auto lhs_values = p_R_u->cellValues();
-                auto rhs_arrays = p_Vector3_v->cellArrays();
-                bool is_same    = true;
-                for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
-                  for (size_t i = 0; i < fuv.size(); ++i) {
-                    if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
-                      is_same = false;
-                      break;
-                    }
-                  }
-                }
-
-                REQUIRE(is_same);
-              }
-
-              REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
-
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v,
-                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v,
-                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v,
-                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
-
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
-
-              REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
-
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
-
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh * X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
-              REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0:R^2) and R^2");
-              REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R^3) and R^3");
-              REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}),
-                                  "error: incompatible operand types Vh(P0:R^1) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R^2) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R^3) and R^3x3");
-              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
-                                  "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
-              REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
-            }
-
-            SECTION("X * Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                                     4.7, 2.3, 7.1,   //
-                                                                     9.7, 3.2, 6.8}),
-                                                      *, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                                     4.7, 2.3, 7.1,   //
-                                                                     9.7, 3.2, 6.8}),
-                                                      *, p_R3x3_u);
-
-              CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("ratio")
-          {
-            SECTION("Vh / Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
-
-              REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-
-              REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
-            }
-
-            SECTION("X / Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
-            }
-          }
-        }
-      }
-    }
-
-    SECTION("3D")
-    {
-      constexpr size_t Dimension = 3;
-
-      using Rd = TinyVector<Dimension>;
-
-      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh = named_mesh.mesh();
-
-          std::shared_ptr other_mesh =
-            std::make_shared<Mesh<Connectivity<Dimension>>>(mesh->shared_connectivity(), mesh->xr());
-
-          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-          CellValue<double> u_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          CellValue<double> v_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.6 + std::sin(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-          std::shared_ptr p_other_mesh_R_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, double>>(other_mesh, u_R_values);
-          std::shared_ptr p_R_v = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, v_R_values);
-
-          std::shared_ptr p_R1_u = [=] {
-            CellValue<TinyVector<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R1_v = [=] {
-            CellValue<TinyVector<1>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id][0] = xj[cell_id][0] * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R1_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(other_mesh, p_R1_u->cellValues());
-
-          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<2>{x[0], x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<2>{x[0], x[1] + x[2]};
-            }
-          };
-
-          std::shared_ptr p_R2_u = [=] {
-            CellValue<TinyVector<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R2_v = [=] {
-            CellValue<TinyVector<2>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                vj[cell_id]           = TinyVector<2>{x[0] * x[1] + 1, 2 * x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R2_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(other_mesh, p_R2_u->cellValues());
-
-          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<3>{x[0], x[1], x[2]};
-            }
-          };
-
-          std::shared_ptr p_R3_u = [=] {
-            CellValue<TinyVector<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R3_v = [=] {
-            CellValue<TinyVector<3>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                vj[cell_id]           = TinyVector<3>{x[0] * x[1] + 1, 2 * x[1], x[2] * x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_other_mesh_R3_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(other_mesh, p_R3_u->cellValues());
-
-          std::shared_ptr p_R1x1_u = [=] {
-            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R1x1_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(other_mesh, p_R1x1_u->cellValues());
-
-          std::shared_ptr p_R1x1_v = [=] {
-            CellValue<TinyMatrix<1>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { vj[cell_id] = TinyMatrix<1>{0.3 - xj[cell_id][0]}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_R2x2_u = [=] {
-            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                            2 * x[1], -x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R2x2_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(other_mesh, p_R2x2_u->cellValues());
-
-          std::shared_ptr p_R2x2_v = [=] {
-            CellValue<TinyMatrix<2>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                vj[cell_id] = TinyMatrix<2>{x[0] + 0.3, 1 - x[1] - x[0],   //
-                                            2 * x[1] + x[0], x[1] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_R3x3_u = [=] {
-            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                            2 * x[1],        -x[0],           x[0] - x[1],   //
-                                            3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_other_mesh_R3x3_u =
-            std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(other_mesh, p_R3x3_u->cellValues());
-
-          std::shared_ptr p_R3x3_v = [=] {
-            CellValue<TinyMatrix<3>> vj{mesh->connectivity()};
-            parallel_for(
-              vj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                vj[cell_id] = TinyMatrix<3>{0.2 * x[0] + 1,  2 + x[1],          3 - x[2],      //
-                                            2.3 * x[2],      x[1] - x[0],       x[2] - x[1],   //
-                                            2 * x[2] + x[0], x[1] + 0.2 * x[2], x[2] - 2 * x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, vj);
-          }();
-
-          std::shared_ptr p_Vector3_u = [=] {
-            CellArray<double> uj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj_vector[cell_id][0] = 2 * x[0] + 1;
-                uj_vector[cell_id][1] = 1 - x[1] * x[2];
-                uj_vector[cell_id][2] = x[0] + x[2];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-          }();
-
-          std::shared_ptr p_other_mesh_Vector3_u =
-            std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(other_mesh, p_Vector3_u->cellArrays());
-
-          std::shared_ptr p_Vector3_v = [=] {
-            CellArray<double> vj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              vj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                vj_vector[cell_id][0] = x[0] * x[1] + 1;
-                vj_vector[cell_id][1] = 2 * x[1];
-                vj_vector[cell_id][2] = x[2] * x[0];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, vj_vector);
-          }();
-
-          std::shared_ptr p_Vector2_w = [=] {
-            CellArray<double> wj_vector{mesh->connectivity(), 2};
-            parallel_for(
-              wj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                wj_vector[cell_id][0] = x[0] + x[1] * 2;
-                wj_vector[cell_id][1] = x[0] * x[1];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, wj_vector);
-          }();
-
-          SECTION("sum")
-          {
-            SECTION("Vh + Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, +, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1_u, +, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2_u, +, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3_u, +, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, +, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, +, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, +, p_R3x3_v);
-
-              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, +, p_Vector3_v);
-
-              REQUIRE_THROWS_WITH(p_R_u + p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u + p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u + p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_R_u + p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-              REQUIRE_THROWS_WITH(p_R_u + p_other_mesh_R_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1_u + p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2_u + p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3_u + p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u + p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u + p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u + p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_Vector3_u + p_other_mesh_Vector3_u,
-                                  "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh + X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, +, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, +, (TinyVector<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, +, (TinyVector<2>{1.2, 2.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, +, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, +, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, +, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, +,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R_u + (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u + (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<1>{1}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
-              REQUIRE_THROWS_WITH(p_Vector3_u + (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
-            }
-
-            SECTION("X + Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, +, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, +, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), +, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), +, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), +, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), +, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), +, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}),
-                                      +, p_R3x3_u);
-
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) + p_R_u,
-                                  "error: incompatible operand types R^3 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) + p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) + p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) + p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((double{1}) + p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) + p_Vector3_u,
-                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) + p_Vector3_u,
-                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("difference")
-          {
-            SECTION("Vh - Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, -, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1_u, -, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2_u, -, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3_u, -, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, -, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, -, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, -, p_R3x3_v);
-
-              CHECK_VECTOR_VH2_TO_VH(p_Vector3_u, -, p_Vector3_v);
-
-              REQUIRE_THROWS_WITH(p_R_u - p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u - p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u - p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u - p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_R_v, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_Vector2_w, "error: Vh(P0Vector:R) spaces have different sizes");
-
-              REQUIRE_THROWS_WITH(p_R_u - p_other_mesh_R_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1_u - p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2_u - p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3_u - p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u - p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u - p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u - p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_Vector3_u - p_other_mesh_Vector3_u,
-                                  "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh - X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, -, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1_u, -, (TinyVector<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2_u, -, (TinyVector<2>{1.2, 2.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3_u, -, (TinyVector<3>{3.2, 7.1, 5.2}));
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, -, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, -, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, -,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R) and R^1");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<2>{1, 2}), "error: incompatible operand types Vh(P0:R) and R^2");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<1>{2}), "error: incompatible operand types Vh(P0:R) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R_u - (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u - (double{1}), "error: incompatible operand types Vh(P0Vector:R) and R");
-              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<1>{1}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^1");
-              REQUIRE_THROWS_WITH(p_Vector3_u - (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^2");
-            }
-
-            SECTION("X - Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, -, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, -, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<1>{1.3}), -, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<2>{1.2, 2.3}), -, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyVector<3>{3.2, 7.1, 5.2}), -, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), -, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), -, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}),
-                                      -, p_R3x3_u);
-
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_R_u, "error: incompatible operand types R^1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_R_u, "error: incompatible operand types R^2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyVector<3>{2, 3, 2}) - p_R_u,
-                                  "error: incompatible operand types R^3 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) - p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) - p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) - p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((double{1}) - p_Vector3_u, "error: incompatible operand types R and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<1>{1}) - p_Vector3_u,
-                                  "error: incompatible operand types R^1 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyVector<2>{1, 2}) - p_Vector3_u,
-                                  "error: incompatible operand types R^2 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("product")
-          {
-            SECTION("Vh * Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3x3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R1x1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R2x2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, *, p_R3x3_v);
-
-              CHECK_SCALAR_VH2_TO_VH(p_R1x1_u, *, p_R1_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R2x2_u, *, p_R2_v);
-              CHECK_SCALAR_VH2_TO_VH(p_R3x3_u, *, p_R3_v);
-
-              {
-                std::shared_ptr p_fuv = p_R_u * p_Vector3_v;
-
-                REQUIRE(p_fuv.use_count() > 0);
-                REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv));
-
-                const auto& fuv = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*p_fuv);
-
-                auto lhs_values = p_R_u->cellValues();
-                auto rhs_arrays = p_Vector3_v->cellArrays();
-                bool is_same    = true;
-                for (CellId cell_id = 0; cell_id < lhs_values.numberOfItems(); ++cell_id) {
-                  for (size_t i = 0; i < fuv.size(); ++i) {
-                    if (fuv[cell_id][i] != (lhs_values[cell_id] * rhs_arrays[cell_id][i])) {
-                      is_same = false;
-                      break;
-                    }
-                  }
-                }
-
-                REQUIRE(is_same);
-              }
-
-              REQUIRE_THROWS_WITH(p_R1_u * p_R1_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u * p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u * p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R1_u * p_R2x2_v, "error: incompatible operand types Vh(P0:R^1) and Vh(P0:R^2x2)");
-
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2x2_v,
-                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3x3_v,
-                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3x3)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1x1_v,
-                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1x1)");
-
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_R2_v, "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_R3_v, "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_R1_v, "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0:R^1)");
-
-              REQUIRE_THROWS_WITH(p_R1_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^1) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R2_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^2) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^3) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^1x1) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^2x2) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0:R^3x3) and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_u * p_Vector3_v,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0Vector:R)");
-
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R_u, "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R1x1_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R2x2_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH(p_Vector3_v * p_R3x3_u,
-                                  "error: incompatible operand types Vh(P0Vector:R) and Vh(P0:R^3x3)");
-
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R1x1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R2x2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_R3x3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R1x1_u * p_other_mesh_R1_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R2x2_u * p_other_mesh_R2_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R3x3_u * p_other_mesh_R3_u, "error: operands are defined on different meshes");
-              REQUIRE_THROWS_WITH(p_R_u * p_other_mesh_Vector3_u, "error: operands are defined on different meshes");
-            }
-
-            SECTION("Vh * X -> Vh")
-            {
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, bool{true});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, uint64_t{1});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, int64_t{2});
-              CHECK_SCALAR_VHxX_TO_VH(p_R_u, *, double{1.3});
-
-              CHECK_SCALAR_VHxX_TO_VH(p_R1x1_u, *, (TinyMatrix<1>{1.3}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R2x2_u, *, (TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}));
-              CHECK_SCALAR_VHxX_TO_VH(p_R3x3_u, *,
-                                      (TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                     4.7, 2.3, 7.1,   //
-                                                     9.7, 3.2, 6.8}));
-
-              REQUIRE_THROWS_WITH(p_R1_u * (TinyVector<1>{1}), "error: incompatible operand types Vh(P0:R^1) and R^1");
-              REQUIRE_THROWS_WITH(p_R2_u * (TinyVector<2>{1, 2}),
-                                  "error: incompatible operand types Vh(P0:R^2) and R^2");
-              REQUIRE_THROWS_WITH(p_R3_u * (TinyVector<3>{2, 3, 2}),
-                                  "error: incompatible operand types Vh(P0:R^3) and R^3");
-              REQUIRE_THROWS_WITH(p_R1_u * (TinyMatrix<1>{2}),
-                                  "error: incompatible operand types Vh(P0:R^1) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R2_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R^2) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R^3) and R^3x3");
-              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<1>{2}),
-                                  "error: incompatible operand types Vh(P0:R^2x2) and R^1x1");
-              REQUIRE_THROWS_WITH(p_R1x1_u * (TinyMatrix<2>{2, 3, 1, 4}),
-                                  "error: incompatible operand types Vh(P0:R^1x1) and R^2x2");
-              REQUIRE_THROWS_WITH(p_R2x2_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0:R^2x2) and R^3x3");
-
-              REQUIRE_THROWS_WITH(p_Vector3_u * (TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}),
-                                  "error: incompatible operand types Vh(P0Vector:R) and R^3x3");
-              REQUIRE_THROWS_WITH(p_Vector3_u * (double{2}), "error: incompatible operand types Vh(P0Vector:R) and R");
-            }
-
-            SECTION("X * Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R1x1_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R2x2_u);
-
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, *, p_R3x3_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, *, p_R3x3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                                     4.7, 2.3, 7.1,   //
-                                                                     9.7, 3.2, 6.8}),
-                                                      *, p_R3_u);
-
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<1>{1.3}), *, p_R1x1_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<2>{1.2, 2.3, 4.2, 5.1}), *, p_R2x2_u);
-              CHECK_SCALAR_XxVH_TO_VH((TinyMatrix<3>{3.2, 7.1, 5.2,   //
-                                                                     4.7, 2.3, 7.1,   //
-                                                                     9.7, 3.2, 6.8}),
-                                                      *, p_R3x3_u);
-
-              CHECK_VECTOR_XxVH_TO_VH(bool{true}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(uint64_t{1}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(int64_t{2}, *, p_Vector3_u);
-              CHECK_VECTOR_XxVH_TO_VH(double{1.3}, *, p_Vector3_u);
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R_u, "error: incompatible operand types R^1x1 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R1_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^1)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_R2x2_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R3x3_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^3x3)");
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_R2x2_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0:R^2x2)");
-              REQUIRE_THROWS_WITH((TinyMatrix<2>{2, 3, 1, 4}) * p_R1x1_u,
-                                  "error: incompatible operand types R^2x2 and Vh(P0:R^1x1)");
-
-              REQUIRE_THROWS_WITH((TinyMatrix<3>{1, 3, 6, 4, 7, 2, 5, 9, 8}) * p_Vector3_u,
-                                  "error: incompatible operand types R^3x3 and Vh(P0Vector:R)");
-              REQUIRE_THROWS_WITH((TinyMatrix<1>{2}) * p_Vector3_u,
-                                  "error: incompatible operand types R^1x1 and Vh(P0Vector:R)");
-            }
-          }
-
-          SECTION("ratio")
-          {
-            SECTION("Vh / Vh -> Vh")
-            {
-              CHECK_SCALAR_VH2_TO_VH(p_R_u, /, p_R_v);
-
-              REQUIRE_THROWS_WITH(p_R_u / p_R1_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R2_u / p_R1_v, "error: incompatible operand types Vh(P0:R^2) and Vh(P0:R^1)");
-              REQUIRE_THROWS_WITH(p_R3_u / p_R1x1_v, "error: incompatible operand types Vh(P0:R^3) and Vh(P0:R^1x1)");
-              REQUIRE_THROWS_WITH(p_R_u / p_R2x2_v, "error: incompatible operand types Vh(P0:R) and Vh(P0:R^2x2)");
-
-              REQUIRE_THROWS_WITH(p_R_u / p_other_mesh_R_u, "error: operands are defined on different meshes");
-            }
-
-            SECTION("X / Vh -> Vh")
-            {
-              CHECK_SCALAR_XxVH_TO_VH(bool{true}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(uint64_t{1}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(int64_t{2}, /, p_R_u);
-              CHECK_SCALAR_XxVH_TO_VH(double{1.3}, /, p_R_u);
-            }
-          }
-        }
-      }
-    }
-  }
-
-  SECTION("unary operators")
-  {
-    SECTION("1D")
-    {
-      constexpr size_t Dimension = 1;
-
-      using Rd = TinyVector<Dimension>;
-
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh = named_mesh.mesh();
-
-          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-          CellValue<double> u_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-
-          std::shared_ptr p_R1_u = [=] {
-            CellValue<TinyVector<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-          }();
-
-          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<2>{x[0], x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<2>{x[0], x[1] + x[2]};
-            }
-          };
-
-          std::shared_ptr p_R2_u = [=] {
-            CellValue<TinyVector<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-          }();
-
-          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<3>{x[0], x[1], x[2]};
-            }
-          };
-
-          std::shared_ptr p_R3_u = [=] {
-            CellValue<TinyVector<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R1x1_u = [=] {
-            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R2x2_u = [=] {
-            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                            2 * x[1], -x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R3x3_u = [=] {
-            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                            2 * x[1],        -x[0],           x[0] - x[1],   //
-                                            3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_Vector3_u = [=] {
-            CellArray<double> uj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj_vector[cell_id][0] = 2 * x[0] + 1;
-                uj_vector[cell_id][1] = 1 - x[1] * x[2];
-                uj_vector[cell_id][2] = x[0] + x[2];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-          }();
-
-          SECTION("unary minus")
-          {
-            SECTION("- Vh -> Vh")
-            {
-              CHECK_SCALAR_VH_TO_VH(-, p_R_u);
-
-              CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
-
-              CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
-
-              CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
-            }
-          }
-        }
-      }
-    }
-
-    SECTION("2D")
-    {
-      constexpr size_t Dimension = 2;
-
-      using Rd = TinyVector<Dimension>;
-
-      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh = named_mesh.mesh();
-
-          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-          CellValue<double> u_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-
-          std::shared_ptr p_R1_u = [=] {
-            CellValue<TinyVector<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-          }();
-
-          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<2>{x[0], x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<2>{x[0], x[1] + x[2]};
-            }
-          };
-
-          std::shared_ptr p_R2_u = [=] {
-            CellValue<TinyVector<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-          }();
-
-          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<3>{x[0], x[1], x[2]};
-            }
-          };
-
-          std::shared_ptr p_R3_u = [=] {
-            CellValue<TinyVector<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R1x1_u = [=] {
-            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R2x2_u = [=] {
-            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                            2 * x[1], -x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R3x3_u = [=] {
-            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                            2 * x[1],        -x[0],           x[0] - x[1],   //
-                                            3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_Vector3_u = [=] {
-            CellArray<double> uj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj_vector[cell_id][0] = 2 * x[0] + 1;
-                uj_vector[cell_id][1] = 1 - x[1] * x[2];
-                uj_vector[cell_id][2] = x[0] + x[2];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-          }();
-
-          SECTION("unary minus")
-          {
-            SECTION("- Vh -> Vh")
-            {
-              CHECK_SCALAR_VH_TO_VH(-, p_R_u);
-
-              CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
-
-              CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
-
-              CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
-            }
-          }
-        }
-      }
-    }
-
-    SECTION("3D")
-    {
-      constexpr size_t Dimension = 3;
-
-      using Rd = TinyVector<Dimension>;
-
-      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh = named_mesh.mesh();
-
-          CellValue<const Rd> xj = MeshDataManager::instance().getMeshData(*mesh).xj();
-
-          CellValue<double> u_R_values = [=] {
-            CellValue<double> build_values{mesh->connectivity()};
-            parallel_for(
-              build_values.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { build_values[cell_id] = 0.2 + std::cos(l2Norm(xj[cell_id])); });
-            return build_values;
-          }();
-
-          std::shared_ptr p_R_u = std::make_shared<const DiscreteFunctionP0<Dimension, double>>(mesh, u_R_values);
-
-          std::shared_ptr p_R1_u = [=] {
-            CellValue<TinyVector<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) { uj[cell_id][0] = 2 * xj[cell_id][0] + 1; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<1>>>(mesh, uj);
-          }();
-
-          constexpr auto to_2d = [&](const TinyVector<Dimension>& x) -> TinyVector<2> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<2>{x[0], 1 + x[0] * x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<2>{x[0], x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<2>{x[0], x[1] + x[2]};
-            }
-          };
-
-          std::shared_ptr p_R2_u = [=] {
-            CellValue<TinyVector<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<2>{2 * x[0] + 1, 1 - x[1]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<2>>>(mesh, uj);
-          }();
-
-          constexpr auto to_3d = [&](const TinyVector<Dimension>& x) -> TinyVector<3> {
-            if constexpr (Dimension == 1) {
-              return TinyVector<3>{x[0], 1 + x[0] * x[0], 2 - x[0]};
-            } else if constexpr (Dimension == 2) {
-              return TinyVector<3>{x[0], x[1], x[0] + x[1]};
-            } else if constexpr (Dimension == 3) {
-              return TinyVector<3>{x[0], x[1], x[2]};
-            }
-          };
-
-          std::shared_ptr p_R3_u = [=] {
-            CellValue<TinyVector<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj[cell_id]           = TinyVector<3>{2 * x[0] + 1, 1 - x[1] * x[2], x[0] + x[2]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyVector<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R1x1_u = [=] {
-            CellValue<TinyMatrix<1>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(),
-              PUGS_LAMBDA(const CellId cell_id) { uj[cell_id] = TinyMatrix<1>{2 * xj[cell_id][0] + 1}; });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<1>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R2x2_u = [=] {
-            CellValue<TinyMatrix<2>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<2> x = to_2d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<2>{2 * x[0] + 1, 1 - x[1],   //
-                                            2 * x[1], -x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<2>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_R3x3_u = [=] {
-            CellValue<TinyMatrix<3>> uj{mesh->connectivity()};
-            parallel_for(
-              uj.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-
-                uj[cell_id] = TinyMatrix<3>{2 * x[0] + 1,    1 - x[1],        3,             //
-                                            2 * x[1],        -x[0],           x[0] - x[1],   //
-                                            3 * x[2] - x[1], x[1] - 2 * x[2], x[2] - x[0]};
-              });
-
-            return std::make_shared<const DiscreteFunctionP0<Dimension, TinyMatrix<3>>>(mesh, uj);
-          }();
-
-          std::shared_ptr p_Vector3_u = [=] {
-            CellArray<double> uj_vector{mesh->connectivity(), 3};
-            parallel_for(
-              uj_vector.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
-                const TinyVector<3> x = to_3d(xj[cell_id]);
-                uj_vector[cell_id][0] = 2 * x[0] + 1;
-                uj_vector[cell_id][1] = 1 - x[1] * x[2];
-                uj_vector[cell_id][2] = x[0] + x[2];
-              });
-
-            return std::make_shared<const DiscreteFunctionP0Vector<Dimension, double>>(mesh, uj_vector);
-          }();
-
-          SECTION("unary minus")
-          {
-            SECTION("- Vh -> Vh")
-            {
-              CHECK_SCALAR_VH_TO_VH(-, p_R_u);
-
-              CHECK_SCALAR_VH_TO_VH(-, p_R1_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R2_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R3_u);
-
-              CHECK_SCALAR_VH_TO_VH(-, p_R1x1_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R2x2_u);
-              CHECK_SCALAR_VH_TO_VH(-, p_R3x3_u);
-
-              CHECK_VECTOR_VH_TO_VH(-, p_Vector3_u);
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-#ifdef __clang__
-#pragma clang optimize on
-#endif   // __clang__
diff --git a/tests/test_EmbeddedIDiscreteFunctionUtils.cpp b/tests/test_EmbeddedIDiscreteFunctionUtils.cpp
deleted file mode 100644
index 476103c42897ca8f86f2a5515f110e87b2ac0d9c..0000000000000000000000000000000000000000
--- a/tests/test_EmbeddedIDiscreteFunctionUtils.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/matchers/catch_matchers_all.hpp>
-
-#include <language/utils/EmbeddedIDiscreteFunctionUtils.hpp>
-#include <scheme/DiscreteFunctionP0.hpp>
-#include <scheme/DiscreteFunctionP0Vector.hpp>
-
-#include <MeshDataBaseForTests.hpp>
-
-// clazy:excludeall=non-pod-global-static
-
-TEST_CASE("EmbeddedIDiscreteFunctionUtils", "[language]")
-{
-  using R1 = TinyVector<1, double>;
-  using R2 = TinyVector<2, double>;
-  using R3 = TinyVector<3, double>;
-
-  using R1x1 = TinyMatrix<1, 1, double>;
-  using R2x2 = TinyMatrix<2, 2, double>;
-  using R3x3 = TinyMatrix<3, 3, double>;
-
-  SECTION("operand type name")
-  {
-    SECTION("basic types")
-    {
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(double{1}) == "R");
-      REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(std::make_shared<double>(1)) == "R");
-    }
-
-    SECTION("discrete P0 function")
-    {
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh_1d = named_mesh.mesh();
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, double>{mesh_1d}) ==
-                  "Vh(P0:R)");
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1>{mesh_1d}) ==
-                  "Vh(P0:R^1)");
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2>{mesh_1d}) ==
-                  "Vh(P0:R^2)");
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3>{mesh_1d}) ==
-                  "Vh(P0:R^3)");
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R1x1>{mesh_1d}) ==
-                  "Vh(P0:R^1x1)");
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R2x2>{mesh_1d}) ==
-                  "Vh(P0:R^2x2)");
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0<1, R3x3>{mesh_1d}) ==
-                  "Vh(P0:R^3x3)");
-        }
-      }
-    }
-
-    SECTION("discrete P0Vector function")
-    {
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh_1d = named_mesh.mesh();
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(DiscreteFunctionP0Vector<1, double>{mesh_1d, 2}) ==
-                  "Vh(P0Vector:R)");
-        }
-      }
-    }
-  }
-
-  SECTION("check if is same discretization")
-  {
-    SECTION("from shared_ptr")
-    {
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh_1d = named_mesh.mesh();
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(std::make_shared<DiscreteFunctionP0<1, double>>(
-                                                                         mesh_1d),
-                                                                       std::make_shared<DiscreteFunctionP0<1, double>>(
-                                                                         mesh_1d)));
-
-          REQUIRE(not EmbeddedIDiscreteFunctionUtils::
-                    isSameDiscretization(std::make_shared<DiscreteFunctionP0<1, double>>(mesh_1d),
-                                         std::make_shared<DiscreteFunctionP0Vector<1, double>>(mesh_1d, 1)));
-        }
-      }
-    }
-
-    SECTION("from value")
-    {
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh_1d = named_mesh.mesh();
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, double>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, double>{mesh_1d}));
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R1>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R1>{mesh_1d}));
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R2>{mesh_1d}));
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R3>{mesh_1d}));
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R1x1>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R1x1>{mesh_1d}));
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2x2>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R2x2>{mesh_1d}));
-
-          REQUIRE(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3x3>{mesh_1d},
-                                                                       DiscreteFunctionP0<1, R3x3>{mesh_1d}));
-
-          REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, double>{mesh_1d},
-                                                                           DiscreteFunctionP0<1, R1>{mesh_1d}));
-
-          REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R2>{mesh_1d},
-                                                                           DiscreteFunctionP0<1, R2x2>{mesh_1d}));
-
-          REQUIRE(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1, R3x3>{mesh_1d},
-                                                                           DiscreteFunctionP0<1, R2x2>{mesh_1d}));
-        }
-      }
-    }
-
-    SECTION("invalid data type")
-    {
-      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
-
-      for (const auto& named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
-        {
-          auto mesh_1d = named_mesh.mesh();
-
-          REQUIRE_THROWS_WITH(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(DiscreteFunctionP0<1,
-                                                                                                      int64_t>{mesh_1d},
-                                                                                   DiscreteFunctionP0<1, int64_t>{
-                                                                                     mesh_1d}),
-                              "unexpected error: invalid data type Vh(P0:Z)");
-        }
-      }
-    }
-  }
-
-#ifndef NDEBUG
-  SECTION("errors")
-  {
-    REQUIRE_THROWS_WITH(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(std::shared_ptr<double>()),
-                        "dangling shared_ptr");
-  }
-
-#endif   // NDEBUG
-}