diff --git a/CMakeLists.txt b/CMakeLists.txt
index fb52f0b84b67d28f2af26fc316e5a1c904faa28a..c95457bc6c19b6467eedabcb97f9d332d5442d1d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -638,7 +638,9 @@ message(" kokkos devices: ${PUGS_BUILD_KOKKOS_DEVICES}")
 if (MPI_FOUND)
   message(" MPI: ${MPI_CXX_LIBRARY_VERSION_STRING}")
 else()
-  if(NOT PARMETIS_LIBRARIES)
+  if (PUGS_ENABLE_MPI MATCHES "^OFF$")
+    message(" MPI: deactivated!")
+  elseif(NOT PARMETIS_LIBRARIES)
     message(" MPI: deactivated: ParMETIS cannot be found!")
   else()
     if (PUGS_ENABLE_MPI MATCHES "^(AUTO|ON)$")
@@ -669,6 +671,8 @@ else()
   endif()
 endif()
 
+message("----------- utilities ----------")
+
 if(CLANG_FORMAT)
   message(" clang-format: ${CLANG_FORMAT}")
 else()
@@ -693,5 +697,41 @@ else()
   message(" emacs: not found!")
 endif()
 
+if (GNUPLOT_FOUND)
+  message(" gnuplot: ${GNUPLOT}")
+else()
+  message(" gnuplot: not found!")
+endif()
+
+if (PYGMENTIZE)
+  message(" pygmentize: ${PYGMENTIZE}")
+else()
+  message(" pygmentize: not found!")
+endif()
+
+if (LATEX_PDFLATEX_FOUND)
+  message(" pdflatex: ${PDFLATEX_COMPILER}")
+else()
+  message(" pdflatex: not found!")
+endif()
+
+if (NOT EMACS OR NOT GNUPLOT_FOUND)
+  message(" ** Cannot build documentation: missing ")
+elseif(NOT LATEX_PDFLATEX_FOUND OR NOT PYGMENTIZE)
+  message(" ** Cannot build pdf documentation: missing")
+endif()
+if (NOT EMACS)
+  message("    - emacs")
+endif()
+if (NOT GNUPLOT_FOUND)
+  message("    - gnuplot")
+endif()
+if (NOT LATEX_PDFLATEX_FOUND)
+  message("    - pdflatex")
+endif()
+if (NOT PYGMENTIZE)
+  message("    - pygmentize")
+endif()
+
 message("================================")
 message("")
diff --git a/README.md b/README.md
index 4d1ed7f40f80eeb7a25b6b39871ae3c5402b6ee5..62d6b43773cb0164a29e8a3716a818c5ec219c7d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,263 @@
-The Pugs framework
-==================
+The `pugs` framework
+====================
 
-Pugs stands for Parallel Unstructured Grid Solver.
+`pugs` stands for Parallel Unstructured Grid Solvers.
+
+It aims at providing a collection of numerical methods and utilities
+that are assembled together by a user friendly language.
+
+# Building `pugs`
+
+## Requirements
+
+For the most basic build, `pugs` only requires
+- a C++-17 compiler.
+  - g++-10 or higher versions
+  - clang-10 or higher versions
+- `CMake` (at least 3.16)
+
+> **Warning**:<br>
+> Building `pugs` in its source directory is **forbidden**. Trying to
+> do so will result in a failure. However it generally leaves some
+> garbage files in the source directory, namely the `CMakeCache.txt`
+> and the `CMakeFiles` directory. `CMake` itself is not able to remove
+> them, to avoid the risk of compilation issues, one has to dot it
+> manually...
+
+In the build directory, one for instance launches
+```shell
+cmake path-to-pugs-source
+```
+and then
+```shell
+make -j nb_jobs
+```
+Specifying the compiler is done for instance as
+```shell
+CXX=g++ CC=gcc cmake path-to-pugs-source
+```
+
+When compilation is done, `pugs` binary is produce. Additionally, one
+can run unit tests by
+```shell
+make check
+```
+
+## Optional packages
+
+`pugs` can benefit from additional packages to gain more
+functionalities or development tools.
+
+### Optional numerical tools
+
+#### MPI support
+
+`pugs` integrates MPI support and should compile with any MPI-3
+implementation. However in order to dispatch meshes on MPI processes
+`pugs` requires `ParMETIS`. Without, MPI support will be automatically
+deactivated.
+
+The easiest way to enable MPI support on Debian-like systems is to run
+```shell
+apt install libparmetis-dev
+```
+
+#### `PETSc`
+
+`pugs` can use `PETSc` to solve linear systems. In `pugs` the `PETSc`
+support requires MPI support.
+
+To install `PETSc` on Debian-like systems
+```shell
+apt install petsc-dev
+```
+
+#### `SLEPc`
+
+`SLEPc` is an eigenvalue problem solver based on `PETSc`. It requires
+`PETSc`.
+
+To install `SLEPc` on Debian-like systems
+```shell
+apt install slepc-dev
+```
+
+## Documentation
+
+### User documentation
+
+To build documentation one requires `emacs` and `gnuplot`,
+additionally since examples results are generated, the documentation
+can only be produced after the compilation of `pugs` itself.
+
+To install `emacs` on Debian-like systems
+```shell
+apt install emacs
+```
+To install `gnuplot` one can either use
+```shell
+apt install gnuplot-nox
+```
+or
+```shell
+apt install gnuplot-x11
+```
+> **Warning**:<br>
+> When building the documentation for the first time, a local `emacs`
+> configuration is generated. *This requires an internet connection.*
+
+These packages are enough to build the html documentation. To build
+the pdf documentation one requires a few more packages: `pdflatex`
+(actually a fresh texlive installation is probably necessary) and `pygmentize`
+
+On Debian-like systems these two packages are installed after
+```shell
+apt install texlive-full
+```
+
+Running the command
+```shell
+make doc
+```
+produces both the `html` and `pdf` documentation. Additional targets
+are
+- `userdoc`, `userdoc-pdf` and `userdoc-html`.
+
+> **Warning**:<br>
+> The documentation building **should not** be run in
+> parallel. Generation uses similar temporary files. A generation race
+> can produce corrupted documentations.
+
+### `doxygen` documentation
+
+## Development tools
+
+### `clang-format`
+
+If `clang-format` is found, a new compilation target is defined which
+allows to run
+
+```shell
+make clang-format
+```
+
+This allows to run `clang-format` on the entire code. **This should
+never be run** on non main development branches since `clang-format`
+versions may experience unpleasant compatibility issues.
+
+### Static analysis
+
+#### `scan-build`
+
+To install `scan-build`
+```shell
+apt install clang-tools
+```
+It is fairly simple to use. First launch
+```shell
+scan-build cmake path-to-pugs-source -DCMAKE_BUILD_TYPE=Debug
+```
+Observe that the code is built in debug mode to reduce false
+positives.
+
+Then compile `pugs`
+```shell
+scan-build make -j nb-jobs pugs
+```
+One can use any other generator such as `Ninja` for instance. Here we
+only build `pugs`'s binary, which is generally enough.
+
+At the end of the compilation, an hint such as
+```
+scan-build: Run 'scan-view /tmp/scan-build-XXXX-XX-XX-XXXXXX-XXXXX-X' to examine bug reports.
+```
+is produced. Running this command opens the report in the default
+browser.
+
+`scan-build` is a precious tool but may unfortunately produce false
+positives.
+
+#### `clang-tidy`
+
+We do not give details here about `clang-tidy`. It is a much more
+complex tool and should be used by more experienced developers since
+it requires fine tuning and produces a lot of false positive.
+
+#### `clazy`
+
+`clazy` is another great static-analysis tool. It checks constructions
+and gives some performances hints. It can be install by
+```shell
+apt install clazy
+```
+When `clazy` is installed a new target is defined and one just has to
+run
+```shell
+make clazy-standalone
+```
+
+> **Warning**:<br>
+> Since some generated sources files are required, one must first run
+> a classic compilation in the same build directory.
+
+This is a special homemade compilation procedure: specifying jobs has
+no effect. Each file is checked serially.
+
+## `CMake` building options
+
+In this section we give specific options that can be used to modify
+the way `pugs` is built.
+
+### Optimization options
+
+| Description | Variable                | Values                                  |
+|:------------|:------------------------|:---------------------------------------:|
+| Build type  | `CMAKE_BUILD_TYPE`      | `Release`(default), `Debug`, `Coverage` |
+| OpenMP      | `Kokkos_ENABLE_OPENMP`  | `ON`(default),  `OFF`                   |
+| Pthread     | `Kokkos_ENABLE_PTHREAD` | `ON`, `OFF`(default)                    |
+| Serial      | `Kokkos_ENABLE_SERIAL`  | `ON`, `OFF`(default)                    |
+
+- `Coverage` is a special compilation mode. It forces the run of unit
+  tests and produces coverage reports. See below for coverage
+  instructions.
+- OpenMP, PThread and Serial builds are mutually exclusive. OpenMP
+  support is automatically enabled as soon as the C++ compiler
+  supports it.
+
+### Additional packages support
+
+| Description     | Variable            | Values                       |
+|:----------------|:--------------------|:----------------------------:|
+| MPI support     | `PUGS_ENABLE_MPI`   | `AUTO`(default), `ON`, `OFF` |
+| `PETSc` support | `PUGS_ENABLE_PETSC` | `AUTO`(default), `ON`, `OFF` |
+| `SLEPc` support | `PUGS_ENABLE_SLEPC` | `AUTO`(default), `ON`, `OFF` |
+
+### Code coverage build
+
+To enable coverage more, one requires the following tools:
+- `lcov`
+- `gcov`
+- and `fastcov` when using `g++` as a compiler.
+
+To install these tools
+```shell
+apt install lcov gcov python3-pip
+```
+then to install `fastcov`, as the user do
+```shell
+pip3 install fastcov
+```
+
+Then to get coverage reports one can do
+```shell
+CXX=g++ CC=gcc cmake path-to-pugs-source -DCMAKE_BUILD_TYPE=Coverage
+```
+and
+```shell
+make -j nb-jobs
+```
+This produces a text report summary and a detailed html report is
+produced in the `coverage` directory.
+
+One can also use `clang` but the production of the report is much
+slower (does not use `fastcov`).
diff --git a/cmake/PugsDoc.cmake b/cmake/PugsDoc.cmake
index 7be934abae82d06b1e5dcf7265c7710521e02c8e..1f8701ccc20f6416b529908f3893d971e86c7452 100644
--- a/cmake/PugsDoc.cmake
+++ b/cmake/PugsDoc.cmake
@@ -6,10 +6,16 @@ find_program(EMACS emacs)
 # check for LaTeX
 find_package(LATEX COMPONENTS PDFLATEX)
 
+# check for pygmentize
+find_program(PYGMENTIZE pygmentize)
+
+# check for gnuplot
+find_package(Gnuplot)
+
 add_custom_target(userdoc)
 add_custom_target(doc DEPENDS userdoc)
 
-if (EMACS)
+if (EMACS AND GNUPLOT_FOUND)
 
   add_custom_command(
     OUTPUT "${PUGS_BINARY_DIR}/doc"
@@ -64,7 +70,7 @@ if (EMACS)
 
   add_dependencies(userdoc userdoc-html)
 
-  if (LATEX_FOUND)
+  if (LATEX_PDFLATEX_FOUND AND PYGMENTIZE)
 
     add_custom_command(
       OUTPUT "${PUGS_BINARY_DIR}/doc/userdoc.pdf"
@@ -92,7 +98,37 @@ if (EMACS)
 
     add_dependencies(userdoc userdoc-pdf)
 
+  else()
+    if (NOT LATEX_PDFLATEX_FOUND)
+      add_custom_target(userdoc-missing-latex
+	COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --no-newline "Cannot build pdf documentation: "
+	COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold "pdflatex missing")
+      add_dependencies(userdoc userdoc-missing-latex)
+    endif()
+
+    if (NOT PIGMENTIZE_FOUND)
+      add_custom_target(userdoc-missing-pygmentize
+	COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --no-newline "Cannot build pdf documentation: "
+	COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold "pygmentize missing")
+      add_dependencies(userdoc userdoc-missing-pygmentize)
+    endif()
+
   endif()
 
-  add_dependencies(doc userdoc)
+else()
+  if (NOT EMACS)
+    add_custom_target(userdoc-missing-emacs
+      COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --no-newline "Cannot build documentation: "
+      COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold "emacs missing")
+    add_dependencies(userdoc userdoc-missing-emacs)
+  endif()
+
+  if (NOT GNUPLOT_FOUND)
+    add_custom_target(userdoc-missing-gnuplot
+      COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --no-newline "Cannot build documentation: "
+      COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold "gnuplot missing")
+    add_dependencies(userdoc userdoc-missing-gnuplot)
+  endif()
 endif()
+
+add_dependencies(doc userdoc)
diff --git a/doc/userdoc.org b/doc/userdoc.org
index 6538215304ff3da89bd385e8b7918569490cc85e..ba7cd798a6a7991d6451c405a5b9c81dfa2c0362 100644
--- a/doc/userdoc.org
+++ b/doc/userdoc.org
@@ -1223,8 +1223,7 @@ are sorted by type of left hand side variable.
 Observe that for these small matrix types ($\mathbb{R}^{d\times d}$) the
 construction ~A *= B;~ where ~B~ is a matrix of the same type as ~A~ is not
 allowed. The main reason for that is that for $d>1$ this operation has
-no interest since it requires a temporary. One will see bellow that it
-is possible to write ~A = A*B;~ if needed.
+no interest since it requires a temporary. One will see below that it
 #+END_note
 
 - ~string~: the ~*=~ operator is not defined for left hand side string variables.
@@ -2588,6 +2587,61 @@ $\mathbb{R}^1$, $\mathbb{R}^2$ and $\mathbb{R}^3$.
 The output is
 #+RESULTS: dot-examples
 
+A set of ~det~ functions is defined to get the determinant of matrices
+of $\mathbb{R}^{1\times1}$, $\mathbb{R}^{2\times2}$ and
+$\mathbb{R}^{3\times3}$.
+#+NAME: det-examples
+#+BEGIN_SRC pugs :exports both :results output
+   import math;
+   cout << "det([[1.2]]) = " << det([[1.2]]) << "\n";
+   cout << "det([[1,2],[3,4]]) = " << det([[1,2],[3,4]]) << "\n";
+   cout << "det([[1,2,3],[4,5,6],[7,8,9]]) = "
+        << det([[1,2,3],[4,5,6],[7,8,9]]) << "\n";
+#+END_SRC
+The output is
+#+RESULTS: det-examples
+
+The ~trace~ functions compute the trace of matrices of
+$\mathbb{R}^{1\times1}$, $\mathbb{R}^{2\times2}$ and $\mathbb{R}^{3\times3}$.
+#+NAME: trace-examples
+#+BEGIN_SRC pugs :exports both :results output
+   import math;
+   cout << "trace([[1.2]]) = " << trace([[1.2]]) << "\n";
+   cout << "trace([[1,2],[3,4]]) = " << trace([[1,2],[3,4]]) << "\n";
+   cout << "trace([[1,2,3],[4,5,6],[7,8,9]]) = "
+        << trace([[1,2,3],[4,5,6],[7,8,9]]) << "\n";
+#+END_SRC
+The output is
+#+RESULTS: trace-examples
+
+Also, one can compute inverses of $\mathbb{R}^{1\times1}$,
+$\mathbb{R}^{2\times2}$ and $\mathbb{R}^{3\times3}$ matrices using the
+~inverse~ function set.
+#+NAME: inverse-examples
+#+BEGIN_SRC pugs :exports both :results output
+   import math;
+   cout << "inverse([[1.2]]) = " << inverse([[1.2]]) << "\n";
+   cout << "inverse([[1,2],[3,4]]) = " << inverse([[1,2],[3,4]]) << "\n";
+   cout << "inverse([[3,2,1],[5,6,4],[7,8,9]]) = "
+        << inverse([[3,2,1],[5,6,4],[7,8,9]]) << "\n";
+#+END_SRC
+The output is
+#+RESULTS: inverse-examples
+
+Transpose of matrices of $\mathbb{R}^{1\times1}$,
+$\mathbb{R}^{2\times2}$ and $\mathbb{R}^{3\times3}$ are obtained using the
+~transpose~ functions.
+#+NAME: transpose-examples
+#+BEGIN_SRC pugs :exports both :results output
+   import math;
+   cout << "transpose([[1.2]]) = " << transpose([[1.2]]) << "\n";
+   cout << "transpose([[1,2],[3,4]]) = " << transpose([[1,2],[3,4]]) << "\n";
+   cout << "transpose([[3,2,1],[5,6,4],[7,8,9]]) = "
+        << transpose([[3,2,1],[5,6,4],[7,8,9]]) << "\n";
+#+END_SRC
+The output is
+#+RESULTS: transpose-examples
+
 #+BEGIN_note
 Observe that the use of a proper rounding or truncation function is
 the right way to convert a real value to an integer one. Available
@@ -3196,11 +3250,12 @@ description.
 
 ****** ~Vh -> Vh~
 
-These functions are defined for $\mathbb{P}_0(\mathbb{R})$ data and the
-return value is also a $\mathbb{P}_0(\mathbb{R})$ function. The value
-is simply the application of the function to the cell values.
+The majority of these functions are defined for
+$\mathbb{P}_0(\mathbb{R})$ data and the return value is also a
+$\mathbb{P}_0(\mathbb{R})$ function. The value is simply the
+application of the function to the cell values.
 
-Here is the list of the functions
+Here is the list of these functions
 - ~abs: Vh -> Vh~
 - ~acos: Vh -> Vh~
 - ~acosh: Vh -> Vh~
@@ -3218,6 +3273,21 @@ Here is the list of the functions
 - ~tan: Vh -> Vh~
 - ~tanh: Vh -> Vh~
 
+A few functions are defined for $\mathbb{P}_0(\mathbb{R^{}}^{1\times1})$,
+$\mathbb{P}_0(\mathbb{R^{}}^{2\times2})$ and
+$\mathbb{P}_0(\mathbb{R^{}}^{3\times3})$ data and the return value is a
+$\mathbb{P}_0(\mathbb{R})$ function. The value is simply the
+application of the function to the cell values.
+- ~det: Vh -> Vh~
+- ~trace: Vh -> Vh~
+
+Also, functions are defined for $\mathbb{P}_0(\mathbb{R}^{d\times d})$
+data and the return value is a $\mathbb{P}_0(\mathbb{R}^{d\times d})$
+function, with $d\in\{1,2,3\}$. The value is simply the application of
+the function to the cell values.
+- ~inverse: Vh -> Vh~
+- ~transpose: Vh -> Vh~
+
 ******  ~Vh*Vh -> Vh~
 
 These functions are defined for $\mathbb{P}_0(\mathbb{R})$ data and the
@@ -3406,6 +3476,19 @@ and the return value is an $\mathbb{R}^{3\times3}$ matrix.
   return value is $$\sum_{j\in\mathcal{J}} A_j.$$
 
 
+***** Utilities
+
+****** ~sum_of_Vh: Vh -> Vh~
+
+This function it computes the sum of all the components of
+$\vec{\mathbb{P}}_0(\mathbb{R})$ function and stores the result into a
+$\mathbb{P}_0(\mathbb{R})$.
+
+****** ~vectorize: (Vh) -> Vh~
+
+This function creates a $\vec{\mathbb{P}}_0(\mathbb{R})$ function using
+a tuple of $\mathbb{P}_0(\mathbb{R})$.
+
 ***** Interpolation and integration functions
 
 These functions are ways to define discrete functions from analytic
diff --git a/packages/kokkos/.github/workflows/continuous-integration-workflow.yml b/packages/kokkos/.github/workflows/continuous-integration-workflow.yml
index b2b4bfc3109d9aa10966b2866c472fa4d8457c5f..4ae42bc93ff649401818962ee7e4f16c2c468cb2 100644
--- a/packages/kokkos/.github/workflows/continuous-integration-workflow.yml
+++ b/packages/kokkos/.github/workflows/continuous-integration-workflow.yml
@@ -10,7 +10,7 @@ jobs:
     continue-on-error: true
     strategy:
       matrix:
-        distro: ['fedora:latest', 'fedora:rawhide', 'ubuntu:latest']
+        distro: ['fedora:latest', 'ubuntu:latest']
         cxx: ['g++', 'clang++']
         cmake_build_type: ['Release', 'Debug']
         backend: ['OPENMP']
diff --git a/packages/kokkos/.gitrepo b/packages/kokkos/.gitrepo
index 9b53d527695cc0db4672b75762f449344c305baa..4c3eed6dba9d2682fd85d2185169ac1f5c8e17a6 100644
--- a/packages/kokkos/.gitrepo
+++ b/packages/kokkos/.gitrepo
@@ -6,7 +6,7 @@
 [subrepo]
 	remote = git@github.com:kokkos/kokkos.git
 	branch = master
-	commit = 61d7db55fceac3318c987a291f77b844fd94c165
-	parent = 91d53e3cfb9a55832aae102ca677044a47f2515d
+	commit = 5ad609661e570ba6aa7716a26a91cb67d559f8a2
+	parent = db24c19be339723f3a10fffce3075fd72dccdfeb
 	method = merge
-	cmdver = 0.4.3
+	cmdver = 0.4.5
diff --git a/packages/kokkos/.jenkins b/packages/kokkos/.jenkins
index 09052840e6a7ea4115c1be9abb3f2aa2e644eabd..3025cb558eab0c10d6be328b199515807fbba03b 100644
--- a/packages/kokkos/.jenkins
+++ b/packages/kokkos/.jenkins
@@ -347,7 +347,7 @@ pipeline {
                         dockerfile {
                             filename 'Dockerfile.nvcc'
                             dir 'scripts/docker'
-                            additionalBuildArgs '--build-arg BASE=nvidia/cuda:11.0-devel --build-arg ADDITIONAL_PACKAGES="g++-8 gfortran clang" --build-arg CMAKE_VERSION=3.17.3'
+                            additionalBuildArgs '--build-arg BASE=nvidia/cuda:11.0.3-devel-ubuntu18.04 --build-arg ADDITIONAL_PACKAGES="g++-8 gfortran clang" --build-arg CMAKE_VERSION=3.17.3'
                             label 'nvidia-docker'
                             args '-v /tmp/ccache.kokkos:/tmp/ccache --env NVIDIA_VISIBLE_DEVICES=$NVIDIA_VISIBLE_DEVICES'
                         }
diff --git a/packages/kokkos/CHANGELOG.md b/packages/kokkos/CHANGELOG.md
index e81f2944519e1b39b31e1c9d7332b3aa6cb8d45e..bdbc75604bab5fbbfd436767531ab30371cc788b 100644
--- a/packages/kokkos/CHANGELOG.md
+++ b/packages/kokkos/CHANGELOG.md
@@ -1,5 +1,27 @@
 # Change Log
 
+## [3.7.01](https://github.com/kokkos/kokkos/tree/3.7.01) (2022-12-01)
+[Full Changelog](https://github.com/kokkos/kokkos/compare/3.7.00...3.7.01)
+
+### Bug Fixes:
+- Add fences to all sorting routines not taking an execution space instance argument [\#5547](https://github.com/kokkos/kokkos/pull/5547)
+- Fix repeated `team_reduce` without barrier [\#5552](https://github.com/kokkos/kokkos/pull/5552)
+- Fix memory spaces in `create_mirror_view` overloads using `view_alloc` [\#5521](https://github.com/kokkos/kokkos/pull/5521)
+- Allow `as_view_of_rank_n()` to be overloaded for "special" scalar types [\#5553](https://github.com/kokkos/kokkos/pull/5553)
+- Fix warning calling a `__host__` function from a `__host__ __device__` from `View:: as_view_of_rank_n` [\#5591](https://github.com/kokkos/kokkos/pull/5591)
+- OpenMPTarget: adding implementation to set device id. [\#5557](https://github.com/kokkos/kokkos/pull/5557)
+- Use `Kokkos::atomic_load` to Correct Race Condition Giving Rise to Seg Faulting Error in OpenMP tests [\#5559](https://github.com/kokkos/kokkos/pull/5559)
+- cmake: define `KOKKOS_ARCH_A64FX` [\#5561](https://github.com/kokkos/kokkos/pull/5561)
+- Only link against libatomic in gnu-make OpenMPTarget build [\#5565](https://github.com/kokkos/kokkos/pull/5565)
+- Fix static extents assignment for LayoutLeft/LayoutRight assignment [\#5566](https://github.com/kokkos/kokkos/pull/5566)
+- Do not add -cuda to the link line with NVHPC compiler when the CUDA backend is not actually enabled [\#5569](https://github.com/kokkos/kokkos/pull/5569)
+- Export the flags in `KOKKOS_AMDGPU_OPTIONS` when using Trilinos [\#5571](https://github.com/kokkos/kokkos/pull/5571)
+- Add support for detecting MPI local rank with MPICH and PMI [\#5570](https://github.com/kokkos/kokkos/pull/5570) [\#5582](https://github.com/kokkos/kokkos/pull/5582)
+- Remove listing of undefined TPL dependencies [\#5573](https://github.com/kokkos/kokkos/pull/5573)
+- ClockTic changed to 64 bit to fix overflow on Power [\#5592](https://github.com/kokkos/kokkos/pull/5592)
+- Fix incorrect offset in CUDA and HIP parallel scan for < 4 byte types [\#5607](https://github.com/kokkos/kokkos/pull/5607)
+- Fix initialization of Cuda lock arrays [\#5622](https://github.com/kokkos/kokkos/pull/5622)
+
 ## [3.7.00](https://github.com/kokkos/kokkos/tree/3.7.00) (2022-08-22)
 [Full Changelog](https://github.com/kokkos/kokkos/compare/3.6.01...3.7.00)
 
@@ -102,7 +124,6 @@
 - Deprecate command line arguments (other than `--help`) that are not prefixed with `kokkos-*` [\#5120](https://github.com/kokkos/kokkos/pull/5120)
 - Deprecate `--[kokkos-]numa` cmdline arg and `KOKKOS_NUMA` env var [\#5117](https://github.com/kokkos/kokkos/pull/5117)
 - Deprecate `--[kokkos-]threads` command line argument in favor of `--[kokkos-]num-threads` [\#5111](https://github.com/kokkos/kokkos/pull/5111)
-- Deprecate `Kokkos::common_view_alloc_prop` [\#5059](https://github.com/kokkos/kokkos/pull/5059)
 - Deprecate `Kokkos::is_reducer_type` [\#4957](https://github.com/kokkos/kokkos/pull/4957)
 - Deprecate `OffsetView` constructors taking `index_list_type` [\#4810](https://github.com/kokkos/kokkos/pull/4810)
 - Deprecate overloads of `Kokkos::sort` taking a parameter `bool always_use_kokkos_sort` [\#5382](https://github.com/kokkos/kokkos/issues/5382)
diff --git a/packages/kokkos/CMakeLists.txt b/packages/kokkos/CMakeLists.txt
index a05bfcdb94d53e0a7d453d62909e9a5686f6cc41..7b78f29d7340499aff394302911e59c5ef120d52 100644
--- a/packages/kokkos/CMakeLists.txt
+++ b/packages/kokkos/CMakeLists.txt
@@ -129,7 +129,7 @@ ENDIF()
 
 set(Kokkos_VERSION_MAJOR 3)
 set(Kokkos_VERSION_MINOR 7)
-set(Kokkos_VERSION_PATCH 00)
+set(Kokkos_VERSION_PATCH 01)
 set(Kokkos_VERSION "${Kokkos_VERSION_MAJOR}.${Kokkos_VERSION_MINOR}.${Kokkos_VERSION_PATCH}")
 math(EXPR KOKKOS_VERSION "${Kokkos_VERSION_MAJOR} * 10000 + ${Kokkos_VERSION_MINOR} * 100 + ${Kokkos_VERSION_PATCH}")
 
@@ -152,6 +152,7 @@ ENDIF()
 # but scoping issues can make it difficult
 GLOBAL_SET(KOKKOS_COMPILE_OPTIONS)
 GLOBAL_SET(KOKKOS_LINK_OPTIONS)
+GLOBAL_SET(KOKKOS_AMDGPU_OPTIONS)
 GLOBAL_SET(KOKKOS_CUDA_OPTIONS)
 GLOBAL_SET(KOKKOS_CUDAFE_OPTIONS)
 GLOBAL_SET(KOKKOS_XCOMPILER_OPTIONS)
@@ -228,6 +229,9 @@ IF (KOKKOS_HAS_TRILINOS)
   # we have to match the annoying behavior, also we have to preserve quotes
   # which needs another workaround.
   SET(KOKKOS_COMPILE_OPTIONS_TMP)
+  IF (KOKKOS_ENABLE_HIP)
+    LIST(APPEND KOKKOS_COMPILE_OPTIONS ${KOKKOS_AMDGPU_OPTIONS})
+  ENDIF()
   FOREACH(OPTION ${KOKKOS_COMPILE_OPTIONS})
     STRING(FIND "${OPTION}" " " OPTION_HAS_WHITESPACE)
     IF(OPTION_HAS_WHITESPACE EQUAL -1)
diff --git a/packages/kokkos/Makefile.kokkos b/packages/kokkos/Makefile.kokkos
index d493abbf1421973a973e93775d90ef83e502e2cd..2e32c9d53893bf552381eb618db37acaf8156822 100644
--- a/packages/kokkos/Makefile.kokkos
+++ b/packages/kokkos/Makefile.kokkos
@@ -2,7 +2,7 @@
 
 KOKKOS_VERSION_MAJOR = 3
 KOKKOS_VERSION_MINOR = 7
-KOKKOS_VERSION_PATCH = 00
+KOKKOS_VERSION_PATCH = 01
 KOKKOS_VERSION = $(shell echo $(KOKKOS_VERSION_MAJOR)*10000+$(KOKKOS_VERSION_MINOR)*100+$(KOKKOS_VERSION_PATCH) | bc)
 
 # Options: Cuda,HIP,SYCL,OpenMPTarget,OpenMP,Threads,Serial
@@ -495,10 +495,6 @@ KOKKOS_LINK_FLAGS =
 KOKKOS_SRC =
 KOKKOS_HEADERS =
 
-#ifeq ($(KOKKOS_INTERNAL_COMPILER_GCC), 1)
-  KOKKOS_LIBS += -latomic
-#endif
-
 # Generating the KokkosCore_config.h file.
 
 KOKKOS_INTERNAL_CONFIG_TMP=KokkosCore_config.tmp
@@ -540,6 +536,7 @@ ifeq ($(KOKKOS_INTERNAL_USE_SYCL), 1)
 endif
 
 ifeq ($(KOKKOS_INTERNAL_USE_OPENMPTARGET), 1)
+  KOKKOS_LIBS += -latomic
   tmp := $(call kokkos_append_header,'$H''define KOKKOS_ENABLE_OPENMPTARGET')
   ifeq ($(KOKKOS_INTERNAL_COMPILER_GCC), 1)
   tmp := $(call kokkos_append_header,"$H""define KOKKOS_WORKAROUND_OPENMPTARGET_GCC")
diff --git a/packages/kokkos/algorithms/cmake/Dependencies.cmake b/packages/kokkos/algorithms/cmake/Dependencies.cmake
index 1b413106817cc6adf18dc94189203a27e641c6d5..c36b62523fadb628e970b6eccf57a9caaa317f1e 100644
--- a/packages/kokkos/algorithms/cmake/Dependencies.cmake
+++ b/packages/kokkos/algorithms/cmake/Dependencies.cmake
@@ -1,5 +1,5 @@
 TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
   LIB_REQUIRED_PACKAGES KokkosCore KokkosContainers
-  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC HPX
+  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC
   TEST_OPTIONAL_TPLS CUSPARSE
   )
diff --git a/packages/kokkos/algorithms/src/Kokkos_Sort.hpp b/packages/kokkos/algorithms/src/Kokkos_Sort.hpp
index ad0c2d47b6d20d2022b5c60e81e7268b53d47f13..c7be70e09a48eff3fb2bf3a7cb89be1cf1fe6664 100644
--- a/packages/kokkos/algorithms/src/Kokkos_Sort.hpp
+++ b/packages/kokkos/algorithms/src/Kokkos_Sort.hpp
@@ -265,8 +265,8 @@ class BinSort {
   //----------------------------------------
   // Create the permutation vector, the bin_offset array and the bin_count
   // array. Can be called again if keys changed
-  template <class ExecutionSpace = exec_space>
-  void create_permute_vector(const ExecutionSpace& exec = exec_space{}) {
+  template <class ExecutionSpace>
+  void create_permute_vector(const ExecutionSpace& exec) {
     static_assert(
         Kokkos::SpaceAccessibility<ExecutionSpace,
                                    typename Space::memory_space>::accessible,
@@ -297,6 +297,15 @@ class BinSort {
           *this);
   }
 
+  // Create the permutation vector, the bin_offset array and the bin_count
+  // array. Can be called again if keys changed
+  void create_permute_vector() {
+    Kokkos::fence("Kokkos::Binsort::create_permute_vector: before");
+    exec_space e{};
+    create_permute_vector(e);
+    e.fence("Kokkos::Binsort::create_permute_vector: after");
+  }
+
   // Sort a subset of a view with respect to the first dimension using the
   // permutation array
   template <class ExecutionSpace, class ValuesViewType>
@@ -372,9 +381,10 @@ class BinSort {
   template <class ValuesViewType>
   void sort(ValuesViewType const& values, int values_range_begin,
             int values_range_end) const {
+    Kokkos::fence("Kokkos::Binsort::sort: before");
     exec_space exec;
     sort(exec, values, values_range_begin, values_range_end);
-    exec.fence("Kokkos::Sort: fence after sorting");
+    exec.fence("Kokkos::BinSort:sort: after");
   }
 
   template <class ExecutionSpace, class ValuesViewType>
@@ -641,9 +651,10 @@ std::enable_if_t<Kokkos::is_execution_space<ExecutionSpace>::value> sort(
 
 template <class ViewType>
 void sort(ViewType const& view) {
+  Kokkos::fence("Kokkos::sort: before");
   typename ViewType::execution_space exec;
   sort(exec, view);
-  exec.fence("Kokkos::Sort: fence after sorting");
+  exec.fence("Kokkos::sort: fence after sorting");
 }
 
 #ifdef KOKKOS_ENABLE_DEPRECATED_CODE_3
@@ -682,6 +693,7 @@ std::enable_if_t<Kokkos::is_execution_space<ExecutionSpace>::value> sort(
 
 template <class ViewType>
 void sort(ViewType view, size_t const begin, size_t const end) {
+  Kokkos::fence("Kokkos::sort: before");
   typename ViewType::execution_space exec;
   sort(exec, view, begin, end);
   exec.fence("Kokkos::Sort: fence after sorting");
diff --git a/packages/kokkos/cmake/KokkosCore_config.h.in b/packages/kokkos/cmake/KokkosCore_config.h.in
index 34807ac2b26228a4f0c10aa3ee5c4f7951ac235f..88ddc483786112ca0e70f418921d89ae102b9dde 100644
--- a/packages/kokkos/cmake/KokkosCore_config.h.in
+++ b/packages/kokkos/cmake/KokkosCore_config.h.in
@@ -66,6 +66,7 @@
 #cmakedefine KOKKOS_ARCH_ARMV8_THUNDERX
 #cmakedefine KOKKOS_ARCH_ARMV81
 #cmakedefine KOKKOS_ARCH_ARMV8_THUNDERX2
+#cmakedefine KOKKOS_ARCH_A64FX
 #cmakedefine KOKKOS_ARCH_AMD_AVX2
 #cmakedefine KOKKOS_ARCH_AVX
 #cmakedefine KOKKOS_ARCH_AVX2
diff --git a/packages/kokkos/cmake/kokkos_arch.cmake b/packages/kokkos/cmake/kokkos_arch.cmake
index d4c2cda651f3510bd66e9b8faff344ebf0cf666a..ef16aad047a96cfb31f3ae6c5ecaa93ff8175539 100644
--- a/packages/kokkos/cmake/kokkos_arch.cmake
+++ b/packages/kokkos/cmake/kokkos_arch.cmake
@@ -187,7 +187,9 @@ IF (KOKKOS_CXX_COMPILER_ID STREQUAL Clang)
 ELSEIF (KOKKOS_CXX_COMPILER_ID STREQUAL NVHPC)
   SET(CUDA_ARCH_FLAG "-gpu")
   GLOBAL_APPEND(KOKKOS_CUDA_OPTIONS -cuda)
-  GLOBAL_APPEND(KOKKOS_LINK_OPTIONS -cuda)
+  IF (KOKKOS_ENABLE_CUDA) # FIXME ideally unreachable when CUDA not enabled
+    GLOBAL_APPEND(KOKKOS_LINK_OPTIONS -cuda)
+  ENDIF()
 ELSEIF(KOKKOS_CXX_COMPILER_ID STREQUAL NVIDIA)
   SET(CUDA_ARCH_FLAG "-arch")
 ENDIF()
diff --git a/packages/kokkos/containers/cmake/Dependencies.cmake b/packages/kokkos/containers/cmake/Dependencies.cmake
index 5e29157369c9ab8cab935a1bfc4c6dad2fdd0296..1d71d8af341181f689a6a8bf63036b67584cb138 100644
--- a/packages/kokkos/containers/cmake/Dependencies.cmake
+++ b/packages/kokkos/containers/cmake/Dependencies.cmake
@@ -1,5 +1,5 @@
 TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
   LIB_REQUIRED_PACKAGES KokkosCore
-  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC HPX
+  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC
   TEST_OPTIONAL_TPLS CUSPARSE
   )
diff --git a/packages/kokkos/containers/src/Kokkos_DynRankView.hpp b/packages/kokkos/containers/src/Kokkos_DynRankView.hpp
index 442f0d8617524dc0c1459bf10110891e97b3a6b2..059ce8a610d26c9072b1cd15a282364855130d9a 100644
--- a/packages/kokkos/containers/src/Kokkos_DynRankView.hpp
+++ b/packages/kokkos/containers/src/Kokkos_DynRankView.hpp
@@ -1701,7 +1701,11 @@ namespace Impl {
    underlying memory, to facilitate implementation of deep_copy() and
    other routines that are defined on View */
 template <unsigned N, typename T, typename... Args>
-KOKKOS_FUNCTION auto as_view_of_rank_n(DynRankView<T, Args...> v) {
+KOKKOS_FUNCTION auto as_view_of_rank_n(
+    DynRankView<T, Args...> v,
+    typename std::enable_if<std::is_same<
+        typename ViewTraits<T, Args...>::specialize, void>::value>::type* =
+        nullptr) {
   if (v.rank() != N) {
     KOKKOS_IF_ON_HOST(
         const std::string message =
@@ -2114,9 +2118,10 @@ inline auto create_mirror(
 namespace Impl {
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    std::is_same<
-        typename DynRankView<T, P...>::memory_space,
-        typename DynRankView<T, P...>::HostMirror::memory_space>::value &&
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        std::is_same<
+            typename DynRankView<T, P...>::memory_space,
+            typename DynRankView<T, P...>::HostMirror::memory_space>::value &&
         std::is_same<
             typename DynRankView<T, P...>::data_type,
             typename DynRankView<T, P...>::HostMirror::data_type>::value,
@@ -2128,12 +2133,13 @@ create_mirror_view(const DynRankView<T, P...>& src,
 
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    !(std::is_same<
-          typename DynRankView<T, P...>::memory_space,
-          typename DynRankView<T, P...>::HostMirror::memory_space>::value &&
-      std::is_same<
-          typename DynRankView<T, P...>::data_type,
-          typename DynRankView<T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        !(std::is_same<
+              typename DynRankView<T, P...>::memory_space,
+              typename DynRankView<T, P...>::HostMirror::memory_space>::value &&
+          std::is_same<
+              typename DynRankView<T, P...>::data_type,
+              typename DynRankView<T, P...>::HostMirror::data_type>::value),
     typename DynRankView<T, P...>::HostMirror>
 create_mirror_view(
     const DynRankView<T, P...>& src,
@@ -2141,29 +2147,39 @@ create_mirror_view(
   return Kokkos::Impl::create_mirror(src, arg_prop);
 }
 
-template <class Space, class T, class... P, class... ViewCtorArgs>
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
 inline std::enable_if_t<
-    Kokkos::is_space<Space>::value &&
-        Impl::MirrorDRViewType<Space, T, P...>::is_same_memspace,
-    typename Impl::MirrorDRViewType<Space, T, P...>::view_type>
-create_mirror_view(const Space&, const Kokkos::DynRankView<T, P...>& src,
+    Kokkos::is_space<
+        typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space>::value &&
+        Impl::MirrorDRViewType<
+            typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space, T,
+            P...>::is_same_memspace,
+    typename Impl::MirrorDRViewType<
+        typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space, T,
+        P...>::view_type>
+create_mirror_view(const Kokkos::DynRankView<T, P...>& src,
                    const typename Impl::ViewCtorProp<ViewCtorArgs...>&) {
   return src;
 }
 
-template <class Space, class T, class... P, class... ViewCtorArgs>
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
 inline std::enable_if_t<
-    Kokkos::is_space<Space>::value &&
-        !Impl::MirrorDRViewType<Space, T, P...>::is_same_memspace,
-    typename Impl::MirrorDRViewType<Space, T, P...>::view_type>
+    Kokkos::is_space<
+        typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space>::value &&
+        !Impl::MirrorDRViewType<
+            typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space, T,
+            P...>::is_same_memspace,
+    typename Impl::MirrorDRViewType<
+        typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space, T,
+        P...>::view_type>
 create_mirror_view(
-    const Space&, const Kokkos::DynRankView<T, P...>& src,
+    const Kokkos::DynRankView<T, P...>& src,
     const typename Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
-  using MemorySpace = typename Space::memory_space;
-  using alloc_prop  = Impl::ViewCtorProp<ViewCtorArgs..., MemorySpace>;
-  alloc_prop prop_copy(arg_prop);
-
-  return Kokkos::Impl::create_mirror(src, prop_copy);
+  return Kokkos::Impl::create_mirror(src, arg_prop);
 }
 }  // namespace Impl
 
@@ -2224,9 +2240,10 @@ create_mirror_view(
 
 template <class Space, class T, class... P>
 inline auto create_mirror_view(Kokkos::Impl::WithoutInitializing_t wi,
-                               const Space& space,
+                               const Space&,
                                const Kokkos::DynRankView<T, P...>& src) {
-  return Impl::create_mirror_view(space, src, Kokkos::view_alloc(wi));
+  return Impl::create_mirror_view(
+      src, Kokkos::view_alloc(typename Space::memory_space{}, wi));
 }
 
 template <class T, class... P, class... ViewCtorArgs>
diff --git a/packages/kokkos/containers/src/Kokkos_DynamicView.hpp b/packages/kokkos/containers/src/Kokkos_DynamicView.hpp
index 015a75cb0b02c602db2a3bded219497c3414595c..a2b68064de13bd2b8988b6a2025bc3c9ef2c2685 100644
--- a/packages/kokkos/containers/src/Kokkos_DynamicView.hpp
+++ b/packages/kokkos/containers/src/Kokkos_DynamicView.hpp
@@ -710,7 +710,7 @@ template <class Space, class T, class... P>
 inline auto create_mirror(
     const Space&, const Kokkos::Experimental::DynamicView<T, P...>& src) {
   return Impl::create_mirror(
-      src, Impl::ViewCtorProp<>{typename Space::memory_space{}});
+      src, Kokkos::view_alloc(typename Space::memory_space{}));
 }
 
 template <class Space, class T, class... P>
@@ -729,48 +729,68 @@ inline auto create_mirror(
 }
 
 namespace Impl {
+
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    (std::is_same<
-         typename Kokkos::Experimental::DynamicView<T, P...>::memory_space,
-         typename Kokkos::Experimental::DynamicView<
-             T, P...>::HostMirror::memory_space>::value &&
-     std::is_same<
-         typename Kokkos::Experimental::DynamicView<T, P...>::data_type,
-         typename Kokkos::Experimental::DynamicView<
-             T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        (std::is_same<
+             typename Kokkos::Experimental::DynamicView<T, P...>::memory_space,
+             typename Kokkos::Experimental::DynamicView<
+                 T, P...>::HostMirror::memory_space>::value &&
+         std::is_same<
+             typename Kokkos::Experimental::DynamicView<T, P...>::data_type,
+             typename Kokkos::Experimental::DynamicView<
+                 T, P...>::HostMirror::data_type>::value),
     typename Kokkos::Experimental::DynamicView<T, P...>::HostMirror>
-create_mirror_view(
-    const typename Kokkos::Experimental::DynamicView<T, P...>& src,
-    const Impl::ViewCtorProp<ViewCtorArgs...>&) {
+create_mirror_view(const Kokkos::Experimental::DynamicView<T, P...>& src,
+                   const Impl::ViewCtorProp<ViewCtorArgs...>&) {
   return src;
 }
 
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    !(std::is_same<
-          typename Kokkos::Experimental::DynamicView<T, P...>::memory_space,
-          typename Kokkos::Experimental::DynamicView<
-              T, P...>::HostMirror::memory_space>::value &&
-      std::is_same<
-          typename Kokkos::Experimental::DynamicView<T, P...>::data_type,
-          typename Kokkos::Experimental::DynamicView<
-              T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        !(std::is_same<
+              typename Kokkos::Experimental::DynamicView<T, P...>::memory_space,
+              typename Kokkos::Experimental::DynamicView<
+                  T, P...>::HostMirror::memory_space>::value &&
+          std::is_same<
+              typename Kokkos::Experimental::DynamicView<T, P...>::data_type,
+              typename Kokkos::Experimental::DynamicView<
+                  T, P...>::HostMirror::data_type>::value),
     typename Kokkos::Experimental::DynamicView<T, P...>::HostMirror>
 create_mirror_view(const Kokkos::Experimental::DynamicView<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
   return Kokkos::create_mirror(arg_prop, src);
 }
 
-template <class Space, class T, class... P, class... ViewCtorArgs>
-inline std::enable_if_t<
-    Impl::MirrorDynamicViewType<Space, T, P...>::is_same_memspace,
-    typename Kokkos::Impl::MirrorDynamicViewType<Space, T, P...>::view_type>
-create_mirror_view(const Space&,
-                   const Kokkos::Experimental::DynamicView<T, P...>& src,
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+std::enable_if_t<Impl::MirrorDynamicViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::is_same_memspace,
+                 typename Impl::MirrorDynamicViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::view_type>
+create_mirror_view(const Kokkos::Experimental::DynamicView<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>&) {
   return src;
 }
+
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+std::enable_if_t<!Impl::MirrorDynamicViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::is_same_memspace,
+                 typename Impl::MirrorDynamicViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::view_type>
+create_mirror_view(const Kokkos::Experimental::DynamicView<T, P...>& src,
+                   const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
+  return Kokkos::Impl::create_mirror(src, arg_prop);
+}
 }  // namespace Impl
 
 // Create a mirror view in host space
@@ -790,8 +810,9 @@ inline auto create_mirror_view(
 // Create a mirror in a new space
 template <class Space, class T, class... P>
 inline auto create_mirror_view(
-    const Space& space, const Kokkos::Experimental::DynamicView<T, P...>& src) {
-  return Impl::create_mirror_view(space, src, Impl::ViewCtorProp<>{});
+    const Space&, const Kokkos::Experimental::DynamicView<T, P...>& src) {
+  return Impl::create_mirror_view(src,
+                                  view_alloc(typename Space::memory_space{}));
 }
 
 template <class Space, class T, class... P>
diff --git a/packages/kokkos/containers/src/Kokkos_OffsetView.hpp b/packages/kokkos/containers/src/Kokkos_OffsetView.hpp
index 0b54d1bdd952f33e433f17b05c56ef415ee286b4..5027763a0297a00c2b9dfb28734da628e763d7dc 100644
--- a/packages/kokkos/containers/src/Kokkos_OffsetView.hpp
+++ b/packages/kokkos/containers/src/Kokkos_OffsetView.hpp
@@ -1901,19 +1901,22 @@ struct MirrorOffsetType {
 
 namespace Impl {
 template <class T, class... P, class... ViewCtorArgs>
-inline typename Kokkos::Experimental::OffsetView<T, P...>::HostMirror
+inline std::enable_if_t<
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space,
+    typename Kokkos::Experimental::OffsetView<T, P...>::HostMirror>
 create_mirror(const Kokkos::Experimental::OffsetView<T, P...>& src,
               const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
   return typename Kokkos::Experimental::OffsetView<T, P...>::HostMirror(
       Kokkos::create_mirror(arg_prop, src.view()), src.begins());
 }
 
-template <class Space, class T, class... P, class... ViewCtorArgs>
-inline typename Kokkos::Impl::MirrorOffsetType<Space, T, P...>::view_type
-create_mirror(const Space&,
-              const Kokkos::Experimental::OffsetView<T, P...>& src,
-              const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+inline auto create_mirror(const Kokkos::Experimental::OffsetView<T, P...>& src,
+                          const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
   using alloc_prop_input = Impl::ViewCtorProp<ViewCtorArgs...>;
+  using Space = typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space;
 
   static_assert(
       !alloc_prop_input::has_label,
@@ -1923,10 +1926,6 @@ create_mirror(const Space&,
       !alloc_prop_input::has_pointer,
       "The view constructor arguments passed to Kokkos::create_mirror must "
       "not include a pointer!");
-  static_assert(
-      !alloc_prop_input::has_memory_space,
-      "The view constructor arguments passed to Kokkos::create_mirror must "
-      "not include a memory space instance!");
   static_assert(
       !alloc_prop_input::allow_padding,
       "The view constructor arguments passed to Kokkos::create_mirror must "
@@ -1962,15 +1961,17 @@ inline auto create_mirror(
 template <class Space, class T, class... P,
           typename Enable = std::enable_if_t<Kokkos::is_space<Space>::value>>
 inline auto create_mirror(
-    const Space& space, const Kokkos::Experimental::OffsetView<T, P...>& src) {
-  return Impl::create_mirror(space, src, Impl::ViewCtorProp<>{});
+    const Space&, const Kokkos::Experimental::OffsetView<T, P...>& src) {
+  return Impl::create_mirror(
+      src, Kokkos::view_alloc(typename Space::memory_space{}));
 }
 
 template <class Space, class T, class... P>
 typename Kokkos::Impl::MirrorOffsetType<Space, T, P...>::view_type
-create_mirror(Kokkos::Impl::WithoutInitializing_t wi, const Space& space,
+create_mirror(Kokkos::Impl::WithoutInitializing_t wi, const Space&,
               const Kokkos::Experimental::OffsetView<T, P...>& src) {
-  return Impl::create_mirror(space, src, Kokkos::view_alloc(wi));
+  return Impl::create_mirror(
+      src, Kokkos::view_alloc(typename Space::memory_space{}, wi));
 }
 
 template <class T, class... P, class... ViewCtorArgs>
@@ -1983,54 +1984,64 @@ inline auto create_mirror(
 namespace Impl {
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    (std::is_same<
-         typename Kokkos::Experimental::OffsetView<T, P...>::memory_space,
-         typename Kokkos::Experimental::OffsetView<
-             T, P...>::HostMirror::memory_space>::value &&
-     std::is_same<typename Kokkos::Experimental::OffsetView<T, P...>::data_type,
-                  typename Kokkos::Experimental::OffsetView<
-                      T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        (std::is_same<
+             typename Kokkos::Experimental::OffsetView<T, P...>::memory_space,
+             typename Kokkos::Experimental::OffsetView<
+                 T, P...>::HostMirror::memory_space>::value &&
+         std::is_same<
+             typename Kokkos::Experimental::OffsetView<T, P...>::data_type,
+             typename Kokkos::Experimental::OffsetView<
+                 T, P...>::HostMirror::data_type>::value),
     typename Kokkos::Experimental::OffsetView<T, P...>::HostMirror>
-create_mirror_view(
-    const typename Kokkos::Experimental::OffsetView<T, P...>& src,
-    const Impl::ViewCtorProp<ViewCtorArgs...>&) {
+create_mirror_view(const Kokkos::Experimental::OffsetView<T, P...>& src,
+                   const Impl::ViewCtorProp<ViewCtorArgs...>&) {
   return src;
 }
 
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    !(std::is_same<
-          typename Kokkos::Experimental::OffsetView<T, P...>::memory_space,
-          typename Kokkos::Experimental::OffsetView<
-              T, P...>::HostMirror::memory_space>::value &&
-      std::is_same<
-          typename Kokkos::Experimental::OffsetView<T, P...>::data_type,
-          typename Kokkos::Experimental::OffsetView<
-              T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        !(std::is_same<
+              typename Kokkos::Experimental::OffsetView<T, P...>::memory_space,
+              typename Kokkos::Experimental::OffsetView<
+                  T, P...>::HostMirror::memory_space>::value &&
+          std::is_same<
+              typename Kokkos::Experimental::OffsetView<T, P...>::data_type,
+              typename Kokkos::Experimental::OffsetView<
+                  T, P...>::HostMirror::data_type>::value),
     typename Kokkos::Experimental::OffsetView<T, P...>::HostMirror>
 create_mirror_view(const Kokkos::Experimental::OffsetView<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
   return Kokkos::create_mirror(arg_prop, src);
 }
 
-template <class Space, class T, class... P, class... ViewCtorArgs>
-inline std::enable_if_t<
-    Impl::MirrorOffsetViewType<Space, T, P...>::is_same_memspace,
-    Kokkos::Experimental::OffsetView<T, P...>>
-create_mirror_view(const Space&,
-                   const Kokkos::Experimental::OffsetView<T, P...>& src,
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+std::enable_if_t<Impl::MirrorOffsetViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::is_same_memspace,
+                 typename Impl::MirrorOffsetViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::view_type>
+create_mirror_view(const Kokkos::Experimental::OffsetView<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>&) {
   return src;
 }
 
-template <class Space, class T, class... P, class... ViewCtorArgs>
-std::enable_if_t<
-    !Impl::MirrorOffsetViewType<Space, T, P...>::is_same_memspace,
-    typename Kokkos::Impl::MirrorOffsetViewType<Space, T, P...>::view_type>
-create_mirror_view(const Space& space,
-                   const Kokkos::Experimental::OffsetView<T, P...>& src,
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+std::enable_if_t<!Impl::MirrorOffsetViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::is_same_memspace,
+                 typename Impl::MirrorOffsetViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::view_type>
+create_mirror_view(const Kokkos::Experimental::OffsetView<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
-  return create_mirror(space, src, arg_prop);
+  return Kokkos::Impl::create_mirror(src, arg_prop);
 }
 }  // namespace Impl
 
@@ -2052,15 +2063,17 @@ inline auto create_mirror_view(
 template <class Space, class T, class... P,
           typename Enable = std::enable_if_t<Kokkos::is_space<Space>::value>>
 inline auto create_mirror_view(
-    const Space& space, const Kokkos::Experimental::OffsetView<T, P...>& src) {
-  return Impl::create_mirror_view(space, src, Impl::ViewCtorProp<>{});
+    const Space&, const Kokkos::Experimental::OffsetView<T, P...>& src) {
+  return Impl::create_mirror_view(
+      src, Kokkos::view_alloc(typename Space::memory_space{}));
 }
 
 template <class Space, class T, class... P>
 inline auto create_mirror_view(
-    Kokkos::Impl::WithoutInitializing_t wi, const Space& space,
+    Kokkos::Impl::WithoutInitializing_t wi, const Space&,
     const Kokkos::Experimental::OffsetView<T, P...>& src) {
-  return Impl::create_mirror_view(space, src, Kokkos::view_alloc(wi));
+  return Impl::create_mirror_view(
+      src, Kokkos::view_alloc(typename Space::memory_space{}, wi));
 }
 
 template <class T, class... P, class... ViewCtorArgs>
diff --git a/packages/kokkos/containers/unit_tests/CMakeLists.txt b/packages/kokkos/containers/unit_tests/CMakeLists.txt
index f16572b60300562eabd01563ee2469cfa899bf65..261d9dcd4215d712ef7b6fca3b0ad08c9ecb0052 100644
--- a/packages/kokkos/containers/unit_tests/CMakeLists.txt
+++ b/packages/kokkos/containers/unit_tests/CMakeLists.txt
@@ -46,3 +46,13 @@ foreach(Tag Threads;Serial;OpenMP;HPX;Cuda;HIP;SYCL)
     KOKKOS_ADD_EXECUTABLE_AND_TEST(UnitTest_${Tag} SOURCES ${UnitTestSources})
   endif()
 endforeach()
+
+SET(COMPILE_ONLY_SOURCES
+  TestCreateMirror.cpp
+)
+KOKKOS_ADD_EXECUTABLE(
+  TestCompileOnly
+  SOURCES
+  TestCompileMain.cpp
+  ${COMPILE_ONLY_SOURCES}
+)
diff --git a/packages/kokkos/containers/unit_tests/TestCompileMain.cpp b/packages/kokkos/containers/unit_tests/TestCompileMain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..237c8ce181774d991a9dbdd8cacf1a5fb9f199f1
--- /dev/null
+++ b/packages/kokkos/containers/unit_tests/TestCompileMain.cpp
@@ -0,0 +1 @@
+int main() {}
diff --git a/packages/kokkos/containers/unit_tests/TestCreateMirror.cpp b/packages/kokkos/containers/unit_tests/TestCreateMirror.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0e43be4364154393b30cd349a563d7984a5ca2f0
--- /dev/null
+++ b/packages/kokkos/containers/unit_tests/TestCreateMirror.cpp
@@ -0,0 +1,179 @@
+/*
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 3.0
+//       Copyright (2020) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the Corporation nor the names of the
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Questions? Contact Christian R. Trott (crtrott@sandia.gov)
+//
+// ************************************************************************
+//@HEADER
+*/
+
+#include <Kokkos_Core.hpp>
+#include <Kokkos_DynamicView.hpp>
+#include <Kokkos_DynRankView.hpp>
+#include <Kokkos_OffsetView.hpp>
+
+template <typename TestView, typename MemorySpace>
+void check_memory_space(TestView, MemorySpace) {
+  static_assert(
+      std::is_same<typename TestView::memory_space, MemorySpace>::value, "");
+}
+
+template <class View>
+auto host_mirror_test_space(View) {
+  return std::conditional_t<
+      Kokkos::SpaceAccessibility<Kokkos::HostSpace,
+                                 typename View::memory_space>::accessible,
+      typename View::memory_space, Kokkos::HostSpace>{};
+}
+
+template <typename View>
+void test_create_mirror_properties(const View& view) {
+  using namespace Kokkos;
+  using DeviceMemorySpace = typename DefaultExecutionSpace::memory_space;
+
+  // clang-format off
+  
+  // create_mirror
+#ifndef KOKKOS_ENABLE_CXX14
+  // FIXME DynamicView: HostMirror is the same type
+  if constexpr (!is_dynamic_view<View>::value) {
+    check_memory_space(create_mirror(WithoutInitializing,                        view), host_mirror_test_space(view));
+    check_memory_space(create_mirror(                                            view), host_mirror_test_space(view));
+  }
+#endif
+  check_memory_space(create_mirror(WithoutInitializing, DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+  check_memory_space(create_mirror(                     DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+
+  // create_mirror_view
+#ifndef KOKKOS_ENABLE_CXX14
+  // FIXME DynamicView: HostMirror is the same type
+  if constexpr (!is_dynamic_view<View>::value) {
+    check_memory_space(create_mirror_view(WithoutInitializing,                        view), host_mirror_test_space(view));
+    check_memory_space(create_mirror_view(                                            view), host_mirror_test_space(view));
+  }
+#endif
+  check_memory_space(create_mirror_view(WithoutInitializing, DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+  check_memory_space(create_mirror_view(                     DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+
+  // create_mirror view_alloc
+#ifndef KOKKOS_ENABLE_CXX14
+  // FIXME DynamicView: HostMirror is the same type
+  if constexpr (!is_dynamic_view<View>::value) {
+    check_memory_space(create_mirror(view_alloc(WithoutInitializing),                    view), host_mirror_test_space(view));
+    check_memory_space(create_mirror(view_alloc(),                                       view), host_mirror_test_space(view));
+  }
+#endif
+  check_memory_space(create_mirror(view_alloc(WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror(view_alloc(                     DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view view_alloc
+#ifndef KOKKOS_ENABLE_CXX14
+  // FIXME DynamicView: HostMirror is the same type
+  if constexpr (!is_dynamic_view<View>::value) {
+    check_memory_space(create_mirror_view(view_alloc(WithoutInitializing),                    view), host_mirror_test_space(view));
+    check_memory_space(create_mirror_view(view_alloc(),                                       view), host_mirror_test_space(view));
+  }
+#endif
+  check_memory_space(create_mirror_view(view_alloc(WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror_view(view_alloc(                     DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror view_alloc + execution space
+#ifndef KOKKOS_ENABLE_CXX14
+  // FIXME DynamicView: HostMirror is the same type
+  if constexpr (!is_dynamic_view<View>::value) {
+    check_memory_space(create_mirror(view_alloc(DefaultExecutionSpace{}, WithoutInitializing),                      view), host_mirror_test_space(view));
+    check_memory_space(create_mirror(view_alloc(DefaultHostExecutionSpace{}),                                       view), host_mirror_test_space(view));
+  }
+#endif
+  check_memory_space(create_mirror(view_alloc(DefaultExecutionSpace{},   WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror(view_alloc(DefaultExecutionSpace{},                        DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view view_alloc + execution space
+#ifndef KOKKOS_ENABLE_CXX14
+  // FIXME DynamicView: HostMirror is the same type
+  if constexpr (!is_dynamic_view<View>::value) {
+    check_memory_space(create_mirror_view(view_alloc(DefaultExecutionSpace{}, WithoutInitializing),                      view), host_mirror_test_space(view));
+    check_memory_space(create_mirror_view(view_alloc(DefaultHostExecutionSpace{}),                                       view), host_mirror_test_space(view));
+  }
+#endif
+  check_memory_space(create_mirror_view(view_alloc(DefaultExecutionSpace{},   WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror_view(view_alloc(DefaultExecutionSpace{},                        DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view_and_copy
+  check_memory_space(create_mirror_view_and_copy(HostSpace{},         view), HostSpace{});
+  check_memory_space(create_mirror_view_and_copy(DeviceMemorySpace{}, view), DeviceMemorySpace{});
+
+  // create_mirror_view_and_copy view_alloc
+  check_memory_space(create_mirror_view_and_copy(view_alloc(HostSpace{}),         view), HostSpace{});
+  check_memory_space(create_mirror_view_and_copy(view_alloc(DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view_and_copy view_alloc + execution space
+  check_memory_space(create_mirror_view_and_copy(view_alloc(HostSpace{},         DefaultHostExecutionSpace{}),   view), HostSpace{});
+  check_memory_space(create_mirror_view_and_copy(view_alloc(DeviceMemorySpace{}, DefaultExecutionSpace{}),       view), DeviceMemorySpace{});
+
+  // clang-format on
+}
+
+void test_create_mirror_dynrankview() {
+  Kokkos::DynRankView<int, Kokkos::DefaultExecutionSpace> device_view(
+      "device view", 10);
+  Kokkos::DynRankView<int, Kokkos::HostSpace> host_view("host view", 10);
+
+  test_create_mirror_properties(device_view);
+  test_create_mirror_properties(host_view);
+}
+
+void test_reate_mirror_offsetview() {
+  Kokkos::Experimental::OffsetView<int*, Kokkos::DefaultExecutionSpace>
+      device_view("device view", {0, 10});
+  Kokkos::Experimental::OffsetView<int*, Kokkos::HostSpace> host_view(
+      "host view", {0, 10});
+
+  test_create_mirror_properties(device_view);
+  test_create_mirror_properties(host_view);
+}
+
+void test_create_mirror_dynamicview() {
+  Kokkos::Experimental::DynamicView<int*, Kokkos::DefaultExecutionSpace>
+      device_view("device view", 2, 10);
+  Kokkos::Experimental::DynamicView<int*, Kokkos::HostSpace> host_view(
+      "host view", 2, 10);
+
+  test_create_mirror_properties(device_view);
+  test_create_mirror_properties(host_view);
+}
diff --git a/packages/kokkos/core/cmake/Dependencies.cmake b/packages/kokkos/core/cmake/Dependencies.cmake
index cc901a4ede0c6b17fbb89bfa9edfaf6544d7b269..611c089b2e3feec2ec79228360f93c242fc055e2 100644
--- a/packages/kokkos/core/cmake/Dependencies.cmake
+++ b/packages/kokkos/core/cmake/Dependencies.cmake
@@ -1,5 +1,5 @@
 TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
-  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC DLlib HPX
+  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC DLlib
   TEST_OPTIONAL_TPLS CUSPARSE
   )
 
diff --git a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_KernelLaunch.hpp b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_KernelLaunch.hpp
index 88810b6fc2bbcf5c6fef3bd4a9de0a72fb30c5e8..b7a80ad84ff22b00d9666956cf5896b259d38b6a 100644
--- a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_KernelLaunch.hpp
+++ b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_KernelLaunch.hpp
@@ -636,7 +636,7 @@ struct CudaParallelLaunchImpl<
           DriverType, Kokkos::LaunchBounds<MaxThreadsPerBlock, MinBlocksPerSM>>(
           base_t::get_kernel_func(), prefer_shmem);
 
-      ensure_cuda_lock_arrays_on_device();
+      KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE();
 
       // Invoke the driver function on the device
       base_t::invoke_kernel(driver, grid, block, shmem, cuda_instance);
diff --git a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.cpp b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.cpp
index 3796534816a8bdb6bbeb1e517c6e54a04f2c82e1..84d4307cfd549f9567cb2bc5982a882543e19168 100644
--- a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.cpp
+++ b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.cpp
@@ -79,7 +79,8 @@ CudaLockArrays g_host_cuda_lock_arrays = {nullptr, 0};
 void initialize_host_cuda_lock_arrays() {
 #ifdef KOKKOS_ENABLE_IMPL_DESUL_ATOMICS
   desul::Impl::init_lock_arrays();
-  desul::ensure_cuda_lock_arrays_on_device();
+
+  DESUL_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE();
 #endif
   if (g_host_cuda_lock_arrays.atomic != nullptr) return;
   KOKKOS_IMPL_CUDA_SAFE_CALL(
@@ -88,7 +89,7 @@ void initialize_host_cuda_lock_arrays() {
   Impl::cuda_device_synchronize(
       "Kokkos::Impl::initialize_host_cuda_lock_arrays: Pre Init Lock Arrays");
   g_host_cuda_lock_arrays.n = Cuda::concurrency();
-  copy_cuda_lock_arrays_to_device();
+  KOKKOS_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE();
   init_lock_array_kernel_atomic<<<(CUDA_SPACE_ATOMIC_MASK + 1 + 255) / 256,
                                   256>>>();
   Impl::cuda_device_synchronize(
@@ -105,7 +106,7 @@ void finalize_host_cuda_lock_arrays() {
   g_host_cuda_lock_arrays.atomic = nullptr;
   g_host_cuda_lock_arrays.n      = 0;
 #ifdef KOKKOS_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE
-  copy_cuda_lock_arrays_to_device();
+  KOKKOS_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE();
 #endif
 }
 
diff --git a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.hpp b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.hpp
index 244f142f0d83550bf79d5cfb5288494e2629226f..bdb7723985e5a3c6c0451ada3d0b6b7303204089 100644
--- a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.hpp
+++ b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Locks.hpp
@@ -67,7 +67,7 @@ struct CudaLockArrays {
 
 /// \brief This global variable in Host space is the central definition
 ///        of these arrays.
-extern CudaLockArrays g_host_cuda_lock_arrays;
+extern Kokkos::Impl::CudaLockArrays g_host_cuda_lock_arrays;
 
 /// \brief After this call, the g_host_cuda_lock_arrays variable has
 ///        valid, initialized arrays.
@@ -105,12 +105,12 @@ namespace Impl {
 /// instances in other translation units, we must update this CUDA global
 /// variable based on the Host global variable prior to running any kernels
 /// that will use it.
-/// That is the purpose of the ensure_cuda_lock_arrays_on_device function.
+/// That is the purpose of the KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE macro.
 __device__
 #ifdef KOKKOS_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE
     __constant__ extern
 #endif
-    CudaLockArrays g_device_cuda_lock_arrays;
+    Kokkos::Impl::CudaLockArrays g_device_cuda_lock_arrays;
 
 #define CUDA_SPACE_ATOMIC_MASK 0x1FFFF
 
@@ -123,7 +123,9 @@ __device__ inline bool lock_address_cuda_space(void* ptr) {
   size_t offset = size_t(ptr);
   offset        = offset >> 2;
   offset        = offset & CUDA_SPACE_ATOMIC_MASK;
-  return (0 == atomicCAS(&g_device_cuda_lock_arrays.atomic[offset], 0, 1));
+  return (
+      0 ==
+      atomicCAS(&Kokkos::Impl::g_device_cuda_lock_arrays.atomic[offset], 0, 1));
 }
 
 /// \brief Release lock for the address
@@ -136,7 +138,7 @@ __device__ inline void unlock_address_cuda_space(void* ptr) {
   size_t offset = size_t(ptr);
   offset        = offset >> 2;
   offset        = offset & CUDA_SPACE_ATOMIC_MASK;
-  atomicExch(&g_device_cuda_lock_arrays.atomic[offset], 0);
+  atomicExch(&Kokkos::Impl::g_device_cuda_lock_arrays.atomic[offset], 0);
 }
 
 }  // namespace Impl
@@ -149,49 +151,45 @@ namespace {
 static int lock_array_copied = 0;
 inline int eliminate_warning_for_lock_array() { return lock_array_copied; }
 }  // namespace
+}  // namespace Impl
+}  // namespace Kokkos
 
-#ifdef KOKKOS_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE
-inline
-#else
-static
-#endif
-    void
-    copy_cuda_lock_arrays_to_device() {
-  if (lock_array_copied == 0) {
-    KOKKOS_IMPL_CUDA_SAFE_CALL(cudaMemcpyToSymbol(g_device_cuda_lock_arrays,
-                                                  &g_host_cuda_lock_arrays,
-                                                  sizeof(CudaLockArrays)));
+/* Dan Ibanez: it is critical that this code be a macro, so that it will
+   capture the right address for Kokkos::Impl::g_device_cuda_lock_arrays!
+   putting this in an inline function will NOT do the right thing! */
+#define KOKKOS_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE()                      \
+  {                                                                   \
+    if (::Kokkos::Impl::lock_array_copied == 0) {                     \
+      KOKKOS_IMPL_CUDA_SAFE_CALL(                                     \
+          cudaMemcpyToSymbol(Kokkos::Impl::g_device_cuda_lock_arrays, \
+                             &Kokkos::Impl::g_host_cuda_lock_arrays,  \
+                             sizeof(Kokkos::Impl::CudaLockArrays)));  \
+    }                                                                 \
+    lock_array_copied = 1;                                            \
   }
-  lock_array_copied = 1;
-}
 
 #ifndef KOKKOS_ENABLE_IMPL_DESUL_ATOMICS
 
 #ifdef KOKKOS_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE
-inline void ensure_cuda_lock_arrays_on_device() {}
+#define KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE()
 #else
-inline static void ensure_cuda_lock_arrays_on_device() {
-  copy_cuda_lock_arrays_to_device();
-}
+#define KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE() \
+  KOKKOS_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE()
 #endif
 
 #else
 
 #ifdef KOKKOS_ENABLE_CUDA_RELOCATABLE_DEVICE_CODE
-inline void ensure_cuda_lock_arrays_on_device() {}
+#define KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE()
 #else
 // Still Need COPY_CUDA_LOCK_ARRAYS for team scratch etc.
-inline static void ensure_cuda_lock_arrays_on_device() {
-  copy_cuda_lock_arrays_to_device();
-  desul::ensure_cuda_lock_arrays_on_device();
-}
+#define KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE() \
+  KOKKOS_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE()         \
+  DESUL_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE()
 #endif
 
 #endif /* defined( KOKKOS_ENABLE_IMPL_DESUL_ATOMICS ) */
 
-}  // namespace Impl
-}  // namespace Kokkos
-
 #endif /* defined( KOKKOS_ENABLE_CUDA ) */
 
 #endif /* #ifndef KOKKOS_CUDA_LOCKS_HPP */
diff --git a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Parallel_Range.hpp b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Parallel_Range.hpp
index 98733430063d98815ececb0ac9fcf83592a4e681..ac160f8fe268a42e04eebcee2639160e7edbd512 100644
--- a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Parallel_Range.hpp
+++ b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_Parallel_Range.hpp
@@ -465,8 +465,24 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
  public:
   using pointer_type   = typename Analysis::pointer_type;
   using reference_type = typename Analysis::reference_type;
+  using value_type     = typename Analysis::value_type;
   using functor_type   = FunctorType;
   using size_type      = Cuda::size_type;
+  // Conditionally set word_size_type to int16_t or int8_t if value_type is
+  // smaller than int32_t (Kokkos::Cuda::size_type)
+  // word_size_type is used to determine the word count, shared memory buffer
+  // size, and global memory buffer size before the scan is performed.
+  // Within the scan, the word count is recomputed based on word_size_type
+  // and when calculating indexes into the shared/global memory buffers for
+  // performing the scan, word_size_type is used again.
+  // For scalars > 4 bytes in size, indexing into shared/global memory relies
+  // on the block and grid dimensions to ensure that we index at the correct
+  // offset rather than at every 4 byte word; such that, when the join is
+  // performed, we have the correct data that was copied over in chunks of 4
+  // bytes.
+  using word_size_type = std::conditional_t<
+      sizeof(value_type) < sizeof(size_type),
+      std::conditional_t<sizeof(value_type) == 2, int16_t, int8_t>, size_type>;
 
  private:
   // Algorithmic constraints:
@@ -477,7 +493,7 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
 
   const FunctorType m_functor;
   const Policy m_policy;
-  size_type* m_scratch_space;
+  word_size_type* m_scratch_space;
   size_type* m_scratch_flags;
   size_type m_final;
 #ifdef KOKKOS_IMPL_DEBUG_CUDA_SERIAL_EXECUTION
@@ -501,12 +517,12 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
   __device__ inline void initial() const {
     typename Analysis::Reducer final_reducer(&m_functor);
 
-    const integral_nonzero_constant<size_type, Analysis::StaticValueSize /
-                                                   sizeof(size_type)>
-        word_count(Analysis::value_size(m_functor) / sizeof(size_type));
+    const integral_nonzero_constant<word_size_type, Analysis::StaticValueSize /
+                                                        sizeof(word_size_type)>
+        word_count(Analysis::value_size(m_functor) / sizeof(word_size_type));
 
-    size_type* const shared_value =
-        kokkos_impl_cuda_shared_memory<size_type>() +
+    word_size_type* const shared_value =
+        kokkos_impl_cuda_shared_memory<word_size_type>() +
         word_count.value * threadIdx.y;
 
     final_reducer.init(reinterpret_cast<pointer_type>(shared_value));
@@ -532,7 +548,7 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
     // gridDim.x
     cuda_single_inter_block_reduce_scan<true>(
         final_reducer, blockIdx.x, gridDim.x,
-        kokkos_impl_cuda_shared_memory<size_type>(), m_scratch_space,
+        kokkos_impl_cuda_shared_memory<word_size_type>(), m_scratch_space,
         m_scratch_flags);
   }
 
@@ -541,21 +557,22 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
   __device__ inline void final() const {
     typename Analysis::Reducer final_reducer(&m_functor);
 
-    const integral_nonzero_constant<size_type, Analysis::StaticValueSize /
-                                                   sizeof(size_type)>
-        word_count(Analysis::value_size(m_functor) / sizeof(size_type));
+    const integral_nonzero_constant<word_size_type, Analysis::StaticValueSize /
+                                                        sizeof(word_size_type)>
+        word_count(Analysis::value_size(m_functor) / sizeof(word_size_type));
 
     // Use shared memory as an exclusive scan: { 0 , value[0] , value[1] ,
     // value[2] , ... }
-    size_type* const shared_data = kokkos_impl_cuda_shared_memory<size_type>();
-    size_type* const shared_prefix =
+    word_size_type* const shared_data =
+        kokkos_impl_cuda_shared_memory<word_size_type>();
+    word_size_type* const shared_prefix =
         shared_data + word_count.value * threadIdx.y;
-    size_type* const shared_accum =
+    word_size_type* const shared_accum =
         shared_data + word_count.value * (blockDim.y + 1);
 
     // Starting value for this thread block is the previous block's total.
     if (blockIdx.x) {
-      size_type* const block_total =
+      word_size_type* const block_total =
           m_scratch_space + word_count.value * (blockIdx.x - 1);
       for (unsigned i = threadIdx.y; i < word_count.value; ++i) {
         shared_accum[i] = block_total[i];
@@ -602,7 +619,7 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
           typename Analysis::pointer_type(shared_data + word_count.value));
 
       {
-        size_type* const block_total =
+        word_size_type* const block_total =
             shared_data + word_count.value * blockDim.y;
         for (unsigned i = threadIdx.y; i < word_count.value; ++i) {
           shared_accum[i] = block_total[i];
@@ -690,8 +707,9 @@ class ParallelScan<FunctorType, Kokkos::RangePolicy<Traits...>, Kokkos::Cuda> {
       // How many block are really needed for this much work:
       const int grid_x = (nwork + work_per_block - 1) / work_per_block;
 
-      m_scratch_space = cuda_internal_scratch_space(
-          m_policy.space(), Analysis::value_size(m_functor) * grid_x);
+      m_scratch_space =
+          reinterpret_cast<word_size_type*>(cuda_internal_scratch_space(
+              m_policy.space(), Analysis::value_size(m_functor) * grid_x));
       m_scratch_flags =
           cuda_internal_scratch_flags(m_policy.space(), sizeof(size_type) * 1);
 
@@ -752,10 +770,26 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
                                                  Policy, FunctorType>;
 
  public:
+  using value_type     = typename Analysis::value_type;
   using pointer_type   = typename Analysis::pointer_type;
   using reference_type = typename Analysis::reference_type;
   using functor_type   = FunctorType;
   using size_type      = Cuda::size_type;
+  // Conditionally set word_size_type to int16_t or int8_t if value_type is
+  // smaller than int32_t (Kokkos::Cuda::size_type)
+  // word_size_type is used to determine the word count, shared memory buffer
+  // size, and global memory buffer size before the scan is performed.
+  // Within the scan, the word count is recomputed based on word_size_type
+  // and when calculating indexes into the shared/global memory buffers for
+  // performing the scan, word_size_type is used again.
+  // For scalars > 4 bytes in size, indexing into shared/global memory relies
+  // on the block and grid dimensions to ensure that we index at the correct
+  // offset rather than at every 4 byte word; such that, when the join is
+  // performed, we have the correct data that was copied over in chunks of 4
+  // bytes.
+  using word_size_type = std::conditional_t<
+      sizeof(value_type) < sizeof(size_type),
+      std::conditional_t<sizeof(value_type) == 2, int16_t, int8_t>, size_type>;
 
  private:
   // Algorithmic constraints:
@@ -766,7 +800,7 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
 
   const FunctorType m_functor;
   const Policy m_policy;
-  size_type* m_scratch_space;
+  word_size_type* m_scratch_space;
   size_type* m_scratch_flags;
   size_type m_final;
   ReturnType& m_returnvalue;
@@ -791,12 +825,12 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
   __device__ inline void initial() const {
     typename Analysis::Reducer final_reducer(&m_functor);
 
-    const integral_nonzero_constant<size_type, Analysis::StaticValueSize /
-                                                   sizeof(size_type)>
-        word_count(Analysis::value_size(m_functor) / sizeof(size_type));
+    const integral_nonzero_constant<word_size_type, Analysis::StaticValueSize /
+                                                        sizeof(word_size_type)>
+        word_count(Analysis::value_size(m_functor) / sizeof(word_size_type));
 
-    size_type* const shared_value =
-        kokkos_impl_cuda_shared_memory<size_type>() +
+    word_size_type* const shared_value =
+        kokkos_impl_cuda_shared_memory<word_size_type>() +
         word_count.value * threadIdx.y;
 
     final_reducer.init(reinterpret_cast<pointer_type>(shared_value));
@@ -822,7 +856,7 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
     // gridDim.x
     cuda_single_inter_block_reduce_scan<true>(
         final_reducer, blockIdx.x, gridDim.x,
-        kokkos_impl_cuda_shared_memory<size_type>(), m_scratch_space,
+        kokkos_impl_cuda_shared_memory<word_size_type>(), m_scratch_space,
         m_scratch_flags);
   }
 
@@ -831,21 +865,22 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
   __device__ inline void final() const {
     typename Analysis::Reducer final_reducer(&m_functor);
 
-    const integral_nonzero_constant<size_type, Analysis::StaticValueSize /
-                                                   sizeof(size_type)>
-        word_count(Analysis::value_size(m_functor) / sizeof(size_type));
+    const integral_nonzero_constant<word_size_type, Analysis::StaticValueSize /
+                                                        sizeof(word_size_type)>
+        word_count(Analysis::value_size(m_functor) / sizeof(word_size_type));
 
     // Use shared memory as an exclusive scan: { 0 , value[0] , value[1] ,
     // value[2] , ... }
-    size_type* const shared_data = kokkos_impl_cuda_shared_memory<size_type>();
-    size_type* const shared_prefix =
+    word_size_type* const shared_data =
+        kokkos_impl_cuda_shared_memory<word_size_type>();
+    word_size_type* const shared_prefix =
         shared_data + word_count.value * threadIdx.y;
-    size_type* const shared_accum =
+    word_size_type* const shared_accum =
         shared_data + word_count.value * (blockDim.y + 1);
 
     // Starting value for this thread block is the previous block's total.
     if (blockIdx.x) {
-      size_type* const block_total =
+      word_size_type* const block_total =
           m_scratch_space + word_count.value * (blockIdx.x - 1);
       for (unsigned i = threadIdx.y; i < word_count.value; ++i) {
         shared_accum[i] = block_total[i];
@@ -894,7 +929,7 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
           typename Analysis::pointer_type(shared_data + word_count.value));
 
       {
-        size_type* const block_total =
+        word_size_type* const block_total =
             shared_data + word_count.value * blockDim.y;
         for (unsigned i = threadIdx.y; i < word_count.value; ++i) {
           shared_accum[i] = block_total[i];
@@ -983,8 +1018,9 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
       // How many block are really needed for this much work:
       const int grid_x = (nwork + work_per_block - 1) / work_per_block;
 
-      m_scratch_space = cuda_internal_scratch_space(
-          m_policy.space(), Analysis::value_size(m_functor) * grid_x);
+      m_scratch_space =
+          reinterpret_cast<word_size_type*>(cuda_internal_scratch_space(
+              m_policy.space(), Analysis::value_size(m_functor) * grid_x));
       m_scratch_flags =
           cuda_internal_scratch_flags(m_policy.space(), sizeof(size_type) * 1);
 
@@ -1022,7 +1058,8 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
 #endif
         DeepCopy<HostSpace, CudaSpace, Cuda>(
             m_policy.space(), &m_returnvalue,
-            m_scratch_space + (grid_x - 1) * size / sizeof(int), size);
+            m_scratch_space + (grid_x - 1) * size / sizeof(word_size_type),
+            size);
     }
   }
 
diff --git a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_ReduceScan.hpp b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_ReduceScan.hpp
index 078315b65dd20d37adc6973f5ffff3a94836236b..34d4bef9fdaaf038a2fcdab257d043438a348887 100644
--- a/packages/kokkos/core/src/Cuda/Kokkos_Cuda_ReduceScan.hpp
+++ b/packages/kokkos/core/src/Cuda/Kokkos_Cuda_ReduceScan.hpp
@@ -116,6 +116,7 @@ __device__ inline void cuda_inter_warp_reduction(
   value = result[0];
   for (int i = 1; (i * step < max_active_thread) && i < STEP_WIDTH; i++)
     reducer.join(&value, &result[i]);
+  __syncthreads();
 }
 
 template <class ValueType, class ReducerType>
diff --git a/packages/kokkos/core/src/HIP/Kokkos_HIP_Parallel_Range.hpp b/packages/kokkos/core/src/HIP/Kokkos_HIP_Parallel_Range.hpp
index 5c871e0d615fc58bb01b93566bc0ab7a0ad892b2..dca1fb9073e6de4f5889e0ae61c0f5a5787254de 100644
--- a/packages/kokkos/core/src/HIP/Kokkos_HIP_Parallel_Range.hpp
+++ b/packages/kokkos/core/src/HIP/Kokkos_HIP_Parallel_Range.hpp
@@ -448,11 +448,27 @@ class ParallelScanHIPBase {
                                                  Policy, FunctorType>;
 
  public:
+  using value_type     = typename Analysis::value_type;
   using pointer_type   = typename Analysis::pointer_type;
   using reference_type = typename Analysis::reference_type;
   using functor_type   = FunctorType;
   using size_type      = Kokkos::Experimental::HIP::size_type;
   using index_type     = typename Policy::index_type;
+  // Conditionally set word_size_type to int16_t or int8_t if value_type is
+  // smaller than int32_t (Kokkos::HIP::size_type)
+  // word_size_type is used to determine the word count, shared memory buffer
+  // size, and global memory buffer size before the scan is performed.
+  // Within the scan, the word count is recomputed based on word_size_type
+  // and when calculating indexes into the shared/global memory buffers for
+  // performing the scan, word_size_type is used again.
+  // For scalars > 4 bytes in size, indexing into shared/global memory relies
+  // on the block and grid dimensions to ensure that we index at the correct
+  // offset rather than at every 4 byte word; such that, when the join is
+  // performed, we have the correct data that was copied over in chunks of 4
+  // bytes.
+  using word_size_type = std::conditional_t<
+      sizeof(value_type) < sizeof(size_type),
+      std::conditional_t<sizeof(value_type) == 2, int16_t, int8_t>, size_type>;
 
  protected:
   // Algorithmic constraints:
@@ -463,10 +479,10 @@ class ParallelScanHIPBase {
 
   const FunctorType m_functor;
   const Policy m_policy;
-  size_type* m_scratch_space = nullptr;
-  size_type* m_scratch_flags = nullptr;
-  size_type m_final          = false;
-  int m_grid_x               = 0;
+  word_size_type* m_scratch_space = nullptr;
+  size_type* m_scratch_flags      = nullptr;
+  size_type m_final               = false;
+  int m_grid_x                    = 0;
   // Only let one ParallelReduce/Scan modify the shared memory. The
   // constructor acquires the mutex which is released in the destructor.
   std::lock_guard<std::mutex> m_shared_memory_lock;
@@ -489,12 +505,12 @@ class ParallelScanHIPBase {
   __device__ inline void initial() const {
     typename Analysis::Reducer final_reducer(&m_functor);
 
-    const integral_nonzero_constant<size_type, Analysis::StaticValueSize /
-                                                   sizeof(size_type)>
-        word_count(Analysis::value_size(m_functor) / sizeof(size_type));
+    const integral_nonzero_constant<word_size_type, Analysis::StaticValueSize /
+                                                        sizeof(word_size_type)>
+        word_count(Analysis::value_size(m_functor) / sizeof(word_size_type));
 
     pointer_type const shared_value = reinterpret_cast<pointer_type>(
-        Kokkos::Experimental::kokkos_impl_hip_shared_memory<size_type>() +
+        Kokkos::Experimental::kokkos_impl_hip_shared_memory<word_size_type>() +
         word_count.value * threadIdx.y);
 
     final_reducer.init(shared_value);
@@ -518,7 +534,7 @@ class ParallelScanHIPBase {
     // gridDim.x
     hip_single_inter_block_reduce_scan<true>(
         final_reducer, blockIdx.x, gridDim.x,
-        Kokkos::Experimental::kokkos_impl_hip_shared_memory<size_type>(),
+        Kokkos::Experimental::kokkos_impl_hip_shared_memory<word_size_type>(),
         m_scratch_space, m_scratch_flags);
   }
 
@@ -527,22 +543,22 @@ class ParallelScanHIPBase {
   __device__ inline void final() const {
     typename Analysis::Reducer final_reducer(&m_functor);
 
-    const integral_nonzero_constant<size_type, Analysis::StaticValueSize /
-                                                   sizeof(size_type)>
-        word_count(Analysis::value_size(m_functor) / sizeof(size_type));
+    const integral_nonzero_constant<word_size_type, Analysis::StaticValueSize /
+                                                        sizeof(word_size_type)>
+        word_count(Analysis::value_size(m_functor) / sizeof(word_size_type));
 
     // Use shared memory as an exclusive scan: { 0 , value[0] , value[1] ,
     // value[2] , ... }
-    size_type* const shared_data =
-        Kokkos::Experimental::kokkos_impl_hip_shared_memory<size_type>();
-    size_type* const shared_prefix =
+    word_size_type* const shared_data =
+        Kokkos::Experimental::kokkos_impl_hip_shared_memory<word_size_type>();
+    word_size_type* const shared_prefix =
         shared_data + word_count.value * threadIdx.y;
-    size_type* const shared_accum =
+    word_size_type* const shared_accum =
         shared_data + word_count.value * (blockDim.y + 1);
 
     // Starting value for this thread block is the previous block's total.
     if (blockIdx.x) {
-      size_type* const block_total =
+      word_size_type* const block_total =
           m_scratch_space + word_count.value * (blockIdx.x - 1);
       for (unsigned i = threadIdx.y; i < word_count.value; ++i) {
         shared_accum[i] = block_total[i];
@@ -588,7 +604,7 @@ class ParallelScanHIPBase {
           typename Analysis::pointer_type(shared_data + word_count.value));
 
       {
-        size_type* const block_total =
+        word_size_type* const block_total =
             shared_data + word_count.value * blockDim.y;
         for (unsigned i = threadIdx.y; i < word_count.value; ++i) {
           shared_accum[i] = block_total[i];
@@ -647,8 +663,9 @@ class ParallelScanHIPBase {
       // How many block are really needed for this much work:
       m_grid_x = (nwork + work_per_block - 1) / work_per_block;
 
-      m_scratch_space = Kokkos::Experimental::Impl::hip_internal_scratch_space(
-          m_policy.space(), Analysis::value_size(m_functor) * m_grid_x);
+      m_scratch_space = reinterpret_cast<word_size_type*>(
+          Kokkos::Experimental::Impl::hip_internal_scratch_space(
+              m_policy.space(), Analysis::value_size(m_functor) * m_grid_x));
       m_scratch_flags = Kokkos::Experimental::Impl::hip_internal_scratch_flags(
           m_policy.space(), sizeof(size_type) * 1);
 
@@ -734,7 +751,8 @@ class ParallelScanWithTotal<FunctorType, Kokkos::RangePolicy<Traits...>,
       DeepCopy<HostSpace, Kokkos::Experimental::HIPSpace,
                Kokkos::Experimental::HIP>(
           Base::m_policy.space(), &m_returnvalue,
-          Base::m_scratch_space + (Base::m_grid_x - 1) * size / sizeof(int),
+          Base::m_scratch_space + (Base::m_grid_x - 1) * size /
+                                      sizeof(typename Base::word_size_type),
           size);
     }
   }
diff --git a/packages/kokkos/core/src/HIP/Kokkos_HIP_ReduceScan.hpp b/packages/kokkos/core/src/HIP/Kokkos_HIP_ReduceScan.hpp
index 1091ad5ceadf6a14b2f162c49d797c6c9564d390..9002f695894e987d34fd1437bbcc1f3da7e2a04c 100644
--- a/packages/kokkos/core/src/HIP/Kokkos_HIP_ReduceScan.hpp
+++ b/packages/kokkos/core/src/HIP/Kokkos_HIP_ReduceScan.hpp
@@ -225,11 +225,11 @@ struct HIPReductionsFunctor<FunctorType, false> {
     }
   }
 
+  template <typename SizeType>
   __device__ static inline bool scalar_inter_block_reduction(
       FunctorType const& functor,
       ::Kokkos::Experimental::HIP::size_type const block_count,
-      ::Kokkos::Experimental::HIP::size_type* const shared_data,
-      ::Kokkos::Experimental::HIP::size_type* const global_data,
+      SizeType* const shared_data, SizeType* const global_data,
       ::Kokkos::Experimental::HIP::size_type* const global_flags) {
     Scalar* const global_team_buffer_element =
         reinterpret_cast<Scalar*>(global_data);
@@ -411,16 +411,14 @@ __device__ void hip_intra_block_reduce_scan(
  *  Global reduce result is in the last threads' 'shared_data' location.
  */
 
-template <bool DoScan, class FunctorType>
+template <bool DoScan, typename FunctorType, typename SizeType>
 __device__ bool hip_single_inter_block_reduce_scan_impl(
     FunctorType const& functor,
     ::Kokkos::Experimental::HIP::size_type const block_id,
     ::Kokkos::Experimental::HIP::size_type const block_count,
-    ::Kokkos::Experimental::HIP::size_type* const shared_data,
-    ::Kokkos::Experimental::HIP::size_type* const global_data,
+    SizeType* const shared_data, SizeType* const global_data,
     ::Kokkos::Experimental::HIP::size_type* const global_flags) {
-  using size_type = ::Kokkos::Experimental::HIP::size_type;
-
+  using size_type    = SizeType;
   using value_type   = typename FunctorType::value_type;
   using pointer_type = typename FunctorType::pointer_type;
 
@@ -518,13 +516,12 @@ __device__ bool hip_single_inter_block_reduce_scan_impl(
   return is_last_block;
 }
 
-template <bool DoScan, typename FunctorType>
+template <bool DoScan, typename FunctorType, typename SizeType>
 __device__ bool hip_single_inter_block_reduce_scan(
     FunctorType const& functor,
     ::Kokkos::Experimental::HIP::size_type const block_id,
     ::Kokkos::Experimental::HIP::size_type const block_count,
-    ::Kokkos::Experimental::HIP::size_type* const shared_data,
-    ::Kokkos::Experimental::HIP::size_type* const global_data,
+    SizeType* const shared_data, SizeType* const global_data,
     ::Kokkos::Experimental::HIP::size_type* const global_flags) {
   // If we are doing a reduction and we don't do an array reduction, we use the
   // reduction-only path. Otherwise, we use the common path between reduction
diff --git a/packages/kokkos/core/src/HIP/Kokkos_HIP_Shuffle_Reduce.hpp b/packages/kokkos/core/src/HIP/Kokkos_HIP_Shuffle_Reduce.hpp
index eb85ed4709ed453f40856b05b07e76fd50e06430..d0bbc18da8a1c64839444f5abb8c4d507a01a30b 100644
--- a/packages/kokkos/core/src/HIP/Kokkos_HIP_Shuffle_Reduce.hpp
+++ b/packages/kokkos/core/src/HIP/Kokkos_HIP_Shuffle_Reduce.hpp
@@ -116,6 +116,7 @@ __device__ inline void hip_inter_warp_shuffle_reduction(
   value = result[0];
   for (int i = 1; (i * step < max_active_thread) && (i < step_width); ++i)
     reducer.join(&value, &result[i]);
+  __syncthreads();
 }
 
 template <typename ValueType, typename ReducerType>
diff --git a/packages/kokkos/core/src/Kokkos_CopyViews.hpp b/packages/kokkos/core/src/Kokkos_CopyViews.hpp
index 0a66ee9da71fdaf3bb2bf649fb1a7081e4651ea4..d859a5d8ae0f1908b35c4dc31aa9229cfd578bf6 100644
--- a/packages/kokkos/core/src/Kokkos_CopyViews.hpp
+++ b/packages/kokkos/core/src/Kokkos_CopyViews.hpp
@@ -3711,12 +3711,13 @@ namespace Impl {
 
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    (std::is_same<
-         typename Kokkos::View<T, P...>::memory_space,
-         typename Kokkos::View<T, P...>::HostMirror::memory_space>::value &&
-     std::is_same<
-         typename Kokkos::View<T, P...>::data_type,
-         typename Kokkos::View<T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        (std::is_same<
+             typename Kokkos::View<T, P...>::memory_space,
+             typename Kokkos::View<T, P...>::HostMirror::memory_space>::value &&
+         std::is_same<
+             typename Kokkos::View<T, P...>::data_type,
+             typename Kokkos::View<T, P...>::HostMirror::data_type>::value),
     typename Kokkos::View<T, P...>::HostMirror>
 create_mirror_view(const Kokkos::View<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>&) {
@@ -3725,12 +3726,13 @@ create_mirror_view(const Kokkos::View<T, P...>& src,
 
 template <class T, class... P, class... ViewCtorArgs>
 inline std::enable_if_t<
-    !(std::is_same<
-          typename Kokkos::View<T, P...>::memory_space,
-          typename Kokkos::View<T, P...>::HostMirror::memory_space>::value &&
-      std::is_same<
-          typename Kokkos::View<T, P...>::data_type,
-          typename Kokkos::View<T, P...>::HostMirror::data_type>::value),
+    !Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space &&
+        !(std::is_same<typename Kokkos::View<T, P...>::memory_space,
+                       typename Kokkos::View<
+                           T, P...>::HostMirror::memory_space>::value &&
+          std::is_same<
+              typename Kokkos::View<T, P...>::data_type,
+              typename Kokkos::View<T, P...>::HostMirror::data_type>::value),
     typename Kokkos::View<T, P...>::HostMirror>
 create_mirror_view(const Kokkos::View<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
@@ -3738,25 +3740,33 @@ create_mirror_view(const Kokkos::View<T, P...>& src,
 }
 
 // Create a mirror view in a new space (specialization for same space)
-template <class Space, class T, class... P, class... ViewCtorArgs>
-std::enable_if_t<Impl::MirrorViewType<Space, T, P...>::is_same_memspace,
-                 typename Impl::MirrorViewType<Space, T, P...>::view_type>
-create_mirror_view(const Space&, const Kokkos::View<T, P...>& src,
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+std::enable_if_t<Impl::MirrorViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::is_same_memspace,
+                 typename Impl::MirrorViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::view_type>
+create_mirror_view(const Kokkos::View<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>&) {
   return src;
 }
 
 // Create a mirror view in a new space (specialization for different space)
-template <class Space, class T, class... P, class... ViewCtorArgs>
-std::enable_if_t<!Impl::MirrorViewType<Space, T, P...>::is_same_memspace,
-                 typename Impl::MirrorViewType<Space, T, P...>::view_type>
-create_mirror_view(const Space&, const Kokkos::View<T, P...>& src,
+template <class T, class... P, class... ViewCtorArgs,
+          class = std::enable_if_t<
+              Impl::ViewCtorProp<ViewCtorArgs...>::has_memory_space>>
+std::enable_if_t<!Impl::MirrorViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::is_same_memspace,
+                 typename Impl::MirrorViewType<
+                     typename Impl::ViewCtorProp<ViewCtorArgs...>::memory_space,
+                     T, P...>::view_type>
+create_mirror_view(const Kokkos::View<T, P...>& src,
                    const Impl::ViewCtorProp<ViewCtorArgs...>& arg_prop) {
-  using MemorySpace = typename Space::memory_space;
-  using alloc_prop  = Impl::ViewCtorProp<ViewCtorArgs..., MemorySpace>;
-  alloc_prop prop_copy(arg_prop);
-
-  return Kokkos::Impl::create_mirror(src, prop_copy);
+  return Kokkos::Impl::create_mirror(src, arg_prop);
 }
 }  // namespace Impl
 
@@ -3815,9 +3825,10 @@ typename Impl::MirrorViewType<Space, T, P...>::view_type create_mirror_view(
 template <class Space, class T, class... P,
           typename Enable = std::enable_if_t<Kokkos::is_space<Space>::value>>
 typename Impl::MirrorViewType<Space, T, P...>::view_type create_mirror_view(
-    Kokkos::Impl::WithoutInitializing_t wi, Space const& space,
+    Kokkos::Impl::WithoutInitializing_t wi, Space const&,
     Kokkos::View<T, P...> const& v) {
-  return Impl::create_mirror_view(space, v, view_alloc(wi));
+  return Impl::create_mirror_view(
+      v, view_alloc(typename Space::memory_space{}, wi));
 }
 
 template <class T, class... P, class... ViewCtorArgs>
diff --git a/packages/kokkos/core/src/Kokkos_View.hpp b/packages/kokkos/core/src/Kokkos_View.hpp
index e92ed7d2e91395aef45292b3a3b3a4f5c9cd5cf7..f8dcfc869e195b93f2ed899b89f6f64be62c46e3 100644
--- a/packages/kokkos/core/src/Kokkos_View.hpp
+++ b/packages/kokkos/core/src/Kokkos_View.hpp
@@ -1754,7 +1754,10 @@ struct RankDataType<ValueType, 0> {
 };
 
 template <unsigned N, typename... Args>
-KOKKOS_FUNCTION std::enable_if_t<N == View<Args...>::Rank, View<Args...>>
+KOKKOS_FUNCTION std::enable_if_t<
+    N == View<Args...>::Rank &&
+        std::is_same<typename ViewTraits<Args...>::specialize, void>::value,
+    View<Args...>>
 as_view_of_rank_n(View<Args...> v) {
   return v;
 }
@@ -1762,13 +1765,13 @@ as_view_of_rank_n(View<Args...> v) {
 // Placeholder implementation to compile generic code for DynRankView; should
 // never be called
 template <unsigned N, typename T, typename... Args>
-std::enable_if_t<
-    N != View<T, Args...>::Rank,
+KOKKOS_FUNCTION std::enable_if_t<
+    N != View<T, Args...>::Rank &&
+        std::is_same<typename ViewTraits<T, Args...>::specialize, void>::value,
     View<typename RankDataType<typename View<T, Args...>::value_type, N>::type,
          Args...>>
 as_view_of_rank_n(View<T, Args...>) {
-  Kokkos::Impl::throw_runtime_exception(
-      "Trying to get at a View of the wrong rank");
+  Kokkos::abort("Trying to get at a View of the wrong rank");
   return {};
 }
 
diff --git a/packages/kokkos/core/src/Kokkos_WorkGraphPolicy.hpp b/packages/kokkos/core/src/Kokkos_WorkGraphPolicy.hpp
index fafd825df297123e100ccf008069f24e4a2cf1e5..129a489387a46297bdd5ce17d3ad7873cbc1c80b 100644
--- a/packages/kokkos/core/src/Kokkos_WorkGraphPolicy.hpp
+++ b/packages/kokkos/core/src/Kokkos_WorkGraphPolicy.hpp
@@ -101,8 +101,8 @@ class WorkGraphPolicy : public Kokkos::Impl::PolicyTraits<Properties...> {
   void push_work(const std::int32_t w) const noexcept {
     const std::int32_t N = m_graph.numRows();
 
-    std::int32_t volatile* const ready_queue = &m_queue[0];
-    std::int32_t volatile* const end_hint    = &m_queue[2 * N + 1];
+    std::int32_t* const ready_queue = &m_queue[0];
+    std::int32_t* const end_hint    = &m_queue[2 * N + 1];
 
     // Push work to end of queue
     const std::int32_t j = atomic_fetch_add(end_hint, 1);
@@ -134,14 +134,14 @@ class WorkGraphPolicy : public Kokkos::Impl::PolicyTraits<Properties...> {
   std::int32_t pop_work() const noexcept {
     const std::int32_t N = m_graph.numRows();
 
-    std::int32_t volatile* const ready_queue = &m_queue[0];
-    std::int32_t volatile* const begin_hint  = &m_queue[2 * N];
+    std::int32_t* const ready_queue = &m_queue[0];
+    std::int32_t* const begin_hint  = &m_queue[2 * N];
 
     // begin hint is guaranteed to be less than or equal to
     // actual begin location in the queue.
 
-    for (std::int32_t i = *begin_hint; i < N; ++i) {
-      const std::int32_t w = ready_queue[i];
+    for (std::int32_t i = Kokkos::atomic_load(begin_hint); i < N; ++i) {
+      const std::int32_t w = Kokkos::atomic_load(&ready_queue[i]);
 
       if (w == END_TOKEN) {
         return END_TOKEN;
@@ -169,7 +169,7 @@ class WorkGraphPolicy : public Kokkos::Impl::PolicyTraits<Properties...> {
 
     const std::int32_t N = m_graph.numRows();
 
-    std::int32_t volatile* const count_queue = &m_queue[N];
+    std::int32_t* const count_queue = &m_queue[N];
 
     const std::int32_t B = m_graph.row_map(w);
     const std::int32_t E = m_graph.row_map(w + 1);
@@ -199,7 +199,7 @@ class WorkGraphPolicy : public Kokkos::Impl::PolicyTraits<Properties...> {
 
   KOKKOS_INLINE_FUNCTION
   void operator()(const TagCount, int i) const noexcept {
-    std::int32_t volatile* const count_queue = &m_queue[m_graph.numRows()];
+    std::int32_t* const count_queue = &m_queue[m_graph.numRows()];
 
     atomic_increment(count_queue + m_graph.entries[i]);
   }
diff --git a/packages/kokkos/core/src/OpenMPTarget/Kokkos_OpenMPTarget_Instance.cpp b/packages/kokkos/core/src/OpenMPTarget/Kokkos_OpenMPTarget_Instance.cpp
index 51921765baf249a1f1dacc57221bc4f4a398c79d..a9bc085912356ec90bbef0c63688cd3f91d11a95 100644
--- a/packages/kokkos/core/src/OpenMPTarget/Kokkos_OpenMPTarget_Instance.cpp
+++ b/packages/kokkos/core/src/OpenMPTarget/Kokkos_OpenMPTarget_Instance.cpp
@@ -47,6 +47,7 @@
 #endif
 
 #include <Kokkos_Macros.hpp>
+#include <impl/Kokkos_DeviceManagement.hpp>
 
 #if defined(KOKKOS_ENABLE_OPENMPTARGET) && defined(_OPENMP)
 
@@ -164,7 +165,11 @@ void OpenMPTarget::impl_static_fence(const std::string& name) {
       name, Kokkos::Experimental::Impl::openmp_fence_is_static::yes);
 }
 
-void OpenMPTarget::impl_initialize(InitializationSettings const&) {
+void OpenMPTarget::impl_initialize(InitializationSettings const& settings) {
+  using Kokkos::Impl::get_gpu;
+  const int device_num = get_gpu(settings);
+  omp_set_default_device(device_num);
+
   Impl::OpenMPTargetInternal::impl_singleton()->impl_initialize();
 }
 void OpenMPTarget::impl_finalize() {
diff --git a/packages/kokkos/core/src/impl/Kokkos_ClockTic.hpp b/packages/kokkos/core/src/impl/Kokkos_ClockTic.hpp
index c1cb6a7d91b54b951f445131e43347eaaa33a027..ecece72cf958c937702dd18b4a22642e479c857c 100644
--- a/packages/kokkos/core/src/impl/Kokkos_ClockTic.hpp
+++ b/packages/kokkos/core/src/impl/Kokkos_ClockTic.hpp
@@ -110,10 +110,9 @@ KOKKOS_IMPL_HOST_FUNCTION inline uint64_t clock_tic_host() noexcept {
 
   return ((uint64_t)a) | (((uint64_t)d) << 32);
 
-#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || \
-    defined(__POWERPC__) || defined(__ppc__) || defined(__ppc64__)
+#elif defined(__powerpc64__) || defined(__ppc64__)
 
-  unsigned int cycles = 0;
+  unsigned long cycles = 0;
 
   asm volatile("mftb %0" : "=r"(cycles));
 
diff --git a/packages/kokkos/core/src/impl/Kokkos_Core.cpp b/packages/kokkos/core/src/impl/Kokkos_Core.cpp
index f624e7a14cb21b4a395898125536ec9b55bfeaae..a5bd0032374ffc5e0e73627e33aeb3fa7c1b788e 100644
--- a/packages/kokkos/core/src/impl/Kokkos_Core.cpp
+++ b/packages/kokkos/core/src/impl/Kokkos_Core.cpp
@@ -166,6 +166,8 @@ int get_device_count() {
 #elif defined(KOKKOS_ENABLE_OPENACC)
   return acc_get_num_devices(
       Kokkos::Experimental::Impl::OpenACC_Traits::dev_type);
+#elif defined(KOKKOS_ENABLE_OPENMPTARGET)
+  return omp_get_num_devices();
 #else
   Kokkos::abort("implementation bug");
   return -1;
@@ -426,11 +428,17 @@ int Kokkos::Impl::get_gpu(const InitializationSettings& settings) {
     Kokkos::abort("implementation bug");
   }
 
-  auto const* local_rank_str =
-      std::getenv("OMPI_COMM_WORLD_LOCAL_RANK");  // OpenMPI
-  if (!local_rank_str)
-    local_rank_str = std::getenv("MV2_COMM_WORLD_LOCAL_RANK");  // MVAPICH2
-  if (!local_rank_str) local_rank_str = std::getenv("SLURM_LOCALID");  // SLURM
+  char const* local_rank_str = nullptr;
+  for (char const* env_var : {
+           "OMPI_COMM_WORLD_LOCAL_RANK",  // OpenMPI
+           "MV2_COMM_WORLD_LOCAL_RANK",   // MVAPICH2
+           "MPI_LOCALRANKID",             // MPICH
+           "SLURM_LOCALID",               // SLURM
+           "PMI_LOCAL_RANK"               // PMI
+       }) {
+    local_rank_str = std::getenv(env_var);
+    if (local_rank_str) break;
+  }
 
   // use first GPU available for execution if unable to detect local MPI rank
   if (!local_rank_str) {
diff --git a/packages/kokkos/core/src/impl/Kokkos_ViewMapping.hpp b/packages/kokkos/core/src/impl/Kokkos_ViewMapping.hpp
index 738231677c600f6d928122269d848b1a2b51ac46..994dd0b2adf65d9a2440abeb5c4930b43a662d54 100644
--- a/packages/kokkos/core/src/impl/Kokkos_ViewMapping.hpp
+++ b/packages/kokkos/core/src/impl/Kokkos_ViewMapping.hpp
@@ -1128,9 +1128,8 @@ struct ViewOffset<
   KOKKOS_INLINE_FUNCTION constexpr ViewOffset(
       const ViewOffset<DimRHS, Kokkos::LayoutRight, void>& rhs)
       : m_dim(rhs.m_dim.N0, 0, 0, 0, 0, 0, 0, 0) {
-    static_assert((DimRHS::rank == 0 && dimension_type::rank == 0) ||
-                      (DimRHS::rank == 1 && dimension_type::rank == 1 &&
-                       dimension_type::rank_dynamic == 1),
+    static_assert(((DimRHS::rank == 0 && dimension_type::rank == 0) ||
+                   (DimRHS::rank == 1 && dimension_type::rank == 1)),
                   "ViewOffset LayoutLeft and LayoutRight are only compatible "
                   "when rank <= 1");
   }
@@ -1778,8 +1777,7 @@ struct ViewOffset<
       const ViewOffset<DimRHS, Kokkos::LayoutLeft, void>& rhs)
       : m_dim(rhs.m_dim.N0, 0, 0, 0, 0, 0, 0, 0) {
     static_assert((DimRHS::rank == 0 && dimension_type::rank == 0) ||
-                      (DimRHS::rank == 1 && dimension_type::rank == 1 &&
-                       dimension_type::rank_dynamic == 1),
+                      (DimRHS::rank == 1 && dimension_type::rank == 1),
                   "ViewOffset LayoutRight and LayoutLeft are only compatible "
                   "when rank <= 1");
   }
@@ -3059,10 +3057,10 @@ struct ViewValueFunctor<DeviceType, ValueType, true /* is_scalar */> {
                    std::is_trivially_copy_assignable<Dummy>::value>
   construct_shared_allocation() {
     // Shortcut for zero initialization
-    ValueType value{};
 // On A64FX memset seems to do the wrong thing with regards to first touch
 // leading to the significant performance issues
 #ifndef KOKKOS_ARCH_A64FX
+    ValueType value{};
     if (Impl::is_zero_byte(value)) {
       uint64_t kpID = 0;
       if (Kokkos::Profiling::profileLibraryLoaded()) {
@@ -3539,9 +3537,7 @@ class ViewMapping<
                      typename SrcTraits::array_layout>::value ||
         std::is_same<typename DstTraits::array_layout,
                      Kokkos::LayoutStride>::value ||
-        (DstTraits::dimension::rank == 0) ||
-        (DstTraits::dimension::rank == 1 &&
-         DstTraits::dimension::rank_dynamic == 1)
+        (DstTraits::dimension::rank == 0) || (DstTraits::dimension::rank == 1)
   };
 
  public:
diff --git a/packages/kokkos/core/unit_test/CMakeLists.txt b/packages/kokkos/core/unit_test/CMakeLists.txt
index 24f70c0ccb3208ca3db1acf82e08bfb56d1ef0de..16fdb39d1a36e9dd8b7d65bbe846c28b37fcf496 100644
--- a/packages/kokkos/core/unit_test/CMakeLists.txt
+++ b/packages/kokkos/core/unit_test/CMakeLists.txt
@@ -73,6 +73,7 @@ KOKKOS_INCLUDE_DIRECTORIES(${KOKKOS_SOURCE_DIR}/core/unit_test/category_files)
 
 SET(COMPILE_ONLY_SOURCES
   TestArray.cpp
+  TestCreateMirror.cpp
   TestDetectionIdiom.cpp
   TestInterOp.cpp
   TestLegionInteroperability.cpp
@@ -86,6 +87,7 @@ ENDIF()
 KOKKOS_ADD_EXECUTABLE(
   TestCompileOnly
   SOURCES
+  TestCompileMain.cpp
   ${COMPILE_ONLY_SOURCES}
 )
 
diff --git a/packages/kokkos/core/unit_test/TestCompileMain.cpp b/packages/kokkos/core/unit_test/TestCompileMain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..237c8ce181774d991a9dbdd8cacf1a5fb9f199f1
--- /dev/null
+++ b/packages/kokkos/core/unit_test/TestCompileMain.cpp
@@ -0,0 +1 @@
+int main() {}
diff --git a/packages/kokkos/core/unit_test/TestCreateMirror.cpp b/packages/kokkos/core/unit_test/TestCreateMirror.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e8b3b6ea105950267a862a78ebcb632120e74f66
--- /dev/null
+++ b/packages/kokkos/core/unit_test/TestCreateMirror.cpp
@@ -0,0 +1,126 @@
+/*
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 3.0
+//       Copyright (2020) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the Corporation nor the names of the
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Questions? Contact Christian R. Trott (crtrott@sandia.gov)
+//
+// ************************************************************************
+//@HEADER
+*/
+
+#include <Kokkos_Core.hpp>
+
+template <typename TestView, typename MemorySpace>
+void check_memory_space(TestView, MemorySpace) {
+  static_assert(
+      std::is_same<typename TestView::memory_space, MemorySpace>::value, "");
+}
+
+template <class View>
+auto host_mirror_test_space(View) {
+  return std::conditional_t<
+      Kokkos::SpaceAccessibility<Kokkos::HostSpace,
+                                 typename View::memory_space>::accessible,
+      typename View::memory_space, Kokkos::HostSpace>{};
+}
+
+template <typename View>
+void test_create_mirror_properties(const View& view) {
+  using namespace Kokkos;
+  using DeviceMemorySpace = typename DefaultExecutionSpace::memory_space;
+
+  // clang-format off
+  
+  // create_mirror
+  check_memory_space(create_mirror(WithoutInitializing,                          view), host_mirror_test_space(view));
+  check_memory_space(create_mirror(                                              view), host_mirror_test_space(view));
+  check_memory_space(create_mirror(WithoutInitializing, DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+  check_memory_space(create_mirror(                     DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+
+  // create_mirror_view
+  check_memory_space(create_mirror_view(WithoutInitializing,                          view), host_mirror_test_space(view));
+  check_memory_space(create_mirror_view(                                              view), host_mirror_test_space(view));
+  check_memory_space(create_mirror_view(WithoutInitializing, DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+  check_memory_space(create_mirror_view(                     DefaultExecutionSpace{}, view), DeviceMemorySpace{});
+
+  // create_mirror view_alloc
+  check_memory_space(create_mirror(view_alloc(WithoutInitializing),                      view), host_mirror_test_space(view));
+  check_memory_space(create_mirror(view_alloc(),                                         view), host_mirror_test_space(view));
+  check_memory_space(create_mirror(view_alloc(WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror(view_alloc(                     DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view view_alloc
+  check_memory_space(create_mirror_view(view_alloc(WithoutInitializing),                      view), host_mirror_test_space(view));
+  check_memory_space(create_mirror_view(view_alloc(),                                         view), host_mirror_test_space(view));
+  check_memory_space(create_mirror_view(view_alloc(WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror_view(view_alloc(                     DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror view_alloc + execution space
+  check_memory_space(create_mirror(view_alloc(DefaultExecutionSpace{}, WithoutInitializing),                        view), host_mirror_test_space(view));
+  check_memory_space(create_mirror(view_alloc(DefaultHostExecutionSpace{}),                                         view), host_mirror_test_space(view));
+  check_memory_space(create_mirror(view_alloc(DefaultExecutionSpace{},   WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror(view_alloc(DefaultExecutionSpace{},                        DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view view_alloc + execution space
+  check_memory_space(create_mirror_view(view_alloc(DefaultExecutionSpace{}, WithoutInitializing),                        view), host_mirror_test_space(view));
+  check_memory_space(create_mirror_view(view_alloc(DefaultHostExecutionSpace{}),                                         view), host_mirror_test_space(view));
+  check_memory_space(create_mirror_view(view_alloc(DefaultExecutionSpace{},   WithoutInitializing, DeviceMemorySpace{}), view), DeviceMemorySpace{});
+  check_memory_space(create_mirror_view(view_alloc(DefaultExecutionSpace{},                        DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view_and_copy
+  check_memory_space(create_mirror_view_and_copy(HostSpace{},         view), HostSpace{});
+  check_memory_space(create_mirror_view_and_copy(DeviceMemorySpace{}, view), DeviceMemorySpace{});
+
+  // create_mirror_view_and_copy view_alloc
+  check_memory_space(create_mirror_view_and_copy(view_alloc(HostSpace{}),         view), HostSpace{});
+  check_memory_space(create_mirror_view_and_copy(view_alloc(DeviceMemorySpace{}), view), DeviceMemorySpace{});
+
+  // create_mirror_view_and_copy view_alloc + execution space
+  check_memory_space(create_mirror_view_and_copy(view_alloc(HostSpace{},         DefaultHostExecutionSpace{}),   view), HostSpace{});
+  check_memory_space(create_mirror_view_and_copy(view_alloc(DeviceMemorySpace{}, DefaultExecutionSpace{}),       view), DeviceMemorySpace{});
+
+  // clang-format on
+}
+
+void test() {
+  Kokkos::View<int*, Kokkos::DefaultExecutionSpace> device_view("device view",
+                                                                10);
+  Kokkos::View<int*, Kokkos::HostSpace> host_view("host view", 10);
+
+  test_create_mirror_properties(device_view);
+  test_create_mirror_properties(host_view);
+}
diff --git a/packages/kokkos/core/unit_test/TestDetectionIdiom.cpp b/packages/kokkos/core/unit_test/TestDetectionIdiom.cpp
index f87fda615643c5a75ef5ee4da7349bab0eea40cd..23da339cae03246084c0e67e8781d08972fb864e 100644
--- a/packages/kokkos/core/unit_test/TestDetectionIdiom.cpp
+++ b/packages/kokkos/core/unit_test/TestDetectionIdiom.cpp
@@ -92,5 +92,3 @@ static_assert(std::is_same<difference_type<Woof>, int>::value,
 static_assert(std::is_same<difference_type<Bark>, std::ptrdiff_t>::value,
               "Bark's difference_type should be ptrdiff_t!");
 }  // namespace Example
-
-int main() {}
diff --git a/packages/kokkos/core/unit_test/TestScan.hpp b/packages/kokkos/core/unit_test/TestScan.hpp
index 1a4056af07d3f9584b105cd536e5abca051b30c2..356ffde9565aaf40035e033748056cbb3028f678 100644
--- a/packages/kokkos/core/unit_test/TestScan.hpp
+++ b/packages/kokkos/core/unit_test/TestScan.hpp
@@ -45,20 +45,23 @@
 #include <Kokkos_Core.hpp>
 #include <cstdio>
 
-namespace Test {
+namespace {
 
-template <class Device>
+template <class Device, class T, T ImbalanceSz>
 struct TestScan {
   using execution_space = Device;
-  using value_type      = int64_t;
+  using value_type      = T;
 
   Kokkos::View<int, Device, Kokkos::MemoryTraits<Kokkos::Atomic> > errors;
 
   KOKKOS_INLINE_FUNCTION
   void operator()(const int iwork, value_type& update,
                   const bool final_pass) const {
-    const value_type n         = iwork + 1;
-    const value_type imbalance = ((1000 <= n) && (0 == n % 1000)) ? 1000 : 0;
+    const value_type n = iwork + 1;
+    const value_type imbalance =
+        ((ImbalanceSz <= n) && (value_type(0) == n % ImbalanceSz))
+            ? ImbalanceSz
+            : value_type(0);
 
     // Insert an artificial load imbalance
 
@@ -133,12 +136,29 @@ struct TestScan {
     }
   }
 };
+}  // namespace
 
 TEST(TEST_CATEGORY, scan) {
-  TestScan<TEST_EXECSPACE>::test_range(1, 1000);
-  TestScan<TEST_EXECSPACE>(0);
-  TestScan<TEST_EXECSPACE>(100000);
-  TestScan<TEST_EXECSPACE>(10000000);
-  TEST_EXECSPACE().fence();
+  constexpr auto imbalance_size = 1000;
+  TestScan<TEST_EXECSPACE, int64_t, imbalance_size>::test_range(1, 1000);
+  TestScan<TEST_EXECSPACE, int64_t, imbalance_size>(0);
+  TestScan<TEST_EXECSPACE, int64_t, imbalance_size>(100000);
+  TestScan<TEST_EXECSPACE, int64_t, imbalance_size>(10000000);
+}
+
+TEST(TEST_CATEGORY, small_size_scan) {
+  constexpr auto imbalance_size = 10;  // Pick to not overflow...
+  TestScan<TEST_EXECSPACE, std::int8_t, imbalance_size>(0);
+  TestScan<TEST_EXECSPACE, std::int8_t, imbalance_size>(5);
+  TestScan<TEST_EXECSPACE, std::int8_t, imbalance_size>(10);
+  TestScan<TEST_EXECSPACE, std::int8_t, imbalance_size>(
+      static_cast<std::size_t>(
+          std::sqrt(std::numeric_limits<std::int8_t>::max())));
+  constexpr auto short_imbalance_size = 100;  // Pick to not overflow...
+  TestScan<TEST_EXECSPACE, std::int16_t, short_imbalance_size>(0);
+  TestScan<TEST_EXECSPACE, std::int16_t, short_imbalance_size>(5);
+  TestScan<TEST_EXECSPACE, std::int16_t, short_imbalance_size>(100);
+  TestScan<TEST_EXECSPACE, std::int16_t, short_imbalance_size>(
+      static_cast<std::size_t>(
+          std::sqrt(std::numeric_limits<std::int16_t>::max())));
 }
-}  // namespace Test
diff --git a/packages/kokkos/core/unit_test/TestTeam.hpp b/packages/kokkos/core/unit_test/TestTeam.hpp
index f1d0f9cb3b8a37f35f9b4962e2f183f26701072c..3f05b2ef66a04783a94f854259253cb984411819 100644
--- a/packages/kokkos/core/unit_test/TestTeam.hpp
+++ b/packages/kokkos/core/unit_test/TestTeam.hpp
@@ -1616,6 +1616,73 @@ struct TestTeamPolicyHandleByValue {
 
 }  // namespace
 
+namespace {
+template <typename ExecutionSpace>
+struct TestRepeatedTeamReduce {
+  static constexpr int ncol = 1500;  // nothing special, just some work
+
+  KOKKOS_FUNCTION void operator()(
+      const typename Kokkos::TeamPolicy<ExecutionSpace>::member_type &team)
+      const {
+    // non-divisible by power of two to make triggering problems easier
+    constexpr int nlev = 129;
+    constexpr auto pi  = Kokkos::Experimental::pi_v<double>;
+    double b           = 0.;
+    for (int ri = 0; ri < 10; ++ri) {
+      // The contributions here must be sufficiently complex, simply adding ones
+      // wasn't enough to trigger the bug.
+      const auto g1 = [&](const int k, double &acc) {
+        acc += Kokkos::cos(pi * double(k) / nlev);
+      };
+      const auto g2 = [&](const int k, double &acc) {
+        acc += Kokkos::sin(pi * double(k) / nlev);
+      };
+      double a1, a2;
+      Kokkos::parallel_reduce(Kokkos::TeamThreadRange(team, nlev), g1, a1);
+      Kokkos::parallel_reduce(Kokkos::TeamThreadRange(team, nlev), g2, a2);
+      b += a1;
+      b += a2;
+    }
+    const auto h = [&]() {
+      const auto col = team.league_rank();
+      v(col)         = b + col;
+    };
+    Kokkos::single(Kokkos::PerTeam(team), h);
+  }
+
+  KOKKOS_FUNCTION void operator()(const int i, int &bad) const {
+    if (v(i) != v(0) + i) {
+      ++bad;
+      KOKKOS_IMPL_DO_NOT_USE_PRINTF("Failing at %d!\n", i);
+    }
+  }
+
+  TestRepeatedTeamReduce() : v("v", ncol) { test(); }
+
+  void test() {
+    int team_size_recommended =
+        Kokkos::TeamPolicy<ExecutionSpace>(1, 1).team_size_recommended(
+            *this, Kokkos::ParallelForTag());
+    // Choose a non-recommened (non-power of two for GPUs) team size
+    int team_size = team_size_recommended > 1 ? team_size_recommended - 1 : 1;
+
+    // The failure was non-deterministic so run the test a bunch of times
+    for (int it = 0; it < 100; ++it) {
+      Kokkos::parallel_for(
+          Kokkos::TeamPolicy<ExecutionSpace>(ncol, team_size, 1), *this);
+
+      int bad = 0;
+      Kokkos::parallel_reduce(Kokkos::RangePolicy<ExecutionSpace>(0, ncol),
+                              *this, bad);
+      ASSERT_EQ(bad, 0) << " Failing in iteration " << it;
+    }
+  }
+
+  Kokkos::View<double *, ExecutionSpace> v;
+};
+
+}  // namespace
+
 }  // namespace Test
 
 /*--------------------------------------------------------------------------*/
diff --git a/packages/kokkos/core/unit_test/TestTeamReductionScan.hpp b/packages/kokkos/core/unit_test/TestTeamReductionScan.hpp
index 469bba23b73ee9bd316f7c2fbcd9389144f03e12..4d4f3b1f4d34eeae219b9436922162f7279ac8ca 100644
--- a/packages/kokkos/core/unit_test/TestTeamReductionScan.hpp
+++ b/packages/kokkos/core/unit_test/TestTeamReductionScan.hpp
@@ -134,5 +134,15 @@ TEST(TEST_CATEGORY, team_parallel_dummy_with_reducer_and_scratch_space) {
   }
 }
 
+TEST(TEST_CATEGORY, repeated_team_reduce) {
+#ifdef KOKKOS_ENABLE_OPENMPTARGET
+  if (std::is_same<TEST_EXECSPACE, Kokkos::Experimental::OpenMPTarget>::value)
+    GTEST_SKIP() << "skipping since team_reduce for OpenMPTarget is not "
+                    "properly implemented";
+#endif
+
+  TestRepeatedTeamReduce<TEST_EXECSPACE>();
+}
+
 }  // namespace Test
 #endif
diff --git a/packages/kokkos/core/unit_test/TestViewIsAssignable.hpp b/packages/kokkos/core/unit_test/TestViewIsAssignable.hpp
index 03c3b977edeab7ec5b51c406da65b0e089f5a0de..3ac392d3e98860fe536a07fbff43cc7d5fc1aecd 100644
--- a/packages/kokkos/core/unit_test/TestViewIsAssignable.hpp
+++ b/packages/kokkos/core/unit_test/TestViewIsAssignable.hpp
@@ -92,8 +92,18 @@ TEST(TEST_CATEGORY, view_is_assignable) {
                           View<double*, left, d_exec>>::test(false, false, 10);
 
   // Layout assignment
+  Impl::TestAssignability<View<int, left, d_exec>,
+                          View<int, right, d_exec>>::test(true, true);
   Impl::TestAssignability<View<int*, left, d_exec>,
                           View<int*, right, d_exec>>::test(true, true, 10);
+  Impl::TestAssignability<View<int[5], left, d_exec>,
+                          View<int*, right, d_exec>>::test(false, false, 10);
+  Impl::TestAssignability<View<int[10], left, d_exec>,
+                          View<int*, right, d_exec>>::test(false, true, 10);
+  Impl::TestAssignability<View<int*, left, d_exec>,
+                          View<int[5], right, d_exec>>::test(true, true);
+  Impl::TestAssignability<View<int[5], left, d_exec>,
+                          View<int[10], right, d_exec>>::test(false, false);
 
   // This could be made possible (due to the degenerate nature of the views) but
   // we do not allow this yet
diff --git a/packages/kokkos/master_history.txt b/packages/kokkos/master_history.txt
index a1a87ce3199d10449b92be4a8e09ecaa790a303f..bd639c847e03cdd0909fc83ccf6d0843148d6bea 100644
--- a/packages/kokkos/master_history.txt
+++ b/packages/kokkos/master_history.txt
@@ -29,3 +29,4 @@ tag:  3.5.00     date: 11:19:2021    master: c28a8b03    release: 21b879e4
 tag:  3.6.00     date: 04:14:2022    master: 2834f94a    release: 6ea708ff
 tag:  3.6.01     date: 06:16:2022    master: b52f8c83    release: afe9b404
 tag:  3.7.00     date: 08:25:2022    master: d19aab99    release: 0018e5fb
+tag:  3.7.01     date: 12:01:2022    master: 61d7db55    release: d3bb8cfe
diff --git a/packages/kokkos/simd/cmake/Dependencies.cmake b/packages/kokkos/simd/cmake/Dependencies.cmake
index 5e29157369c9ab8cab935a1bfc4c6dad2fdd0296..1d71d8af341181f689a6a8bf63036b67584cb138 100644
--- a/packages/kokkos/simd/cmake/Dependencies.cmake
+++ b/packages/kokkos/simd/cmake/Dependencies.cmake
@@ -1,5 +1,5 @@
 TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
   LIB_REQUIRED_PACKAGES KokkosCore
-  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC HPX
+  LIB_OPTIONAL_TPLS Pthread CUDA HWLOC
   TEST_OPTIONAL_TPLS CUSPARSE
   )
diff --git a/packages/kokkos/tpls/desul/include/desul/atomics/Lock_Array_Cuda.hpp b/packages/kokkos/tpls/desul/include/desul/atomics/Lock_Array_Cuda.hpp
index 2166fa3cb78e70af887ff7f74e2cac9f141bf1de..1815adb4a7621c8b4b3d93ac626c417f1c42644b 100644
--- a/packages/kokkos/tpls/desul/include/desul/atomics/Lock_Array_Cuda.hpp
+++ b/packages/kokkos/tpls/desul/include/desul/atomics/Lock_Array_Cuda.hpp
@@ -76,7 +76,7 @@ namespace Impl {
 /// instances in other translation units, we must update this CUDA global
 /// variable based on the Host global variable prior to running any kernels
 /// that will use it.
-/// That is the purpose of the ensure_cuda_lock_arrays_on_device function.
+/// That is the purpose of the KOKKOS_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE macro.
 __device__
 #ifdef __CUDACC_RDC__
     __constant__ extern
@@ -138,42 +138,33 @@ namespace {
 static int lock_array_copied = 0;
 inline int eliminate_warning_for_lock_array() { return lock_array_copied; }
 }  // namespace
-
-#ifdef __CUDACC_RDC__
-inline
-#else
-static
-#endif
-    void
-    copy_cuda_lock_arrays_to_device() {
-  if (lock_array_copied == 0) {
-    cudaMemcpyToSymbol(CUDA_SPACE_ATOMIC_LOCKS_DEVICE,
-                       &CUDA_SPACE_ATOMIC_LOCKS_DEVICE_h,
-                       sizeof(int32_t*));
-    cudaMemcpyToSymbol(CUDA_SPACE_ATOMIC_LOCKS_NODE,
-                       &CUDA_SPACE_ATOMIC_LOCKS_NODE_h,
-                       sizeof(int32_t*));
-  }
-  lock_array_copied = 1;
-}
-
 }  // namespace Impl
 }  // namespace desul
+/* It is critical that this code be a macro, so that it will
+   capture the right address for desul::Impl::CUDA_SPACE_ATOMIC_LOCKS_DEVICE
+   putting this in an inline function will NOT do the right thing! */
+#define DESUL_IMPL_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE()                       \
+  {                                                                        \
+    if (::desul::Impl::lock_array_copied == 0) {                           \
+      cudaMemcpyToSymbol(::desul::Impl::CUDA_SPACE_ATOMIC_LOCKS_DEVICE,    \
+                         &::desul::Impl::CUDA_SPACE_ATOMIC_LOCKS_DEVICE_h, \
+                         sizeof(int32_t*));                                \
+      cudaMemcpyToSymbol(::desul::Impl::CUDA_SPACE_ATOMIC_LOCKS_NODE,      \
+                         &::desul::Impl::CUDA_SPACE_ATOMIC_LOCKS_NODE_h,   \
+                         sizeof(int32_t*));                                \
+    }                                                                      \
+    ::desul::Impl::lock_array_copied = 1;                                  \
+  }
 
 #endif /* defined( __CUDACC__ ) */
 
 #endif /* defined( DESUL_HAVE_CUDA_ATOMICS ) */
 
-namespace desul {
-
 #if defined(__CUDACC_RDC__) || (!defined(__CUDACC__))
-inline void ensure_cuda_lock_arrays_on_device() {}
+#define DESUL_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE()
 #else
-static inline void ensure_cuda_lock_arrays_on_device() {
-  Impl::copy_cuda_lock_arrays_to_device();
-}
+#define DESUL_ENSURE_CUDA_LOCK_ARRAYS_ON_DEVICE() \
+  DESUL_IMPL_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE()
 #endif
 
-}  // namespace desul
-
-#endif /* #ifndef DESUL_ATOMICS_LOCK_ARRAY_CUDA_HPP_ */
+#endif /* #ifndef KOKKOS_CUDA_LOCKS_HPP_ */
diff --git a/packages/kokkos/tpls/desul/src/Lock_Array_CUDA.cpp b/packages/kokkos/tpls/desul/src/Lock_Array_CUDA.cpp
index 19944b378e2c47090dbe3ce28913017a3f308933..cb8482c5da8b83bb1fc6323dea09fffce86d115b 100644
--- a/packages/kokkos/tpls/desul/src/Lock_Array_CUDA.cpp
+++ b/packages/kokkos/tpls/desul/src/Lock_Array_CUDA.cpp
@@ -70,7 +70,7 @@ void init_lock_arrays_cuda() {
                              "init_lock_arrays_cuda: cudaMalloc host locks");
 
   auto error_sync1 = cudaDeviceSynchronize();
-  copy_cuda_lock_arrays_to_device();
+  DESUL_IMPL_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE();
   check_error_and_throw_cuda(error_sync1, "init_lock_arrays_cuda: post mallocs");
   init_lock_arrays_cuda_kernel<<<(CUDA_SPACE_ATOMIC_MASK + 1 + 255) / 256, 256>>>();
   auto error_sync2 = cudaDeviceSynchronize();
@@ -85,7 +85,7 @@ void finalize_lock_arrays_cuda() {
   CUDA_SPACE_ATOMIC_LOCKS_DEVICE_h = nullptr;
   CUDA_SPACE_ATOMIC_LOCKS_NODE_h = nullptr;
 #ifdef __CUDACC_RDC__
-  copy_cuda_lock_arrays_to_device();
+  DESUL_IMPL_COPY_CUDA_LOCK_ARRAYS_TO_DEVICE();
 #endif
 }
 
diff --git a/src/algebra/TinyMatrix.hpp b/src/algebra/TinyMatrix.hpp
index e34f98ed8c25df7a77ba90bbd7cd05305b5eb115..1717b48c78deaedc7cabd9f60feb215e29a80ebe 100644
--- a/src/algebra/TinyMatrix.hpp
+++ b/src/algebra/TinyMatrix.hpp
@@ -21,16 +21,21 @@ class [[nodiscard]] TinyMatrix
   using data_type = T;
 
  private:
+  static_assert((M > 0), "TinyMatrix number of rows must be strictly positive");
+  static_assert((N > 0), "TinyMatrix number of columns must be strictly positive");
+
   T m_values[M * N];
 
   PUGS_FORCEINLINE
-  constexpr size_t _index(size_t i, size_t j) const noexcept   // LCOV_EXCL_LINE (due to forced inline)
+  constexpr size_t
+  _index(size_t i, size_t j) const noexcept   // LCOV_EXCL_LINE (due to forced inline)
   {
     return i * N + j;
   }
 
   template <typename... Args>
-  PUGS_FORCEINLINE constexpr void _unpackVariadicInput(const T& t, Args&&... args) noexcept
+  PUGS_FORCEINLINE constexpr void
+  _unpackVariadicInput(const T& t, Args&&... args) noexcept
   {
     m_values[M * N - 1 - sizeof...(args)] = t;
     if constexpr (sizeof...(args) > 0) {
@@ -40,13 +45,15 @@ class [[nodiscard]] TinyMatrix
 
  public:
   PUGS_INLINE
-  constexpr bool isSquare() const noexcept
+  constexpr bool
+  isSquare() const noexcept
   {
     return M == N;
   }
 
   PUGS_INLINE
-  constexpr friend TinyMatrix<N, M, T> transpose(const TinyMatrix& A)
+  constexpr friend TinyMatrix<N, M, T>
+  transpose(const TinyMatrix& A)
   {
     TinyMatrix<N, M, T> tA;
     for (size_t i = 0; i < M; ++i) {
@@ -58,31 +65,36 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr size_t dimension() const
+  constexpr size_t
+  dimension() const
   {
     return M * N;
   }
 
   PUGS_INLINE
-  constexpr size_t numberOfValues() const
+  constexpr size_t
+  numberOfValues() const
   {
     return this->dimension();
   }
 
   PUGS_INLINE
-  constexpr size_t numberOfRows() const
+  constexpr size_t
+  numberOfRows() const
   {
     return M;
   }
 
   PUGS_INLINE
-  constexpr size_t numberOfColumns() const
+  constexpr size_t
+  numberOfColumns() const
   {
     return N;
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix operator-() const
+  constexpr TinyMatrix
+  operator-() const
   {
     TinyMatrix opposite;
     for (size_t i = 0; i < M * N; ++i) {
@@ -92,20 +104,23 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr friend TinyMatrix operator*(const T& t, const TinyMatrix& A)
+  constexpr friend TinyMatrix
+  operator*(const T& t, const TinyMatrix& A)
   {
     TinyMatrix B = A;
     return B *= t;
   }
 
   PUGS_INLINE
-  constexpr friend TinyMatrix operator*(const T& t, TinyMatrix&& A)
+  constexpr friend TinyMatrix
+  operator*(const T& t, TinyMatrix&& A)
   {
     return std::move(A *= t);
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix& operator*=(const T& t)
+  constexpr TinyMatrix&
+  operator*=(const T& t)
   {
     for (size_t i = 0; i < M * N; ++i) {
       m_values[i] *= t;
@@ -114,7 +129,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   template <size_t P>
-  PUGS_INLINE constexpr TinyMatrix<M, P, T> operator*(const TinyMatrix<N, P, T>& B) const
+  PUGS_INLINE constexpr TinyMatrix<M, P, T>
+  operator*(const TinyMatrix<N, P, T>& B) const
   {
     const TinyMatrix& A = *this;
     TinyMatrix<M, P, T> AB;
@@ -131,7 +147,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr TinyVector<M, T> operator*(const TinyVector<N, T>& x) const
+  constexpr TinyVector<M, T>
+  operator*(const TinyVector<N, T>& x) const
   {
     const TinyMatrix& A = *this;
     TinyVector<M, T> Ax;
@@ -146,7 +163,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr friend std::ostream& operator<<(std::ostream& os, const TinyMatrix& A)
+  constexpr friend std::ostream&
+  operator<<(std::ostream& os, const TinyMatrix& A)
   {
     os << '[';
     for (size_t i = 0; i < M; ++i) {
@@ -165,7 +183,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr bool operator==(const TinyMatrix& A) const
+  constexpr bool
+  operator==(const TinyMatrix& A) const
   {
     for (size_t i = 0; i < M * N; ++i) {
       if (m_values[i] != A.m_values[i])
@@ -175,13 +194,15 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr bool operator!=(const TinyMatrix& A) const
+  constexpr bool
+  operator!=(const TinyMatrix& A) const
   {
     return not this->operator==(A);
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix operator+(const TinyMatrix& A) const
+  constexpr TinyMatrix
+  operator+(const TinyMatrix& A) const
   {
     TinyMatrix sum;
     for (size_t i = 0; i < M * N; ++i) {
@@ -191,14 +212,16 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix operator+(TinyMatrix&& A) const
+  constexpr TinyMatrix
+  operator+(TinyMatrix&& A) const
   {
     A += *this;
     return std::move(A);
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix operator-(const TinyMatrix& A) const
+  constexpr TinyMatrix
+  operator-(const TinyMatrix& A) const
   {
     TinyMatrix difference;
     for (size_t i = 0; i < M * N; ++i) {
@@ -208,7 +231,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix operator-(TinyMatrix&& A) const
+  constexpr TinyMatrix
+  operator-(TinyMatrix&& A) const
   {
     for (size_t i = 0; i < M * N; ++i) {
       A.m_values[i] = m_values[i] - A.m_values[i];
@@ -217,7 +241,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix& operator+=(const TinyMatrix& A)
+  constexpr TinyMatrix&
+  operator+=(const TinyMatrix& A)
   {
     for (size_t i = 0; i < M * N; ++i) {
       m_values[i] += A.m_values[i];
@@ -226,7 +251,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr void operator+=(const volatile TinyMatrix& A) volatile
+  constexpr void
+  operator+=(const volatile TinyMatrix& A) volatile
   {
     for (size_t i = 0; i < M * N; ++i) {
       m_values[i] += A.m_values[i];
@@ -234,7 +260,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix& operator-=(const TinyMatrix& A)
+  constexpr TinyMatrix&
+  operator-=(const TinyMatrix& A)
   {
     for (size_t i = 0; i < M * N; ++i) {
       m_values[i] -= A.m_values[i];
@@ -243,21 +270,24 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr T& operator()(size_t i, size_t j) noexcept(NO_ASSERT)
+  constexpr T&
+  operator()(size_t i, size_t j) noexcept(NO_ASSERT)
   {
     Assert((i < M) and (j < N));
     return m_values[_index(i, j)];
   }
 
   PUGS_INLINE
-  constexpr const T& operator()(size_t i, size_t j) const noexcept(NO_ASSERT)
+  constexpr const T&
+  operator()(size_t i, size_t j) const noexcept(NO_ASSERT)
   {
     Assert((i < M) and (j < N));
     return m_values[_index(i, j)];
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix& operator=(ZeroType) noexcept
+  constexpr TinyMatrix&
+  operator=(ZeroType) noexcept
   {
     static_assert(std::is_arithmetic<T>(), "Cannot assign 'zero' value for non-arithmetic types");
     for (size_t i = 0; i < M * N; ++i) {
@@ -267,7 +297,8 @@ class [[nodiscard]] TinyMatrix
   }
 
   PUGS_INLINE
-  constexpr TinyMatrix& operator=(IdentityType) noexcept
+  constexpr TinyMatrix&
+  operator=(IdentityType) noexcept
   {
     static_assert(std::is_arithmetic<T>(), "Cannot assign 'identity' value for non-arithmetic types");
     for (size_t i = 0; i < M; ++i) {
@@ -329,7 +360,7 @@ class [[nodiscard]] TinyMatrix
   constexpr TinyMatrix(const TinyMatrix&) noexcept = default;
 
   PUGS_INLINE
-  TinyMatrix(TinyMatrix && A) noexcept = default;
+  TinyMatrix(TinyMatrix&& A) noexcept = default;
 
   PUGS_INLINE
   ~TinyMatrix() = default;
@@ -450,6 +481,19 @@ getMinor(const TinyMatrix<M, N, T>& A, size_t I, size_t J)
   return m;
 }
 
+template <size_t N, typename T>
+PUGS_INLINE T
+trace(const TinyMatrix<N, N, T>& A)
+{
+  static_assert(std::is_arithmetic<T>::value, "trace is not defined for non-arithmetic types");
+
+  T t = A(0, 0);
+  for (size_t i = 1; i < N; ++i) {
+    t += A(i, i);
+  }
+  return t;
+}
+
 template <size_t N, typename T>
 PUGS_INLINE constexpr TinyMatrix<N, N, T> inverse(const TinyMatrix<N, N, T>& A);
 
diff --git a/src/language/ast/ASTNodeDataTypeFlattener.cpp b/src/language/ast/ASTNodeDataTypeFlattener.cpp
index 9cc1fa483196815ed97b3b8134797b79aeea770e..00b77e0bf98e13314fce52396c7a6cef5717254e 100644
--- a/src/language/ast/ASTNodeDataTypeFlattener.cpp
+++ b/src/language/ast/ASTNodeDataTypeFlattener.cpp
@@ -52,7 +52,7 @@ ASTNodeDataTypeFlattener::ASTNodeDataTypeFlattener(ASTNode& node, FlattenedDataT
       case ASTNodeDataType::builtin_function_t: {
         const auto& compound_data_type = getBuiltinFunctionEmbedder(node)->getReturnDataType();
 
-        for (auto data_type : compound_data_type.contentTypeList()) {
+        for (const auto& data_type : compound_data_type.contentTypeList()) {
           flattened_datatype_list.push_back({*data_type, node});
         }
 
diff --git a/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp b/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp
index a381123884250e9cd574495d7c3add369863aa59..656000c69cd45b85bcf72907850230c7e0f9615b 100644
--- a/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp
+++ b/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp
@@ -15,7 +15,7 @@ ASTNodeFunctionExpressionBuilder::_getArgumentConverter(SymbolType& parameter_sy
 {
   const size_t parameter_id = std::get<size_t>(parameter_symbol.attributes().value());
 
-  ASTNodeNaturalConversionChecker{node_sub_data_type, parameter_symbol.attributes().dataType()};
+  ASTNodeNaturalConversionChecker<AllowRToR1Conversion>{node_sub_data_type, parameter_symbol.attributes().dataType()};
 
   auto get_function_argument_converter_for =
     [&](const auto& parameter_v) -> std::unique_ptr<IFunctionArgumentConverter> {
@@ -78,13 +78,48 @@ ASTNodeFunctionExpressionBuilder::_getArgumentConverter(SymbolType& parameter_sy
         // LCOV_EXCL_STOP
       }
     }
+    case ASTNodeDataType::bool_t: {
+      if ((parameter_v.dimension() == 1)) {
+        return std::make_unique<FunctionTinyVectorArgumentConverter<ParameterT, bool>>(parameter_id);
+      } else {
+        // LCOV_EXCL_START
+        throw ParseError("unexpected error: invalid argument dimension",
+                         std::vector{node_sub_data_type.m_parent_node.begin()});
+        // LCOV_EXCL_STOP
+      }
+    }
     case ASTNodeDataType::int_t: {
-      if (node_sub_data_type.m_parent_node.is_type<language::integer>()) {
+      if ((parameter_v.dimension() == 1)) {
+        return std::make_unique<FunctionTinyVectorArgumentConverter<ParameterT, int64_t>>(parameter_id);
+      } else if (node_sub_data_type.m_parent_node.is_type<language::integer>()) {
         if (std::stoi(node_sub_data_type.m_parent_node.string()) == 0) {
           return std::make_unique<FunctionTinyVectorArgumentConverter<ParameterT, ZeroType>>(parameter_id);
         }
       }
-      [[fallthrough]];
+      // LCOV_EXCL_START
+      throw ParseError("unexpected error: invalid argument type",
+                       std::vector{node_sub_data_type.m_parent_node.begin()});
+      // LCOV_EXCL_STOP
+    }
+    case ASTNodeDataType::unsigned_int_t: {
+      if ((parameter_v.dimension() == 1)) {
+        return std::make_unique<FunctionTinyVectorArgumentConverter<ParameterT, uint64_t>>(parameter_id);
+      } else {
+        // LCOV_EXCL_START
+        throw ParseError("unexpected error: invalid argument dimension",
+                         std::vector{node_sub_data_type.m_parent_node.begin()});
+        // LCOV_EXCL_STOP
+      }
+    }
+    case ASTNodeDataType::double_t: {
+      if ((parameter_v.dimension() == 1)) {
+        return std::make_unique<FunctionTinyVectorArgumentConverter<ParameterT, double>>(parameter_id);
+      } else {
+        // LCOV_EXCL_START
+        throw ParseError("unexpected error: invalid argument dimension",
+                         std::vector{node_sub_data_type.m_parent_node.begin()});
+        // LCOV_EXCL_STOP
+      }
     }
       // LCOV_EXCL_START
     default: {
@@ -110,13 +145,48 @@ ASTNodeFunctionExpressionBuilder::_getArgumentConverter(SymbolType& parameter_sy
         // LCOV_EXCL_STOP
       }
     }
+    case ASTNodeDataType::bool_t: {
+      if ((parameter_v.numberOfRows() == 1) and (parameter_v.numberOfColumns() == 1)) {
+        return std::make_unique<FunctionTinyMatrixArgumentConverter<ParameterT, bool>>(parameter_id);
+      } else {
+        // LCOV_EXCL_START
+        throw ParseError("unexpected error: invalid argument type",
+                         std::vector{node_sub_data_type.m_parent_node.begin()});
+        // LCOV_EXCL_STOP
+      }
+    }
     case ASTNodeDataType::int_t: {
-      if (node_sub_data_type.m_parent_node.is_type<language::integer>()) {
+      if ((parameter_v.numberOfRows() == 1) and (parameter_v.numberOfColumns() == 1)) {
+        return std::make_unique<FunctionTinyMatrixArgumentConverter<ParameterT, int64_t>>(parameter_id);
+      } else if (node_sub_data_type.m_parent_node.is_type<language::integer>()) {
         if (std::stoi(node_sub_data_type.m_parent_node.string()) == 0) {
           return std::make_unique<FunctionTinyMatrixArgumentConverter<ParameterT, ZeroType>>(parameter_id);
         }
       }
-      [[fallthrough]];
+      // LCOV_EXCL_START
+      throw ParseError("unexpected error: invalid argument type",
+                       std::vector{node_sub_data_type.m_parent_node.begin()});
+      // LCOV_EXCL_STOP
+    }
+    case ASTNodeDataType::unsigned_int_t: {
+      if ((parameter_v.numberOfRows() == 1) and (parameter_v.numberOfColumns() == 1)) {
+        return std::make_unique<FunctionTinyMatrixArgumentConverter<ParameterT, uint64_t>>(parameter_id);
+      } else {
+        // LCOV_EXCL_START
+        throw ParseError("unexpected error: invalid argument type",
+                         std::vector{node_sub_data_type.m_parent_node.begin()});
+        // LCOV_EXCL_STOP
+      }
+    }
+    case ASTNodeDataType::double_t: {
+      if ((parameter_v.numberOfRows() == 1) and (parameter_v.numberOfColumns() == 1)) {
+        return std::make_unique<FunctionTinyMatrixArgumentConverter<ParameterT, double>>(parameter_id);
+      } else {
+        // LCOV_EXCL_START
+        throw ParseError("unexpected error: invalid argument type",
+                         std::vector{node_sub_data_type.m_parent_node.begin()});
+        // LCOV_EXCL_STOP
+      }
     }
       // LCOV_EXCL_START
     default: {
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 b15e9796f39f71c266741616db2afe1df2d5b0e1..38dc08b6e4bec82be8cab2666080563bd7cb9736 100644
--- a/src/language/modules/MathFunctionRegisterForVh.cpp
+++ b/src/language/modules/MathFunctionRegisterForVh.cpp
@@ -2,330 +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 DiscreteFunctionVariant> b)
+                                        -> std::shared_ptr<const DiscreteFunctionVariant> { return dot(a, b); }
+
+                                      ));
+
+  scheme_module._addBuiltinFunction("det", std::function(
 
-                                             [](const TinyVector<3>& 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> { 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 DiscreteFunctionVariant> A)
+                                                 -> std::shared_ptr<const DiscreteFunctionVariant> { return trace(A); }
+
+                                               ));
+  scheme_module._addBuiltinFunction("min",
+                                    std::function(
+
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> double { return min(a); }
+
+                                      ));
+
+  scheme_module._addBuiltinFunction("transpose",
+                                    std::function(
+
+                                      [](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",
+  scheme_module._addBuiltinFunction("sum_of_R", std::function(
+
+                                                  [](std::shared_ptr<const DiscreteFunctionVariant> a) -> double {
+                                                    return sum_of<double>(a);
+                                                  }
+
+                                                  ));
+
+  scheme_module._addBuiltinFunction("sum_of_R1",
                                     std::function(
 
-                                      [](std::shared_ptr<const IDiscreteFunction> a,
-                                         double b) -> std::shared_ptr<const IDiscreteFunction> { return max(a, b); }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<1> {
+                                        return sum_of<TinyVector<1>>(a);
+                                      }
 
                                       ));
 
-  scheme_module._addBuiltinFunction("sum_of_R", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R2",
+                                    std::function(
 
-                                                  [](std::shared_ptr<const IDiscreteFunction> a) -> double {
-                                                    return sum_of<double>(a);
-                                                  }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<2> {
+                                        return sum_of<TinyVector<2>>(a);
+                                      }
 
-                                                  ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R1", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R3",
+                                    std::function(
 
-                                                   [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<1> {
-                                                     return sum_of<TinyVector<1>>(a);
-                                                   }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyVector<3> {
+                                        return sum_of<TinyVector<3>>(a);
+                                      }
 
-                                                   ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R2", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R1x1",
+                                    std::function(
 
-                                                   [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<2> {
-                                                     return sum_of<TinyVector<2>>(a);
-                                                   }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<1> {
+                                        return sum_of<TinyMatrix<1>>(a);
+                                      }
 
-                                                   ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R3", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R2x2",
+                                    std::function(
 
-                                                   [](std::shared_ptr<const IDiscreteFunction> a) -> TinyVector<3> {
-                                                     return sum_of<TinyVector<3>>(a);
-                                                   }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<2> {
+                                        return sum_of<TinyMatrix<2>>(a);
+                                      }
 
-                                                   ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R1x1", std::function(
+  scheme_module._addBuiltinFunction("sum_of_R3x3",
+                                    std::function(
 
-                                                     [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<1> {
-                                                       return sum_of<TinyMatrix<1>>(a);
-                                                     }
+                                      [](std::shared_ptr<const DiscreteFunctionVariant> a) -> TinyMatrix<3> {
+                                        return sum_of<TinyMatrix<3>>(a);
+                                      }
 
-                                                     ));
+                                      ));
 
-  scheme_module._addBuiltinFunction("sum_of_R2x2", std::function(
+  scheme_module._addBuiltinFunction("sum_of_Vh", std::function(
 
-                                                     [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<2> {
-                                                       return sum_of<TinyMatrix<2>>(a);
-                                                     }
+                                                   [](std::shared_ptr<const DiscreteFunctionVariant> a)
+                                                     -> std::shared_ptr<const DiscreteFunctionVariant> {
+                                                     return sum_of_Vh_components(a);
+                                                   }
 
-                                                     ));
+                                                   ));
 
-  scheme_module._addBuiltinFunction("sum_of_R3x3", std::function(
+  scheme_module._addBuiltinFunction("vectorize",
+                                    std::function(
 
-                                                     [](std::shared_ptr<const IDiscreteFunction> a) -> TinyMatrix<3> {
-                                                       return sum_of<TinyMatrix<3>>(a);
-                                                     }
+                                      [](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);
                                                        }
 
@@ -334,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);
                                       }
 
@@ -343,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);
                                       }
 
@@ -352,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);
                                       }
 
@@ -361,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);
                                       }
 
@@ -370,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);
                                       }
 
@@ -379,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/MathModule.cpp b/src/language/modules/MathModule.cpp
index 4e8e10d1534f55f8c02cccb2f3f48974fdc944d2..4a1e7b35b76a4d9b8b05ebb1355f3cdca4f751d9 100644
--- a/src/language/modules/MathModule.cpp
+++ b/src/language/modules/MathModule.cpp
@@ -70,6 +70,36 @@ MathModule::MathModule()
   this->_addBuiltinFunction("dot", std::function([](const TinyVector<3>& x, const TinyVector<3>& y) -> double {
                               return dot(x, y);
                             }));
+
+  this->_addBuiltinFunction("det", std::function([](const TinyMatrix<1> A) -> double { return det(A); }));
+
+  this->_addBuiltinFunction("det", std::function([](const TinyMatrix<2>& A) -> double { return det(A); }));
+
+  this->_addBuiltinFunction("det", std::function([](const TinyMatrix<3>& A) -> double { return det(A); }));
+
+  this->_addBuiltinFunction("trace", std::function([](const TinyMatrix<1> A) -> double { return trace(A); }));
+
+  this->_addBuiltinFunction("trace", std::function([](const TinyMatrix<2>& A) -> double { return trace(A); }));
+
+  this->_addBuiltinFunction("trace", std::function([](const TinyMatrix<3>& A) -> double { return trace(A); }));
+
+  this->_addBuiltinFunction("inverse",
+                            std::function([](const TinyMatrix<1> A) -> TinyMatrix<1> { return inverse(A); }));
+
+  this->_addBuiltinFunction("inverse",
+                            std::function([](const TinyMatrix<2>& A) -> TinyMatrix<2> { return inverse(A); }));
+
+  this->_addBuiltinFunction("inverse",
+                            std::function([](const TinyMatrix<3>& A) -> TinyMatrix<3> { return inverse(A); }));
+
+  this->_addBuiltinFunction("transpose",
+                            std::function([](const TinyMatrix<1> A) -> TinyMatrix<1> { return transpose(A); }));
+
+  this->_addBuiltinFunction("transpose",
+                            std::function([](const TinyMatrix<2>& A) -> TinyMatrix<2> { return transpose(A); }));
+
+  this->_addBuiltinFunction("transpose",
+                            std::function([](const TinyMatrix<3>& A) -> TinyMatrix<3> { return transpose(A); }));
 }
 
 void
diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 9c5cf3aec1e11d16f652357db53f68db54f6ed79..af1fca45e7dec5898a12731430358691728c7a2a 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -12,6 +12,7 @@
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/CartesianMeshBuilder.hpp>
 #include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
 #include <mesh/DualMeshManager.hpp>
 #include <mesh/GmshReader.hpp>
 #include <mesh/IBoundaryDescriptor.hpp>
@@ -143,6 +144,37 @@ MeshModule::MeshModule()
 
                                        ));
 
+  this->_addBuiltinFunction("check_connectivity_ordering",
+                            std::function(
+
+                              [](const std::shared_ptr<const IMesh>& i_mesh) -> bool {
+                                switch (i_mesh->dimension()) {
+                                case 1: {
+                                  using MeshType = Mesh<Connectivity<1>>;
+
+                                  std::shared_ptr p_mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+                                  return checkConnectivityOrdering(p_mesh->connectivity());
+                                }
+                                case 2: {
+                                  using MeshType = Mesh<Connectivity<2>>;
+
+                                  std::shared_ptr p_mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+                                  return checkConnectivityOrdering(p_mesh->connectivity());
+                                }
+                                case 3: {
+                                  using MeshType = Mesh<Connectivity<3>>;
+
+                                  std::shared_ptr p_mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+                                  return checkConnectivityOrdering(p_mesh->connectivity());
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("cartesianMesh",
                             std::function(
 
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 250dc0aad77d999319e3e535a0c42959368493a8..5bdad8bceff609288e318cce8b3f37304afc69b5 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -96,7 +96,7 @@ ModuleRepository::_populateSymbolTable(const ASTNode& module_node,
                                        const IModule::NameValueMap& name_value_descriptor_map,
                                        SymbolTable& symbol_table)
 {
-  for (auto [symbol_name, value_descriptor] : name_value_descriptor_map) {
+  for (const auto& [symbol_name, value_descriptor] : name_value_descriptor_map) {
     auto [i_symbol, success] = symbol_table.add(symbol_name, module_node.begin());
 
     if (not success) {
@@ -139,7 +139,7 @@ ModuleRepository::populateSymbolTable(const ASTNode& module_name_node, SymbolTab
 
     this->_populateSymbolTable(module_name_node, module_name, populating_module.getNameValueMap(), symbol_table);
 
-    for (auto [symbol_name, embedded] : populating_module.getNameTypeMap()) {
+    for (const auto& [symbol_name, embedded] : populating_module.getNameTypeMap()) {
       BasicAffectationRegisterFor<EmbeddedData>(ASTNodeDataType::build<ASTNodeDataType::type_id_t>(symbol_name));
     }
 
@@ -163,7 +163,7 @@ ModuleRepository::populateMandatorySymbolTable(const ASTNode& root_node, SymbolT
 
       this->_populateSymbolTable(root_node, module_name, i_module->getNameValueMap(), symbol_table);
 
-      for (auto [symbol_name, embedded] : i_module->getNameTypeMap()) {
+      for (const auto& [symbol_name, embedded] : i_module->getNameTypeMap()) {
         BasicAffectationRegisterFor<EmbeddedData>(ASTNodeDataType::build<ASTNodeDataType::type_id_t>(symbol_name));
       }
 
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index c1c8eebef320a24ea9c5bcbc2c6ea2bc8b11c579..799a208937f471171e4fb10f2e74600bdfce0f54 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -26,14 +26,15 @@
 #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>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
 #include <scheme/FreeBoundaryConditionDescriptor.hpp>
+#include <scheme/HyperelasticSolver.hpp>
 #include <scheme/IBoundaryConditionDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <scheme/NeumannBoundaryConditionDescriptor.hpp>
 #include <scheme/ScalarDiamondScheme.hpp>
@@ -45,7 +46,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>>);
 
@@ -99,11 +100,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());
                               }
 
                               ));
@@ -115,34 +116,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());
                               }
 
                               ));
@@ -154,21 +161,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");
@@ -178,30 +186,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(
@@ -267,6 +280,18 @@ SchemeModule::SchemeModule()
 
                                           ));
 
+  this->_addBuiltinFunction("normalstress",
+                            std::function(
+
+                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
+                                 const FunctionSymbolId& normal_stress_id)
+                                -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                return std::make_shared<DirichletBoundaryConditionDescriptor>("normal-stress", boundary,
+                                                                                              normal_stress_id);
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("velocity", std::function(
 
                                           [](std::shared_ptr<const IBoundaryDescriptor> boundary,
@@ -336,10 +361,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>,
@@ -355,17 +380,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,
@@ -377,10 +402,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>,
@@ -396,17 +421,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,
@@ -418,15 +443,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);
@@ -435,30 +460,30 @@ SchemeModule::SchemeModule()
                               ));
 
   this->_addBuiltinFunction(
-    "parabolicheat",
-    std::function(
+    "parabolicheat", std::function(
 
-      [](const std::shared_ptr<const IDiscreteFunction>& alpha,
-         const std::shared_ptr<const IDiscreteFunction>& mub_dual,
-         const std::shared_ptr<const IDiscreteFunction>& mu_dual, const std::shared_ptr<const IDiscreteFunction>& f,
-         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
-        -> std::shared_ptr<const IDiscreteFunction> {
-        return ScalarDiamondSchemeHandler{alpha, mub_dual, mu_dual, f, bc_descriptor_list}.solution();
-      }
+                       [](const std::shared_ptr<const DiscreteFunctionVariant>& alpha,
+                          const std::shared_ptr<const DiscreteFunctionVariant>& mub_dual,
+                          const std::shared_ptr<const DiscreteFunctionVariant>& mu_dual,
+                          const std::shared_ptr<const DiscreteFunctionVariant>& f,
+                          const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+                         -> std::shared_ptr<const DiscreteFunctionVariant> {
+                         return ScalarDiamondSchemeHandler{alpha, mub_dual, mu_dual, f, bc_descriptor_list}.solution();
+                       }
 
-      ));
+                       ));
 
   this->_addBuiltinFunction("unsteadyelasticity",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction> alpha,
-                                 const std::shared_ptr<const IDiscreteFunction> lambdab,
-                                 const std::shared_ptr<const IDiscreteFunction> mub,
-                                 const std::shared_ptr<const IDiscreteFunction> lambda,
-                                 const std::shared_ptr<const IDiscreteFunction> mu,
-                                 const std::shared_ptr<const IDiscreteFunction> f,
+                              [](const std::shared_ptr<const DiscreteFunctionVariant> alpha,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> lambdab,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> mub,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> lambda,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> mu,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> f,
                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
-                                   bc_descriptor_list) -> std::shared_ptr<const IDiscreteFunction> {
+                                   bc_descriptor_list) -> std::shared_ptr<const DiscreteFunctionVariant> {
                                 return VectorDiamondSchemeHandler{alpha, lambdab,           mub, lambda, mu,
                                                                   f,     bc_descriptor_list}
                                   .solution();
@@ -469,15 +494,15 @@ SchemeModule::SchemeModule()
   this->_addBuiltinFunction("moleculardiffusion",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction> alpha,
-                                 const std::shared_ptr<const IDiscreteFunction> lambdab,
-                                 const std::shared_ptr<const IDiscreteFunction> mub,
-                                 const std::shared_ptr<const IDiscreteFunction> lambda,
-                                 const std::shared_ptr<const IDiscreteFunction> mu,
-                                 const std::shared_ptr<const IDiscreteFunction> f,
+                              [](const std::shared_ptr<const DiscreteFunctionVariant> alpha,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> lambdab,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> mub,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> lambda,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> mu,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> f,
                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
-                                   bc_descriptor_list) -> std::tuple<std::shared_ptr<const IDiscreteFunction>,
-                                                                     std::shared_ptr<const IDiscreteFunction>> {
+                                   bc_descriptor_list) -> std::tuple<std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                     std::shared_ptr<const DiscreteFunctionVariant>> {
                                 return VectorDiamondSchemeHandler{alpha, lambdab,           mub, lambda, mu,
                                                                   f,     bc_descriptor_list}
                                   .apply();
@@ -488,76 +513,196 @@ SchemeModule::SchemeModule()
   this->_addBuiltinFunction("energybalance",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction> lambdab,
-                                 const std::shared_ptr<const IDiscreteFunction> mub,
-                                 const std::shared_ptr<const IDiscreteFunction> U,
-                                 const std::shared_ptr<const IDiscreteFunction> dual_U,
-                                 const std::shared_ptr<const IDiscreteFunction> source,
+                              [](const std::shared_ptr<const DiscreteFunctionVariant> lambdab,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> mub,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> U,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> dual_U,
+                                 const std::shared_ptr<const DiscreteFunctionVariant> source,
                                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
-                                   bc_descriptor_list) -> std::tuple<std::shared_ptr<const IDiscreteFunction>,
-                                                                     std::shared_ptr<const IDiscreteFunction>> {
+                                   bc_descriptor_list) -> std::tuple<std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                     std::shared_ptr<const DiscreteFunctionVariant>> {
                                 return EnergyComputerHandler{lambdab, mub, U, dual_U, source, bc_descriptor_list}
                                   .computeEnergyUpdate();
                               }
 
                               ));
 
-  this->_addBuiltinFunction("lagrangian",
+  this->_addBuiltinFunction("hyperelastic_eucclhyd_fluxes",
                             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>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return HyperelasticSolverHandler{getCommonMesh({rho, aL, aT, u, sigma})}
+                                  .solver()
+                                  .compute_fluxes(HyperelasticSolverHandler::SolverType::Eucclhyd, rho, aL, aT, u,
+                                                  sigma, bc_descriptor_list);
+                              }
 
                               ));
 
-  this->_addBuiltinFunction("acoustic_dt",
+  this->_addBuiltinFunction("hyperelastic_eucclhyd_solver",
                             std::function(
 
-                              [](const std::shared_ptr<const IDiscreteFunction>& c) -> double { return acoustic_dt(c); }
+                              [](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>& CG,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 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 DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperelasticSolverHandler{getCommonMesh({rho, u, E, CG, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperelasticSolverHandler::SolverType::Eucclhyd, dt, rho, u, E, CG, aL, aT,
+                                         sigma, bc_descriptor_list);
+                              }
 
                               ));
 
-  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("hyperelastic_glace_fluxes",
+                            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()));
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return HyperelasticSolverHandler{getCommonMesh({rho, aL, aT, u, sigma})}
+                                  .solver()
+                                  .compute_fluxes(HyperelasticSolverHandler::SolverType::Glace,   //
+                                                  rho, aL, aT, u, sigma, bc_descriptor_list);
                               }
-                              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()));
+
+                              ));
+
+  this->_addBuiltinFunction("hyperelastic_glace_solver",
+                            std::function(
+
+                              [](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>& CG,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 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 DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperelasticSolverHandler{getCommonMesh({rho, u, E, CG, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperelasticSolverHandler::SolverType::Glace, dt, rho, u, E, CG, aL, aT, sigma,
+                                         bc_descriptor_list);
                               }
-                              default: {
-                                throw UnexpectedError("invalid mesh dimension");
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperelastic_fluxes",
+                            std::function(
+
+                              [](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>& CG,       //
+                                 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 DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperelasticSolverHandler{getCommonMesh({rho, u, E, CG})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, CG, ur, Fjr);
                               }
+
+                              ));
+
+  this->_addBuiltinFunction("lagrangian",
+                            std::function(
+
+                              [](const std::shared_ptr<const IMesh>& mesh,
+                                 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 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 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");
+                                }
+                                }
                               }
-                            }
 
-                            ));
+                              ));
+
+  this->_addBuiltinFunction("hyperelastic_dt", std::function(
+
+                                                 [](const std::shared_ptr<const DiscreteFunctionVariant>& c) -> double {
+                                                   return hyperelastic_dt(c);
+                                                 }
+
+                                                 ));
 
   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/node_processor/ASTNodeListProcessor.hpp b/src/language/node_processor/ASTNodeListProcessor.hpp
index df29d82de3cf317aa02ddb3f49838ec0a0325c0e..5a9d8c0b8be23ad9c563ce7d8ed30f25df29505f 100644
--- a/src/language/node_processor/ASTNodeListProcessor.hpp
+++ b/src/language/node_processor/ASTNodeListProcessor.hpp
@@ -1,8 +1,10 @@
 #ifndef AST_NODE_LIST_PROCESSOR_HPP
 #define AST_NODE_LIST_PROCESSOR_HPP
 
+#include <language/PEGGrammar.hpp>
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/SymbolTable.hpp>
 
 class ASTNodeListProcessor final : public INodeProcessor
 {
diff --git a/src/language/node_processor/FunctionArgumentConverter.hpp b/src/language/node_processor/FunctionArgumentConverter.hpp
index bb125b821361989e6e8bb898ba0b56208c32ef84..6a4075307e3af9f225cd493176fbd0a7873a3c75 100644
--- a/src/language/node_processor/FunctionArgumentConverter.hpp
+++ b/src/language/node_processor/FunctionArgumentConverter.hpp
@@ -116,10 +116,23 @@ class FunctionTinyVectorArgumentConverter final : public IFunctionArgumentConver
         value);
     } else if constexpr (std::is_same_v<ProvidedValueType, ZeroType>) {
       exec_policy.currentContext()[m_argument_id] = ExpectedValueType{ZeroType::zero};
+    } else if constexpr (std::is_same_v<ExpectedValueType, TinyVector<1>>) {
+      if constexpr (std::is_same_v<ProvidedValueType, bool>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else if constexpr (std::is_same_v<ProvidedValueType, int64_t>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else if constexpr (std::is_same_v<ProvidedValueType, uint64_t>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else if constexpr (std::is_same_v<ProvidedValueType, double>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else {
+        static_assert(std::is_same_v<ExpectedValueType, TinyVector<1>>);
+        exec_policy.currentContext()[m_argument_id] =
+          std::move(static_cast<ExpectedValueType>(std::get<ProvidedValueType>(value)));
+      }
     } else {
-      static_assert(std::is_same_v<ExpectedValueType, TinyVector<1>>);
-      exec_policy.currentContext()[m_argument_id] =
-        std::move(static_cast<ExpectedValueType>(std::get<ProvidedValueType>(value)));
+      throw UnexpectedError(std::string{"cannot convert '"} + demangle<ProvidedValueType>() + "' to '" +
+                            demangle<ExpectedValueType>() + "'");
     }
     return {};
   }
@@ -165,11 +178,25 @@ class FunctionTinyMatrixArgumentConverter final : public IFunctionArgumentConver
         value);
     } else if constexpr (std::is_same_v<ProvidedValueType, ZeroType>) {
       exec_policy.currentContext()[m_argument_id] = ExpectedValueType{ZeroType::zero};
+    } else if constexpr (std::is_same_v<ExpectedValueType, TinyMatrix<1>>) {
+      if constexpr (std::is_same_v<ProvidedValueType, bool>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else if constexpr (std::is_same_v<ProvidedValueType, int64_t>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else if constexpr (std::is_same_v<ProvidedValueType, uint64_t>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else if constexpr (std::is_same_v<ProvidedValueType, double>) {
+        exec_policy.currentContext()[m_argument_id] = ExpectedValueType(std::get<ProvidedValueType>(value));
+      } else {
+        static_assert(std::is_same_v<ExpectedValueType, TinyMatrix<1>>);
+        exec_policy.currentContext()[m_argument_id] =
+          std::move(static_cast<ExpectedValueType>(std::get<ProvidedValueType>(value)));
+      }
     } else {
-      static_assert(std::is_same_v<ExpectedValueType, TinyMatrix<1>>);
-      exec_policy.currentContext()[m_argument_id] =
-        std::move(static_cast<ExpectedValueType>(std::get<ProvidedValueType>(value)));
+      throw UnexpectedError(std::string{"cannot convert '"} + demangle<ProvidedValueType>() + "' to '" +
+                            demangle<ExpectedValueType>() + "'");
     }
+
     return {};
   }
 
diff --git a/src/language/utils/ASTNodeDataType.hpp b/src/language/utils/ASTNodeDataType.hpp
index 4ef3a80c188faa5898fd29e0bc35cce651c7f6c8..f148e99f1396c125ad21277b3d29a0fdd2b2d782 100644
--- a/src/language/utils/ASTNodeDataType.hpp
+++ b/src/language/utils/ASTNodeDataType.hpp
@@ -176,7 +176,7 @@ class ASTNodeDataType
   {
     static_assert((data_type == list_t), "incorrect data_type construction: cannot provide a list of data types");
 
-    for (auto i : list_of_types) {
+    for (const auto& i : list_of_types) {
       Assert(i->m_data_type != ASTNodeDataType::undefined_t, "cannot build a type list containing undefined types");
     }
 
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/BuiltinFunctionEmbedderUtils.cpp b/src/language/utils/BuiltinFunctionEmbedderUtils.cpp
index be3474ef7ae4e657d7553715c8414180ddc15d52..f4259526fb10a7d1977eec7c575e69c0837a4a26 100644
--- a/src/language/utils/BuiltinFunctionEmbedderUtils.cpp
+++ b/src/language/utils/BuiltinFunctionEmbedderUtils.cpp
@@ -135,7 +135,7 @@ getBuiltinFunctionEmbedder(ASTNode& n)
               switch (tuple_content_type) {
               case ASTNodeDataType::vector_t: {
                 if (arg_type == ASTNodeDataType::list_t) {
-                  for (auto element_type : arg_type.contentTypeList()) {
+                  for (const auto& element_type : arg_type.contentTypeList()) {
                     is_castable &= is_castable_to_vector(*element_type, tuple_content_type);
                   }
                 } else {
@@ -145,7 +145,7 @@ getBuiltinFunctionEmbedder(ASTNode& n)
               }
               case ASTNodeDataType::matrix_t: {
                 if (arg_type == ASTNodeDataType::list_t) {
-                  for (auto element_type : arg_type.contentTypeList()) {
+                  for (const auto& element_type : arg_type.contentTypeList()) {
                     is_castable &= is_castable_to_matrix(*element_type, tuple_content_type);
                   }
                 } else {
@@ -155,7 +155,7 @@ getBuiltinFunctionEmbedder(ASTNode& n)
               }
               default:
                 if (arg_type == ASTNodeDataType::list_t) {
-                  for (auto element_type : arg_type.contentTypeList()) {
+                  for (const auto& element_type : arg_type.contentTypeList()) {
                     is_castable &= isNaturalConversion(*element_type, tuple_content_type);
                   }
                 } else {
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 f49c4c033251bf46d7cd91e7c524af4d146db9d3..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
+++ /dev/null
@@ -1,834 +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>&);
-
-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));
-  }
-}
-
-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 582e63d8551842e9f93e3962a08669ba34baf756..0000000000000000000000000000000000000000
--- a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.hpp
+++ /dev/null
@@ -1,92 +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>&);
-
-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/CMakeLists.txt b/src/mesh/CMakeLists.txt
index 66ce42ae6f9e490a0b3e6e406934478b0f026f4a..62dbd50536ad2e725e67a25d9cddf481a62b40c6 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -7,6 +7,7 @@ add_library(
   ConnectivityBuilderBase.cpp
   ConnectivityComputer.cpp
   ConnectivityDispatcher.cpp
+  ConnectivityUtils.cpp
   DiamondDualConnectivityBuilder.cpp
   DiamondDualMeshBuilder.cpp
   Dual1DConnectivityBuilder.cpp
diff --git a/src/mesh/Connectivity.cpp b/src/mesh/Connectivity.cpp
index 5622294b2af633f38b1cc283eaf0b1576498f0d4..dc03c1ee65224a07466e5f08352b53004ad3c1ea 100644
--- a/src/mesh/Connectivity.cpp
+++ b/src/mesh/Connectivity.cpp
@@ -13,44 +13,36 @@ template <size_t Dimension>
 void
 Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
 {
-  Assert(descriptor.cell_to_node_vector.size() == descriptor.cell_type_vector.size());
-  Assert(descriptor.cell_number_vector.size() == descriptor.cell_type_vector.size());
+  Assert(descriptor.cellToNodeMatrix().numberOfRows() == descriptor.cellTypeVector().size());
+  Assert(descriptor.cellNumberVector().size() == descriptor.cellTypeVector().size());
   if constexpr (Dimension > 1) {
-    Assert(descriptor.cell_to_face_vector.size() == descriptor.cell_type_vector.size());
-    Assert(descriptor.face_to_node_vector.size() == descriptor.face_number_vector.size());
-    Assert(descriptor.face_owner_vector.size() == descriptor.face_number_vector.size());
+    Assert(descriptor.cellToFaceMatrix().numberOfRows() == descriptor.cellTypeVector().size());
+    Assert(descriptor.faceToNodeMatrix().numberOfRows() == descriptor.faceNumberVector().size());
+    Assert(descriptor.faceOwnerVector().size() == descriptor.faceNumberVector().size());
   }
 
-  m_number_of_cells = descriptor.cell_number_vector.size();
-  m_number_of_nodes = descriptor.node_number_vector.size();
+  m_number_of_cells = descriptor.cellNumberVector().size();
+  m_number_of_nodes = descriptor.nodeNumberVector().size();
 
   if constexpr (Dimension == 1) {
     m_number_of_edges = m_number_of_nodes;
     m_number_of_faces = m_number_of_nodes;
   } else {
-    m_number_of_faces = descriptor.face_number_vector.size();
+    m_number_of_faces = descriptor.faceNumberVector().size();
     if constexpr (Dimension == 2) {
       m_number_of_edges = m_number_of_faces;
     } else {
       static_assert(Dimension == 3, "unexpected dimension");
-      m_number_of_edges = descriptor.edge_number_vector.size();
+      m_number_of_edges = descriptor.edgeNumberVector().size();
     }
   }
 
   auto& cell_to_node_matrix = m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::node)];
-  cell_to_node_matrix       = descriptor.cell_to_node_vector;
+  cell_to_node_matrix       = descriptor.cellToNodeMatrix();
 
-  {
-    WeakCellValue<CellType> cell_type(*this);
-    parallel_for(
-      this->numberOfCells(), PUGS_LAMBDA(CellId j) { cell_type[j] = descriptor.cell_type_vector[j]; });
-    m_cell_type = cell_type;
-  }
-
-  m_cell_number = WeakCellValue<int>(*this, convert_to_array(descriptor.cell_number_vector));
-
-  Array node_number_array = convert_to_array(descriptor.node_number_vector);
-  m_node_number           = WeakNodeValue<int>(*this, node_number_array);
+  m_cell_type   = WeakCellValue<const CellType>(*this, descriptor.cellTypeVector());
+  m_cell_number = WeakCellValue<const int>(*this, descriptor.cellNumberVector());
+  m_node_number = WeakNodeValue<const int>(*this, descriptor.nodeNumberVector());
 
   {
     WeakCellValue<int> cell_global_index(*this);
@@ -60,7 +52,7 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
     m_cell_global_index = cell_global_index;
   }
 
-  m_cell_owner = WeakCellValue<int>(*this, convert_to_array(descriptor.cell_owner_vector));
+  m_cell_owner = WeakCellValue<const int>(*this, descriptor.cellOwnerVector());
 
   {
     const int rank = parallel::rank();
@@ -70,8 +62,7 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
     m_cell_is_owned = cell_is_owned;
   }
 
-  Array node_owner_array = convert_to_array(descriptor.node_owner_vector);
-  m_node_owner           = WeakNodeValue<int>{*this, node_owner_array};
+  m_node_owner = WeakNodeValue<const int>{*this, descriptor.nodeOwnerVector()};
 
   Array<bool> node_is_owned_array(this->numberOfNodes());
   {
@@ -87,19 +78,19 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
 
   if constexpr (Dimension == 1) {
     // faces are similar to nodes
-    m_face_number   = WeakFaceValue<int>(*this, node_number_array);
-    m_face_owner    = WeakFaceValue<int>(*this, node_owner_array);
+    m_face_number   = WeakFaceValue<const int>(*this, descriptor.nodeNumberVector());
+    m_face_owner    = WeakFaceValue<const int>(*this, descriptor.nodeOwnerVector());
     m_face_is_owned = WeakFaceValue<bool>(*this, node_is_owned_array);
 
     // edges are similar to nodes
-    m_edge_number   = WeakEdgeValue<int>(*this, node_number_array);
-    m_edge_owner    = WeakEdgeValue<int>(*this, node_owner_array);
+    m_edge_number   = WeakEdgeValue<const int>(*this, descriptor.nodeNumberVector());
+    m_edge_owner    = WeakEdgeValue<const int>(*this, descriptor.nodeOwnerVector());
     m_edge_is_owned = WeakEdgeValue<bool>(*this, node_is_owned_array);
 
     // edge and face references are set equal to node references
     m_ref_edge_list_vector.reserve(descriptor.template refItemListVector<ItemType::node>().size());
     m_ref_face_list_vector.reserve(descriptor.template refItemListVector<ItemType::node>().size());
-    for (auto ref_node_list : descriptor.template refItemListVector<ItemType::node>()) {
+    for (const auto& ref_node_list : descriptor.template refItemListVector<ItemType::node>()) {
       const RefId ref_id            = ref_node_list.refId();
       Array<const NodeId> node_list = ref_node_list.list();
       Array<EdgeId> edge_list(node_list.size());
@@ -114,26 +105,15 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
     }
 
   } else {
-    m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::node)] = descriptor.face_to_node_vector;
+    m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::node)] = descriptor.faceToNodeMatrix();
 
-    m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::face)] = descriptor.cell_to_face_vector;
+    m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::face)] = descriptor.cellToFaceMatrix();
 
-    {
-      FaceValuePerCell<bool> cell_face_is_reversed(*this);
-      for (CellId j = 0; j < descriptor.cell_face_is_reversed_vector.size(); ++j) {
-        const auto& face_cells_vector = descriptor.cell_face_is_reversed_vector[j];
-        for (unsigned short lj = 0; lj < face_cells_vector.size(); ++lj) {
-          cell_face_is_reversed(j, lj) = face_cells_vector[lj];
-        }
-      }
-      m_cell_face_is_reversed = cell_face_is_reversed;
-    }
+    m_cell_face_is_reversed = FaceValuePerCell<const bool>(*this, descriptor.cellFaceIsReversed());
 
-    Array face_number_array = convert_to_array(descriptor.face_number_vector);
-    m_face_number           = WeakFaceValue<int>(*this, face_number_array);
+    m_face_number = WeakFaceValue<const int>(*this, descriptor.faceNumberVector());
 
-    Array face_owner_array = convert_to_array(descriptor.face_owner_vector);
-    m_face_owner           = WeakFaceValue<int>(*this, face_owner_array);
+    m_face_owner = WeakFaceValue<const int>(*this, descriptor.faceOwnerVector());
 
     Array<bool> face_is_owned_array(this->numberOfFaces());
     {
@@ -148,13 +128,13 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
 
     if constexpr (Dimension == 2) {
       // edges are similar to faces
-      m_edge_number   = WeakEdgeValue<int>(*this, face_number_array);
-      m_edge_owner    = WeakEdgeValue<int>(*this, face_owner_array);
+      m_edge_number   = WeakEdgeValue<const int>(*this, descriptor.faceNumberVector());
+      m_edge_owner    = WeakEdgeValue<const int>(*this, descriptor.faceOwnerVector());
       m_edge_is_owned = WeakEdgeValue<bool>(*this, face_is_owned_array);
 
       // edge references are set equal to face references
       m_ref_edge_list_vector.reserve(descriptor.template refItemListVector<ItemType::face>().size());
-      for (auto ref_face_list : descriptor.template refItemListVector<ItemType::face>()) {
+      for (const auto& ref_face_list : descriptor.template refItemListVector<ItemType::face>()) {
         const RefId ref_id            = ref_face_list.refId();
         Array<const FaceId> face_list = ref_face_list.list();
         Array<EdgeId> edge_list(face_list.size());
@@ -166,25 +146,16 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
       }
 
     } else {
-      m_item_to_item_matrix[itemTId(ItemType::edge)][itemTId(ItemType::node)] = descriptor.edge_to_node_vector;
+      m_item_to_item_matrix[itemTId(ItemType::edge)][itemTId(ItemType::node)] = descriptor.edgeToNodeMatrix();
 
-      m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::edge)] = descriptor.face_to_edge_vector;
+      m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::edge)] = descriptor.faceToEdgeMatrix();
 
-      m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::edge)] = descriptor.cell_to_edge_vector;
+      m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::edge)] = descriptor.cellToEdgeMatrix();
 
-      {
-        EdgeValuePerFace<bool> face_edge_is_reversed(*this);
-        for (FaceId l = 0; l < descriptor.face_edge_is_reversed_vector.size(); ++l) {
-          const auto& edge_faces_vector = descriptor.face_edge_is_reversed_vector[l];
-          for (unsigned short el = 0; el < edge_faces_vector.size(); ++el) {
-            face_edge_is_reversed(l, el) = edge_faces_vector[el];
-          }
-        }
-        m_face_edge_is_reversed = face_edge_is_reversed;
-      }
+      m_face_edge_is_reversed = EdgeValuePerFace<const bool>(*this, descriptor.faceEdgeIsReversed());
 
-      m_edge_number = WeakEdgeValue<int>(*this, convert_to_array(descriptor.edge_number_vector));
-      m_edge_owner  = WeakEdgeValue<int>(*this, convert_to_array(descriptor.edge_owner_vector));
+      m_edge_number = WeakEdgeValue<const int>(*this, descriptor.edgeNumberVector());
+      m_edge_owner  = WeakEdgeValue<const int>(*this, descriptor.edgeOwnerVector());
 
       {
         const int rank = parallel::rank();
diff --git a/src/mesh/Connectivity.hpp b/src/mesh/Connectivity.hpp
index b0279507ee41e6f6d3433bda2b5dd7beaf406265..ee7b07fe043a44262c23afc053e748bcac448b50 100644
--- a/src/mesh/Connectivity.hpp
+++ b/src/mesh/Connectivity.hpp
@@ -122,7 +122,7 @@ class Connectivity final : public IConnectivity
     const ConnectivityMatrix& connectivity_matrix = m_item_to_item_matrix[itemTId(item_type_0)][itemTId(item_type_1)];
     if (not connectivity_matrix.isBuilt()) {
       const_cast<ConnectivityMatrix&>(connectivity_matrix) =
-        m_connectivity_computer.computeConnectivityMatrix(*this, item_type_0, item_type_1);
+        m_connectivity_computer.computeInverseConnectivityMatrix(*this, item_type_0, item_type_1);
     }
     return connectivity_matrix;
   }
diff --git a/src/mesh/ConnectivityBuilderBase.cpp b/src/mesh/ConnectivityBuilderBase.cpp
index 55c56b3a4b78f28ecdf8613d04d4d0348b9e5c4a..e7eb95fc6ed064805d12b5a8ece6e2844c37b9e9 100644
--- a/src/mesh/ConnectivityBuilderBase.cpp
+++ b/src/mesh/ConnectivityBuilderBase.cpp
@@ -7,8 +7,6 @@
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsMacros.hpp>
 
-#include <map>
-#include <unordered_map>
 #include <vector>
 
 template <size_t Dimension>
@@ -16,240 +14,583 @@ void
 ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities(ConnectivityDescriptor& descriptor)
 {
   static_assert((Dimension == 2) or (Dimension == 3), "Invalid dimension to compute cell-face connectivities");
-  using CellFaceInfo = std::tuple<CellId, unsigned short, bool>;
-  using Face         = ConnectivityFace<Dimension>;
 
-  const auto& node_number_vector = descriptor.node_number_vector;
-  Array<unsigned short> cell_nb_faces(descriptor.cell_to_node_vector.size());
-  std::map<Face, std::vector<CellFaceInfo>> face_cells_map;
-  for (CellId j = 0; j < descriptor.cell_to_node_vector.size(); ++j) {
-    const auto& cell_nodes = descriptor.cell_to_node_vector[j];
+  const auto& node_number_vector  = descriptor.nodeNumberVector();
+  const auto& cell_to_node_matrix = descriptor.cellToNodeMatrix();
+  const auto& cell_type_vector    = descriptor.cellTypeVector();
+
+  size_t total_number_of_faces = 0;
+
+  for (CellId j = 0; j < cell_to_node_matrix.numberOfRows(); ++j) {
+    const auto& cell_nodes = cell_to_node_matrix[j];
 
     if constexpr (Dimension == 2) {
-      switch (descriptor.cell_type_vector[j]) {
-      case CellType::Triangle: {
-        cell_nb_faces[j] = 3;
-        // face 0
-        Face f0({cell_nodes[1], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes[2], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes[0], cell_nodes[1]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+      total_number_of_faces += cell_nodes.size();
+    } else if constexpr (Dimension == 3) {
+      switch (cell_type_vector[j]) {
+      case CellType::Hexahedron: {
+        total_number_of_faces += 6;
         break;
       }
-      case CellType::Quadrangle: {
-        cell_nb_faces[j] = 4;
-        // face 0
-        Face f0({cell_nodes[0], cell_nodes[1]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes[1], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes[2], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
-
-        // face 3
-        Face f3({cell_nodes[3], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+      case CellType::Tetrahedron: {
+        total_number_of_faces += 4;
         break;
       }
-      case CellType::Polygon: {
-        cell_nb_faces[j] = cell_nodes.size();
-        for (size_t i = 0; i < cell_nodes.size(); ++i) {
-          Face f({cell_nodes[i], cell_nodes[(i + 1) % cell_nodes.size()]}, node_number_vector);
-          face_cells_map[f].emplace_back(std::make_tuple(j, i, f.reversed()));
-        }
+      case CellType::Prism: {
+        total_number_of_faces += 5;
+        break;
+      }
+      case CellType::Pyramid: {
+        total_number_of_faces += cell_nodes.size();
+        break;
+      }
+      case CellType::Diamond: {
+        total_number_of_faces += 2 * (cell_nodes.size() - 2);
         break;
       }
       default: {
         std::ostringstream error_msg;
-        error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 2";
+        error_msg << name(cell_type_vector[j]) << ": unexpected cell type in dimension 3";
         throw UnexpectedError(error_msg.str());
       }
       }
-    } else if constexpr (Dimension == 3) {
-      switch (descriptor.cell_type_vector[j]) {
-      case CellType::Hexahedron: {
-        // face 0
-        Face f0({cell_nodes[3], cell_nodes[2], cell_nodes[1], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+    }
+  }
 
-        // face 1
-        Face f1({cell_nodes[4], cell_nodes[5], cell_nodes[6], cell_nodes[7]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+  const size_t total_number_of_face_by_node = [&] {
+    if constexpr (Dimension == 2) {
+      return 2 * total_number_of_faces;
+    } else {
+      Assert(Dimension == 3);
+      size_t count_number_of_face_by_node = 0;
+      for (CellId j = 0; j < cell_to_node_matrix.numberOfRows(); ++j) {
+        switch (cell_type_vector[j]) {
+        case CellType::Hexahedron: {
+          count_number_of_face_by_node += 6 * 4;
+          break;
+        }
+        case CellType::Tetrahedron: {
+          count_number_of_face_by_node += 4 * 3;
+          break;
+        }
+        case CellType::Prism: {
+          count_number_of_face_by_node += 3 * 4 + 2 * 3;
+          break;
+        }
+        case CellType::Pyramid: {
+          const auto& cell_nodes = cell_to_node_matrix[j];
+          count_number_of_face_by_node += 1 * cell_nodes.size() + cell_nodes.size() * 3;
+          break;
+        }
+        case CellType::Diamond: {
+          const auto& cell_nodes = cell_to_node_matrix[j];
+          count_number_of_face_by_node += cell_nodes.size() * 3 * 2;
+          break;
+        }
+        default: {
+          std::ostringstream error_msg;
+          error_msg << name(cell_type_vector[j]) << ": unexpected cell type in dimension 3";
+          throw UnexpectedError(error_msg.str());
+        }
+        }
+      }
+      return count_number_of_face_by_node;
+    }
+  }();
 
-        // face 2
-        Face f2({cell_nodes[0], cell_nodes[4], cell_nodes[7], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+  Array<unsigned int> dup_faces_to_node_list(total_number_of_face_by_node);
+  Array<unsigned int> dup_face_to_node_row(total_number_of_faces + 1);
+  size_t i_face           = 0;
+  dup_face_to_node_row[0] = 0;
 
-        // face 3
-        Face f3({cell_nodes[1], cell_nodes[2], cell_nodes[6], cell_nodes[5]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+  Array<unsigned short> cell_nb_faces(cell_to_node_matrix.numberOfRows());
+  {
+    size_t i_face_node = 0;
+    for (CellId j = 0; j < cell_to_node_matrix.numberOfRows(); ++j) {
+      const auto& cell_nodes = cell_to_node_matrix[j];
+
+      if constexpr (Dimension == 2) {
+        switch (cell_type_vector[j]) {
+        case CellType::Triangle: {
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          cell_nb_faces[j] = 3;
+          break;
+        }
+        case CellType::Quadrangle: {
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+          i_face++;
+          cell_nb_faces[j] = 4;
+          break;
+        }
+        case CellType::Polygon: {
+          for (size_t i = 0; i < cell_nodes.size(); ++i) {
+            dup_faces_to_node_list[i_face_node] = cell_nodes[i];
+            i_face_node++;
+            dup_faces_to_node_list[i_face_node] = cell_nodes[(i + 1) % cell_nodes.size()];
+            i_face_node++;
+
+            dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 2;
+            i_face++;
+          }
 
-        // face 4
-        Face f4({cell_nodes[0], cell_nodes[1], cell_nodes[5], cell_nodes[4]}, node_number_vector);
-        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+          cell_nb_faces[j] = cell_nodes.size();
+          break;
+        }
+        default: {
+          std::ostringstream error_msg;
+          error_msg << name(cell_type_vector[j]) << ": unexpected cell type in dimension 2";
+          throw UnexpectedError(error_msg.str());
+        }
+        }
+      } else if constexpr (Dimension == 3) {
+        switch (cell_type_vector[j]) {
+        case CellType::Hexahedron: {
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[4];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[5];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[6];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[7];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[4];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[7];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[6];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[5];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[5];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[4];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[7];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[6];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          cell_nb_faces[j] = 6;
+          break;
+        }
+        case CellType::Tetrahedron: {
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
 
-        // face 5
-        Face f5({cell_nodes[3], cell_nodes[7], cell_nodes[6], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f5].emplace_back(std::make_tuple(j, 5, f5.reversed()));
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+          i_face++;
 
-        cell_nb_faces[j] = 6;
-        break;
-      }
-      case CellType::Tetrahedron: {
-        cell_nb_faces[j] = 4;
-        // face 0
-        Face f0({cell_nodes[1], cell_nodes[2], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes[0], cell_nodes[3], cell_nodes[2]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes[0], cell_nodes[1], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
-
-        // face 3
-        Face f3({cell_nodes[0], cell_nodes[2], cell_nodes[1]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
-        break;
-      }
-      case CellType::Prism: {
-        // face 0
-        Face f0({cell_nodes[2], cell_nodes[1], cell_nodes[0]}, node_number_vector);
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
 
-        // face 1
-        Face f1({cell_nodes[3], cell_nodes[4], cell_nodes[5]}, node_number_vector);
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+          i_face++;
 
-        // face 2
-        Face f2({cell_nodes[1], cell_nodes[2], cell_nodes[5], cell_nodes[4]}, node_number_vector);
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
 
-        // face 3
-        Face f3({cell_nodes[0], cell_nodes[1], cell_nodes[4], cell_nodes[3]}, node_number_vector);
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+          i_face++;
 
-        // face 4
-        Face f4({cell_nodes[2], cell_nodes[0], cell_nodes[3], cell_nodes[5]}, node_number_vector);
-        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
 
-        cell_nb_faces[j] = 5;
-        break;
-      }
-      case CellType::Pyramid: {
-        cell_nb_faces[j] = cell_nodes.size();
-        std::vector<unsigned int> base_nodes(cell_nodes.size() - 1);
-        for (size_t i = 0; i < base_nodes.size(); ++i) {
-          base_nodes[base_nodes.size() - 1 - i] = cell_nodes[i];
-        }
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+          i_face++;
 
-        // base face
-        {
-          Face base_face(base_nodes, node_number_vector);
-          face_cells_map[base_face].emplace_back(std::make_tuple(j, 0, base_face.reversed()));
+          cell_nb_faces[j] = 4;
+          break;
         }
-        // side faces
-        const auto pyramid_vertex = cell_nodes[cell_nodes.size() - 1];
-        for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
-          Face side_face({base_nodes[(i_node + 1) % base_nodes.size()], base_nodes[i_node], pyramid_vertex},
-                         node_number_vector);
-          face_cells_map[side_face].emplace_back(std::make_tuple(j, i_node + 1, side_face.reversed()));
+        case CellType::Prism: {
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[4];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[5];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[5];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[4];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[1];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[4];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[2];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[0];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[3];
+          dup_faces_to_node_list[i_face_node++] = cell_nodes[5];
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 4;
+          i_face++;
+
+          cell_nb_faces[j] = 5;
+          break;
         }
-        break;
-      }
-      case CellType::Diamond: {
-        cell_nb_faces[j] = 2 * (cell_nodes.size() - 2);
-        std::vector<unsigned int> base_nodes;
-        std::copy_n(cell_nodes.begin() + 1, cell_nodes.size() - 2, std::back_inserter(base_nodes));
+        case CellType::Pyramid: {
+          cell_nb_faces[j] = cell_nodes.size();
+          std::vector<unsigned int> base_nodes(cell_nodes.size() - 1);
+          for (size_t i = 0; i < base_nodes.size(); ++i) {
+            base_nodes[base_nodes.size() - 1 - i] = cell_nodes[i];
+          }
 
-        {   // top faces
-          const auto top_vertex = cell_nodes[cell_nodes.size() - 1];
+          for (size_t i = 0; i < base_nodes.size(); ++i) {
+            dup_faces_to_node_list[i_face_node++] = base_nodes[i];
+          }
+
+          dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + base_nodes.size();
+          i_face++;
+
+          // side faces
+          const auto pyramid_vertex = cell_nodes[cell_nodes.size() - 1];
           for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
-            Face top_face({base_nodes[i_node], base_nodes[(i_node + 1) % base_nodes.size()], top_vertex},
-                          node_number_vector);
-            face_cells_map[top_face].emplace_back(std::make_tuple(j, i_node, top_face.reversed()));
+            dup_faces_to_node_list[i_face_node++] = base_nodes[(i_node + 1) % base_nodes.size()];
+            dup_faces_to_node_list[i_face_node++] = base_nodes[i_node];
+            dup_faces_to_node_list[i_face_node++] = pyramid_vertex;
+
+            dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+            i_face++;
           }
+          break;
         }
+        case CellType::Diamond: {
+          auto base_nodes = [&](size_t i) { return cell_nodes[i + 1]; };
+
+          {   // top faces
+            const auto top_vertex = cell_nodes[cell_nodes.size() - 1];
+            for (size_t i_node = 0; i_node < cell_nodes.size() - 2; ++i_node) {
+              dup_faces_to_node_list[i_face_node++] = base_nodes(i_node);
+              dup_faces_to_node_list[i_face_node++] = base_nodes((i_node + 1) % (cell_nodes.size() - 2));
+              dup_faces_to_node_list[i_face_node++] = top_vertex;
+
+              dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+              i_face++;
+            }
+          }
 
-        {   // bottom faces
-          const auto bottom_vertex = cell_nodes[0];
-          for (size_t i_node = 0; i_node < base_nodes.size(); ++i_node) {
-            Face bottom_face({base_nodes[(i_node + 1) % base_nodes.size()], base_nodes[i_node], bottom_vertex},
-                             node_number_vector);
-            face_cells_map[bottom_face].emplace_back(
-              std::make_tuple(j, i_node + base_nodes.size(), bottom_face.reversed()));
+          {   // bottom faces
+            const auto bottom_vertex = cell_nodes[0];
+            for (size_t i_node = 0; i_node < cell_nodes.size() - 2; ++i_node) {
+              dup_faces_to_node_list[i_face_node++] = base_nodes((i_node + 1) % (cell_nodes.size() - 2));
+              dup_faces_to_node_list[i_face_node++] = base_nodes(i_node);
+              dup_faces_to_node_list[i_face_node++] = bottom_vertex;
+
+              dup_face_to_node_row[i_face + 1] = dup_face_to_node_row[i_face] + 3;
+              i_face++;
+            }
           }
+          cell_nb_faces[j] = 2 * (cell_nodes.size() - 2);
+          break;
+        }
+        default: {
+          std::ostringstream error_msg;
+          error_msg << name(cell_type_vector[j]) << ": unexpected cell type in dimension 3";
+          throw UnexpectedError(error_msg.str());
+        }
         }
-        break;
       }
-      default: {
-        std::ostringstream error_msg;
-        error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 3";
-        throw UnexpectedError(error_msg.str());
+    }
+  }
+
+  Array<bool> cell_face_is_reversed(total_number_of_faces);
+
+  if constexpr (Dimension == 2) {
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      if (node_number_vector[dup_faces_to_node_list[2 * i_face]] >
+          node_number_vector[dup_faces_to_node_list[2 * i_face + 1]]) {
+        std::swap(dup_faces_to_node_list[2 * i_face], dup_faces_to_node_list[2 * i_face + 1]);
+        cell_face_is_reversed[i_face] = true;
+      } else {
+        cell_face_is_reversed[i_face] = false;
       }
+    }
+  } else if constexpr (Dimension == 3) {
+    std::vector<int> buffer;
+
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      const size_t face_node_number      = dup_face_to_node_row[i_face + 1] - dup_face_to_node_row[i_face];
+      size_t i_face_node_smallest_number = 0;
+      for (size_t i_face_node = 1; i_face_node < face_node_number; ++i_face_node) {
+        if (node_number_vector[dup_faces_to_node_list[dup_face_to_node_row[i_face] + i_face_node]] <
+            node_number_vector[dup_faces_to_node_list[dup_face_to_node_row[i_face] + i_face_node_smallest_number]]) {
+          i_face_node_smallest_number = i_face_node;
+        }
       }
+
+      if (i_face_node_smallest_number != 0) {
+        buffer.resize(face_node_number);
+        for (size_t i_node = i_face_node_smallest_number; i_node < face_node_number; ++i_node) {
+          buffer[i_node - i_face_node_smallest_number] = dup_faces_to_node_list[dup_face_to_node_row[i_face] + i_node];
+        }
+        for (size_t i_node = 0; i_node < i_face_node_smallest_number; ++i_node) {
+          buffer[i_node + face_node_number - i_face_node_smallest_number] =
+            dup_faces_to_node_list[dup_face_to_node_row[i_face] + i_node];
+        }
+
+        for (size_t i_node = 0; i_node < face_node_number; ++i_node) {
+          dup_faces_to_node_list[dup_face_to_node_row[i_face] + i_node] = buffer[i_node];
+        }
+      }
+
+      if (node_number_vector[dup_faces_to_node_list[dup_face_to_node_row[i_face] + 1]] >
+          node_number_vector[dup_faces_to_node_list[dup_face_to_node_row[i_face + 1] - 1]]) {
+        for (size_t i_node = 1; i_node <= (face_node_number + 1) / 2 - 1; ++i_node) {
+          std::swap(dup_faces_to_node_list[dup_face_to_node_row[i_face] + i_node],
+                    dup_faces_to_node_list[dup_face_to_node_row[i_face + 1] - i_node]);
+        }
+
+        cell_face_is_reversed[i_face] = true;
+      } else {
+        cell_face_is_reversed[i_face] = false;
+      }
+    }
+  }
+
+  Array<unsigned int> node_to_duplicate_face_row(node_number_vector.size() + 1);
+  node_to_duplicate_face_row.fill(0);
+  for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+    for (size_t i_node_face = dup_face_to_node_row[i_face]; i_node_face < dup_face_to_node_row[i_face + 1];
+         ++i_node_face) {
+      node_to_duplicate_face_row[dup_faces_to_node_list[i_node_face] + 1]++;
     }
   }
 
+  for (size_t i_node = 1; i_node < node_to_duplicate_face_row.size(); ++i_node) {
+    node_to_duplicate_face_row[i_node] += node_to_duplicate_face_row[i_node - 1];
+  }
+
+  Array<unsigned int> node_duplicate_face_list(node_to_duplicate_face_row[node_to_duplicate_face_row.size() - 1]);
+
   {
-    descriptor.cell_to_face_vector.resize(descriptor.cell_to_node_vector.size());
-    for (CellId j = 0; j < descriptor.cell_to_face_vector.size(); ++j) {
-      descriptor.cell_to_face_vector[j].resize(cell_nb_faces[j]);
+    Array<unsigned int> node_duplicate_face_row_idx(node_number_vector.size());
+    node_duplicate_face_row_idx.fill(0);
+
+    for (size_t i_face = 0; i_face < total_number_of_faces; ++i_face) {
+      for (size_t i_node_face = dup_face_to_node_row[i_face]; i_node_face < dup_face_to_node_row[i_face + 1];
+           ++i_node_face) {
+        const size_t node_id = dup_faces_to_node_list[i_node_face];
+        node_duplicate_face_list[node_to_duplicate_face_row[node_id] + node_duplicate_face_row_idx[node_id]] = i_face;
+        node_duplicate_face_row_idx[node_id]++;
+      }
     }
-    FaceId l = 0;
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& cells_vector = face_cells_vector.second;
-      for (unsigned short lj = 0; lj < cells_vector.size(); ++lj) {
-        const auto& [cell_number, cell_local_face, reversed]         = cells_vector[lj];
-        descriptor.cell_to_face_vector[cell_number][cell_local_face] = l;
-      }
-      ++l;
+  }
+
+  Array<unsigned int> dup_face_to_face(total_number_of_faces);
+  parallel_for(
+    total_number_of_faces, PUGS_LAMBDA(size_t i_face) { dup_face_to_face[i_face] = i_face; });
+
+  auto is_same_face = [=](const size_t i_face, const size_t j_face) {
+    if ((dup_face_to_node_row[i_face + 1] - dup_face_to_node_row[i_face]) !=
+        (dup_face_to_node_row[j_face + 1] - dup_face_to_node_row[j_face])) {
+      return false;
+    } else {
+      auto i_face_node = dup_face_to_node_row[i_face];
+      auto j_face_node = dup_face_to_node_row[j_face];
+      while (i_face_node < dup_face_to_node_row[i_face + 1]) {
+        if (dup_faces_to_node_list[i_face_node] != dup_faces_to_node_list[j_face_node]) {
+          return false;
+        }
+        i_face_node++;
+        j_face_node++;
+      }
+      return true;
+    }
+  };
+
+  size_t nb_dup_faces = 0;
+  for (size_t i_node = 0; i_node < node_number_vector.size(); ++i_node) {
+    for (size_t i_node_face = node_to_duplicate_face_row[i_node];
+         i_node_face < node_to_duplicate_face_row[i_node + 1] - 1; ++i_node_face) {
+      for (size_t j_node_face = i_node_face + 1; j_node_face < node_to_duplicate_face_row[i_node + 1]; ++j_node_face) {
+        if (dup_face_to_face[node_duplicate_face_list[i_node_face]] ==
+            dup_face_to_face[node_duplicate_face_list[j_node_face]])
+          continue;
+        if (is_same_face(node_duplicate_face_list[i_node_face], node_duplicate_face_list[j_node_face])) {
+          dup_face_to_face[node_duplicate_face_list[j_node_face]] =
+            dup_face_to_face[node_duplicate_face_list[i_node_face]];
+          nb_dup_faces++;
+        }
+      }
     }
   }
 
+  // compute face_id
+  Array<FaceId> dup_face_to_face_id(total_number_of_faces);
   {
-    descriptor.cell_face_is_reversed_vector.resize(descriptor.cell_to_node_vector.size());
-    for (CellId j = 0; j < descriptor.cell_face_is_reversed_vector.size(); ++j) {
-      descriptor.cell_face_is_reversed_vector[j] = Array<bool>(cell_nb_faces[j]);
+    FaceId face_id = 0;
+    for (size_t i_dup_face = 0; i_dup_face < total_number_of_faces; ++i_dup_face) {
+      if (dup_face_to_face[i_dup_face] == i_dup_face) {
+        dup_face_to_face_id[i_dup_face] = face_id;
+        ++face_id;
+      } else {
+        size_t i_face = dup_face_to_face[i_dup_face];
+        while (i_face != dup_face_to_face[i_face]) {
+          i_face = dup_face_to_face[i_face];
+        }
+        dup_face_to_face_id[i_dup_face] = dup_face_to_face_id[i_face];
+      }
     }
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& cells_vector = face_cells_vector.second;
-      for (unsigned short lj = 0; lj < cells_vector.size(); ++lj) {
-        const auto& [cell_number, cell_local_face, reversed]                  = cells_vector[lj];
-        descriptor.cell_face_is_reversed_vector[cell_number][cell_local_face] = reversed;
+  }
+
+  Array<unsigned int> node_to_face_row(node_to_duplicate_face_row.size());
+  {
+    unsigned int nb_faces = 0;
+    for (size_t i_node = 0; i_node < node_to_duplicate_face_row.size() - 1; ++i_node) {
+      node_to_face_row[i_node] = nb_faces;
+      for (size_t i_face = node_to_duplicate_face_row[i_node]; i_face < node_to_duplicate_face_row[i_node + 1];
+           ++i_face) {
+        if (dup_face_to_face[node_duplicate_face_list[i_face]] == node_duplicate_face_list[i_face]) {
+          ++nb_faces;
+        }
+      }
+    }
+    node_to_face_row[node_to_duplicate_face_row.size() - 1] = nb_faces;
+  }
+
+  Array<unsigned int> node_to_face_list(node_to_face_row[node_to_duplicate_face_row.size() - 1]);
+  {
+    unsigned int i_node_to_face = 0;
+    for (size_t i_node = 0; i_node < node_to_duplicate_face_row.size() - 1; ++i_node) {
+      for (size_t i_face = node_to_duplicate_face_row[i_node]; i_face < node_to_duplicate_face_row[i_node + 1];
+           ++i_face) {
+        if (dup_face_to_face[node_duplicate_face_list[i_face]] == node_duplicate_face_list[i_face]) {
+          node_to_face_list[i_node_to_face++] = dup_face_to_face_id[node_duplicate_face_list[i_face]];
+        }
+      }
+    }
+  }
+
+  descriptor.setNodeToFaceMatrix(ConnectivityMatrix(node_to_face_row, node_to_face_list));
+
+  Array<unsigned int> cell_to_face_row(cell_nb_faces.size() + 1);
+  cell_to_face_row[0] = 0;
+  for (size_t i = 0; i < cell_nb_faces.size(); ++i) {
+    cell_to_face_row[i + 1] = cell_to_face_row[i] + cell_nb_faces[i];
+  }
+
+  Array<unsigned int> cell_to_face_list(cell_to_face_row[cell_to_face_row.size() - 1]);
+  {
+    size_t i_cell_face = 0;
+    for (CellId cell_id = 0; cell_id < cell_nb_faces.size(); ++cell_id) {
+      for (size_t i_face = 0; i_face < cell_nb_faces[cell_id]; ++i_face) {
+        cell_to_face_list[i_cell_face++] = dup_face_to_face_id[cell_to_face_row[cell_id] + i_face];
       }
     }
   }
 
+  descriptor.setCellToFaceMatrix(ConnectivityMatrix(cell_to_face_row, cell_to_face_list));
+
+  descriptor.setFaceNumberVector([&] {
+    Array<int> face_number_vector(total_number_of_faces - nb_dup_faces);
+    parallel_for(
+      face_number_vector.size(), PUGS_LAMBDA(const size_t l) { face_number_vector[l] = l; });
+    return face_number_vector;
+  }());
+
+  Array<unsigned int> face_ending(total_number_of_faces - nb_dup_faces + 1);
   {
-    descriptor.face_to_node_vector.resize(face_cells_map.size());
-    int l = 0;
-    for (const auto& face_info : face_cells_map) {
-      const Face& face                  = face_info.first;
-      descriptor.face_to_node_vector[l] = face.nodeIdList();
-      ++l;
+    size_t i_face  = 0;
+    face_ending[0] = 0;
+    for (size_t i_dup_face = 0; i_dup_face < dup_face_to_face.size(); ++i_dup_face) {
+      if (dup_face_to_face[i_dup_face] == i_dup_face) {
+        face_ending[i_face + 1] =
+          dup_face_to_node_row[i_dup_face + 1] - dup_face_to_node_row[i_dup_face] + face_ending[i_face];
+        ++i_face;
+      }
     }
   }
 
+  Array<unsigned int> faces_node_list(face_ending[face_ending.size() - 1]);
   {
-    // Face numbers may change if numbers are provided in the file
-    descriptor.face_number_vector.resize(face_cells_map.size());
-    for (size_t l = 0; l < face_cells_map.size(); ++l) {
-      descriptor.face_number_vector[l] = l;
+    size_t i_face  = 0;
+    face_ending[0] = 0;
+    for (size_t i_dup_face = 0; i_dup_face < dup_face_to_face.size(); ++i_dup_face) {
+      if (dup_face_to_face[i_dup_face] == i_dup_face) {
+        size_t dup_face_node_begin = dup_face_to_node_row[i_dup_face];
+        size_t face_node_begin     = face_ending[i_face];
+
+        for (size_t i_face_node = 0;
+             i_face_node < dup_face_to_node_row[i_dup_face + 1] - dup_face_to_node_row[i_dup_face]; ++i_face_node) {
+          faces_node_list[face_node_begin + i_face_node] = dup_faces_to_node_list[dup_face_node_begin + i_face_node];
+        }
+        ++i_face;
+      }
     }
   }
+
+  descriptor.setFaceToNodeMatrix(ConnectivityMatrix(face_ending, faces_node_list));
+
+  descriptor.setCellFaceIsReversed(cell_face_is_reversed);
 }
 
 template <size_t Dimension>
@@ -257,206 +598,331 @@ void
 ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(ConnectivityDescriptor& descriptor)
 {
   static_assert(Dimension == 3, "Invalid dimension to compute face-edge connectivities");
-  using FaceEdgeInfo = std::tuple<FaceId, unsigned short, bool>;
-  using Edge         = ConnectivityFace<2>;
-
-  const auto& node_number_vector = descriptor.node_number_vector;
-  Array<unsigned short> face_nb_edges(descriptor.face_to_node_vector.size());
-  std::map<Edge, std::vector<FaceEdgeInfo>> edge_faces_map;
-  for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-    const auto& face_nodes = descriptor.face_to_node_vector[l];
-
-    face_nb_edges[l] = face_nodes.size();
-    for (size_t r = 0; r < face_nodes.size() - 1; ++r) {
-      Edge e({face_nodes[r], face_nodes[r + 1]}, node_number_vector);
-      edge_faces_map[e].emplace_back(std::make_tuple(l, r, e.reversed()));
-    }
-    {
-      Edge e({face_nodes[face_nodes.size() - 1], face_nodes[0]}, node_number_vector);
-      edge_faces_map[e].emplace_back(std::make_tuple(l, face_nodes.size() - 1, e.reversed()));
+
+  const auto& face_to_node_matrix = descriptor.faceToNodeMatrix();
+
+  Array<const unsigned int> face_to_edge_row = face_to_node_matrix.rowsMap();
+
+  const size_t total_number_of_face_edges = face_to_edge_row[face_to_edge_row.size() - 1];
+
+  const size_t total_number_of_node_by_face_edges = 2 * total_number_of_face_edges;
+
+  Array<unsigned int> face_edge_to_node_list(total_number_of_node_by_face_edges);
+  {
+    size_t i_edge_node = 0;
+    for (size_t i_face = 0; i_face < face_to_edge_row.size() - 1; ++i_face) {
+      const auto& face_node_list = face_to_node_matrix[i_face];
+      for (size_t i_node = 0; i_node < face_node_list.size() - 1; ++i_node) {
+        face_edge_to_node_list[i_edge_node++] = face_node_list[i_node];
+        face_edge_to_node_list[i_edge_node++] = face_node_list[i_node + 1];
+      }
+      face_edge_to_node_list[i_edge_node++] = face_node_list[face_node_list.size() - 1];
+      face_edge_to_node_list[i_edge_node++] = face_node_list[0];
     }
   }
 
-  std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_id_map;
-  {
-    descriptor.face_to_edge_vector.resize(descriptor.face_to_node_vector.size());
-    for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-      descriptor.face_to_edge_vector[l].resize(face_nb_edges[l]);
+  auto node_number_vector = descriptor.nodeNumberVector();
+  Array<bool> face_edge_is_reversed(total_number_of_face_edges);
+  for (size_t i_edge = 0; i_edge < total_number_of_face_edges; ++i_edge) {
+    if (node_number_vector[face_edge_to_node_list[2 * i_edge]] >
+        node_number_vector[face_edge_to_node_list[2 * i_edge + 1]]) {
+      std::swap(face_edge_to_node_list[2 * i_edge], face_edge_to_node_list[2 * i_edge + 1]);
+      face_edge_is_reversed[i_edge] = true;
+    } else {
+      face_edge_is_reversed[i_edge] = false;
     }
-    EdgeId e = 0;
-    for (const auto& edge_faces_vector : edge_faces_map) {
-      const auto& faces_vector = edge_faces_vector.second;
-      for (unsigned short l = 0; l < faces_vector.size(); ++l) {
-        const auto& [face_number, face_local_edge, reversed]         = faces_vector[l];
-        descriptor.face_to_edge_vector[face_number][face_local_edge] = e;
-      }
-      edge_id_map[edge_faces_vector.first] = e;
-      ++e;
+  }
+
+  const size_t total_number_of_edges = face_edge_is_reversed.size();
+  Array<unsigned int> node_to_face_edge_row(node_number_vector.size() + 1);
+  node_to_face_edge_row.fill(0);
+  for (size_t i_edge = 0; i_edge < total_number_of_edges; ++i_edge) {
+    for (size_t i_edge_node = 0; i_edge_node < 2; ++i_edge_node) {
+      node_to_face_edge_row[face_edge_to_node_list[2 * i_edge + i_edge_node] + 1]++;
     }
   }
+  for (size_t i_node = 1; i_node < node_to_face_edge_row.size(); ++i_node) {
+    node_to_face_edge_row[i_node] += node_to_face_edge_row[i_node - 1];
+  }
+
+  Array<unsigned int> node_to_face_edge_list(node_to_face_edge_row[node_to_face_edge_row.size() - 1]);
 
   {
-    descriptor.face_edge_is_reversed_vector.resize(descriptor.face_to_node_vector.size());
-    for (FaceId j = 0; j < descriptor.face_edge_is_reversed_vector.size(); ++j) {
-      descriptor.face_edge_is_reversed_vector[j] = Array<bool>(face_nb_edges[j]);
+    Array<unsigned int> node_to_face_edge_row_idx(node_number_vector.size());
+    node_to_face_edge_row_idx.fill(0);
+
+    for (size_t i_edge = 0; i_edge < total_number_of_edges; ++i_edge) {
+      for (size_t i_edge_node = 0; i_edge_node < 2; ++i_edge_node) {
+        const size_t node_id = face_edge_to_node_list[2 * i_edge + i_edge_node];
+
+        node_to_face_edge_list[node_to_face_edge_row[node_id] + node_to_face_edge_row_idx[node_id]] = i_edge;
+        node_to_face_edge_row_idx[node_id]++;
+      }
     }
-    for (const auto& edge_faces_vector : edge_faces_map) {
-      const auto& faces_vector = edge_faces_vector.second;
-      for (unsigned short lj = 0; lj < faces_vector.size(); ++lj) {
-        const auto& [face_number, face_local_edge, reversed]                  = faces_vector[lj];
-        descriptor.face_edge_is_reversed_vector[face_number][face_local_edge] = reversed;
+  }
+
+  Array<unsigned int> face_edge_to_edge(total_number_of_edges);
+  parallel_for(
+    total_number_of_edges, PUGS_LAMBDA(size_t i_edge) { face_edge_to_edge[i_edge] = i_edge; });
+
+  auto is_same_face = [=](const size_t i_edge, const size_t j_edge) {
+    auto i_edge_first_node = 2 * i_edge;
+    auto j_edge_first_node = 2 * j_edge;
+    return ((face_edge_to_node_list[i_edge_first_node] == face_edge_to_node_list[j_edge_first_node]) and
+            (face_edge_to_node_list[i_edge_first_node + 1] == face_edge_to_node_list[j_edge_first_node + 1]));
+  };
+
+  size_t nb_duplicate_edges = 0;
+  for (size_t i_node = 0; i_node < node_number_vector.size(); ++i_node) {
+    for (size_t i_node_face = node_to_face_edge_row[i_node]; i_node_face < node_to_face_edge_row[i_node + 1] - 1;
+         ++i_node_face) {
+      const unsigned int i_edge    = node_to_face_edge_list[i_node_face];
+      const unsigned int i_edge_id = face_edge_to_edge[i_edge];
+      for (size_t j_node_face = i_node_face + 1; j_node_face < node_to_face_edge_row[i_node + 1]; ++j_node_face) {
+        const unsigned int j_edge = node_to_face_edge_list[j_node_face];
+        unsigned int& j_edge_id   = face_edge_to_edge[j_edge];
+        if (i_edge_id != j_edge_id) {
+          if (is_same_face(i_edge, j_edge)) {
+            j_edge_id = i_edge_id;
+            nb_duplicate_edges++;
+          }
+        }
       }
     }
   }
 
+  // compute edge_id
+  Array<EdgeId> dup_edge_to_edge_id(total_number_of_edges);
   {
-    descriptor.edge_to_node_vector.resize(edge_faces_map.size());
-    int e = 0;
-    for (const auto& edge_info : edge_faces_map) {
-      const Edge& edge                  = edge_info.first;
-      descriptor.edge_to_node_vector[e] = edge.nodeIdList();
-      ++e;
+    EdgeId edge_id = 0;
+    for (size_t i_dup_edge = 0; i_dup_edge < total_number_of_edges; ++i_dup_edge) {
+      if (face_edge_to_edge[i_dup_edge] == i_dup_edge) {
+        dup_edge_to_edge_id[i_dup_edge] = edge_id++;
+      } else {
+        size_t i_edge = i_dup_edge;
+        do {
+          i_edge = face_edge_to_edge[i_edge];
+        } while (i_edge != face_edge_to_edge[i_edge]);
+
+        dup_edge_to_edge_id[i_dup_edge] = dup_edge_to_edge_id[i_edge];
+      }
     }
   }
 
+  Array<unsigned int> edge_to_node_list(2 * (total_number_of_edges - nb_duplicate_edges));
   {
-    // Edge numbers may change if numbers are provided in the file
-    descriptor.edge_number_vector.resize(edge_faces_map.size());
-    for (size_t e = 0; e < edge_faces_map.size(); ++e) {
-      descriptor.edge_number_vector[e] = e;
+    for (size_t i_dup_edge = 0; i_dup_edge < total_number_of_edges; ++i_dup_edge) {
+      if (face_edge_to_edge[i_dup_edge] == i_dup_edge) {
+        const EdgeId edge_id               = dup_edge_to_edge_id[i_dup_edge];
+        edge_to_node_list[2 * edge_id]     = face_edge_to_node_list[2 * i_dup_edge];
+        edge_to_node_list[2 * edge_id + 1] = face_edge_to_node_list[2 * i_dup_edge + 1];
+      }
     }
   }
 
-  {
-    descriptor.cell_to_edge_vector.reserve(descriptor.cell_to_node_vector.size());
-    for (CellId j = 0; j < descriptor.cell_to_node_vector.size(); ++j) {
-      const auto& cell_nodes = descriptor.cell_to_node_vector[j];
+  descriptor.setEdgeNumberVector([&] {
+    Array<int> edge_number_vector(total_number_of_edges - nb_duplicate_edges);
+    parallel_for(
+      edge_number_vector.size(), PUGS_LAMBDA(const size_t i_edge) { edge_number_vector[i_edge] = i_edge; });
+    return edge_number_vector;
+  }());
 
-      switch (descriptor.cell_type_vector[j]) {
-      case CellType::Tetrahedron: {
-        constexpr int local_edge[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {2, 3}, {3, 1}};
-        std::vector<unsigned int> cell_edge_vector;
-        cell_edge_vector.reserve(6);
-        for (int i_edge = 0; i_edge < 6; ++i_edge) {
-          const auto e = local_edge[i_edge];
-          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+  Array<unsigned int> edge_to_node_row(total_number_of_edges - nb_duplicate_edges + 1);
+  parallel_for(
+    edge_to_node_row.size(), PUGS_LAMBDA(const size_t i_edge) { edge_to_node_row[i_edge] = 2 * i_edge; });
+
+  descriptor.setEdgeToNodeMatrix(ConnectivityMatrix(edge_to_node_row, edge_to_node_list));
+
+  // Use real edge ids
+  for (size_t i_edge = 0; i_edge < face_edge_to_edge.size(); ++i_edge) {
+    face_edge_to_edge[i_edge] = dup_edge_to_edge_id[face_edge_to_edge[i_edge]];
+  }
+
+  descriptor.setFaceToEdgeMatrix(ConnectivityMatrix(face_to_edge_row, face_edge_to_edge));
+  descriptor.setFaceEdgeIsReversed(face_edge_is_reversed);
+
+  Array<size_t> node_to_duplicated_edge_id_list(node_to_face_edge_list.size());
+  for (size_t i_node_edge = 0; i_node_edge < node_to_duplicated_edge_id_list.size(); ++i_node_edge) {
+    node_to_duplicated_edge_id_list[i_node_edge] = dup_edge_to_edge_id[node_to_face_edge_list[i_node_edge]];
+  }
+
+  Array<bool> node_to_edge_is_duplicated(node_to_face_edge_list.size());
+  node_to_edge_is_duplicated.fill(false);
+
+  Array<unsigned int> node_nb_edges(node_number_vector.size());
+  node_nb_edges.fill(0);
+
+  for (size_t node_id = 0; node_id < node_number_vector.size(); ++node_id) {
+    size_t nb_dup_edges = 0;
+    for (EdgeId i_edge = node_to_face_edge_row[node_id]; i_edge < node_to_face_edge_row[node_id + 1] - 1; ++i_edge) {
+      if (not node_to_edge_is_duplicated[i_edge]) {
+        for (EdgeId j_edge = i_edge + 1; j_edge < node_to_face_edge_row[node_id + 1]; ++j_edge) {
+          if (node_to_duplicated_edge_id_list[i_edge] == node_to_duplicated_edge_id_list[j_edge]) {
+            node_to_edge_is_duplicated[j_edge] = true;
+            nb_dup_edges++;
           }
-          cell_edge_vector.push_back(i->second);
         }
-        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
-        break;
       }
-      case CellType::Hexahedron: {
-        constexpr int local_edge[12][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6},
-                                           {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}};
-        std::vector<unsigned int> cell_edge_vector;
-        cell_edge_vector.reserve(12);
-        for (int i_edge = 0; i_edge < 12; ++i_edge) {
-          const auto e = local_edge[i_edge];
-          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
-        }
-        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
-        break;
+    }
+    node_nb_edges[node_id] = node_to_face_edge_row[node_id + 1] - node_to_face_edge_row[node_id] - nb_dup_edges;
+  }
+
+  Array<unsigned int> node_to_edge_row(node_number_vector.size() + 1);
+  node_to_edge_row[0] = 0;
+  for (size_t node_id = 0; node_id < node_number_vector.size(); ++node_id) {
+    node_to_edge_row[node_id + 1] = node_to_edge_row[node_id] + node_nb_edges[node_id];
+  }
+
+  Array<unsigned int> node_to_edge_list(node_to_edge_row[node_to_edge_row.size() - 1]);
+  {
+    size_t l = 0;
+    for (size_t i_edge_id = 0; i_edge_id < node_to_duplicated_edge_id_list.size(); ++i_edge_id) {
+      if (not node_to_edge_is_duplicated[i_edge_id]) {
+        node_to_edge_list[l++] = node_to_duplicated_edge_id_list[i_edge_id];
       }
-      case CellType::Prism: {
-        constexpr int local_edge[12][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {0, 3}, {1, 4}, {2, 5}};
-        std::vector<unsigned int> cell_edge_vector;
-        cell_edge_vector.reserve(9);
-        for (int i_edge = 0; i_edge < 9; ++i_edge) {
-          const auto e = local_edge[i_edge];
-          Edge edge{{cell_nodes[e[0]], cell_nodes[e[1]]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
-          }
-          cell_edge_vector.push_back(i->second);
+    }
+  }
+
+  descriptor.setNodeToEdgeMatrix(ConnectivityMatrix(node_to_edge_row, node_to_edge_list));
+
+  auto find_edge = [=](const NodeId& node0_id, const NodeId& node1_id) -> EdgeId {
+    auto [first_node_id, second_node_id] = [&] {
+      if (node_number_vector[node0_id] < node_number_vector[node1_id]) {
+        return std::make_pair(node0_id, node1_id);
+      } else {
+        return std::make_pair(node1_id, node0_id);
+      }
+    }();
+
+    for (size_t i_node_edge = node_to_edge_row[first_node_id]; i_node_edge < node_to_edge_row[first_node_id + 1];
+         ++i_node_edge) {
+      EdgeId edge_id = node_to_edge_list[i_node_edge];
+      if (edge_to_node_list[2 * edge_id + 1] == second_node_id) {
+        return edge_id;
+      }
+    }
+    throw UnexpectedError("Cannot find cell edge in edge list");
+  };
+
+  const auto& cell_to_node_matrix = descriptor.cellToNodeMatrix();
+  const auto& cell_type_vector    = descriptor.cellTypeVector();
+  {
+    Array<unsigned int> cell_to_edge_row(cell_to_node_matrix.numberOfRows() + 1);
+    {
+      cell_to_edge_row[0] = 0;
+
+      for (CellId cell_id = 0; cell_id < cell_to_node_matrix.numberOfRows(); ++cell_id) {
+        switch (cell_type_vector[cell_id]) {
+        case CellType::Tetrahedron: {
+          cell_to_edge_row[cell_id + 1] = cell_to_edge_row[cell_id] + 6;
+          break;
+        }
+        case CellType::Hexahedron: {
+          cell_to_edge_row[cell_id + 1] = cell_to_edge_row[cell_id] + 12;
+          break;
+        }
+        case CellType::Prism: {
+          cell_to_edge_row[cell_id + 1] = cell_to_edge_row[cell_id] + 9;
+          break;
+        }
+        case CellType::Pyramid: {
+          cell_to_edge_row[cell_id + 1] = cell_to_edge_row[cell_id] + 2 * (cell_to_node_matrix[cell_id].size() - 1);
+          break;
+        }
+        case CellType::Diamond: {
+          cell_to_edge_row[cell_id + 1] = cell_to_edge_row[cell_id] + 3 * (cell_to_node_matrix[cell_id].size() - 2);
+          break;
+        }
+        default: {
+          std::stringstream error_msg;
+          error_msg << name(cell_type_vector[cell_id]) << ": unexpected cell type in dimension 3";
+          throw NotImplementedError(error_msg.str());
+        }
         }
-        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
-        break;
       }
-      case CellType::Pyramid: {
-        const size_t number_of_edges = 2 * cell_nodes.size();
-        std::vector<unsigned int> base_nodes;
-        std::copy_n(cell_nodes.begin(), cell_nodes.size() - 1, std::back_inserter(base_nodes));
-
-        std::vector<unsigned int> cell_edge_vector;
-        cell_edge_vector.reserve(number_of_edges);
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], base_nodes[(i_edge + 1) % base_nodes.size()]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+    }
+
+    Array<unsigned int> cell_to_edge_list(cell_to_edge_row[cell_to_edge_row.size() - 1]);
+    {
+      size_t i_cell_edge = 0;
+      for (CellId cell_id = 0; cell_id < cell_to_node_matrix.numberOfRows(); ++cell_id) {
+        const auto& cell_nodes = cell_to_node_matrix[cell_id];
+
+        switch (cell_type_vector[cell_id]) {
+        case CellType::Tetrahedron: {
+          constexpr int local_edge[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {2, 3}, {3, 1}};
+          for (int i_edge = 0; i_edge < 6; ++i_edge) {
+            const auto& e                    = local_edge[i_edge];
+            cell_to_edge_list[i_cell_edge++] = find_edge(cell_nodes[e[0]], cell_nodes[e[1]]);
           }
-          cell_edge_vector.push_back(i->second);
+          break;
         }
+        case CellType::Hexahedron: {
+          constexpr int local_edge[12][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6},
+                                             {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}};
 
-        const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], top_vertex}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+          for (int i_edge = 0; i_edge < 12; ++i_edge) {
+            const auto& e                    = local_edge[i_edge];
+            cell_to_edge_list[i_cell_edge++] = find_edge(cell_nodes[e[0]], cell_nodes[e[1]]);
           }
-          cell_edge_vector.push_back(i->second);
+          break;
         }
-        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
-        break;
-      }
-      case CellType::Diamond: {
-        const size_t number_of_edges = 3 * cell_nodes.size();
-        std::vector<unsigned int> base_nodes;
-        std::copy_n(cell_nodes.begin() + 1, cell_nodes.size() - 2, std::back_inserter(base_nodes));
-
-        std::vector<unsigned int> cell_edge_vector;
-        cell_edge_vector.reserve(number_of_edges);
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], base_nodes[(i_edge + 1) % base_nodes.size()]}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+        case CellType::Prism: {
+          constexpr int local_edge[9][2] = {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {0, 3}, {1, 4}, {2, 5}};
+          for (int i_edge = 0; i_edge < 9; ++i_edge) {
+            const auto& e                    = local_edge[i_edge];
+            cell_to_edge_list[i_cell_edge++] = find_edge(cell_nodes[e[0]], cell_nodes[e[1]]);
           }
-          cell_edge_vector.push_back(i->second);
+          break;
         }
+        case CellType::Pyramid: {
+          auto base_nodes = [&](size_t i) { return cell_nodes[i]; };
 
-        const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], top_vertex}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+          for (size_t i_edge = 0; i_edge < cell_nodes.size() - 1; ++i_edge) {
+            cell_to_edge_list[i_cell_edge++] =
+              find_edge(base_nodes(i_edge), base_nodes((i_edge + 1) % (cell_nodes.size() - 1)));
           }
-          cell_edge_vector.push_back(i->second);
-        }
 
-        const unsigned int bottom_vertex = cell_nodes[0];
-        for (size_t i_edge = 0; i_edge < base_nodes.size(); ++i_edge) {
-          Edge edge{{base_nodes[i_edge], bottom_vertex}, node_number_vector};
-          auto i = edge_id_map.find(edge);
-          if (i == edge_id_map.end()) {
-            throw NormalError("could not find this edge");
+          const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
+          for (size_t i_edge = 0; i_edge < cell_nodes.size() - 1; ++i_edge) {
+            cell_to_edge_list[i_cell_edge++] = find_edge(base_nodes(i_edge), top_vertex);
           }
-          cell_edge_vector.push_back(i->second);
+          break;
         }
+        case CellType::Diamond: {
+          auto base_nodes = [&](size_t i) { return cell_nodes[i + 1]; };
 
-        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
+          for (size_t i_edge = 0; i_edge < cell_nodes.size() - 2; ++i_edge) {
+            cell_to_edge_list[i_cell_edge++] =
+              find_edge(base_nodes(i_edge), base_nodes((i_edge + 1) % (cell_nodes.size() - 2)));
+          }
 
-        break;
-      }
-      default: {
-        std::stringstream error_msg;
-        error_msg << name(descriptor.cell_type_vector[j]) << ": unexpected cell type in dimension 3";
-        throw NotImplementedError(error_msg.str());
-      }
+          {
+            const unsigned int top_vertex = cell_nodes[cell_nodes.size() - 1];
+            for (size_t i_edge = 0; i_edge < cell_nodes.size() - 2; ++i_edge) {
+              cell_to_edge_list[i_cell_edge++] = find_edge(base_nodes(i_edge), top_vertex);
+            }
+          }
+
+          {
+            const unsigned int bottom_vertex = cell_nodes[0];
+            for (size_t i_edge = 0; i_edge < cell_nodes.size() - 2; ++i_edge) {
+              cell_to_edge_list[i_cell_edge++] = find_edge(base_nodes(i_edge), bottom_vertex);
+            }
+          }
+
+          break;
+        }
+        default: {
+          std::stringstream error_msg;
+          error_msg << name(cell_type_vector[cell_id]) << ": unexpected cell type in dimension 3";
+          throw NotImplementedError(error_msg.str());
+        }
+        }
       }
     }
+
+    descriptor.setCellToEdgeMatrix(ConnectivityMatrix(cell_to_edge_row, cell_to_edge_list));
   }
 }
 
diff --git a/src/mesh/ConnectivityBuilderBase.hpp b/src/mesh/ConnectivityBuilderBase.hpp
index f9b0d28a7a9389093348648573c85875f1e4a7de..a621e91f46ffe7e6ecb5ea6d496934a03571ba6e 100644
--- a/src/mesh/ConnectivityBuilderBase.hpp
+++ b/src/mesh/ConnectivityBuilderBase.hpp
@@ -15,9 +15,6 @@ class ConnectivityDescriptor;
 class ConnectivityBuilderBase
 {
  protected:
-  template <size_t Dimension>
-  class ConnectivityFace;
-
   std::shared_ptr<const IConnectivity> m_connectivity;
 
   template <size_t Dimension>
@@ -37,190 +34,4 @@ class ConnectivityBuilderBase
   ~ConnectivityBuilderBase() = default;
 };
 
-template <>
-class ConnectivityBuilderBase::ConnectivityFace<2>
-{
- public:
-  friend struct Hash;
-
-  struct Hash
-  {
-    size_t
-    operator()(const ConnectivityFace& f) const
-    {
-      size_t hash = 0;
-      hash ^= std::hash<unsigned int>()(f.m_node0_id);
-      hash ^= std::hash<unsigned int>()(f.m_node1_id) >> 1;
-      return hash;
-    }
-  };
-
- private:
-  const std::vector<int>& m_node_number_vector;
-
-  unsigned int m_node0_id;
-  unsigned int m_node1_id;
-
-  bool m_reversed;
-
- public:
-  std::vector<unsigned int>
-  nodeIdList() const
-  {
-    return {m_node0_id, m_node1_id};
-  }
-
-  bool
-  reversed() const
-  {
-    return m_reversed;
-  }
-
-  PUGS_INLINE
-  bool
-  operator==(const ConnectivityFace& f) const
-  {
-    return ((m_node0_id == f.m_node0_id) and (m_node1_id == f.m_node1_id));
-  }
-
-  PUGS_INLINE
-  bool
-  operator<(const ConnectivityFace& f) const
-  {
-    return ((m_node_number_vector[m_node0_id] < m_node_number_vector[f.m_node0_id]) or
-            ((m_node_number_vector[m_node0_id] == m_node_number_vector[f.m_node0_id]) and
-             (m_node_number_vector[m_node1_id] < m_node_number_vector[f.m_node1_id])));
-  }
-
-  PUGS_INLINE
-  ConnectivityFace(const std::vector<unsigned int>& node_id_list, const std::vector<int>& node_number_vector)
-    : m_node_number_vector(node_number_vector)
-  {
-    Assert(node_id_list.size() == 2);
-
-    if (m_node_number_vector[node_id_list[0]] < m_node_number_vector[node_id_list[1]]) {
-      m_node0_id = node_id_list[0];
-      m_node1_id = node_id_list[1];
-      m_reversed = false;
-    } else {
-      m_node0_id = node_id_list[1];
-      m_node1_id = node_id_list[0];
-      m_reversed = true;
-    }
-  }
-
-  PUGS_INLINE
-  ConnectivityFace(const ConnectivityFace&) = default;
-
-  PUGS_INLINE
-  ~ConnectivityFace() = default;
-};
-
-template <>
-class ConnectivityBuilderBase::ConnectivityFace<3>
-{
- public:
-  friend struct Hash;
-
-  struct Hash
-  {
-    size_t
-    operator()(const ConnectivityFace& f) const
-    {
-      size_t hash = 0;
-      for (size_t i = 0; i < f.m_node_id_list.size(); ++i) {
-        hash ^= std::hash<unsigned int>()(f.m_node_id_list[i]) >> i;
-      }
-      return hash;
-    }
-  };
-
- private:
-  bool m_reversed;
-  std::vector<NodeId::base_type> m_node_id_list;
-  const std::vector<int>& m_node_number_vector;
-
-  PUGS_INLINE
-  std::vector<unsigned int>
-  _sort(const std::vector<unsigned int>& node_list)
-  {
-    const auto min_id = std::min_element(node_list.begin(), node_list.end());
-    const int shift   = std::distance(node_list.begin(), min_id);
-
-    std::vector<unsigned int> rotated_node_list(node_list.size());
-    if (node_list[(shift + 1) % node_list.size()] > node_list[(shift + node_list.size() - 1) % node_list.size()]) {
-      for (size_t i = 0; i < node_list.size(); ++i) {
-        rotated_node_list[i] = node_list[(shift + node_list.size() - i) % node_list.size()];
-        m_reversed           = true;
-      }
-    } else {
-      for (size_t i = 0; i < node_list.size(); ++i) {
-        rotated_node_list[i] = node_list[(shift + i) % node_list.size()];
-      }
-    }
-
-    return rotated_node_list;
-  }
-
- public:
-  PUGS_INLINE
-  const bool&
-  reversed() const
-  {
-    return m_reversed;
-  }
-
-  PUGS_INLINE
-  const std::vector<unsigned int>&
-  nodeIdList() const
-  {
-    return m_node_id_list;
-  }
-
-  PUGS_INLINE
-  ConnectivityFace(const std::vector<unsigned int>& given_node_id_list, const std::vector<int>& node_number_vector)
-    : m_reversed(false), m_node_id_list(_sort(given_node_id_list)), m_node_number_vector(node_number_vector)
-  {
-    ;
-  }
-
- public:
-  bool
-  operator==(const ConnectivityFace& f) const
-  {
-    if (m_node_id_list.size() == f.nodeIdList().size()) {
-      for (size_t j = 0; j < m_node_id_list.size(); ++j) {
-        if (m_node_id_list[j] != f.nodeIdList()[j]) {
-          return false;
-        }
-      }
-      return true;
-    }
-    return false;
-  }
-
-  PUGS_INLINE
-  bool
-  operator<(const ConnectivityFace& f) const
-  {
-    const size_t min_nb_nodes = std::min(f.m_node_id_list.size(), m_node_id_list.size());
-    for (size_t i = 0; i < min_nb_nodes; ++i) {
-      if (m_node_id_list[i] < f.m_node_id_list[i])
-        return true;
-      if (m_node_id_list[i] != f.m_node_id_list[i])
-        return false;
-    }
-    return m_node_id_list.size() < f.m_node_id_list.size();
-  }
-
-  PUGS_INLINE
-  ConnectivityFace(const ConnectivityFace&) = default;
-
-  PUGS_INLINE
-  ConnectivityFace() = delete;
-
-  PUGS_INLINE
-  ~ConnectivityFace() = default;
-};
-
 #endif   // CONNECTIVITY_BUILDER_BASE_HPP
diff --git a/src/mesh/ConnectivityComputer.cpp b/src/mesh/ConnectivityComputer.cpp
index aa8ca779cfb965ffb3e85e4feb1b4b9feabb79ca..1d5514627a5dac75d41ba5e4d08fa182d37e552f 100644
--- a/src/mesh/ConnectivityComputer.cpp
+++ b/src/mesh/ConnectivityComputer.cpp
@@ -7,62 +7,90 @@
 
 template <typename ConnectivityType>
 PUGS_INLINE ConnectivityMatrix
-ConnectivityComputer::computeConnectivityMatrix(const ConnectivityType& connectivity,
-                                                ItemType item_type,
-                                                ItemType child_item_type) const
+ConnectivityComputer::computeInverseConnectivityMatrix(const ConnectivityType& connectivity,
+                                                       ItemType item_type,
+                                                       ItemType child_item_type) const
 {
-  ConnectivityMatrix item_to_child_item_matrix;
-  if (connectivity.isConnectivityMatrixBuilt(child_item_type, item_type)) {
-    const ConnectivityMatrix& child_to_item_matrix = connectivity.getMatrix(child_item_type, item_type);
+  if (item_type < child_item_type) {
+    ConnectivityMatrix item_to_child_item_matrix;
+    if (connectivity.isConnectivityMatrixBuilt(child_item_type, item_type)) {
+      const ConnectivityMatrix& child_to_item_matrix = connectivity.getMatrix(child_item_type, item_type);
+
+      switch (child_item_type) {
+      case ItemType::cell: {
+        item_to_child_item_matrix =
+          this->_computeInverseConnectivity<ConnectivityType, ItemType::cell>(connectivity, child_to_item_matrix);
+        break;
+      }
+      case ItemType::face: {
+        item_to_child_item_matrix =
+          this->_computeInverseConnectivity<ConnectivityType, ItemType::face>(connectivity, child_to_item_matrix);
+        break;
+      }
+      case ItemType::edge: {
+        item_to_child_item_matrix =
+          this->_computeInverseConnectivity<ConnectivityType, ItemType::edge>(connectivity, child_to_item_matrix);
+        break;
+      }
+        // LCOV_EXCL_START
+      default: {
+        throw UnexpectedError("node cannot be child item when computing inverse connectivity");
+      }
+        // LCOV_EXCL_STOP
+      }
+    } else {
+      std::stringstream error_msg;
+      error_msg << "unable to compute connectivity " << itemName(item_type) << " -> " << itemName(child_item_type);
+      throw UnexpectedError(error_msg.str());
+    }
 
-    item_to_child_item_matrix = this->_computeInverse(child_to_item_matrix);
+    return item_to_child_item_matrix;
   } else {
     std::stringstream error_msg;
-    error_msg << "unable to compute connectivity " << itemName(item_type) << " -> " << itemName(child_item_type);
+    error_msg << "cannot deduce " << itemName(item_type) << " -> " << itemName(child_item_type) << " connectivity";
     throw UnexpectedError(error_msg.str());
   }
-
-  return item_to_child_item_matrix;
 }
 
-template ConnectivityMatrix ConnectivityComputer::computeConnectivityMatrix(const Connectivity1D&,
-                                                                            ItemType,
-                                                                            ItemType) const;
+template ConnectivityMatrix ConnectivityComputer::computeInverseConnectivityMatrix(const Connectivity1D&,
+                                                                                   ItemType,
+                                                                                   ItemType) const;
 
-template ConnectivityMatrix ConnectivityComputer::computeConnectivityMatrix(const Connectivity2D&,
-                                                                            ItemType,
-                                                                            ItemType) const;
+template ConnectivityMatrix ConnectivityComputer::computeInverseConnectivityMatrix(const Connectivity2D&,
+                                                                                   ItemType,
+                                                                                   ItemType) const;
 
-template ConnectivityMatrix ConnectivityComputer::computeConnectivityMatrix(const Connectivity3D&,
-                                                                            ItemType,
-                                                                            ItemType) const;
+template ConnectivityMatrix ConnectivityComputer::computeInverseConnectivityMatrix(const Connectivity3D&,
+                                                                                   ItemType,
+                                                                                   ItemType) const;
 
+template <typename ConnectivityType, ItemType child_item_type>
 ConnectivityMatrix
-ConnectivityComputer::_computeInverse(const ConnectivityMatrix& item_to_child_matrix) const
+ConnectivityComputer::_computeInverseConnectivity(const ConnectivityType& connectivity,
+                                                  const ConnectivityMatrix& child_to_item_matrix) const
 {
-  const size_t& number_of_rows = item_to_child_matrix.numberOfRows();
+  const size_t number_of_transposed_columns = child_to_item_matrix.numberOfRows();
 
-  if ((item_to_child_matrix.values().size() > 0)) {
-    const size_t& number_of_columns = max(item_to_child_matrix.values());
+  if ((child_to_item_matrix.values().size() > 0)) {
+    const size_t number_of_transposed_rows = max(child_to_item_matrix.values()) + 1;
 
-    Array<uint32_t> transposed_next_free_column_index(number_of_columns + 1);
+    Array<uint32_t> transposed_next_free_column_index(number_of_transposed_rows);
     transposed_next_free_column_index.fill(0);
 
     Array<uint32_t> transposed_rows_map(transposed_next_free_column_index.size() + 1);
     transposed_rows_map.fill(0);
-    for (size_t i = 0; i < number_of_rows; ++i) {
-      for (size_t j = item_to_child_matrix.rowsMap()[i]; j < item_to_child_matrix.rowsMap()[i + 1]; ++j) {
-        transposed_rows_map[item_to_child_matrix.values()[j] + 1]++;
+    for (size_t i = 0; i < number_of_transposed_columns; ++i) {
+      for (size_t j = child_to_item_matrix.rowsMap()[i]; j < child_to_item_matrix.rowsMap()[i + 1]; ++j) {
+        transposed_rows_map[child_to_item_matrix.values()[j] + 1]++;
       }
     }
     for (size_t i = 1; i < transposed_rows_map.size(); ++i) {
       transposed_rows_map[i] += transposed_rows_map[i - 1];
     }
     Array<uint32_t> transposed_column_indices(transposed_rows_map[transposed_rows_map.size() - 1]);
-
-    for (size_t i = 0; i < number_of_rows; ++i) {
-      for (size_t j = item_to_child_matrix.rowsMap()[i]; j < item_to_child_matrix.rowsMap()[i + 1]; ++j) {
-        size_t i_column_index = item_to_child_matrix.values()[j];
+    for (size_t i = 0; i < number_of_transposed_columns; ++i) {
+      for (size_t j = child_to_item_matrix.rowsMap()[i]; j < child_to_item_matrix.rowsMap()[i + 1]; ++j) {
+        size_t i_column_index = child_to_item_matrix.values()[j];
         uint32_t& shift       = transposed_next_free_column_index[i_column_index];
 
         transposed_column_indices[transposed_rows_map[i_column_index] + shift] = i;
@@ -71,6 +99,18 @@ ConnectivityComputer::_computeInverse(const ConnectivityMatrix& item_to_child_ma
       }
     }
 
+    auto target_item_number = connectivity.template number<child_item_type>();
+    // Finally one sorts target item_ids for parallel reproducibility
+    for (size_t i = 0; i < number_of_transposed_rows; ++i) {
+      auto row_begining = &(transposed_column_indices[transposed_rows_map[i]]);
+      auto row_size     = (transposed_rows_map[i + 1] - transposed_rows_map[i]);
+      std::sort(row_begining, row_begining + row_size,
+                [&target_item_number](const ItemIdT<child_item_type> item0_id,
+                                      const ItemIdT<child_item_type> item1_id) {
+                  return target_item_number[item0_id] < target_item_number[item1_id];
+                });
+    }
+
     return ConnectivityMatrix{transposed_rows_map, transposed_column_indices};
   } else {
     // empty connectivity
diff --git a/src/mesh/ConnectivityComputer.hpp b/src/mesh/ConnectivityComputer.hpp
index b033a25d5ccc04db84c194e79e492a081216a49e..7093aeef17707c37452b5e6874f6ed50a285dbb3 100644
--- a/src/mesh/ConnectivityComputer.hpp
+++ b/src/mesh/ConnectivityComputer.hpp
@@ -7,13 +7,15 @@
 class ConnectivityComputer
 {
  private:
-  ConnectivityMatrix _computeInverse(const ConnectivityMatrix& item_to_child_matrix) const;
+  template <typename ConnectivityType, ItemType child_item_type>
+  ConnectivityMatrix _computeInverseConnectivity(const ConnectivityType& connectivity,
+                                                 const ConnectivityMatrix& item_to_child_matrix) const;
 
  public:
   template <typename ConnectivityType>
-  ConnectivityMatrix computeConnectivityMatrix(const ConnectivityType& connectivity,
-                                               ItemType item_type,
-                                               ItemType child_item_type) const;
+  ConnectivityMatrix computeInverseConnectivityMatrix(const ConnectivityType& connectivity,
+                                                      ItemType item_type,
+                                                      ItemType child_item_type) const;
 
   template <typename ItemOfItem, typename ConnectivityType>
   void computeLocalItemNumberInChildItem(const ConnectivityType& connectivity) const;
diff --git a/src/mesh/ConnectivityDescriptor.hpp b/src/mesh/ConnectivityDescriptor.hpp
index 8f26988eadaa01dfec8aa0f14404a82819273475..dacf84d3cc84ab13629c09f163a16739db70dba3 100644
--- a/src/mesh/ConnectivityDescriptor.hpp
+++ b/src/mesh/ConnectivityDescriptor.hpp
@@ -2,6 +2,7 @@
 #define CONNECTIVITY_DESCRIPTOR_HPP
 
 #include <mesh/CellType.hpp>
+#include <mesh/ConnectivityMatrix.hpp>
 #include <mesh/ItemOfItemType.hpp>
 #include <mesh/RefItemList.hpp>
 #include <utils/PugsTraits.hpp>
@@ -16,68 +17,337 @@ class ConnectivityDescriptor
   std::vector<RefEdgeList> m_ref_edge_list_vector;
   std::vector<RefNodeList> m_ref_node_list_vector;
 
+  ConnectivityMatrix m_cell_to_face_matrix = ConnectivityMatrix{true};
+  ConnectivityMatrix m_cell_to_edge_matrix = ConnectivityMatrix{true};
+  ConnectivityMatrix m_cell_to_node_matrix = ConnectivityMatrix{true};
+
+  ConnectivityMatrix m_face_to_edge_matrix = ConnectivityMatrix{true};
+  ConnectivityMatrix m_face_to_node_matrix = ConnectivityMatrix{true};
+
+  ConnectivityMatrix m_edge_to_node_matrix = ConnectivityMatrix{true};
+
+  ConnectivityMatrix m_node_to_face_matrix = ConnectivityMatrix{true};
+  ConnectivityMatrix m_node_to_edge_matrix = ConnectivityMatrix{true};
+
+  Array<const bool> m_cell_face_is_reversed;
+  Array<const bool> m_face_edge_is_reversed;
+
+  Array<const CellType> m_cell_type_vector;
+
+  Array<const int> m_cell_number_vector;
+  Array<const int> m_face_number_vector;
+  Array<const int> m_edge_number_vector;
+  Array<const int> m_node_number_vector;
+
+  Array<const int> m_cell_owner_vector;
+  Array<const int> m_face_owner_vector;
+  Array<const int> m_edge_owner_vector;
+  Array<const int> m_node_owner_vector;
+
  public:
-  std::vector<std::vector<unsigned int>> cell_to_node_vector;
-  std::vector<std::vector<unsigned int>> cell_to_face_vector;
-  std::vector<std::vector<unsigned int>> cell_to_edge_vector;
+  void
+  setCellNumberVector(const Array<const int>& cell_number_vector)
+  {
+    // No check since it can change reading file for instance
+    m_cell_number_vector = cell_number_vector;
+  }
 
-  std::vector<std::vector<unsigned int>> face_to_node_vector;
-  std::vector<std::vector<unsigned int>> face_to_edge_vector;
+  PUGS_INLINE
+  const Array<const int>&
+  cellNumberVector() const
+  {
+    return m_cell_number_vector;
+  }
 
-  std::vector<std::vector<unsigned int>> edge_to_node_vector;
+  void
+  setFaceNumberVector(const Array<const int>& face_number_vector)
+  {
+    // No check since it can change reading file for instance
+    m_face_number_vector = face_number_vector;
+  }
 
-  template <typename ItemOfItemT>
-  auto&
-  itemOfItemVector()
+  PUGS_INLINE
+  const Array<const int>&
+  faceNumberVector() const
   {
-    if constexpr (std::is_same_v<ItemOfItemT, NodeOfCell>) {
-      return cell_to_node_vector;
-    } else if constexpr (std::is_same_v<ItemOfItemT, FaceOfCell>) {
-      return cell_to_face_vector;
-    } else if constexpr (std::is_same_v<ItemOfItemT, EdgeOfCell>) {
-      return cell_to_edge_vector;
-    } else if constexpr (std::is_same_v<ItemOfItemT, EdgeOfFace>) {
-      return face_to_edge_vector;
-    } else if constexpr (std::is_same_v<ItemOfItemT, NodeOfFace>) {
-      return face_to_node_vector;
-    } else if constexpr (std::is_same_v<ItemOfItemT, NodeOfEdge>) {
-      return edge_to_node_vector;
-    } else {
-      static_assert(is_false_v<ItemOfItemT>, "Unexpected item of item type");
-    }
+    return m_face_number_vector;
+  }
+
+  void
+  setEdgeNumberVector(const Array<const int>& edge_number_vector)
+  {
+    // No check since it can change reading file for instance
+    m_edge_number_vector = edge_number_vector;
   }
 
-  std::vector<Array<bool>> cell_face_is_reversed_vector;
-  std::vector<Array<bool>> face_edge_is_reversed_vector;
+  PUGS_INLINE
+  const Array<const int>&
+  edgeNumberVector() const
+  {
+    return m_edge_number_vector;
+  }
 
-  std::vector<CellType> cell_type_vector;
+  void
+  setNodeNumberVector(const Array<const int>& node_number_vector)
+  {
+    // No check since it can change reading file for instance
+    m_node_number_vector = node_number_vector;
+  }
 
-  std::vector<int> cell_number_vector;
-  std::vector<int> face_number_vector;
-  std::vector<int> edge_number_vector;
-  std::vector<int> node_number_vector;
+  PUGS_INLINE
+  const Array<const int>&
+  nodeNumberVector() const
+  {
+    return m_node_number_vector;
+  }
 
   template <ItemType item_type>
-  const std::vector<int>&
+  Array<const int>
   itemNumberVector() const
   {
     if constexpr (item_type == ItemType::cell) {
-      return cell_number_vector;
+      return m_cell_number_vector;
     } else if constexpr (item_type == ItemType::face) {
-      return face_number_vector;
+      return m_face_number_vector;
     } else if constexpr (item_type == ItemType::edge) {
-      return edge_number_vector;
+      return m_edge_number_vector;
     } else if constexpr (item_type == ItemType::node) {
-      return node_number_vector;
+      return m_node_number_vector;
     } else {
       static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
     }
   }
 
-  std::vector<int> cell_owner_vector;
-  std::vector<int> face_owner_vector;
-  std::vector<int> edge_owner_vector;
-  std::vector<int> node_owner_vector;
+  void
+  setCellOwnerVector(const Array<const int>& cell_owner_vector)
+  {
+    Assert(m_cell_owner_vector.size() == 0);
+    m_cell_owner_vector = cell_owner_vector;
+  }
+
+  PUGS_INLINE
+  const Array<const int>&
+  cellOwnerVector() const
+  {
+    return m_cell_owner_vector;
+  }
+
+  void
+  setFaceOwnerVector(const Array<const int>& face_owner_vector)
+  {
+    Assert(m_face_owner_vector.size() == 0);
+    m_face_owner_vector = face_owner_vector;
+  }
+
+  PUGS_INLINE
+  const Array<const int>&
+  faceOwnerVector() const
+  {
+    return m_face_owner_vector;
+  }
+
+  void
+  setEdgeOwnerVector(const Array<const int>& edge_owner_vector)
+  {
+    Assert(m_edge_owner_vector.size() == 0);
+    m_edge_owner_vector = edge_owner_vector;
+  }
+
+  PUGS_INLINE
+  const Array<const int>&
+  edgeOwnerVector() const
+  {
+    return m_edge_owner_vector;
+  }
+
+  void
+  setNodeOwnerVector(const Array<const int>& node_owner_vector)
+  {
+    Assert(m_node_owner_vector.size() == 0);
+    m_node_owner_vector = node_owner_vector;
+  }
+
+  PUGS_INLINE
+  const Array<const int>&
+  nodeOwnerVector() const
+  {
+    return m_node_owner_vector;
+  }
+
+  void
+  setCellFaceIsReversed(const Array<const bool>& cell_face_is_reversed)
+  {
+    Assert(m_cell_face_is_reversed.size() == 0);
+    m_cell_face_is_reversed = cell_face_is_reversed;
+  }
+
+  PUGS_INLINE
+  const Array<const bool>&
+  cellFaceIsReversed() const
+  {
+    return m_cell_face_is_reversed;
+  }
+
+  void
+  setFaceEdgeIsReversed(const Array<const bool>& face_edge_is_reversed)
+  {
+    Assert(m_face_edge_is_reversed.size() == 0);
+    m_face_edge_is_reversed = face_edge_is_reversed;
+  }
+
+  PUGS_INLINE
+  const Array<const bool>&
+  faceEdgeIsReversed() const
+  {
+    return m_face_edge_is_reversed;
+  }
+
+  void
+  setCellTypeVector(const Array<const CellType>& cell_type_vector)
+  {
+    Assert(m_face_edge_is_reversed.size() == 0);
+    m_cell_type_vector = cell_type_vector;
+  }
+
+  PUGS_INLINE
+  const Array<const CellType>&
+  cellTypeVector() const
+  {
+    return m_cell_type_vector;
+  }
+
+  void
+  setCellToFaceMatrix(const ConnectivityMatrix& cell_to_face_matrix)
+  {
+    Assert(m_cell_to_face_matrix.numberOfRows() == 0);
+    m_cell_to_face_matrix = cell_to_face_matrix;
+  }
+
+  void
+  setCellToEdgeMatrix(const ConnectivityMatrix& cell_to_edge_matrix)
+  {
+    Assert(m_cell_to_edge_matrix.numberOfRows() == 0);
+    m_cell_to_edge_matrix = cell_to_edge_matrix;
+  }
+
+  void
+  setCellToNodeMatrix(const ConnectivityMatrix& cell_to_node_matrix)
+  {
+    Assert(m_cell_to_node_matrix.numberOfRows() == 0);
+    m_cell_to_node_matrix = cell_to_node_matrix;
+  }
+
+  void
+  setFaceToEdgeMatrix(const ConnectivityMatrix& face_to_edge_matrix)
+  {
+    Assert(m_face_to_edge_matrix.numberOfRows() == 0);
+    m_face_to_edge_matrix = face_to_edge_matrix;
+  }
+
+  void
+  setFaceToNodeMatrix(const ConnectivityMatrix& face_to_node_matrix)
+  {
+    Assert(m_face_to_node_matrix.numberOfRows() == 0);
+    m_face_to_node_matrix = face_to_node_matrix;
+  }
+
+  void
+  setEdgeToNodeMatrix(const ConnectivityMatrix& edge_to_node_matrix)
+  {
+    Assert(m_edge_to_node_matrix.numberOfRows() == 0);
+    m_edge_to_node_matrix = edge_to_node_matrix;
+  }
+
+  void
+  setNodeToFaceMatrix(const ConnectivityMatrix& node_to_face_matrix)
+  {
+    Assert(m_node_to_face_matrix.numberOfRows() == 0);
+    m_node_to_face_matrix = node_to_face_matrix;
+  }
+
+  void
+  setNodeToEdgeMatrix(const ConnectivityMatrix& node_to_edge_matrix)
+  {
+    Assert(m_node_to_edge_matrix.numberOfRows() == 0);
+    m_node_to_edge_matrix = node_to_edge_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  cellToFaceMatrix() const
+  {
+    return m_cell_to_face_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  cellToEdgeMatrix() const
+  {
+    return m_cell_to_edge_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  cellToNodeMatrix() const
+  {
+    return m_cell_to_node_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  faceToEdgeMatrix() const
+  {
+    return m_face_to_edge_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  faceToNodeMatrix() const
+  {
+    return m_face_to_node_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  edgeToNodeMatrix() const
+  {
+    return m_edge_to_node_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  nodeToFaceMatrix() const
+  {
+    return m_node_to_face_matrix;
+  }
+
+  PUGS_INLINE
+  const ConnectivityMatrix&
+  nodeToEdgeMatrix() const
+  {
+    return m_node_to_edge_matrix;
+  }
+
+  template <typename ItemOfItemT>
+  auto&
+  itemOfItemVector()
+  {
+    if constexpr (std::is_same_v<ItemOfItemT, NodeOfCell>) {
+      return m_cell_to_node_matrix;
+    } else if constexpr (std::is_same_v<ItemOfItemT, FaceOfCell>) {
+      return m_cell_to_face_matrix;
+    } else if constexpr (std::is_same_v<ItemOfItemT, EdgeOfCell>) {
+      return m_cell_to_edge_matrix;
+    } else if constexpr (std::is_same_v<ItemOfItemT, EdgeOfFace>) {
+      return m_face_to_edge_matrix;
+    } else if constexpr (std::is_same_v<ItemOfItemT, NodeOfFace>) {
+      return m_face_to_node_matrix;
+    } else if constexpr (std::is_same_v<ItemOfItemT, NodeOfEdge>) {
+      return m_edge_to_node_matrix;
+    } else {
+      static_assert(is_false_v<ItemOfItemT>, "Unexpected item of item type");
+    }
+  }
 
   template <ItemType item_type>
   const std::vector<RefItemList<item_type>>&
@@ -114,7 +384,7 @@ class ConnectivityDescriptor
   }
 
   ConnectivityDescriptor& operator=(const ConnectivityDescriptor&) = delete;
-  ConnectivityDescriptor& operator=(ConnectivityDescriptor&&) = delete;
+  ConnectivityDescriptor& operator=(ConnectivityDescriptor&&)      = delete;
 
   ConnectivityDescriptor()                              = default;
   ConnectivityDescriptor(const ConnectivityDescriptor&) = default;
diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp
index fd5b58c6daaf5fd44c0cb440e7af472b9f9aebbf..641d7f8666946a448d98e0a3311d5bd2a94329fc 100644
--- a/src/mesh/ConnectivityDispatcher.cpp
+++ b/src/mesh/ConnectivityDispatcher.cpp
@@ -145,19 +145,19 @@ template <int Dimension>
 template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 void
 ConnectivityDispatcher<Dimension>::_gatherFrom(const ItemValue<DataType, item_type, ConnectivityPtr>& data_to_gather,
-                                               std::vector<std::remove_const_t<DataType>>& gathered_vector)
+                                               Array<std::remove_const_t<DataType>>& gathered_array)
 {
   std::vector<Array<const DataType>> recv_item_data_by_proc = this->exchange(data_to_gather);
 
   const auto& recv_id_correspondance_by_proc = this->_dispatchedInfo<item_type>().m_recv_id_correspondance_by_proc;
   Assert(recv_id_correspondance_by_proc.size() == parallel::size());
 
-  gathered_vector.resize(this->_dispatchedInfo<item_type>().m_number_to_id_map.size());
+  gathered_array = Array<std::remove_const_t<DataType>>(this->_dispatchedInfo<item_type>().m_number_to_id_map.size());
   for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
     Assert(recv_id_correspondance_by_proc[i_rank].size() == recv_item_data_by_proc[i_rank].size());
     for (size_t r = 0; r < recv_id_correspondance_by_proc[i_rank].size(); ++r) {
-      const auto& item_id      = recv_id_correspondance_by_proc[i_rank][r];
-      gathered_vector[item_id] = recv_item_data_by_proc[i_rank][r];
+      const auto& item_id     = recv_id_correspondance_by_proc[i_rank][r];
+      gathered_array[item_id] = recv_item_data_by_proc[i_rank][r];
     }
   }
 }
@@ -167,7 +167,7 @@ template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
 void
 ConnectivityDispatcher<Dimension>::_gatherFrom(
   const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& data_to_gather,
-  std::vector<Array<std::remove_const_t<DataType>>>& gathered_vector)
+  Array<std::remove_const_t<DataType>>& gathered_array)
 {
   using MutableDataType = std::remove_const_t<DataType>;
 
@@ -201,17 +201,23 @@ ConnectivityDispatcher<Dimension>::_gatherFrom(
 
   parallel::exchange(data_to_send_by_proc, recv_data_to_gather_by_proc);
 
-  const auto& item_list_to_recv_size_by_proc = this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc;
+  const size_t recv_array_size = [&] {
+    size_t size = 0;
+    for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+      size += recv_data_to_gather_by_proc[i_rank].size();
+    }
+    return size;
+  }();
 
-  for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
-    int l = 0;
-    for (size_t i = 0; i < item_list_to_recv_size_by_proc[i_rank]; ++i) {
-      Array<MutableDataType> data_vector(number_of_sub_item_per_item_to_recv_by_proc[i_rank][i]);
-      for (size_t k = 0; k < data_vector.size(); ++k) {
-        data_vector[k] = recv_data_to_gather_by_proc[i_rank][l++];
+  gathered_array = Array<std::remove_const_t<DataType>>(recv_array_size);
+  {
+    size_t l = 0;
+    for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+      for (size_t j = 0; j < recv_data_to_gather_by_proc[i_rank].size(); ++j) {
+        gathered_array[l++] = recv_data_to_gather_by_proc[i_rank][j];
       }
-      gathered_vector.emplace_back(data_vector);
     }
+    Assert(gathered_array.size() == l);
   }
 }
 
@@ -342,6 +348,8 @@ ConnectivityDispatcher<Dimension>::_buildItemToSubItemDescriptor()
   const auto& recv_item_of_item_numbers_by_proc =
     this->_dispatchedInfo<ItemOfItemT>().m_sub_item_numbers_to_recv_by_proc;
 
+  std::vector<std::vector<unsigned int>> item_to_subitem_legacy;
+  size_t number_of_node_by_cell = 0;
   for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
     int l = 0;
     for (size_t i = 0; i < item_list_to_recv_size_by_proc[i_rank]; ++i) {
@@ -351,9 +359,30 @@ ConnectivityDispatcher<Dimension>::_buildItemToSubItemDescriptor()
         Assert(searched_sub_item_id != sub_item_number_id_map.end());
         sub_item_vector.push_back(searched_sub_item_id->second);
       }
-      m_new_descriptor.itemOfItemVector<ItemOfItemT>().emplace_back(sub_item_vector);
+      number_of_node_by_cell += sub_item_vector.size();
+
+      item_to_subitem_legacy.emplace_back(sub_item_vector);
+    }
+  }
+  Array<unsigned int> item_to_subitem_row_map(item_to_subitem_legacy.size() + 1);
+  Array<unsigned int> item_to_subitem_list(number_of_node_by_cell);
+
+  item_to_subitem_row_map.fill(10000000);
+  item_to_subitem_list.fill(10000000);
+
+  item_to_subitem_row_map[0] = 0;
+  for (size_t i = 0; i < item_to_subitem_legacy.size(); ++i) {
+    item_to_subitem_row_map[i + 1] = item_to_subitem_row_map[i] + item_to_subitem_legacy[i].size();
+  }
+  size_t l = 0;
+  for (size_t i = 0; i < item_to_subitem_legacy.size(); ++i) {
+    const auto& subitem_list = item_to_subitem_legacy[i];
+    for (size_t j = 0; j < subitem_list.size(); ++j, ++l) {
+      item_to_subitem_list[l] = subitem_list[j];
     }
   }
+
+  m_new_descriptor.itemOfItemVector<ItemOfItemT>() = ConnectivityMatrix(item_to_subitem_row_map, item_to_subitem_list);
 }
 
 template <int Dimension>
@@ -582,7 +611,11 @@ ConnectivityDispatcher<Dimension>::_dispatchEdges()
     this->_buildSubItemNumberToIdMap<EdgeOfCell>();
     this->_buildItemToExchangeLists<ItemType::edge>();
 
-    this->_gatherFrom(m_connectivity.template number<ItemType::edge>(), m_new_descriptor.edge_number_vector);
+    m_new_descriptor.setEdgeNumberVector([&] {
+      Array<int> edge_number_vector;
+      this->_gatherFrom(m_connectivity.template number<ItemType::edge>(), edge_number_vector);
+      return edge_number_vector;
+    }());
 
     this->_buildItemToSubItemDescriptor<EdgeOfCell>();
 
@@ -594,9 +627,17 @@ ConnectivityDispatcher<Dimension>::_dispatchEdges()
     this->_buildSubItemNumbersToRecvByProc<EdgeOfFace>();
     this->_buildItemToSubItemDescriptor<EdgeOfFace>();
 
-    this->_gatherFrom(m_connectivity.faceEdgeIsReversed(), m_new_descriptor.face_edge_is_reversed_vector);
+    m_new_descriptor.setFaceEdgeIsReversed([&] {
+      Array<bool> face_edge_is_reversed;
+      this->_gatherFrom(m_connectivity.faceEdgeIsReversed(), face_edge_is_reversed);
+      return face_edge_is_reversed;
+    }());
 
-    this->_gatherFrom(this->_dispatchedInfo<ItemType::edge>().m_new_owner, m_new_descriptor.edge_owner_vector);
+    m_new_descriptor.setEdgeOwnerVector([&] {
+      Array<int> edge_owner_vector;
+      this->_gatherFrom(this->_dispatchedInfo<ItemType::edge>().m_new_owner, edge_owner_vector);
+      return edge_owner_vector;
+    }());
 
     this->_buildItemReferenceList<ItemType::edge>();
   }
@@ -616,13 +657,25 @@ ConnectivityDispatcher<Dimension>::_dispatchFaces()
     this->_buildSubItemNumbersToRecvByProc<NodeOfFace>();
     this->_buildItemToSubItemDescriptor<NodeOfFace>();
 
-    this->_gatherFrom(m_connectivity.template number<ItemType::face>(), m_new_descriptor.face_number_vector);
+    m_new_descriptor.setFaceNumberVector([&] {
+      Array<int> face_number_vector;
+      this->_gatherFrom(m_connectivity.template number<ItemType::face>(), face_number_vector);
+      return face_number_vector;
+    }());
 
     this->_buildItemToSubItemDescriptor<FaceOfCell>();
 
-    this->_gatherFrom(m_connectivity.cellFaceIsReversed(), m_new_descriptor.cell_face_is_reversed_vector);
+    m_new_descriptor.setCellFaceIsReversed([&] {
+      Array<bool> cell_face_is_reversed;
+      this->_gatherFrom(m_connectivity.cellFaceIsReversed(), cell_face_is_reversed);
+      return cell_face_is_reversed;
+    }());
 
-    this->_gatherFrom(this->_dispatchedInfo<ItemType::face>().m_new_owner, m_new_descriptor.face_owner_vector);
+    m_new_descriptor.setFaceOwnerVector([&] {
+      Array<int> face_owner_vector;
+      this->_gatherFrom(this->_dispatchedInfo<ItemType::face>().m_new_owner, face_owner_vector);
+      return face_owner_vector;
+    }());
 
     this->_buildItemReferenceList<ItemType::face>();
   }
@@ -647,18 +700,39 @@ ConnectivityDispatcher<Dimension>::ConnectivityDispatcher(const ConnectivityType
 
   this->_buildSubItemNumbersToRecvByProc<NodeOfCell>();
 
-  this->_gatherFrom(m_connectivity.template number<ItemType::cell>(), m_new_descriptor.cell_number_vector);
+  m_new_descriptor.setCellNumberVector([&] {
+    Array<int> cell_number_vector;
+    this->_gatherFrom(m_connectivity.template number<ItemType::cell>(), cell_number_vector);
+    return cell_number_vector;
+  }());
 
   this->_buildSubItemNumberToIdMap<NodeOfCell>();
 
   this->_buildItemToExchangeLists<ItemType::node>();
 
-  // Fill new descriptor
-  this->_gatherFrom(m_connectivity.cellType(), m_new_descriptor.cell_type_vector);
-  this->_gatherFrom(this->_dispatchedInfo<ItemType::cell>().m_new_owner, m_new_descriptor.cell_owner_vector);
-
-  this->_gatherFrom(m_connectivity.template number<ItemType::node>(), m_new_descriptor.node_number_vector);
-  this->_gatherFrom(this->_dispatchedInfo<ItemType::node>().m_new_owner, m_new_descriptor.node_owner_vector);
+  m_new_descriptor.setCellTypeVector([&] {
+    Array<CellType> cell_type_vector;
+    this->_gatherFrom(m_connectivity.cellType(), cell_type_vector);
+    return cell_type_vector;
+  }());
+
+  m_new_descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector;
+    this->_gatherFrom(this->_dispatchedInfo<ItemType::cell>().m_new_owner, cell_owner_vector);
+    return cell_owner_vector;
+  }());
+
+  m_new_descriptor.setNodeNumberVector([&] {
+    Array<int> node_number_vector;
+    this->_gatherFrom(m_connectivity.template number<ItemType::node>(), node_number_vector);
+    return node_number_vector;
+  }());
+
+  m_new_descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector;
+    this->_gatherFrom(this->_dispatchedInfo<ItemType::node>().m_new_owner, node_owner_vector);
+    return node_owner_vector;
+  }());
 
   this->_buildItemToSubItemDescriptor<NodeOfCell>();
 
diff --git a/src/mesh/ConnectivityDispatcher.hpp b/src/mesh/ConnectivityDispatcher.hpp
index 73ebc548d98a01e4999611927272206df4af1c23..9ff5913b60799148f99bb6fdf4cff2cedd56973c 100644
--- a/src/mesh/ConnectivityDispatcher.hpp
+++ b/src/mesh/ConnectivityDispatcher.hpp
@@ -180,11 +180,11 @@ class ConnectivityDispatcher
 
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
   void _gatherFrom(const ItemValue<DataType, item_type, ConnectivityPtr>& data_to_gather,
-                   std::vector<std::remove_const_t<DataType>>& gathered_vector);
+                   Array<std::remove_const_t<DataType>>& gathered_array);
 
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
   void _gatherFrom(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& data_to_gather,
-                   std::vector<Array<std::remove_const_t<DataType>>>& gathered_vector);
+                   Array<std::remove_const_t<DataType>>& gathered_array);
 
   template <typename SubItemOfItemT>
   void _buildNumberOfSubItemPerItemToRecvByProc();
diff --git a/src/mesh/ConnectivityMatrix.hpp b/src/mesh/ConnectivityMatrix.hpp
index 6b777288cd6b8e3793a9addff6629c661d45d82d..eadc85347cb756932616e655d7b625a2a15505b7 100644
--- a/src/mesh/ConnectivityMatrix.hpp
+++ b/src/mesh/ConnectivityMatrix.hpp
@@ -70,34 +70,22 @@ class ConnectivityMatrix
 #endif   // NDEBUG
   }
 
-  PUGS_INLINE
-  ConnectivityMatrix(const std::vector<std::vector<unsigned int>>& initializer) noexcept : m_is_built{true}
-  {
-    m_row_map = [&] {
-      Array<uint32_t> row_map(initializer.size() + 1);
-      row_map[0] = 0;
-      for (size_t i = 0; i < initializer.size(); ++i) {
-        row_map[i + 1] = row_map[i] + initializer[i].size();
-      }
-      return row_map;
-    }();
-
-    m_column_indices = [&] {
-      Array<uint32_t> column_indices(m_row_map[m_row_map.size() - 1]);
-      size_t index = 0;
-      for (const auto& row : initializer) {
-        for (const auto& col_index : row) {
-          column_indices[index++] = col_index;
-        }
-      }
-      return column_indices;
-    }();
-  }
-
   ConnectivityMatrix& operator=(const ConnectivityMatrix&) = default;
-  ConnectivityMatrix& operator=(ConnectivityMatrix&&) = default;
+  ConnectivityMatrix& operator=(ConnectivityMatrix&&)      = default;
 
-  ConnectivityMatrix()                          = default;
+  ConnectivityMatrix(bool is_built = false) : m_is_built{is_built}
+  {
+    // this is useful to build
+    if (is_built) {
+      m_row_map = [&] {
+        Array<uint32_t> row_map(1);
+        row_map[0] = 0;
+        return row_map;
+      }();
+
+      m_column_indices = Array<uint32_t>(0);
+    }
+  }
   ConnectivityMatrix(const ConnectivityMatrix&) = default;
   ConnectivityMatrix(ConnectivityMatrix&&)      = default;
   ~ConnectivityMatrix()                         = default;
diff --git a/src/mesh/ConnectivityUtils.cpp b/src/mesh/ConnectivityUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..af0220ac448b76fb2e0101c278719cc07da989bc
--- /dev/null
+++ b/src/mesh/ConnectivityUtils.cpp
@@ -0,0 +1,88 @@
+#include <mesh/ConnectivityUtils.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <utils/Messenger.hpp>
+
+template <size_t Dimension, ItemType SourceItemType, ItemType TargetItemType>
+bool
+checkItemToHigherDimensionItem(const Connectivity<Dimension>& connectivity)
+{
+  static_assert(SourceItemType < TargetItemType);
+  bool is_valid = true;
+
+  const auto& item_to_item_matrix = connectivity.template getItemToItemMatrix<SourceItemType, TargetItemType>();
+  const auto& item_number         = connectivity.template number<TargetItemType>();
+
+  for (ItemIdT<SourceItemType> source_item_id = 0; source_item_id < connectivity.template numberOf<SourceItemType>();
+       ++source_item_id) {
+    auto target_item_list = item_to_item_matrix[source_item_id];
+    for (size_t i = 0; i < target_item_list.size() - 1; ++i) {
+      is_valid &= (item_number[target_item_list[i + 1]] > item_number[target_item_list[i]]);
+    }
+  }
+
+  return is_valid;
+}
+
+template <size_t Dimension>
+bool checkConnectivityOrdering(const Connectivity<Dimension>&);
+
+template <>
+bool
+checkConnectivityOrdering(const Connectivity<1>& connectivity)
+{
+  bool is_valid = true;
+  is_valid &= checkItemToHigherDimensionItem<1, ItemType::node, ItemType::cell>(connectivity);
+
+  return is_valid;
+}
+template <>
+
+bool
+checkConnectivityOrdering(const Connectivity<2>& connectivity)
+{
+  bool is_valid = true;
+  is_valid &= checkItemToHigherDimensionItem<2, ItemType::node, ItemType::face>(connectivity);
+  is_valid &= checkItemToHigherDimensionItem<2, ItemType::node, ItemType::cell>(connectivity);
+
+  is_valid &= checkItemToHigherDimensionItem<2, ItemType::face, ItemType::cell>(connectivity);
+
+  return is_valid;
+}
+
+bool
+checkConnectivityOrdering(const Connectivity<3>& connectivity)
+{
+  bool is_valid = true;
+  is_valid &= checkItemToHigherDimensionItem<3, ItemType::node, ItemType::edge>(connectivity);
+  is_valid &= checkItemToHigherDimensionItem<3, ItemType::node, ItemType::face>(connectivity);
+  is_valid &= checkItemToHigherDimensionItem<3, ItemType::node, ItemType::cell>(connectivity);
+
+  is_valid &= checkItemToHigherDimensionItem<3, ItemType::edge, ItemType::face>(connectivity);
+  is_valid &= checkItemToHigherDimensionItem<3, ItemType::edge, ItemType::cell>(connectivity);
+
+  is_valid &= checkItemToHigherDimensionItem<3, ItemType::face, ItemType::cell>(connectivity);
+
+  return parallel::allReduceAnd(is_valid);
+}
+
+bool
+checkConnectivityOrdering(const IConnectivity& connecticity)
+{
+  switch (connecticity.dimension()) {
+  case 1: {
+    return checkConnectivityOrdering(dynamic_cast<const Connectivity<1>&>(connecticity));
+  }
+  case 2: {
+    return checkConnectivityOrdering(dynamic_cast<const Connectivity<2>&>(connecticity));
+  }
+  case 3: {
+    return checkConnectivityOrdering(dynamic_cast<const Connectivity<3>&>(connecticity));
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
diff --git a/src/mesh/ConnectivityUtils.hpp b/src/mesh/ConnectivityUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fa359a7e1c532e3b83c91d3e42d1bd8e3d7a45d8
--- /dev/null
+++ b/src/mesh/ConnectivityUtils.hpp
@@ -0,0 +1,8 @@
+#ifndef CONNECTIVITY_UTILS_HPP
+#define CONNECTIVITY_UTILS_HPP
+
+#include "mesh/IConnectivity.hpp"
+
+bool checkConnectivityOrdering(const IConnectivity&);
+
+#endif   // CONNECTIVITY_UTILS_HPP
diff --git a/src/mesh/DiamondDualConnectivityBuilder.cpp b/src/mesh/DiamondDualConnectivityBuilder.cpp
index 71e316461d94a0891b721a0fff85e85c145ef26f..c6a8fd78a9774dc63cbc5e5398ce721c5cfb9acb 100644
--- a/src/mesh/DiamondDualConnectivityBuilder.cpp
+++ b/src/mesh/DiamondDualConnectivityBuilder.cpp
@@ -11,6 +11,7 @@
 #include <utils/Messenger.hpp>
 #include <utils/Stringify.hpp>
 
+#include <optional>
 #include <vector>
 
 template <size_t Dimension>
@@ -44,21 +45,21 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityDescriptor(const Connec
     }
   }
 
-  diamond_descriptor.node_number_vector.resize(diamond_number_of_nodes);
+  Array<int> node_number_vector(diamond_number_of_nodes);
 
   parallel_for(m_primal_node_to_dual_node_map.size(), [&](size_t i) {
     const auto [primal_node_id, diamond_dual_node_id] = m_primal_node_to_dual_node_map[i];
 
-    diamond_descriptor.node_number_vector[diamond_dual_node_id] = primal_node_number[primal_node_id];
+    node_number_vector[diamond_dual_node_id] = primal_node_number[primal_node_id];
   });
 
   const size_t cell_number_shift = max(primal_node_number) + 1;
   parallel_for(primal_number_of_cells, [&](size_t i) {
     const auto [primal_cell_id, diamond_dual_node_id] = m_primal_cell_to_dual_node_map[i];
 
-    diamond_descriptor.node_number_vector[diamond_dual_node_id] =
-      primal_cell_number[primal_cell_id] + cell_number_shift;
+    node_number_vector[diamond_dual_node_id] = primal_cell_number[primal_cell_id] + cell_number_shift;
   });
+  diamond_descriptor.setNodeNumberVector(node_number_vector);
 
   {
     m_primal_face_to_dual_cell_map = FaceIdToCellIdMap{primal_number_of_faces};
@@ -68,14 +69,17 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityDescriptor(const Connec
     }
   }
 
-  diamond_descriptor.cell_number_vector.resize(diamond_number_of_cells);
-  const auto& primal_face_number = primal_connectivity.faceNumber();
-  parallel_for(diamond_number_of_cells, [&](size_t i) {
-    const auto [primal_face_id, dual_cell_id]           = m_primal_face_to_dual_cell_map[i];
-    diamond_descriptor.cell_number_vector[dual_cell_id] = primal_face_number[primal_face_id];
-  });
+  diamond_descriptor.setCellNumberVector([&] {
+    Array<int> cell_number_vector(diamond_number_of_cells);
+    const auto& primal_face_number = primal_connectivity.faceNumber();
+    parallel_for(diamond_number_of_cells, [&](size_t i) {
+      const auto [primal_face_id, dual_cell_id] = m_primal_face_to_dual_cell_map[i];
+      cell_number_vector[dual_cell_id]          = primal_face_number[primal_face_id];
+    });
+    return cell_number_vector;
+  }());
 
-  diamond_descriptor.cell_type_vector.resize(diamond_number_of_cells);
+  Array<CellType> cell_type_vector(diamond_number_of_cells);
 
   const auto& primal_face_to_cell_matrix = primal_connectivity.faceToCellMatrix();
   const auto& primal_face_to_node_matrix = primal_connectivity.faceToNodeMatrix();
@@ -87,97 +91,108 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityDescriptor(const Connec
 
     if constexpr (Dimension == 2) {
       if (primal_face_cell_list.size() == 1) {
-        diamond_descriptor.cell_type_vector[i_cell] = CellType::Triangle;
+        cell_type_vector[i_cell] = CellType::Triangle;
       } else {
         Assert(primal_face_cell_list.size() == 2);
-        diamond_descriptor.cell_type_vector[i_cell] = CellType::Quadrangle;
+        cell_type_vector[i_cell] = CellType::Quadrangle;
       }
     } else if constexpr (Dimension == 3) {
       if (primal_face_cell_list.size() == 1) {
         if (primal_face_to_node_matrix[face_id].size() == 3) {
-          diamond_descriptor.cell_type_vector[i_cell] = CellType::Tetrahedron;
+          cell_type_vector[i_cell] = CellType::Tetrahedron;
         } else {
-          diamond_descriptor.cell_type_vector[i_cell] = CellType::Pyramid;
+          cell_type_vector[i_cell] = CellType::Pyramid;
         }
       } else {
         Assert(primal_face_cell_list.size() == 2);
-        diamond_descriptor.cell_type_vector[i_cell] = CellType::Diamond;
+        cell_type_vector[i_cell] = CellType::Diamond;
       }
     }
   });
 
-  diamond_descriptor.cell_to_node_vector.resize(diamond_number_of_cells);
+  diamond_descriptor.setCellTypeVector(cell_type_vector);
 
-  const auto& primal_face_local_number_in_their_cells = primal_connectivity.faceLocalNumbersInTheirCells();
-  const auto& cell_face_is_reversed                   = primal_connectivity.cellFaceIsReversed();
-  parallel_for(primal_number_of_faces, [&](FaceId face_id) {
-    const size_t& i_diamond_cell      = face_id;
-    const auto& primal_face_cell_list = primal_face_to_cell_matrix[face_id];
-    const auto& primal_face_node_list = primal_face_to_node_matrix[face_id];
-    if (primal_face_cell_list.size() == 1) {
-      diamond_descriptor.cell_to_node_vector[i_diamond_cell].resize(primal_face_node_list.size() + 1);
-
-      const CellId cell_id      = primal_face_cell_list[0];
-      const auto i_face_in_cell = primal_face_local_number_in_their_cells(face_id, 0);
+  Array<const unsigned int> cell_to_node_row = [&] {
+    Array<unsigned int> tmp_cell_to_node_row(primal_number_of_faces + 1);
+    tmp_cell_to_node_row[0] = 0;
+    for (FaceId face_id = 0; face_id < primal_number_of_faces; ++face_id) {
+      tmp_cell_to_node_row[face_id + 1] = tmp_cell_to_node_row[face_id] + primal_face_to_node_matrix[face_id].size() +
+                                          primal_face_to_cell_matrix[face_id].size();
+    }
 
-      for (size_t i_node = 0; i_node < primal_face_node_list.size(); ++i_node) {
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][i_node] = primal_face_node_list[i_node];
-      }
-      diamond_descriptor.cell_to_node_vector[i_diamond_cell][primal_face_node_list.size()] =
-        primal_number_of_nodes + cell_id;
+    return tmp_cell_to_node_row;
+  }();
+
+  Array<const unsigned int> cell_to_node_list = [&] {
+    Array<unsigned int> tmp_cell_to_node_list(cell_to_node_row[cell_to_node_row.size() - 1]);
+    const auto& primal_face_local_number_in_their_cells = primal_connectivity.faceLocalNumbersInTheirCells();
+    const auto& cell_face_is_reversed                   = primal_connectivity.cellFaceIsReversed();
+    parallel_for(primal_number_of_faces, [&](FaceId face_id) {
+      const auto& primal_face_cell_list = primal_face_to_cell_matrix[face_id];
+      const auto& primal_face_node_list = primal_face_to_node_matrix[face_id];
+      const size_t first_node           = cell_to_node_row[face_id];
+      if (primal_face_cell_list.size() == 1) {
+        const CellId cell_id      = primal_face_cell_list[0];
+        const auto i_face_in_cell = primal_face_local_number_in_their_cells(face_id, 0);
 
-      if constexpr (Dimension == 2) {
-        if (cell_face_is_reversed(cell_id, i_face_in_cell)) {
-          std::swap(diamond_descriptor.cell_to_node_vector[i_diamond_cell][0],
-                    diamond_descriptor.cell_to_node_vector[i_diamond_cell][1]);
+        for (size_t i_node = 0; i_node < primal_face_node_list.size(); ++i_node) {
+          tmp_cell_to_node_list[first_node + i_node] = primal_face_node_list[i_node];
         }
-      } else {
-        if (not cell_face_is_reversed(cell_id, i_face_in_cell)) {
-          // In 3D the basis of the pyramid is described in the
-          // indirect way IF the face is not reversed. In other words
-          // the "topological normal" must point to the "top" of the
-          // pyramid.
-          for (size_t i_node = 0; i_node < primal_face_node_list.size() / 2; ++i_node) {
-            std::swap(diamond_descriptor.cell_to_node_vector[i_diamond_cell][i_node],
-                      diamond_descriptor
-                        .cell_to_node_vector[i_diamond_cell][primal_face_node_list.size() - 1 - i_node]);
+        tmp_cell_to_node_list[first_node + primal_face_node_list.size()] = primal_number_of_nodes + cell_id;
+
+        if constexpr (Dimension == 2) {
+          if (cell_face_is_reversed(cell_id, i_face_in_cell)) {
+            std::swap(tmp_cell_to_node_list[first_node], tmp_cell_to_node_list[first_node + 1]);
+          }
+        } else {
+          if (not cell_face_is_reversed(cell_id, i_face_in_cell)) {
+            // In 3D the basis of the pyramid is described in the
+            // indirect way IF the face is not reversed. In other words
+            // the "topological normal" must point to the "top" of the
+            // pyramid.
+            for (size_t i_node = 0; i_node < primal_face_node_list.size() / 2; ++i_node) {
+              std::swap(tmp_cell_to_node_list[first_node + i_node],
+                        tmp_cell_to_node_list[first_node + primal_face_node_list.size() - 1 - i_node]);
+            }
           }
-        }
-      }
-    } else {
-      Assert(primal_face_cell_list.size() == 2);
-      diamond_descriptor.cell_to_node_vector[i_diamond_cell].resize(primal_face_node_list.size() + 2);
-
-      const CellId cell0_id      = primal_face_cell_list[0];
-      const CellId cell1_id      = primal_face_cell_list[1];
-      const auto i_face_in_cell0 = primal_face_local_number_in_their_cells(face_id, 0);
-
-      if constexpr (Dimension == 2) {
-        Assert(primal_face_node_list.size() == 2);
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][0] = primal_number_of_nodes + cell0_id;
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][1] = primal_face_node_list[0];
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][2] = primal_number_of_nodes + cell1_id;
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][3] = primal_face_node_list[1];
-
-        if (cell_face_is_reversed(cell0_id, i_face_in_cell0)) {
-          std::swap(diamond_descriptor.cell_to_node_vector[i_diamond_cell][1],
-                    diamond_descriptor.cell_to_node_vector[i_diamond_cell][3]);
         }
       } else {
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][0] = primal_number_of_nodes + cell0_id;
-        for (size_t i_node = 0; i_node < primal_face_node_list.size(); ++i_node) {
-          diamond_descriptor.cell_to_node_vector[i_diamond_cell][i_node + 1] = primal_face_node_list[i_node];
-        }
-        diamond_descriptor.cell_to_node_vector[i_diamond_cell][primal_face_node_list.size() + 1] =
-          primal_number_of_nodes + cell1_id;
+        Assert(primal_face_cell_list.size() == 2);
 
-        if (cell_face_is_reversed(cell0_id, i_face_in_cell0)) {
-          std::swap(diamond_descriptor.cell_to_node_vector[i_diamond_cell][0],
-                    diamond_descriptor.cell_to_node_vector[i_diamond_cell][primal_face_node_list.size() + 1]);
+        const CellId cell0_id      = primal_face_cell_list[0];
+        const CellId cell1_id      = primal_face_cell_list[1];
+        const auto i_face_in_cell0 = primal_face_local_number_in_their_cells(face_id, 0);
+
+        if constexpr (Dimension == 2) {
+          Assert(primal_face_node_list.size() == 2);
+
+          tmp_cell_to_node_list[first_node + 0] = primal_number_of_nodes + cell0_id;
+          tmp_cell_to_node_list[first_node + 1] = primal_face_node_list[0];
+          tmp_cell_to_node_list[first_node + 2] = primal_number_of_nodes + cell1_id;
+          tmp_cell_to_node_list[first_node + 3] = primal_face_node_list[1];
+
+          if (cell_face_is_reversed(cell0_id, i_face_in_cell0)) {
+            std::swap(tmp_cell_to_node_list[first_node + 1], tmp_cell_to_node_list[first_node + 3]);
+          }
+        } else {
+          tmp_cell_to_node_list[first_node + 0] = primal_number_of_nodes + cell0_id;
+          for (size_t i_node = 0; i_node < primal_face_node_list.size(); ++i_node) {
+            tmp_cell_to_node_list[first_node + i_node + 1] = primal_face_node_list[i_node];
+          }
+          tmp_cell_to_node_list[first_node + primal_face_node_list.size() + 1] = primal_number_of_nodes + cell1_id;
+
+          if (cell_face_is_reversed(cell0_id, i_face_in_cell0)) {
+            std::swap(tmp_cell_to_node_list[first_node],
+                      tmp_cell_to_node_list[first_node + primal_face_node_list.size() + 1]);
+          }
         }
       }
-    }
-  });
+    });
+
+    return tmp_cell_to_node_list;
+  }();
+
+  diamond_descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row, cell_to_node_list));
 }
 
 template <size_t Dimension>
@@ -198,11 +213,12 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
     ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities<Dimension>(diamond_descriptor);
   }
 
+  const auto& node_number_vector = diamond_descriptor.nodeNumberVector();
   {
     const std::unordered_map<unsigned int, NodeId> node_to_id_map = [&] {
       std::unordered_map<unsigned int, NodeId> node_to_id_map;
-      for (size_t i_node = 0; i_node < diamond_descriptor.node_number_vector.size(); ++i_node) {
-        node_to_id_map[diamond_descriptor.node_number_vector[i_node]] = i_node;
+      for (size_t i_node = 0; i_node < node_number_vector.size(); ++i_node) {
+        node_to_id_map[node_number_vector[i_node]] = i_node;
       }
       return node_to_id_map;
     }();
@@ -238,20 +254,32 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
   }
 
   {
-    const auto& primal_face_to_node_matrix = primal_connectivity.faceToNodeMatrix();
-
-    using Face = ConnectivityFace<Dimension>;
-
-    const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map = [&] {
-      std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
-      for (FaceId l = 0; l < diamond_descriptor.face_to_node_vector.size(); ++l) {
-        const auto& node_vector = diamond_descriptor.face_to_node_vector[l];
-
-        face_to_id_map[Face(node_vector, diamond_descriptor.node_number_vector)] = l;
+    const auto& primal_face_to_node_matrix  = primal_connectivity.faceToNodeMatrix();
+    const auto& diamond_node_to_face_matrix = diamond_descriptor.nodeToFaceMatrix();
+    const auto& diamond_face_to_node_matrix = diamond_descriptor.faceToNodeMatrix();
+
+    const auto find_face = [&](std::vector<uint32_t> node_list) -> std::optional<FaceId> {
+      // The node list of already sorted correctly
+      const auto& face_id_vector = diamond_node_to_face_matrix[node_list[0]];
+
+      for (size_t i_face = 0; i_face < face_id_vector.size(); ++i_face) {
+        const FaceId face_id          = face_id_vector[i_face];
+        const auto& face_node_id_list = diamond_face_to_node_matrix[face_id];
+        if (face_node_id_list.size() == node_list.size()) {
+          bool is_same = true;
+          for (size_t i_node = 1; i_node < face_node_id_list.size(); ++i_node) {
+            is_same &= (face_node_id_list[i_node] == node_list[i_node]);
+          }
+          if (is_same) {
+            return face_id;
+          }
+        }
       }
-      return face_to_id_map;
-    }();
 
+      return std::nullopt;
+    };
+
+    std::vector<unsigned int> face_node_list;
     for (size_t i_face_list = 0; i_face_list < primal_connectivity.template numberOfRefItemList<ItemType::face>();
          ++i_face_list) {
       const auto& primal_ref_face_list = primal_connectivity.template refItemList<ItemType::face>(i_face_list);
@@ -265,16 +293,15 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
 
           const auto& primal_face_node_list = primal_face_to_node_matrix[primal_face_id];
 
-          const auto i_diamond_face = [&]() {
-            std::vector<unsigned int> node_list(primal_face_node_list.size());
-            for (size_t i = 0; i < primal_face_node_list.size(); ++i) {
-              node_list[i] = primal_face_node_list[i];
-            }
-            return face_to_id_map.find(Face(node_list, diamond_descriptor.node_number_vector));
-          }();
+          face_node_list.clear();
+
+          for (size_t i = 0; i < primal_face_node_list.size(); ++i) {
+            face_node_list.push_back(primal_face_node_list[i]);
+          }
 
-          if (i_diamond_face != face_to_id_map.end()) {
-            diamond_face_list.push_back(i_diamond_face->second);
+          auto face_id = find_face(face_node_list);
+          if (face_id.has_value()) {
+            diamond_face_list.push_back(face_id.value());
           }
         }
         return diamond_face_list;
@@ -293,29 +320,40 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
 
   if constexpr (Dimension == 3) {
     const auto& primal_edge_to_node_matrix = primal_connectivity.edgeToNodeMatrix();
-    using Edge                             = ConnectivityFace<2>;
-
-    const std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map = [&] {
-      std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map;
-      for (EdgeId l = 0; l < diamond_descriptor.edge_to_node_vector.size(); ++l) {
-        const auto& node_vector = diamond_descriptor.edge_to_node_vector[l];
-        edge_to_id_map[Edge(node_vector, diamond_descriptor.node_number_vector)] = l;
-      }
-      return edge_to_id_map;
-    }();
 
     {
-      const size_t number_of_edges = diamond_descriptor.edge_to_node_vector.size();
-      diamond_descriptor.edge_number_vector.resize(number_of_edges);
-      for (size_t i_edge = 0; i_edge < number_of_edges; ++i_edge) {
-        diamond_descriptor.edge_number_vector[i_edge] = i_edge;
-      }
+      const size_t number_of_edges = diamond_descriptor.edgeToNodeMatrix().numberOfRows();
+      diamond_descriptor.setEdgeNumberVector([&] {
+        Array<int> edge_number_vector(number_of_edges);
+        parallel_for(
+          number_of_edges, PUGS_LAMBDA(size_t i_edge) { edge_number_vector[i_edge] = i_edge; });
+        return edge_number_vector;
+      }());
+
       // LCOV_EXCL_START
       if (parallel::size() > 1) {
         throw NotImplementedError("parallel edge numbering is undefined");
       }
       // LCOV_EXCL_STOP
     }
+    const auto& diamond_node_to_edge_matrix = diamond_descriptor.nodeToEdgeMatrix();
+    const auto& diamond_edge_to_node_matrix = diamond_descriptor.edgeToNodeMatrix();
+
+    const auto find_edge = [&](uint32_t node0, uint32_t node1) -> std::optional<EdgeId> {
+      if (node_number_vector[node0] > node_number_vector[node1]) {
+        std::swap(node0, node1);
+      }
+      const auto& edge_id_vector = diamond_node_to_edge_matrix[node0];
+
+      for (size_t i_edge = 0; i_edge < edge_id_vector.size(); ++i_edge) {
+        const EdgeId edge_id = edge_id_vector[i_edge];
+        if (diamond_edge_to_node_matrix[edge_id][1] == node1) {
+          return edge_id;
+        }
+      }
+
+      return std::nullopt;
+    };
 
     for (size_t i_edge_list = 0; i_edge_list < primal_connectivity.template numberOfRefItemList<ItemType::edge>();
          ++i_edge_list) {
@@ -330,16 +368,10 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
 
           const auto& primal_edge_node_list = primal_edge_to_node_matrix[primal_edge_id];
 
-          const auto i_diamond_edge = [&]() {
-            std::vector<unsigned int> node_list(primal_edge_node_list.size());
-            for (size_t i = 0; i < primal_edge_node_list.size(); ++i) {
-              node_list[i] = primal_edge_node_list[i];
-            }
-            return edge_to_id_map.find(Edge(node_list, diamond_descriptor.node_number_vector));
-          }();
+          const auto diamond_edge_id = find_edge(primal_edge_node_list[0], primal_edge_node_list[1]);
 
-          if (i_diamond_edge != edge_to_id_map.end()) {
-            diamond_edge_list.push_back(i_diamond_edge->second);
+          if (diamond_edge_id.has_value()) {
+            diamond_edge_list.push_back(diamond_edge_id.value());
           }
         }
         return diamond_edge_list;
@@ -359,62 +391,64 @@ DiamondDualConnectivityBuilder::_buildDiamondConnectivityFrom(const IConnectivit
   const size_t primal_number_of_nodes = primal_connectivity.numberOfNodes();
   const size_t primal_number_of_cells = primal_connectivity.numberOfCells();
 
-  diamond_descriptor.node_owner_vector.resize(diamond_descriptor.node_number_vector.size());
-
-  {
+  diamond_descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(node_number_vector.size());
     const auto& primal_node_owner = primal_connectivity.nodeOwner();
     for (NodeId primal_node_id = 0; primal_node_id < primal_connectivity.numberOfNodes(); ++primal_node_id) {
-      diamond_descriptor.node_owner_vector[primal_node_id] = primal_node_owner[primal_node_id];
+      node_owner_vector[primal_node_id] = primal_node_owner[primal_node_id];
     }
     const auto& primal_cell_owner = primal_connectivity.cellOwner();
     for (CellId primal_cell_id = 0; primal_cell_id < primal_number_of_cells; ++primal_cell_id) {
-      diamond_descriptor.node_owner_vector[primal_number_of_nodes + primal_cell_id] = primal_cell_owner[primal_cell_id];
+      node_owner_vector[primal_number_of_nodes + primal_cell_id] = primal_cell_owner[primal_cell_id];
     }
-  }
+    return node_owner_vector;
+  }());
 
-  {
-    diamond_descriptor.cell_owner_vector.resize(diamond_descriptor.cell_number_vector.size());
+  diamond_descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(diamond_descriptor.cellNumberVector().size());
     const size_t primal_number_of_faces = primal_connectivity.numberOfFaces();
     const auto& primal_face_owner       = primal_connectivity.faceOwner();
-    for (FaceId primal_face_id = 0; primal_face_id < primal_number_of_faces; ++primal_face_id) {
-      diamond_descriptor.cell_owner_vector[primal_face_id] = primal_face_owner[primal_face_id];
-    }
-  }
-
-  {
-    std::vector<int> face_cell_owner(diamond_descriptor.face_number_vector.size());
-    std::fill(std::begin(face_cell_owner), std::end(face_cell_owner), parallel::size());
-
-    for (size_t i_cell = 0; i_cell < diamond_descriptor.cell_to_face_vector.size(); ++i_cell) {
-      const auto& cell_face_list = diamond_descriptor.cell_to_face_vector[i_cell];
+    parallel_for(
+      primal_number_of_faces, PUGS_LAMBDA(const FaceId primal_face_id) {
+        cell_owner_vector[primal_face_id] = primal_face_owner[primal_face_id];
+      });
+    return cell_owner_vector;
+  }());
+
+  const auto& diamond_cell_owner_vector   = diamond_descriptor.cellOwnerVector();
+  const auto& diamond_cell_to_face_matrix = diamond_descriptor.cellToFaceMatrix();
+
+  diamond_descriptor.setFaceOwnerVector([&] {
+    Array<int> face_owner_vector(diamond_descriptor.faceNumberVector().size());
+    face_owner_vector.fill(parallel::rank());
+
+    for (size_t i_cell = 0; i_cell < diamond_cell_to_face_matrix.numberOfRows(); ++i_cell) {
+      const auto& cell_face_list = diamond_cell_to_face_matrix[i_cell];
       for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) {
-        const size_t face_id     = cell_face_list[i_face];
-        face_cell_owner[face_id] = std::min(face_cell_owner[face_id], diamond_descriptor.cell_number_vector[i_cell]);
+        const size_t face_id       = cell_face_list[i_face];
+        face_owner_vector[face_id] = std::min(face_owner_vector[face_id], diamond_cell_owner_vector[i_cell]);
       }
     }
-
-    diamond_descriptor.face_owner_vector.resize(face_cell_owner.size());
-    for (size_t i_face = 0; i_face < face_cell_owner.size(); ++i_face) {
-      diamond_descriptor.face_owner_vector[i_face] = diamond_descriptor.cell_owner_vector[face_cell_owner[i_face]];
-    }
-  }
+    return face_owner_vector;
+  }());
 
   if constexpr (Dimension == 3) {
-    std::vector<int> edge_cell_owner(diamond_descriptor.edge_number_vector.size());
-    std::fill(std::begin(edge_cell_owner), std::end(edge_cell_owner), parallel::size());
-
-    for (size_t i_cell = 0; i_cell < diamond_descriptor.cell_to_face_vector.size(); ++i_cell) {
-      const auto& cell_edge_list = diamond_descriptor.cell_to_edge_vector[i_cell];
-      for (size_t i_edge = 0; i_edge < cell_edge_list.size(); ++i_edge) {
-        const size_t edge_id     = cell_edge_list[i_edge];
-        edge_cell_owner[edge_id] = std::min(edge_cell_owner[edge_id], diamond_descriptor.cell_number_vector[i_cell]);
+    const auto& diamond_cell_to_edge_matrix = diamond_descriptor.cellToEdgeMatrix();
+
+    diamond_descriptor.setEdgeOwnerVector([&] {
+      Array<int> edge_owner_vector(diamond_descriptor.edgeNumberVector().size());
+      edge_owner_vector.fill(parallel::rank());
+
+      for (size_t i_cell = 0; i_cell < diamond_cell_to_edge_matrix.numberOfRows(); ++i_cell) {
+        const auto& cell_edge_list = diamond_cell_to_edge_matrix[i_cell];
+        for (size_t i_edge = 0; i_edge < cell_edge_list.size(); ++i_edge) {
+          const size_t edge_id       = cell_edge_list[i_edge];
+          edge_owner_vector[edge_id] = std::min(edge_owner_vector[edge_id], diamond_cell_owner_vector[i_cell]);
+        }
       }
-    }
 
-    diamond_descriptor.edge_owner_vector.resize(edge_cell_owner.size());
-    for (size_t i_edge = 0; i_edge < edge_cell_owner.size(); ++i_edge) {
-      diamond_descriptor.edge_owner_vector[i_edge] = diamond_descriptor.cell_owner_vector[edge_cell_owner[i_edge]];
-    }
+      return edge_owner_vector;
+    }());
   }
 
   m_connectivity = ConnectivityType::build(diamond_descriptor);
diff --git a/src/mesh/DiamondDualMeshBuilder.cpp b/src/mesh/DiamondDualMeshBuilder.cpp
index 21e413ff4e94519b79fbc26e87d742e67c0436c2..64711edc72055effd9e5608bc01b71ea184ee1fc 100644
--- a/src/mesh/DiamondDualMeshBuilder.cpp
+++ b/src/mesh/DiamondDualMeshBuilder.cpp
@@ -43,8 +43,6 @@ DiamondDualMeshBuilder::_buildDualDiamondMeshFrom(const IMesh& i_mesh)
 
 DiamondDualMeshBuilder::DiamondDualMeshBuilder(const IMesh& i_mesh)
 {
-  std::cout << "building DiamondDualMesh\n";
-
   switch (i_mesh.dimension()) {
   case 2: {
     this->_buildDualDiamondMeshFrom<2>(i_mesh);
diff --git a/src/mesh/Dual1DConnectivityBuilder.cpp b/src/mesh/Dual1DConnectivityBuilder.cpp
index 30c465b8b07375fce9f82fb76a26ac24848f3c67..52f3ddc977adc531496bf02b10e2b3ef36dfa5f1 100644
--- a/src/mesh/Dual1DConnectivityBuilder.cpp
+++ b/src/mesh/Dual1DConnectivityBuilder.cpp
@@ -28,14 +28,14 @@ Dual1DConnectivityBuilder::_buildConnectivityDescriptor(const Connectivity<1>& p
   const auto& primal_node_to_cell_matrix = primal_connectivity.nodeToCellMatrix();
   size_t next_kept_node_id               = 0;
 
-  dual_descriptor.node_number_vector.resize(dual_number_of_nodes);
+  Array<int> node_number_vector(dual_number_of_nodes);
 
   const auto& primal_node_number = primal_connectivity.nodeNumber();
 
   for (NodeId primal_node_id = 0; primal_node_id < primal_connectivity.numberOfNodes(); ++primal_node_id) {
     const auto& primal_node_cell_list = primal_node_to_cell_matrix[primal_node_id];
     if (primal_node_cell_list.size() == 1) {
-      dual_descriptor.node_number_vector[next_kept_node_id++] = primal_node_number[primal_node_id];
+      node_number_vector[next_kept_node_id++] = primal_node_number[primal_node_id];
     }
   }
 
@@ -46,43 +46,56 @@ Dual1DConnectivityBuilder::_buildConnectivityDescriptor(const Connectivity<1>& p
   const size_t cell_number_shift = max(primal_node_number) + 1;
 
   for (CellId primal_cell_id = 0; primal_cell_id < primal_number_of_cells; ++primal_cell_id) {
-    dual_descriptor.node_number_vector[primal_number_of_kept_nodes + primal_cell_id] =
+    node_number_vector[primal_number_of_kept_nodes + primal_cell_id] =
       primal_cell_number[primal_cell_id] + cell_number_shift;
   }
+  dual_descriptor.setNodeNumberVector(node_number_vector);
 
   Assert(number_of_kept_nodes == next_kept_node_id, "unexpected number of kept nodes");
 
-  dual_descriptor.cell_number_vector.resize(dual_number_of_cells);
-  for (NodeId primal_node_id = 0; primal_node_id < primal_number_of_nodes; ++primal_node_id) {
-    dual_descriptor.cell_number_vector[primal_node_id] = primal_node_number[primal_node_id];
-  }
-
-  dual_descriptor.cell_type_vector = std::vector<CellType>(dual_number_of_cells, CellType::Line);
+  dual_descriptor.setCellNumberVector([&] {
+    Array<int> cell_number_vector(dual_number_of_cells);
+    parallel_for(
+      primal_number_of_nodes, PUGS_LAMBDA(const NodeId primal_node_id) {
+        cell_number_vector[primal_node_id] = primal_node_number[primal_node_id];
+      });
+    return cell_number_vector;
+  }());
 
-  dual_descriptor.cell_to_node_vector.resize(dual_number_of_cells);
+  Array<CellType> cell_type_vector(dual_number_of_cells);
+  cell_type_vector.fill(CellType::Line);
+  dual_descriptor.setCellTypeVector(cell_type_vector);
 
   const auto& primal_node_local_number_in_their_cells = primal_connectivity.nodeLocalNumbersInTheirCells();
 
+  Array<unsigned int> cell_to_node_row(dual_number_of_cells + 1);
+  parallel_for(
+    cell_to_node_row.size(), PUGS_LAMBDA(const CellId cell_id) { cell_to_node_row[cell_id] = 2 * cell_id; });
+
+  Array<unsigned int> cell_to_node_list(cell_to_node_row[cell_to_node_row.size() - 1]);
   {
     size_t next_kept_node_id = 0;
+    size_t i_cell_node       = 0;
     for (NodeId i_node = 0; i_node < primal_connectivity.numberOfNodes(); ++i_node) {
-      const size_t& i_dual_cell         = i_node;
       const auto& primal_node_cell_list = primal_node_to_cell_matrix[i_node];
-      dual_descriptor.cell_to_node_vector[i_dual_cell].resize(2);
       if (primal_node_cell_list.size() == 1) {
         const auto i_node_in_cell = primal_node_local_number_in_their_cells(i_node, 0);
 
-        dual_descriptor.cell_to_node_vector[i_dual_cell][i_node_in_cell] = next_kept_node_id++;
-        dual_descriptor.cell_to_node_vector[i_dual_cell][1 - i_node_in_cell] =
-          number_of_kept_nodes + primal_node_cell_list[0];
+        cell_to_node_list[i_cell_node + i_node_in_cell]     = next_kept_node_id++;
+        cell_to_node_list[i_cell_node + 1 - i_node_in_cell] = number_of_kept_nodes + primal_node_cell_list[0];
+
+        i_cell_node += 2;
+
       } else {
         const auto i0 = primal_node_local_number_in_their_cells(i_node, 0);
 
-        dual_descriptor.cell_to_node_vector[i_dual_cell][0] = number_of_kept_nodes + primal_node_cell_list[1 - i0];
-        dual_descriptor.cell_to_node_vector[i_dual_cell][1] = number_of_kept_nodes + primal_node_cell_list[i0];
+        cell_to_node_list[i_cell_node++] = number_of_kept_nodes + primal_node_cell_list[1 - i0];
+        cell_to_node_list[i_cell_node++] = number_of_kept_nodes + primal_node_cell_list[i0];
       }
     }
   }
+
+  dual_descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row, cell_to_node_list));
 }
 
 void
@@ -97,10 +110,12 @@ Dual1DConnectivityBuilder::_buildConnectivityFrom(const IConnectivity& i_primal_
   this->_buildConnectivityDescriptor(primal_connectivity, dual_descriptor);
 
   {
+    const auto& dual_node_number_vector = dual_descriptor.nodeNumberVector();
+
     const std::unordered_map<unsigned int, NodeId> node_to_id_map = [&] {
       std::unordered_map<unsigned int, NodeId> node_to_id_map;
-      for (size_t i_node = 0; i_node < dual_descriptor.node_number_vector.size(); ++i_node) {
-        node_to_id_map[dual_descriptor.node_number_vector[i_node]] = i_node;
+      for (size_t i_node = 0; i_node < dual_node_number_vector.size(); ++i_node) {
+        node_to_id_map[dual_node_number_vector[i_node]] = i_node;
       }
       return node_to_id_map;
     }();
@@ -138,31 +153,33 @@ Dual1DConnectivityBuilder::_buildConnectivityFrom(const IConnectivity& i_primal_
   const size_t primal_number_of_nodes = primal_connectivity.numberOfNodes();
   const size_t primal_number_of_cells = primal_connectivity.numberOfCells();
 
-  dual_descriptor.node_owner_vector.resize(dual_descriptor.node_number_vector.size());
-
-  {
+  dual_descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(dual_descriptor.nodeNumberVector().size());
     const auto& node_to_cell_matrix = primal_connectivity.nodeToCellMatrix();
     const auto& primal_node_owner   = primal_connectivity.nodeOwner();
     size_t next_kept_node_id        = 0;
     for (NodeId primal_node_id = 0; primal_node_id < primal_connectivity.numberOfNodes(); ++primal_node_id) {
       if (node_to_cell_matrix[primal_node_id].size() == 1) {
-        dual_descriptor.node_owner_vector[next_kept_node_id++] = primal_node_owner[primal_node_id];
+        node_owner_vector[next_kept_node_id++] = primal_node_owner[primal_node_id];
       }
     }
     const size_t number_of_kept_nodes = next_kept_node_id;
     const auto& primal_cell_owner     = primal_connectivity.cellOwner();
     for (CellId primal_cell_id = 0; primal_cell_id < primal_number_of_cells; ++primal_cell_id) {
-      dual_descriptor.node_owner_vector[number_of_kept_nodes + primal_cell_id] = primal_cell_owner[primal_cell_id];
+      node_owner_vector[number_of_kept_nodes + primal_cell_id] = primal_cell_owner[primal_cell_id];
     }
-  }
 
-  {
-    dual_descriptor.cell_owner_vector.resize(dual_descriptor.cell_number_vector.size());
+    return node_owner_vector;
+  }());
+
+  dual_descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(dual_descriptor.cellNumberVector().size());
     const auto& primal_node_owner = primal_connectivity.nodeOwner();
-    for (NodeId primal_node_id = 0; primal_node_id < primal_number_of_nodes; ++primal_node_id) {
-      dual_descriptor.cell_owner_vector[primal_node_id] = primal_node_owner[primal_node_id];
-    }
-  }
+    parallel_for(
+      primal_number_of_nodes,
+      PUGS_LAMBDA(NodeId primal_node_id) { cell_owner_vector[primal_node_id] = primal_node_owner[primal_node_id]; });
+    return cell_owner_vector;
+  }());
 
   m_connectivity = ConnectivityType::build(dual_descriptor);
 
diff --git a/src/mesh/GmshReader.cpp b/src/mesh/GmshReader.cpp
index e555ca224c1d02f11e11696542774f2e470b6432..e4ae59ea7cbb6fa2ec3f622c0868ff79ff9f8bbf 100644
--- a/src/mesh/GmshReader.cpp
+++ b/src/mesh/GmshReader.cpp
@@ -35,19 +35,29 @@ GmshConnectivityBuilder<1>::GmshConnectivityBuilder(const GmshReader::GmshData&
 {
   ConnectivityDescriptor descriptor;
 
-  descriptor.node_number_vector = gmsh_data.__verticesNumbers;
-  descriptor.cell_type_vector.resize(nb_cells);
-  descriptor.cell_number_vector.resize(nb_cells);
-  descriptor.cell_to_node_vector.resize(nb_cells);
+  descriptor.setNodeNumberVector(convert_to_array(gmsh_data.__verticesNumbers));
+  Array<CellType> cell_type_vector(nb_cells);
+  Array<int> cell_number_vector(nb_cells);
 
-  for (size_t j = 0; j < nb_cells; ++j) {
-    descriptor.cell_to_node_vector[j].resize(2);
-    for (int r = 0; r < 2; ++r) {
-      descriptor.cell_to_node_vector[j][r] = gmsh_data.__edges[j][r];
+  Array<unsigned int> cell_to_node_row(nb_cells + 1);
+  parallel_for(
+    cell_to_node_row.size(), PUGS_LAMBDA(const CellId cell_id) { cell_to_node_row[cell_id] = 2 * cell_id; });
+
+  Array<unsigned int> cell_to_node_list(cell_to_node_row[cell_to_node_row.size() - 1]);
+  for (CellId cell_id = 0; cell_id < nb_cells; ++cell_id) {
+    for (int i_node = 0; i_node < 2; ++i_node) {
+      cell_to_node_list[2 * cell_id + i_node] = gmsh_data.__edges[cell_id][i_node];
     }
-    descriptor.cell_type_vector[j]   = CellType::Line;
-    descriptor.cell_number_vector[j] = gmsh_data.__edges_number[j];
   }
+  descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row, cell_to_node_list));
+
+  cell_type_vector.fill(CellType::Line);
+  descriptor.setCellTypeVector(cell_type_vector);
+
+  for (size_t j = 0; j < nb_cells; ++j) {
+    cell_number_vector[j] = gmsh_data.__edges_number[j];
+  }
+  descriptor.setCellNumberVector(cell_number_vector);
 
   std::map<unsigned int, std::vector<unsigned int>> ref_points_map;
   for (unsigned int r = 0; r < gmsh_data.__points.size(); ++r) {
@@ -56,12 +66,14 @@ GmshConnectivityBuilder<1>::GmshConnectivityBuilder(const GmshReader::GmshData&
     ref_points_map[ref].push_back(point_number);
   }
 
-  Array<size_t> node_nb_cell(descriptor.node_number_vector.size());
+  Array<size_t> node_nb_cell(descriptor.nodeNumberVector().size());
   node_nb_cell.fill(0);
 
+  const auto& cell_to_node_matrix = descriptor.cellToNodeMatrix();
   for (size_t j = 0; j < nb_cells; ++j) {
+    const auto& cell_node_list = cell_to_node_matrix[j];
     for (int r = 0; r < 2; ++r) {
-      node_nb_cell[descriptor.cell_to_node_vector[j][r]] += 1;
+      node_nb_cell[cell_node_list[r]] += 1;
     }
   }
 
@@ -116,12 +128,18 @@ GmshConnectivityBuilder<1>::GmshConnectivityBuilder(const GmshReader::GmshData&
     descriptor.addRefItemList(RefCellList(ref_id, cell_list, false));
   }
 
-  descriptor.cell_owner_vector.resize(nb_cells);
-  std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
-
-  descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-  std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
-
+  descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(nb_cells);
+    cell_owner_vector.fill(parallel::rank());
+    return cell_owner_vector;
+  }());
+
+  descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(descriptor.nodeNumberVector().size());
+    node_owner_vector.fill(parallel::rank());
+    return node_owner_vector;
+  }());
+  ;
   m_connectivity = Connectivity1D::build(descriptor);
 }
 
@@ -130,32 +148,56 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
 {
   ConnectivityDescriptor descriptor;
 
-  descriptor.node_number_vector = gmsh_data.__verticesNumbers;
-  descriptor.cell_type_vector.resize(nb_cells);
-  descriptor.cell_number_vector.resize(nb_cells);
-  descriptor.cell_to_node_vector.resize(nb_cells);
+  descriptor.setNodeNumberVector(convert_to_array(gmsh_data.__verticesNumbers));
+  Array<CellType> cell_type_vector(nb_cells);
+  Array<int> cell_number_vector(nb_cells);
 
-  const size_t nb_triangles = gmsh_data.__triangles.size();
-  for (size_t j = 0; j < nb_triangles; ++j) {
-    descriptor.cell_to_node_vector[j].resize(3);
-    for (int r = 0; r < 3; ++r) {
-      descriptor.cell_to_node_vector[j][r] = gmsh_data.__triangles[j][r];
+  Array<unsigned int> cell_to_node_row(gmsh_data.__triangles.size() + gmsh_data.__quadrangles.size() + 1);
+  {
+    cell_to_node_row[0] = 0;
+    size_t i_cell       = 0;
+    for (size_t i_triangle = 0; i_triangle < gmsh_data.__triangles.size(); ++i_triangle, ++i_cell) {
+      cell_to_node_row[i_cell + 1] = cell_to_node_row[i_cell] + 3;
+    }
+    for (size_t i_quadrangle = 0; i_quadrangle < gmsh_data.__quadrangles.size(); ++i_quadrangle, ++i_cell) {
+      cell_to_node_row[i_cell + 1] = cell_to_node_row[i_cell] + 4;
     }
-    descriptor.cell_type_vector[j]   = CellType::Triangle;
-    descriptor.cell_number_vector[j] = gmsh_data.__triangles_number[j];
+  }
+
+  Array<unsigned int> cell_to_node_list(cell_to_node_row[cell_to_node_row.size() - 1]);
+  {
+    size_t i_cell_node = 0;
+    for (size_t i_triangle = 0; i_triangle < gmsh_data.__triangles.size(); ++i_triangle) {
+      cell_to_node_list[i_cell_node++] = gmsh_data.__triangles[i_triangle][0];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__triangles[i_triangle][1];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__triangles[i_triangle][2];
+    }
+    for (size_t i_quadrangle = 0; i_quadrangle < gmsh_data.__quadrangles.size(); ++i_quadrangle) {
+      cell_to_node_list[i_cell_node++] = gmsh_data.__quadrangles[i_quadrangle][0];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__quadrangles[i_quadrangle][1];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__quadrangles[i_quadrangle][2];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__quadrangles[i_quadrangle][3];
+    }
+  }
+  descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row, cell_to_node_list));
+
+  const size_t nb_triangles = gmsh_data.__triangles.size();
+  for (size_t i_triangle = 0; i_triangle < nb_triangles; ++i_triangle) {
+    cell_type_vector[i_triangle]   = CellType::Triangle;
+    cell_number_vector[i_triangle] = gmsh_data.__triangles_number[i_triangle];
   }
 
   const size_t nb_quadrangles = gmsh_data.__quadrangles.size();
-  for (size_t j = 0; j < nb_quadrangles; ++j) {
-    const size_t jq = j + nb_triangles;
-    descriptor.cell_to_node_vector[jq].resize(4);
-    for (int r = 0; r < 4; ++r) {
-      descriptor.cell_to_node_vector[jq][r] = gmsh_data.__quadrangles[j][r];
-    }
-    descriptor.cell_type_vector[jq]   = CellType::Quadrangle;
-    descriptor.cell_number_vector[jq] = gmsh_data.__quadrangles_number[j];
+  for (size_t i_quadrangle = 0; i_quadrangle < nb_quadrangles; ++i_quadrangle) {
+    const size_t i_cell = i_quadrangle + nb_triangles;
+
+    cell_type_vector[i_cell]   = CellType::Quadrangle;
+    cell_number_vector[i_cell] = gmsh_data.__quadrangles_number[i_quadrangle];
   }
 
+  descriptor.setCellNumberVector(cell_number_vector);
+  descriptor.setCellTypeVector(cell_type_vector);
+
   std::map<unsigned int, std::vector<unsigned int>> ref_cells_map;
   for (unsigned int j = 0; j < gmsh_data.__triangles_ref.size(); ++j) {
     const unsigned int elem_number = j;
@@ -189,64 +231,73 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
 
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<2>(descriptor);
 
-  using Face                                                                 = ConnectivityFace<2>;
-  const auto& node_number_vector                                             = descriptor.node_number_vector;
-  const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map = [&] {
-    std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
-    for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-      const auto& node_vector                               = descriptor.face_to_node_vector[l];
-      face_to_id_map[Face(node_vector, node_number_vector)] = l;
-    }
-    return face_to_id_map;
-  }();
-
   std::unordered_map<int, FaceId> face_number_id_map = [&] {
+    const auto& face_number_vector = descriptor.faceNumberVector();
     std::unordered_map<int, FaceId> face_number_id_map;
-    for (size_t l = 0; l < descriptor.face_number_vector.size(); ++l) {
-      face_number_id_map[descriptor.face_number_vector[l]] = l;
+    for (size_t l = 0; l < face_number_vector.size(); ++l) {
+      face_number_id_map[face_number_vector[l]] = l;
     }
-    Assert(face_number_id_map.size() == descriptor.face_number_vector.size());
+    Assert(face_number_id_map.size() == face_number_vector.size());
     return face_number_id_map;
   }();
 
+  const auto& node_number_vector  = descriptor.nodeNumberVector();
+  const auto& node_to_face_matrix = descriptor.nodeToFaceMatrix();
+  const auto& face_to_node_matrix = descriptor.faceToNodeMatrix();
+
+  const auto find_face = [&](uint32_t node0, uint32_t node1) {
+    if (node_number_vector[node0] > node_number_vector[node1]) {
+      std::swap(node0, node1);
+    }
+    const auto& face_id_vector = node_to_face_matrix[node0];
+
+    for (size_t i_face = 0; i_face < face_id_vector.size(); ++i_face) {
+      const FaceId face_id = face_id_vector[i_face];
+      if (face_to_node_matrix[face_id][1] == node1) {
+        return face_id;
+      }
+    }
+
+    std::stringstream error_msg;
+    error_msg << "face (" << node0 << ',' << node1 << ") not found";
+    throw NormalError(error_msg.str());
+  };
+
+  Array<int> face_number_vector = copy(descriptor.faceNumberVector());
   std::map<unsigned int, std::vector<unsigned int>> ref_faces_map;
   for (unsigned int e = 0; e < gmsh_data.__edges.size(); ++e) {
-    const unsigned int edge_id = [&] {
-      auto i = face_to_id_map.find(Face({gmsh_data.__edges[e][0], gmsh_data.__edges[e][1]}, node_number_vector));
-      if (i == face_to_id_map.end()) {
-        std::stringstream error_msg;
-        error_msg << "face " << gmsh_data.__edges[e][0] << " not found";
-        throw NormalError(error_msg.str());
-      }
-      return i->second;
-    }();
+    const unsigned int edge_id = find_face(gmsh_data.__edges[e][0], gmsh_data.__edges[e][1]);
+
     const unsigned int& ref = gmsh_data.__edges_ref[e];
     ref_faces_map[ref].push_back(edge_id);
 
-    if (descriptor.face_number_vector[edge_id] != gmsh_data.__edges_number[e]) {
+    if (face_number_vector[edge_id] != gmsh_data.__edges_number[e]) {
       if (auto i_face = face_number_id_map.find(gmsh_data.__edges_number[e]); i_face != face_number_id_map.end()) {
         const int other_edge_id = i_face->second;
-        std::swap(descriptor.face_number_vector[edge_id], descriptor.face_number_vector[other_edge_id]);
+        std::swap(face_number_vector[edge_id], face_number_vector[other_edge_id]);
 
-        face_number_id_map.erase(descriptor.face_number_vector[edge_id]);
-        face_number_id_map.erase(descriptor.face_number_vector[other_edge_id]);
+        face_number_id_map.erase(face_number_vector[edge_id]);
+        face_number_id_map.erase(face_number_vector[other_edge_id]);
 
-        face_number_id_map[descriptor.face_number_vector[edge_id]]       = edge_id;
-        face_number_id_map[descriptor.face_number_vector[other_edge_id]] = other_edge_id;
+        face_number_id_map[face_number_vector[edge_id]]       = edge_id;
+        face_number_id_map[face_number_vector[other_edge_id]] = other_edge_id;
       } else {
-        face_number_id_map.erase(descriptor.face_number_vector[edge_id]);
-        descriptor.face_number_vector[edge_id]                     = gmsh_data.__edges_number[e];
-        face_number_id_map[descriptor.face_number_vector[edge_id]] = edge_id;
+        face_number_id_map.erase(face_number_vector[edge_id]);
+        face_number_vector[edge_id]                     = gmsh_data.__edges_number[e];
+        face_number_id_map[face_number_vector[edge_id]] = edge_id;
       }
     }
   }
+  descriptor.setFaceNumberVector(face_number_vector);
 
-  Array<size_t> face_nb_cell(descriptor.face_number_vector.size());
+  Array<size_t> face_nb_cell(descriptor.faceNumberVector().size());
   face_nb_cell.fill(0);
 
-  for (size_t j = 0; j < descriptor.cell_to_face_vector.size(); ++j) {
-    for (size_t l = 0; l < descriptor.cell_to_face_vector[j].size(); ++l) {
-      face_nb_cell[descriptor.cell_to_face_vector[j][l]] += 1;
+  const auto& cell_to_face_matrix = descriptor.cellToFaceMatrix();
+  for (size_t j = 0; j < cell_to_face_matrix.numberOfRows(); ++j) {
+    const auto& cell_face_list = cell_to_face_matrix[j];
+    for (size_t l = 0; l < cell_face_list.size(); ++l) {
+      face_nb_cell[cell_face_list[l]] += 1;
     }
   }
 
@@ -276,13 +327,14 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
     descriptor.addRefItemList(RefFaceList{ref_id, face_list, is_boundary});
   }
 
-  Array<bool> is_boundary_node(descriptor.node_number_vector.size());
+  Array<bool> is_boundary_node(node_number_vector.size());
   is_boundary_node.fill(false);
 
   for (size_t i_face = 0; i_face < face_nb_cell.size(); ++i_face) {
     if (face_nb_cell[i_face] == 1) {
-      for (size_t node_id : descriptor.face_to_node_vector[i_face]) {
-        is_boundary_node[node_id] = true;
+      const auto& face_node_list = face_to_node_matrix[i_face];
+      for (size_t i_node = 0; i_node < face_node_list.size(); ++i_node) {
+        is_boundary_node[face_node_list[i_node]] = true;
       }
     }
   }
@@ -319,14 +371,23 @@ GmshConnectivityBuilder<2>::GmshConnectivityBuilder(const GmshReader::GmshData&
     descriptor.addRefItemList(RefNodeList(ref_id, point_list, is_boundary));
   }
 
-  descriptor.cell_owner_vector.resize(nb_cells);
-  std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
+  descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(nb_cells);
+    cell_owner_vector.fill(parallel::rank());
+    return cell_owner_vector;
+  }());
 
-  descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
-  std::fill(descriptor.face_owner_vector.begin(), descriptor.face_owner_vector.end(), parallel::rank());
+  descriptor.setFaceOwnerVector([&] {
+    Array<int> face_owner_vector(descriptor.faceNumberVector().size());
+    face_owner_vector.fill(parallel::rank());
+    return face_owner_vector;
+  }());
 
-  descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-  std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
+  descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(node_number_vector.size());
+    node_owner_vector.fill(parallel::rank());
+    return node_owner_vector;
+  }());
 
   m_connectivity = Connectivity2D::build(descriptor);
 }
@@ -336,54 +397,99 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
 {
   ConnectivityDescriptor descriptor;
 
-  descriptor.node_number_vector = gmsh_data.__verticesNumbers;
-  descriptor.cell_type_vector.resize(nb_cells);
-  descriptor.cell_number_vector.resize(nb_cells);
-  descriptor.cell_to_node_vector.resize(nb_cells);
+  descriptor.setNodeNumberVector(convert_to_array(gmsh_data.__verticesNumbers));
+  Array<CellType> cell_type_vector(nb_cells);
+  Array<int> cell_number_vector(nb_cells);
 
   const size_t nb_tetrahedra = gmsh_data.__tetrahedra.size();
-  for (size_t j = 0; j < nb_tetrahedra; ++j) {
-    descriptor.cell_to_node_vector[j].resize(4);
-    for (int r = 0; r < 4; ++r) {
-      descriptor.cell_to_node_vector[j][r] = gmsh_data.__tetrahedra[j][r];
+  const size_t nb_hexahedra  = gmsh_data.__hexahedra.size();
+  const size_t nb_prisms     = gmsh_data.__prisms.size();
+  const size_t nb_pyramids   = gmsh_data.__pyramids.size();
+
+  Array<unsigned int> cell_to_node_row(nb_cells + 1);
+  {
+    cell_to_node_row[0] = 0;
+    size_t i_cell       = 0;
+    for (size_t i_tetrahedron = 0; i_tetrahedron < nb_tetrahedra; ++i_tetrahedron, ++i_cell) {
+      cell_to_node_row[i_cell + 1] = cell_to_node_row[i_cell] + 4;
+    }
+    for (size_t i_hexahedron = 0; i_hexahedron < nb_hexahedra; ++i_hexahedron, ++i_cell) {
+      cell_to_node_row[i_cell + 1] = cell_to_node_row[i_cell] + 8;
+    }
+    for (size_t i_prism = 0; i_prism < nb_prisms; ++i_prism, ++i_cell) {
+      cell_to_node_row[i_cell + 1] = cell_to_node_row[i_cell] + 6;
+    }
+    for (size_t i_pyramid = 0; i_pyramid < nb_pyramids; ++i_pyramid, ++i_cell) {
+      cell_to_node_row[i_cell + 1] = cell_to_node_row[i_cell] + 5;
+    }
+  }
+
+  Array<unsigned int> cell_to_node_list(cell_to_node_row[cell_to_node_row.size() - 1]);
+  {
+    size_t i_cell_node = 0;
+    for (size_t i_tetrahedron = 0; i_tetrahedron < nb_tetrahedra; ++i_tetrahedron) {
+      cell_to_node_list[i_cell_node++] = gmsh_data.__tetrahedra[i_tetrahedron][0];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__tetrahedra[i_tetrahedron][1];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__tetrahedra[i_tetrahedron][2];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__tetrahedra[i_tetrahedron][3];
+    }
+    for (size_t i_hexahedron = 0; i_hexahedron < nb_hexahedra; ++i_hexahedron) {
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][0];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][1];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][2];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][3];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][4];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][5];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][6];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__hexahedra[i_hexahedron][7];
+    }
+    for (size_t i_prism = 0; i_prism < nb_prisms; ++i_prism) {
+      cell_to_node_list[i_cell_node++] = gmsh_data.__prisms[i_prism][0];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__prisms[i_prism][1];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__prisms[i_prism][2];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__prisms[i_prism][3];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__prisms[i_prism][4];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__prisms[i_prism][5];
+    }
+    for (size_t i_pyramid = 0; i_pyramid < nb_pyramids; ++i_pyramid) {
+      cell_to_node_list[i_cell_node++] = gmsh_data.__pyramids[i_pyramid][0];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__pyramids[i_pyramid][1];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__pyramids[i_pyramid][2];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__pyramids[i_pyramid][3];
+      cell_to_node_list[i_cell_node++] = gmsh_data.__pyramids[i_pyramid][4];
     }
-    descriptor.cell_type_vector[j]   = CellType::Tetrahedron;
-    descriptor.cell_number_vector[j] = gmsh_data.__tetrahedra_number[j];
   }
 
-  const size_t nb_hexahedra = gmsh_data.__hexahedra.size();
+  for (size_t j = 0; j < nb_tetrahedra; ++j) {
+    cell_type_vector[j]   = CellType::Tetrahedron;
+    cell_number_vector[j] = gmsh_data.__tetrahedra_number[j];
+  }
+
   for (size_t j = 0; j < nb_hexahedra; ++j) {
     const size_t jh = nb_tetrahedra + j;
-    descriptor.cell_to_node_vector[jh].resize(8);
-    for (int r = 0; r < 8; ++r) {
-      descriptor.cell_to_node_vector[jh][r] = gmsh_data.__hexahedra[j][r];
-    }
-    descriptor.cell_type_vector[jh]   = CellType::Hexahedron;
-    descriptor.cell_number_vector[jh] = gmsh_data.__hexahedra_number[j];
+
+    cell_type_vector[jh]   = CellType::Hexahedron;
+    cell_number_vector[jh] = gmsh_data.__hexahedra_number[j];
   }
 
-  const size_t nb_prisms = gmsh_data.__prisms.size();
   for (size_t j = 0; j < nb_prisms; ++j) {
     const size_t jp = nb_tetrahedra + nb_hexahedra + j;
-    descriptor.cell_to_node_vector[jp].resize(6);
-    for (int r = 0; r < 6; ++r) {
-      descriptor.cell_to_node_vector[jp][r] = gmsh_data.__prisms[j][r];
-    }
-    descriptor.cell_type_vector[jp]   = CellType::Prism;
-    descriptor.cell_number_vector[jp] = gmsh_data.__prisms_number[j];
+
+    cell_type_vector[jp]   = CellType::Prism;
+    cell_number_vector[jp] = gmsh_data.__prisms_number[j];
   }
 
-  const size_t nb_pyramids = gmsh_data.__pyramids.size();
   for (size_t j = 0; j < nb_pyramids; ++j) {
     const size_t jh = nb_tetrahedra + nb_hexahedra + nb_prisms + j;
-    descriptor.cell_to_node_vector[jh].resize(5);
-    for (int r = 0; r < 5; ++r) {
-      descriptor.cell_to_node_vector[jh][r] = gmsh_data.__pyramids[j][r];
-    }
-    descriptor.cell_type_vector[jh]   = CellType::Pyramid;
-    descriptor.cell_number_vector[jh] = gmsh_data.__pyramids_number[j];
+
+    cell_type_vector[jh]   = CellType::Pyramid;
+    cell_number_vector[jh] = gmsh_data.__pyramids_number[j];
   }
 
+  descriptor.setCellNumberVector(cell_number_vector);
+  descriptor.setCellTypeVector(cell_type_vector);
+  descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row, cell_to_node_list));
+
   std::map<unsigned int, std::vector<unsigned int>> ref_cells_map;
   for (unsigned int j = 0; j < gmsh_data.__tetrahedra_ref.size(); ++j) {
     const unsigned int elem_number = j;
@@ -429,103 +535,140 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
 
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<3>(descriptor);
 
-  const auto& node_number_vector = descriptor.node_number_vector;
+  const auto& node_number_vector = descriptor.nodeNumberVector();
 
-  Array<size_t> face_nb_cell(descriptor.face_number_vector.size());
+  Array<size_t> face_nb_cell(descriptor.faceNumberVector().size());
   face_nb_cell.fill(0);
 
-  for (size_t j = 0; j < descriptor.cell_to_face_vector.size(); ++j) {
-    for (size_t l = 0; l < descriptor.cell_to_face_vector[j].size(); ++l) {
-      face_nb_cell[descriptor.cell_to_face_vector[j][l]] += 1;
+  const auto& cell_to_face_matrix = descriptor.cellToFaceMatrix();
+
+  for (size_t j = 0; j < cell_to_face_matrix.numberOfRows(); ++j) {
+    const auto& cell_face_list = cell_to_face_matrix[j];
+    for (size_t l = 0; l < cell_face_list.size(); ++l) {
+      face_nb_cell[cell_face_list[l]] += 1;
     }
   }
 
+  const auto& face_number_vector = descriptor.faceNumberVector();
   {
-    using Face                                                                 = ConnectivityFace<3>;
-    const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map = [&] {
-      std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
-      for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-        const auto& node_vector                               = descriptor.face_to_node_vector[l];
-        face_to_id_map[Face(node_vector, node_number_vector)] = l;
-      }
-      return face_to_id_map;
-    }();
-
     std::unordered_map<int, FaceId> face_number_id_map = [&] {
       std::unordered_map<int, FaceId> face_number_id_map;
-      for (size_t l = 0; l < descriptor.face_number_vector.size(); ++l) {
-        face_number_id_map[descriptor.face_number_vector[l]] = l;
+      for (size_t l = 0; l < face_number_vector.size(); ++l) {
+        face_number_id_map[face_number_vector[l]] = l;
       }
-      Assert(face_number_id_map.size() == descriptor.face_number_vector.size());
+      Assert(face_number_id_map.size() == face_number_vector.size());
       return face_number_id_map;
     }();
 
+    const auto& node_to_face_matrix = descriptor.nodeToFaceMatrix();
+    const auto& face_to_node_matrix = descriptor.faceToNodeMatrix();
+
+    const auto find_face = [&](std::vector<uint32_t> node_list) {
+      size_t i_node_smallest_number = 0;
+      for (size_t i_node = 1; i_node < node_list.size(); ++i_node) {
+        if (node_number_vector[node_list[i_node]] < node_number_vector[node_list[i_node_smallest_number]]) {
+          i_node_smallest_number = i_node;
+        }
+      }
+
+      if (i_node_smallest_number != 0) {
+        std::vector<uint64_t> buffer(node_list.size());
+        for (size_t i_node = i_node_smallest_number; i_node < buffer.size(); ++i_node) {
+          buffer[i_node - i_node_smallest_number] = node_list[i_node];
+        }
+        for (size_t i_node = 0; i_node < i_node_smallest_number; ++i_node) {
+          buffer[i_node + node_list.size() - i_node_smallest_number] = node_list[i_node];
+        }
+
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          node_list[i_node] = buffer[i_node];
+        }
+      }
+
+      if (node_number_vector[node_list[1]] > node_number_vector[node_list[node_list.size() - 1]]) {
+        for (size_t i_node = 1; i_node <= (node_list.size() + 1) / 2 - 1; ++i_node) {
+          std::swap(node_list[i_node], node_list[node_list.size() - i_node]);
+        }
+      }
+
+      const auto& face_id_vector = node_to_face_matrix[node_list[0]];
+
+      for (size_t i_face = 0; i_face < face_id_vector.size(); ++i_face) {
+        const FaceId face_id          = face_id_vector[i_face];
+        const auto& face_node_id_list = face_to_node_matrix[face_id];
+        if (face_node_id_list.size() == node_list.size()) {
+          bool is_same = true;
+          for (size_t i_node = 1; i_node < face_node_id_list.size(); ++i_node) {
+            is_same &= (face_node_id_list[i_node] == node_list[i_node]);
+          }
+          if (is_same) {
+            return face_id;
+          }
+        }
+      }
+
+      std::stringstream error_msg;
+      error_msg << "face (" << node_list[0];
+      for (size_t i = 1; i < node_list.size(); ++i) {
+        error_msg << ',' << node_list[i];
+      }
+      error_msg << ") not found";
+      throw NormalError(error_msg.str());
+    };
+
+    Array<int> face_number_vector = copy(descriptor.faceNumberVector());
     std::map<unsigned int, std::vector<unsigned int>> ref_faces_map;
     for (unsigned int f = 0; f < gmsh_data.__triangles.size(); ++f) {
-      const unsigned int face_id = [&] {
-        auto i = face_to_id_map.find(
-          Face({gmsh_data.__triangles[f][0], gmsh_data.__triangles[f][1], gmsh_data.__triangles[f][2]},
-               node_number_vector));
-        if (i == face_to_id_map.end()) {
-          throw NormalError("face not found");
-        }
-        return i->second;
-      }();
+      const unsigned int face_id =
+        find_face({gmsh_data.__triangles[f][0], gmsh_data.__triangles[f][1], gmsh_data.__triangles[f][2]});
 
       const unsigned int& ref = gmsh_data.__triangles_ref[f];
       ref_faces_map[ref].push_back(face_id);
 
-      if (descriptor.face_number_vector[face_id] != gmsh_data.__triangles_number[f]) {
+      if (face_number_vector[face_id] != gmsh_data.__triangles_number[f]) {
         if (auto i_face = face_number_id_map.find(gmsh_data.__triangles_number[f]);
             i_face != face_number_id_map.end()) {
           const int other_face_id = i_face->second;
-          std::swap(descriptor.face_number_vector[face_id], descriptor.face_number_vector[other_face_id]);
+          std::swap(face_number_vector[face_id], face_number_vector[other_face_id]);
 
-          face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-          face_number_id_map.erase(descriptor.face_number_vector[other_face_id]);
+          face_number_id_map.erase(face_number_vector[face_id]);
+          face_number_id_map.erase(face_number_vector[other_face_id]);
 
-          face_number_id_map[descriptor.face_number_vector[face_id]]       = face_id;
-          face_number_id_map[descriptor.face_number_vector[other_face_id]] = other_face_id;
+          face_number_id_map[face_number_vector[face_id]]       = face_id;
+          face_number_id_map[face_number_vector[other_face_id]] = other_face_id;
         } else {
-          face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-          descriptor.face_number_vector[face_id]                     = gmsh_data.__triangles_number[f];
-          face_number_id_map[descriptor.face_number_vector[face_id]] = face_id;
+          face_number_id_map.erase(face_number_vector[face_id]);
+          face_number_vector[face_id]                     = gmsh_data.__triangles_number[f];
+          face_number_id_map[face_number_vector[face_id]] = face_id;
         }
       }
     }
 
     for (unsigned int f = 0; f < gmsh_data.__quadrangles.size(); ++f) {
-      const unsigned int face_id = [&] {
-        auto i = face_to_id_map.find(Face({gmsh_data.__quadrangles[f][0], gmsh_data.__quadrangles[f][1],
-                                           gmsh_data.__quadrangles[f][2], gmsh_data.__quadrangles[f][3]},
-                                          node_number_vector));
-        if (i == face_to_id_map.end()) {
-          throw NormalError("face not found");
-        }
-        return i->second;
-      }();
+      const unsigned int face_id = find_face({gmsh_data.__quadrangles[f][0], gmsh_data.__quadrangles[f][1],
+                                              gmsh_data.__quadrangles[f][2], gmsh_data.__quadrangles[f][3]});
 
       const unsigned int& ref = gmsh_data.__quadrangles_ref[f];
       ref_faces_map[ref].push_back(face_id);
-
-      if (descriptor.face_number_vector[face_id] != gmsh_data.__quadrangles_number[f]) {
+      if (face_number_vector[face_id] != gmsh_data.__quadrangles_number[f]) {
         if (auto i_face = face_number_id_map.find(gmsh_data.__quadrangles_number[f]);
             i_face != face_number_id_map.end()) {
           const int other_face_id = i_face->second;
-          std::swap(descriptor.face_number_vector[face_id], descriptor.face_number_vector[other_face_id]);
+          std::swap(face_number_vector[face_id], face_number_vector[other_face_id]);
 
-          face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-          face_number_id_map.erase(descriptor.face_number_vector[other_face_id]);
+          face_number_id_map.erase(face_number_vector[face_id]);
+          face_number_id_map.erase(face_number_vector[other_face_id]);
 
-          face_number_id_map[descriptor.face_number_vector[face_id]]       = face_id;
-          face_number_id_map[descriptor.face_number_vector[other_face_id]] = other_face_id;
+          face_number_id_map[face_number_vector[face_id]]       = face_id;
+          face_number_id_map[face_number_vector[other_face_id]] = other_face_id;
         } else {
-          face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-          descriptor.face_number_vector[face_id]                     = gmsh_data.__quadrangles_number[f];
-          face_number_id_map[descriptor.face_number_vector[face_id]] = face_id;
+          face_number_id_map.erase(face_number_vector[face_id]);
+          face_number_vector[face_id]                     = gmsh_data.__quadrangles_number[f];
+          face_number_id_map[face_number_vector[face_id]] = face_id;
         }
       }
     }
+    descriptor.setFaceNumberVector(face_number_vector);
 
     for (const auto& ref_face_list : ref_faces_map) {
       Array<FaceId> face_list(ref_face_list.second.size());
@@ -556,68 +699,76 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
   ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities<3>(descriptor);
 
   {
-    Array<bool> is_boundary_edge(descriptor.edge_number_vector.size());
+    Array<bool> is_boundary_edge(descriptor.edgeNumberVector().size());
     is_boundary_edge.fill(false);
 
+    const auto& face_to_edge_matrix = descriptor.faceToEdgeMatrix();
+
     for (size_t i_face = 0; i_face < face_nb_cell.size(); ++i_face) {
       if (face_nb_cell[i_face] == 1) {
-        for (size_t node_id : descriptor.face_to_edge_vector[i_face]) {
-          is_boundary_edge[node_id] = true;
+        auto face_edge_list = face_to_edge_matrix[i_face];
+        for (size_t i_edge = 0; i_edge < face_edge_list.size(); ++i_edge) {
+          is_boundary_edge[face_edge_list[i_edge]] = true;
         }
       }
     }
 
-    using Edge                                                                 = ConnectivityFace<2>;
-    const auto& node_number_vector                                             = descriptor.node_number_vector;
-    const std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map = [&] {
-      std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map;
-      for (EdgeId l = 0; l < descriptor.edge_to_node_vector.size(); ++l) {
-        const auto& node_vector                               = descriptor.edge_to_node_vector[l];
-        edge_to_id_map[Edge(node_vector, node_number_vector)] = l;
+    const auto& node_to_edge_matrix = descriptor.nodeToEdgeMatrix();
+    const auto& edge_to_node_matrix = descriptor.edgeToNodeMatrix();
+
+    const auto find_edge = [&](uint32_t node0, uint32_t node1) {
+      if (node_number_vector[node0] > node_number_vector[node1]) {
+        std::swap(node0, node1);
       }
-      return edge_to_id_map;
-    }();
+      const auto& edge_id_vector = node_to_edge_matrix[node0];
+
+      for (size_t i_edge = 0; i_edge < edge_id_vector.size(); ++i_edge) {
+        const EdgeId edge_id = edge_id_vector[i_edge];
+        if (edge_to_node_matrix[edge_id][1] == node1) {
+          return edge_id;
+        }
+      }
+
+      std::stringstream error_msg;
+      error_msg << "edge (" << node0 << ',' << node1 << ") not found";
+      throw NormalError(error_msg.str());
+    };
 
     std::unordered_map<int, EdgeId> edge_number_id_map = [&] {
+      const auto& edge_number_vector = descriptor.edgeNumberVector();
       std::unordered_map<int, EdgeId> edge_number_id_map;
-      for (size_t l = 0; l < descriptor.edge_number_vector.size(); ++l) {
-        edge_number_id_map[descriptor.edge_number_vector[l]] = l;
+      for (size_t l = 0; l < edge_number_vector.size(); ++l) {
+        edge_number_id_map[edge_number_vector[l]] = l;
       }
-      Assert(edge_number_id_map.size() == descriptor.edge_number_vector.size());
+      Assert(edge_number_id_map.size() == edge_number_vector.size());
       return edge_number_id_map;
     }();
 
+    Array<int> edge_number_vector = copy(descriptor.edgeNumberVector());
     std::map<unsigned int, std::vector<unsigned int>> ref_edges_map;
     for (unsigned int e = 0; e < gmsh_data.__edges.size(); ++e) {
-      const unsigned int edge_id = [&] {
-        auto i = edge_to_id_map.find(Edge({gmsh_data.__edges[e][0], gmsh_data.__edges[e][1]}, node_number_vector));
-        if (i == edge_to_id_map.end()) {
-          std::stringstream error_msg;
-          error_msg << "edge " << gmsh_data.__edges[e][0] << " not found";
-          throw NormalError(error_msg.str());
-        }
-        return i->second;
-      }();
-      const unsigned int& ref = gmsh_data.__edges_ref[e];
+      const unsigned int edge_id = find_edge(gmsh_data.__edges[e][0], gmsh_data.__edges[e][1]);
+      const unsigned int& ref    = gmsh_data.__edges_ref[e];
       ref_edges_map[ref].push_back(edge_id);
 
-      if (descriptor.edge_number_vector[edge_id] != gmsh_data.__edges_number[e]) {
+      if (edge_number_vector[edge_id] != gmsh_data.__edges_number[e]) {
         if (auto i_edge = edge_number_id_map.find(gmsh_data.__edges_number[e]); i_edge != edge_number_id_map.end()) {
           const int other_edge_id = i_edge->second;
-          std::swap(descriptor.edge_number_vector[edge_id], descriptor.edge_number_vector[other_edge_id]);
+          std::swap(edge_number_vector[edge_id], edge_number_vector[other_edge_id]);
 
-          edge_number_id_map.erase(descriptor.edge_number_vector[edge_id]);
-          edge_number_id_map.erase(descriptor.edge_number_vector[other_edge_id]);
+          edge_number_id_map.erase(edge_number_vector[edge_id]);
+          edge_number_id_map.erase(edge_number_vector[other_edge_id]);
 
-          edge_number_id_map[descriptor.edge_number_vector[edge_id]]       = edge_id;
-          edge_number_id_map[descriptor.edge_number_vector[other_edge_id]] = other_edge_id;
+          edge_number_id_map[edge_number_vector[edge_id]]       = edge_id;
+          edge_number_id_map[edge_number_vector[other_edge_id]] = other_edge_id;
         } else {
-          edge_number_id_map.erase(descriptor.edge_number_vector[edge_id]);
-          descriptor.edge_number_vector[edge_id]                     = gmsh_data.__edges_number[e];
-          edge_number_id_map[descriptor.edge_number_vector[edge_id]] = edge_id;
+          edge_number_id_map.erase(edge_number_vector[edge_id]);
+          edge_number_vector[edge_id]                     = gmsh_data.__edges_number[e];
+          edge_number_id_map[edge_number_vector[edge_id]] = edge_id;
         }
       }
     }
+    descriptor.setEdgeNumberVector(edge_number_vector);
 
     for (const auto& ref_edge_list : ref_edges_map) {
       Array<EdgeId> edge_list(ref_edge_list.second.size());
@@ -645,12 +796,16 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
     }
   }
 
-  Array<bool> is_boundary_node(descriptor.node_number_vector.size());
+  Array<bool> is_boundary_node(node_number_vector.size());
   is_boundary_node.fill(false);
 
+  const auto& face_to_node_matrix = descriptor.faceToNodeMatrix();
+
   for (size_t i_face = 0; i_face < face_nb_cell.size(); ++i_face) {
     if (face_nb_cell[i_face] == 1) {
-      for (size_t node_id : descriptor.face_to_node_vector[i_face]) {
+      const auto& face_node_list = face_to_node_matrix[i_face];
+      for (size_t i_node = 0; i_node < face_node_list.size(); ++i_node) {
+        const NodeId node_id      = face_node_list[i_node];
         is_boundary_node[node_id] = true;
       }
     }
@@ -688,17 +843,29 @@ GmshConnectivityBuilder<3>::GmshConnectivityBuilder(const GmshReader::GmshData&
     descriptor.addRefItemList(RefNodeList(ref_id, point_list, is_boundary));
   }
 
-  descriptor.cell_owner_vector.resize(nb_cells);
-  std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
-
-  descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
-  std::fill(descriptor.face_owner_vector.begin(), descriptor.face_owner_vector.end(), parallel::rank());
-
-  descriptor.edge_owner_vector.resize(descriptor.edge_number_vector.size());
-  std::fill(descriptor.edge_owner_vector.begin(), descriptor.edge_owner_vector.end(), parallel::rank());
-
-  descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-  std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
+  descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(nb_cells);
+    cell_owner_vector.fill(parallel::rank());
+    return cell_owner_vector;
+  }());
+
+  descriptor.setFaceOwnerVector([&] {
+    Array<int> face_owner_vector(descriptor.faceNumberVector().size());
+    face_owner_vector.fill(parallel::rank());
+    return face_owner_vector;
+  }());
+
+  descriptor.setEdgeOwnerVector([&] {
+    Array<int> edge_owner_vector(descriptor.edgeNumberVector().size());
+    edge_owner_vector.fill(parallel::rank());
+    return edge_owner_vector;
+  }());
+
+  descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(node_number_vector.size());
+    node_owner_vector.fill(parallel::rank());
+    return node_owner_vector;
+  }());
 
   m_connectivity = Connectivity3D::build(descriptor);
 }
@@ -958,255 +1125,6 @@ GmshReader::__readPeriodic2_2()
   }
 }
 
-// std::shared_ptr<IConnectivity>
-// GmshReader::_buildConnectivity3D(const size_t nb_cells)
-// {
-//   ConnectivityDescriptor descriptor;
-
-//   descriptor.node_number_vector = m_mesh_data.__verticesNumbers;
-//   descriptor.cell_type_vector.resize(nb_cells);
-//   descriptor.cell_number_vector.resize(nb_cells);
-//   descriptor.cell_to_node_vector.resize(nb_cells);
-
-//   const size_t nb_tetrahedra = m_mesh_data.__tetrahedra.size();
-//   for (size_t j = 0; j < nb_tetrahedra; ++j) {
-//     descriptor.cell_to_node_vector[j].resize(4);
-//     for (int r = 0; r < 4; ++r) {
-//       descriptor.cell_to_node_vector[j][r] = m_mesh_data.__tetrahedra[j][r];
-//     }
-//     descriptor.cell_type_vector[j]   = CellType::Tetrahedron;
-//     descriptor.cell_number_vector[j] = m_mesh_data.__tetrahedra_number[j];
-//   }
-//   const size_t nb_hexahedra = m_mesh_data.__hexahedra.size();
-//   for (size_t j = 0; j < nb_hexahedra; ++j) {
-//     const size_t jh = nb_tetrahedra + j;
-//     descriptor.cell_to_node_vector[jh].resize(8);
-//     for (int r = 0; r < 8; ++r) {
-//       descriptor.cell_to_node_vector[jh][r] = m_mesh_data.__hexahedra[j][r];
-//     }
-//     descriptor.cell_type_vector[jh]   = CellType::Hexahedron;
-//     descriptor.cell_number_vector[jh] = m_mesh_data.__hexahedra_number[j];
-//   }
-
-//   std::map<unsigned int, std::vector<unsigned int>> ref_cells_map;
-//   for (unsigned int r = 0; r < m_mesh_data.__tetrahedra_ref.size(); ++r) {
-//     const unsigned int elem_number = m_mesh_data.__tetrahedra_ref[r];
-//     const unsigned int& ref        = m_mesh_data.__tetrahedra_ref[r];
-//     ref_cells_map[ref].push_back(elem_number);
-//   }
-
-//   for (unsigned int j = 0; j < m_mesh_data.__hexahedra_ref.size(); ++j) {
-//     const size_t elem_number = nb_tetrahedra + j;
-//     const unsigned int& ref  = m_mesh_data.__hexahedra_ref[j];
-//     ref_cells_map[ref].push_back(elem_number);
-//   }
-
-//   for (const auto& ref_cell_list : ref_cells_map) {
-//     Array<CellId> cell_list(ref_cell_list.second.size());
-//     for (size_t j = 0; j < ref_cell_list.second.size(); ++j) {
-//       cell_list[j] = ref_cell_list.second[j];
-//     }
-//     const PhysicalRefId& physical_ref_id = m_mesh_data.m_physical_ref_map.at(ref_cell_list.first);
-//     descriptor.addRefItemList(RefCellList(physical_ref_id.refId(), cell_list));
-//   }
-
-//   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<3>(descriptor);
-
-//   const auto& node_number_vector = descriptor.node_number_vector;
-
-//   {
-//     using Face                                                                 = ConnectivityFace<3>;
-//     const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map = [&] {
-//       std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
-//       for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-//         const auto& node_vector                               = descriptor.face_to_node_vector[l];
-//         face_to_id_map[Face(node_vector, node_number_vector)] = l;
-//       }
-//       return face_to_id_map;
-//     }();
-
-//     std::unordered_map<int, FaceId> face_number_id_map = [&] {
-//       std::unordered_map<int, FaceId> face_number_id_map;
-//       for (size_t l = 0; l < descriptor.face_number_vector.size(); ++l) {
-//         face_number_id_map[descriptor.face_number_vector[l]] = l;
-//       }
-//       Assert(face_number_id_map.size() == descriptor.face_number_vector.size());
-//       return face_number_id_map;
-//     }();
-
-//     std::map<unsigned int, std::vector<unsigned int>> ref_faces_map;
-//     for (unsigned int f = 0; f < m_mesh_data.__triangles.size(); ++f) {
-//       const unsigned int face_id = [&] {
-//         auto i = face_to_id_map.find(
-//           Face({m_mesh_data.__triangles[f][0], m_mesh_data.__triangles[f][1], m_mesh_data.__triangles[f][2]},
-//                node_number_vector));
-//         if (i == face_to_id_map.end()) {
-//           throw NormalError("face not found");
-//         }
-//         return i->second;
-//       }();
-
-//       const unsigned int& ref = m_mesh_data.__triangles_ref[f];
-//       ref_faces_map[ref].push_back(face_id);
-
-//       if (descriptor.face_number_vector[face_id] != m_mesh_data.__quadrangles_number[f]) {
-//         if (auto i_face = face_number_id_map.find(m_mesh_data.__quadrangles_number[f]);
-//             i_face != face_number_id_map.end()) {
-//           const int other_face_id = i_face->second;
-//           std::swap(descriptor.face_number_vector[face_id], descriptor.face_number_vector[other_face_id]);
-
-//           face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-//           face_number_id_map.erase(descriptor.face_number_vector[other_face_id]);
-
-//           face_number_id_map[descriptor.face_number_vector[face_id]]       = face_id;
-//           face_number_id_map[descriptor.face_number_vector[other_face_id]] = other_face_id;
-//         } else {
-//           face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-//           descriptor.face_number_vector[face_id]                     = m_mesh_data.__quadrangles_number[f];
-//           face_number_id_map[descriptor.face_number_vector[face_id]] = face_id;
-//         }
-//       }
-//     }
-
-//     for (unsigned int f = 0; f < m_mesh_data.__quadrangles.size(); ++f) {
-//       const unsigned int face_id = [&] {
-//         auto i = face_to_id_map.find(Face({m_mesh_data.__quadrangles[f][0], m_mesh_data.__quadrangles[f][1],
-//                                            m_mesh_data.__quadrangles[f][2], m_mesh_data.__quadrangles[f][3]},
-//                                           node_number_vector));
-//         if (i == face_to_id_map.end()) {
-//           throw NormalError("face not found");
-//         }
-//         return i->second;
-//       }();
-
-//       const unsigned int& ref = m_mesh_data.__quadrangles_ref[f];
-//       ref_faces_map[ref].push_back(face_id);
-
-//       if (descriptor.face_number_vector[face_id] != m_mesh_data.__quadrangles_number[f]) {
-//         if (auto i_face = face_number_id_map.find(m_mesh_data.__quadrangles_number[f]);
-//             i_face != face_number_id_map.end()) {
-//           const int other_face_id = i_face->second;
-//           std::swap(descriptor.face_number_vector[face_id], descriptor.face_number_vector[other_face_id]);
-
-//           face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-//           face_number_id_map.erase(descriptor.face_number_vector[other_face_id]);
-
-//           face_number_id_map[descriptor.face_number_vector[face_id]]       = face_id;
-//           face_number_id_map[descriptor.face_number_vector[other_face_id]] = other_face_id;
-//         } else {
-//           face_number_id_map.erase(descriptor.face_number_vector[face_id]);
-//           descriptor.face_number_vector[face_id]                     = m_mesh_data.__quadrangles_number[f];
-//           face_number_id_map[descriptor.face_number_vector[face_id]] = face_id;
-//         }
-//       }
-//     }
-
-//     for (const auto& ref_face_list : ref_faces_map) {
-//       Array<FaceId> face_list(ref_face_list.second.size());
-//       for (size_t j = 0; j < ref_face_list.second.size(); ++j) {
-//         face_list[j] = ref_face_list.second[j];
-//       }
-//       const PhysicalRefId& physical_ref_id = m_mesh_data.m_physical_ref_map.at(ref_face_list.first);
-//       descriptor.addRefItemList(RefFaceList{physical_ref_id.refId(), face_list});
-//     }
-//   }
-
-//   ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities<3>(descriptor);
-
-//   {
-//     using Edge                                                                 = ConnectivityFace<2>;
-//     const auto& node_number_vector                                             = descriptor.node_number_vector;
-//     const std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map = [&] {
-//       std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map;
-//       for (EdgeId l = 0; l < descriptor.edge_to_node_vector.size(); ++l) {
-//         const auto& node_vector                               = descriptor.edge_to_node_vector[l];
-//         edge_to_id_map[Edge(node_vector, node_number_vector)] = l;
-//       }
-//       return edge_to_id_map;
-//     }();
-
-//     std::unordered_map<int, EdgeId> edge_number_id_map = [&] {
-//       std::unordered_map<int, EdgeId> edge_number_id_map;
-//       for (size_t l = 0; l < descriptor.edge_number_vector.size(); ++l) {
-//         edge_number_id_map[descriptor.edge_number_vector[l]] = l;
-//       }
-//       Assert(edge_number_id_map.size() == descriptor.edge_number_vector.size());
-//       return edge_number_id_map;
-//     }();
-
-//     std::map<unsigned int, std::vector<unsigned int>> ref_edges_map;
-//     for (unsigned int e = 0; e < m_mesh_data.__edges.size(); ++e) {
-//       const unsigned int edge_id = [&] {
-//         auto i = edge_to_id_map.find(Edge({m_mesh_data.__edges[e][0], m_mesh_data.__edges[e][1]},
-//         node_number_vector)); if (i == edge_to_id_map.end()) {
-//           std::stringstream error_msg;
-//           error_msg << "edge " << m_mesh_data.__edges[e][0] << " not found";
-//           throw NormalError(error_msg.str());
-//         }
-//         return i->second;
-//       }();
-//       const unsigned int& ref = m_mesh_data.__edges_ref[e];
-//       ref_edges_map[ref].push_back(edge_id);
-
-//       if (descriptor.edge_number_vector[edge_id] != m_mesh_data.__edges_number[e]) {
-//         if (auto i_edge = edge_number_id_map.find(m_mesh_data.__edges_number[e]); i_edge != edge_number_id_map.end())
-//         {
-//           const int other_edge_id = i_edge->second;
-//           std::swap(descriptor.edge_number_vector[edge_id], descriptor.edge_number_vector[other_edge_id]);
-
-//           edge_number_id_map.erase(descriptor.edge_number_vector[edge_id]);
-//           edge_number_id_map.erase(descriptor.edge_number_vector[other_edge_id]);
-
-//           edge_number_id_map[descriptor.edge_number_vector[edge_id]]       = edge_id;
-//           edge_number_id_map[descriptor.edge_number_vector[other_edge_id]] = other_edge_id;
-//         } else {
-//           edge_number_id_map.erase(descriptor.edge_number_vector[edge_id]);
-//           descriptor.edge_number_vector[edge_id]                     = m_mesh_data.__edges_number[e];
-//           edge_number_id_map[descriptor.edge_number_vector[edge_id]] = edge_id;
-//         }
-//       }
-//     }
-
-//     for (const auto& ref_edge_list : ref_edges_map) {
-//       Array<EdgeId> edge_list(ref_edge_list.second.size());
-//       for (size_t j = 0; j < ref_edge_list.second.size(); ++j) {
-//         edge_list[j] = ref_edge_list.second[j];
-//       }
-//       const PhysicalRefId& physical_ref_id = m_mesh_data.m_physical_ref_map.at(ref_edge_list.first);
-//       descriptor.addRefItemList(RefEdgeList{physical_ref_id.refId(), edge_list});
-//     }
-//   }
-
-//   std::map<unsigned int, std::vector<unsigned int>> ref_points_map;
-//   for (unsigned int r = 0; r < m_mesh_data.__points.size(); ++r) {
-//     const unsigned int point_number = m_mesh_data.__points[r];
-//     const unsigned int& ref         = m_mesh_data.__points_ref[r];
-//     ref_points_map[ref].push_back(point_number);
-//   }
-
-//   for (const auto& ref_point_list : ref_points_map) {
-//     Array<NodeId> point_list(ref_point_list.second.size());
-//     for (size_t j = 0; j < ref_point_list.second.size(); ++j) {
-//       point_list[j] = ref_point_list.second[j];
-//     }
-//     const PhysicalRefId& physical_ref_id = m_mesh_data.m_physical_ref_map.at(ref_point_list.first);
-//     descriptor.addRefItemList(RefNodeList(physical_ref_id.refId(), point_list));
-//   }
-
-//   descriptor.cell_owner_vector.resize(nb_cells);
-//   std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
-
-//   descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
-//   std::fill(descriptor.face_owner_vector.begin(), descriptor.face_owner_vector.end(), parallel::rank());//
-//   descriptor.edge_owner_vector.resize(descriptor.edge_number_vector.size());
-//   std::fill(descriptor.edge_owner_vector.begin(), descriptor.edge_owner_vector.end(), parallel::rank());
-
-//   descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-//   std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
-
-//   return Connectivity3D::build(descriptor);
-// }
-
 void
 GmshReader::__proceedData()
 {
diff --git a/src/mesh/LogicalConnectivityBuilder.cpp b/src/mesh/LogicalConnectivityBuilder.cpp
index 41092faaf90a9a3f9a24ac1c561304373ba748d0..dbc41d0681e9ceda822a5896af97ace2e3572f03 100644
--- a/src/mesh/LogicalConnectivityBuilder.cpp
+++ b/src/mesh/LogicalConnectivityBuilder.cpp
@@ -141,28 +141,27 @@ void
 LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>& cell_size,
                                                    ConnectivityDescriptor& descriptor)
 {
-  using Edge                                                                 = ConnectivityFace<2>;
-  const auto& node_number_vector                                             = descriptor.node_number_vector;
-  const std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map = [&] {
-    std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map;
-    for (EdgeId l = 0; l < descriptor.edge_to_node_vector.size(); ++l) {
-      const auto& node_vector                               = descriptor.edge_to_node_vector[l];
-      edge_to_id_map[Edge(node_vector, node_number_vector)] = l;
+  const auto& node_number_vector  = descriptor.nodeNumberVector();
+  const auto& node_to_edge_matrix = descriptor.nodeToEdgeMatrix();
+  const auto& edge_to_node_matrix = descriptor.edgeToNodeMatrix();
+
+  const auto find_edge = [&](uint32_t node0, uint32_t node1) {
+    if (node_number_vector[node0] > node_number_vector[node1]) {
+      std::swap(node0, node1);
     }
-    return edge_to_id_map;
-  }();
+    const auto& node_edge_list = node_to_edge_matrix[node0];
 
-  std::unordered_map<int, EdgeId> edge_number_id_map = [&] {
-    std::unordered_map<int, EdgeId> edge_number_id_map;
-    for (size_t l = 0; l < descriptor.edge_number_vector.size(); ++l) {
-      edge_number_id_map[descriptor.edge_number_vector[l]] = l;
+    for (size_t i_edge = 0; i_edge < node_edge_list.size(); ++i_edge) {
+      const EdgeId edge_id = node_edge_list[i_edge];
+      if (edge_to_node_matrix[edge_id][1] == node1) {
+        return edge_id;
+      }
     }
-    Assert(edge_number_id_map.size() == descriptor.edge_number_vector.size());
-    return edge_number_id_map;
-  }();
+    throw UnexpectedError("Cannot find edge");
+  };
 
   const TinyVector<3, uint64_t> node_size{cell_size[0] + 1, cell_size[1] + 1, cell_size[2] + 1};
-  const auto node_number = [&](const TinyVector<3, uint64_t>& node_logic_id) {
+  const auto get_node_id = [&](const TinyVector<3, uint64_t>& node_logic_id) {
     return (node_logic_id[0] * node_size[1] + node_logic_id[1]) * node_size[2] + node_logic_id[2];
   };
 
@@ -172,13 +171,10 @@ LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>
       Array<EdgeId> boundary_edges(cell_size[2]);
       size_t l = 0;
       for (size_t k = 0; k < cell_size[2]; ++k) {
-        const uint32_t node_0_id = node_number(TinyVector<3, uint64_t>{i, j, k});
-        const uint32_t node_1_id = node_number(TinyVector<3, uint64_t>{i, j, k + 1});
-
-        auto i_edge = edge_to_id_map.find(Edge{{node_0_id, node_1_id}, descriptor.node_number_vector});
-        Assert(i_edge != edge_to_id_map.end());
+        const uint32_t node_0_id = get_node_id(TinyVector<3, uint64_t>{i, j, k});
+        const uint32_t node_1_id = get_node_id(TinyVector<3, uint64_t>{i, j, k + 1});
 
-        boundary_edges[l++] = i_edge->second;
+        boundary_edges[l++] = find_edge(node_0_id, node_1_id);
       }
       Assert(l == cell_size[2]);
       descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, true});
@@ -196,13 +192,10 @@ LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>
       Array<EdgeId> boundary_edges(cell_size[1]);
       size_t l = 0;
       for (size_t j = 0; j < cell_size[1]; ++j) {
-        const uint32_t node_0_id = node_number(TinyVector<3, uint64_t>{i, j, k});
-        const uint32_t node_1_id = node_number(TinyVector<3, uint64_t>{i, j + 1, k});
-
-        auto i_edge = edge_to_id_map.find(Edge{{node_0_id, node_1_id}, descriptor.node_number_vector});
-        Assert(i_edge != edge_to_id_map.end());
+        const uint32_t node_0_id = get_node_id(TinyVector<3, uint64_t>{i, j, k});
+        const uint32_t node_1_id = get_node_id(TinyVector<3, uint64_t>{i, j + 1, k});
 
-        boundary_edges[l++] = i_edge->second;
+        boundary_edges[l++] = find_edge(node_0_id, node_1_id);
       }
       Assert(l == cell_size[1]);
       descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, true});
@@ -220,13 +213,10 @@ LogicalConnectivityBuilder::_buildBoundaryEdgeList(const TinyVector<3, uint64_t>
       Array<EdgeId> boundary_edges(cell_size[0]);
       size_t l = 0;
       for (size_t i = 0; i < cell_size[0]; ++i) {
-        const uint32_t node_0_id = node_number(TinyVector<3, uint64_t>{i, j, k});
-        const uint32_t node_1_id = node_number(TinyVector<3, uint64_t>{i + 1, j, k});
+        const uint32_t node_0_id = get_node_id(TinyVector<3, uint64_t>{i, j, k});
+        const uint32_t node_1_id = get_node_id(TinyVector<3, uint64_t>{i + 1, j, k});
 
-        auto i_edge = edge_to_id_map.find(Edge{{node_0_id, node_1_id}, descriptor.node_number_vector});
-        Assert(i_edge != edge_to_id_map.end());
-
-        boundary_edges[l++] = i_edge->second;
+        boundary_edges[l++] = find_edge(node_0_id, node_1_id);
       }
       Assert(l == cell_size[0]);
       descriptor.addRefItemList(RefEdgeList{RefId{ref_id, ref_name}, boundary_edges, true});
@@ -256,6 +246,8 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
     return cell_logic_id[0] * cell_size[1] + cell_logic_id[1];
   };
 
+  const auto& cell_to_face_matrix = descriptor.cellToFaceMatrix();
+
   {   // xmin
     const size_t i = 0;
     Array<FaceId> boundary_faces(cell_size[1]);
@@ -263,7 +255,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
       constexpr size_t left_face = 3;
 
       const size_t cell_id = cell_number(TinyVector<2, uint64_t>{i, j});
-      const size_t face_id = descriptor.cell_to_face_vector[cell_id][left_face];
+      const size_t face_id = cell_to_face_matrix[cell_id][left_face];
 
       boundary_faces[j] = face_id;
     }
@@ -277,7 +269,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
       constexpr size_t right_face = 1;
 
       const size_t cell_id = cell_number(TinyVector<2, uint64_t>{i, j});
-      const size_t face_id = descriptor.cell_to_face_vector[cell_id][right_face];
+      const size_t face_id = cell_to_face_matrix[cell_id][right_face];
 
       boundary_faces[j] = face_id;
     }
@@ -291,7 +283,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
       constexpr size_t bottom_face = 0;
 
       const size_t cell_id = cell_number(TinyVector<2, uint64_t>{i, j});
-      const size_t face_id = descriptor.cell_to_face_vector[cell_id][bottom_face];
+      const size_t face_id = cell_to_face_matrix[cell_id][bottom_face];
 
       boundary_faces[i] = face_id;
     }
@@ -305,7 +297,7 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(
       constexpr size_t top_face = 2;
 
       const size_t cell_id = cell_number(TinyVector<2, uint64_t>{i, j});
-      const size_t face_id = descriptor.cell_to_face_vector[cell_id][top_face];
+      const size_t face_id = cell_to_face_matrix[cell_id][top_face];
 
       boundary_faces[i] = face_id;
     }
@@ -318,28 +310,53 @@ void
 LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>& cell_size,
                                                    ConnectivityDescriptor& descriptor)
 {
-  using Face = ConnectivityFace<3>;
+  const auto& node_number_vector  = descriptor.nodeNumberVector();
+  const auto& face_to_node_matrix = descriptor.faceToNodeMatrix();
+  const auto& node_to_face_matrix = descriptor.nodeToFaceMatrix();
+
+  const auto find_face = [&](std::array<uint32_t, 4> node_list) {
+    size_t i_node_smallest_number = 0;
+    for (size_t i_node = 1; i_node < node_list.size(); ++i_node) {
+      if (node_number_vector[node_list[i_node]] < node_number_vector[node_list[i_node_smallest_number]]) {
+        i_node_smallest_number = i_node;
+      }
+    }
+
+    if (i_node_smallest_number != 0) {
+      std::array<uint64_t, 4> buffer;
+      for (size_t i_node = i_node_smallest_number; i_node < buffer.size(); ++i_node) {
+        buffer[i_node - i_node_smallest_number] = node_list[i_node];
+      }
+      for (size_t i_node = 0; i_node < i_node_smallest_number; ++i_node) {
+        buffer[i_node + node_list.size() - i_node_smallest_number] = node_list[i_node];
+      }
 
-  const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map = [&] {
-    std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
-    for (FaceId l = 0; l < descriptor.face_to_node_vector.size(); ++l) {
-      const auto& node_vector                                          = descriptor.face_to_node_vector[l];
-      face_to_id_map[Face(node_vector, descriptor.node_number_vector)] = l;
+      for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+        node_list[i_node] = buffer[i_node];
+      }
     }
-    return face_to_id_map;
-  }();
 
-  const std::unordered_map<int, FaceId> face_number_id_map = [&] {
-    std::unordered_map<int, FaceId> face_number_id_map;
-    for (size_t l = 0; l < descriptor.face_number_vector.size(); ++l) {
-      face_number_id_map[descriptor.face_number_vector[l]] = l;
+    if (node_number_vector[node_list[1]] > node_number_vector[node_list[node_list.size() - 1]]) {
+      for (size_t i_node = 1; i_node <= (node_list.size() + 1) / 2 - 1; ++i_node) {
+        std::swap(node_list[i_node], node_list[node_list.size() - i_node]);
+      }
     }
-    Assert(face_number_id_map.size() == descriptor.face_number_vector.size());
-    return face_number_id_map;
-  }();
+
+    const auto& node_face_list = node_to_face_matrix[node_list[0]];
+
+    for (size_t i_face = 0; i_face < node_face_list.size(); ++i_face) {
+      const FaceId face_id       = node_face_list[i_face];
+      const auto& face_node_list = face_to_node_matrix[face_id];
+      if ((face_node_list[1] == node_list[1]) and (face_node_list[2] == node_list[2]) and
+          (face_node_list[3] == node_list[3])) {
+        return face_id;
+      }
+    }
+    throw UnexpectedError("Cannot find edge");
+  };
 
   const TinyVector<3, uint64_t> node_size{cell_size[0] + 1, cell_size[1] + 1, cell_size[2] + 1};
-  const auto node_number = [&](const TinyVector<3, uint64_t>& node_logic_id) {
+  const auto get_node_id = [&](const TinyVector<3, uint64_t>& node_logic_id) {
     return (node_logic_id[0] * node_size[1] + node_logic_id[1]) * node_size[2] + node_logic_id[2];
   };
 
@@ -349,16 +366,12 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>
       size_t l = 0;
       for (size_t j = 0; j < cell_size[1]; ++j) {
         for (size_t k = 0; k < cell_size[2]; ++k) {
-          const uint32_t node_0_id = node_number(TinyVector<3, uint64_t>{i, j, k});
-          const uint32_t node_1_id = node_number(TinyVector<3, uint64_t>{i, j + 1, k});
-          const uint32_t node_2_id = node_number(TinyVector<3, uint64_t>{i, j + 1, k + 1});
-          const uint32_t node_3_id = node_number(TinyVector<3, uint64_t>{i, j, k + 1});
+          const uint32_t node_0_id = get_node_id(TinyVector<3, uint64_t>{i, j, k});
+          const uint32_t node_1_id = get_node_id(TinyVector<3, uint64_t>{i, j + 1, k});
+          const uint32_t node_2_id = get_node_id(TinyVector<3, uint64_t>{i, j + 1, k + 1});
+          const uint32_t node_3_id = get_node_id(TinyVector<3, uint64_t>{i, j, k + 1});
 
-          auto i_face =
-            face_to_id_map.find(Face{{node_0_id, node_1_id, node_2_id, node_3_id}, descriptor.node_number_vector});
-          Assert(i_face != face_to_id_map.end());
-
-          boundary_faces[l++] = i_face->second;
+          boundary_faces[l++] = find_face({node_0_id, node_1_id, node_2_id, node_3_id});
         }
       }
       Assert(l == cell_size[1] * cell_size[2]);
@@ -375,16 +388,12 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>
       size_t l = 0;
       for (size_t i = 0; i < cell_size[0]; ++i) {
         for (size_t k = 0; k < cell_size[2]; ++k) {
-          const uint32_t node_0_id = node_number(TinyVector<3, uint64_t>{i, j, k});
-          const uint32_t node_1_id = node_number(TinyVector<3, uint64_t>{i + 1, j, k});
-          const uint32_t node_2_id = node_number(TinyVector<3, uint64_t>{i + 1, j, k + 1});
-          const uint32_t node_3_id = node_number(TinyVector<3, uint64_t>{i, j, k + 1});
-
-          auto i_face =
-            face_to_id_map.find(Face{{node_0_id, node_1_id, node_2_id, node_3_id}, descriptor.node_number_vector});
-          Assert(i_face != face_to_id_map.end());
+          const uint32_t node_0_id = get_node_id(TinyVector<3, uint64_t>{i, j, k});
+          const uint32_t node_1_id = get_node_id(TinyVector<3, uint64_t>{i + 1, j, k});
+          const uint32_t node_2_id = get_node_id(TinyVector<3, uint64_t>{i + 1, j, k + 1});
+          const uint32_t node_3_id = get_node_id(TinyVector<3, uint64_t>{i, j, k + 1});
 
-          boundary_faces[l++] = i_face->second;
+          boundary_faces[l++] = find_face({node_0_id, node_1_id, node_2_id, node_3_id});
         }
       }
       Assert(l == cell_size[0] * cell_size[2]);
@@ -401,16 +410,12 @@ LogicalConnectivityBuilder::_buildBoundaryFaceList(const TinyVector<3, uint64_t>
       size_t l = 0;
       for (size_t i = 0; i < cell_size[0]; ++i) {
         for (size_t j = 0; j < cell_size[1]; ++j) {
-          const uint32_t node_0_id = node_number(TinyVector<3, uint64_t>{i, j, k});
-          const uint32_t node_1_id = node_number(TinyVector<3, uint64_t>{i + 1, j, k});
-          const uint32_t node_2_id = node_number(TinyVector<3, uint64_t>{i + 1, j + 1, k});
-          const uint32_t node_3_id = node_number(TinyVector<3, uint64_t>{i, j + 1, k});
+          const uint32_t node_0_id = get_node_id(TinyVector<3, uint64_t>{i, j, k});
+          const uint32_t node_1_id = get_node_id(TinyVector<3, uint64_t>{i + 1, j, k});
+          const uint32_t node_2_id = get_node_id(TinyVector<3, uint64_t>{i + 1, j + 1, k});
+          const uint32_t node_3_id = get_node_id(TinyVector<3, uint64_t>{i, j + 1, k});
 
-          auto i_face =
-            face_to_id_map.find(Face{{node_0_id, node_1_id, node_2_id, node_3_id}, descriptor.node_number_vector});
-          Assert(i_face != face_to_id_map.end());
-
-          boundary_faces[l++] = i_face->second;
+          boundary_faces[l++] = find_face({node_0_id, node_1_id, node_2_id, node_3_id});
         }
       }
       Assert(l == cell_size[0] * cell_size[1]);
@@ -431,36 +436,50 @@ LogicalConnectivityBuilder::_buildConnectivity(
   const size_t number_of_nodes = cell_size[0] + 1;
 
   ConnectivityDescriptor descriptor;
-  descriptor.node_number_vector.resize(number_of_nodes);
-  for (size_t i = 0; i < number_of_nodes; ++i) {
-    descriptor.node_number_vector[i] = i;
-  }
+  descriptor.setNodeNumberVector([&] {
+    Array<int> node_number_vector(number_of_nodes);
+    parallel_for(
+      number_of_nodes, PUGS_LAMBDA(const size_t i) { node_number_vector[i] = i; });
+    return node_number_vector;
+  }());
+
+  descriptor.setCellNumberVector([&] {
+    Array<int> cell_number_vector(number_of_cells);
+    for (size_t i = 0; i < number_of_cells; ++i) {
+      cell_number_vector[i] = i;
+    }
+    return cell_number_vector;
+  }());
 
-  descriptor.cell_number_vector.resize(number_of_cells);
+  Array<CellType> cell_type_vector(number_of_cells);
+  cell_type_vector.fill(CellType::Line);
+  descriptor.setCellTypeVector(cell_type_vector);
+
+  Array<unsigned int> cell_to_node_row_map(number_of_cells + 1);
+  for (size_t i = 0; i < cell_to_node_row_map.size(); ++i) {
+    cell_to_node_row_map[i] = 2 * i;
+  }
+  Array<unsigned int> cell_to_node_list(2 * number_of_cells);
   for (size_t i = 0; i < number_of_cells; ++i) {
-    descriptor.cell_number_vector[i] = i;
+    cell_to_node_list[2 * i]     = i;
+    cell_to_node_list[2 * i + 1] = i + 1;
   }
 
-  descriptor.cell_type_vector.resize(number_of_cells);
-  std::fill(descriptor.cell_type_vector.begin(), descriptor.cell_type_vector.end(), CellType::Line);
-
-  descriptor.cell_to_node_vector.resize(number_of_cells);
-  constexpr size_t nb_node_per_cell = 2;
-  for (size_t j = 0; j < number_of_cells; ++j) {
-    descriptor.cell_to_node_vector[j].resize(nb_node_per_cell);
-    for (size_t r = 0; r < nb_node_per_cell; ++r) {
-      descriptor.cell_to_node_vector[j][0] = j;
-      descriptor.cell_to_node_vector[j][1] = j + 1;
-    }
-  }
+  descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row_map, cell_to_node_list));
 
   this->_buildBoundaryNodeList(cell_size, descriptor);
 
-  descriptor.cell_owner_vector.resize(number_of_cells);
-  std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
+  descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(number_of_cells);
+    cell_owner_vector.fill(parallel::rank());
+    return cell_owner_vector;
+  }());
 
-  descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-  std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
+  descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(number_of_nodes);
+    node_owner_vector.fill(parallel::rank());
+    return node_owner_vector;
+  }());
 
   m_connectivity = Connectivity1D::build(descriptor);
 }
@@ -478,18 +497,23 @@ LogicalConnectivityBuilder::_buildConnectivity(
   const size_t number_of_nodes = node_size[0] * node_size[1];
 
   ConnectivityDescriptor descriptor;
-  descriptor.node_number_vector.resize(number_of_nodes);
-  for (size_t i = 0; i < number_of_nodes; ++i) {
-    descriptor.node_number_vector[i] = i;
-  }
-
-  descriptor.cell_number_vector.resize(number_of_cells);
-  for (size_t i = 0; i < number_of_cells; ++i) {
-    descriptor.cell_number_vector[i] = i;
-  }
-
-  descriptor.cell_type_vector.resize(number_of_cells);
-  std::fill(descriptor.cell_type_vector.begin(), descriptor.cell_type_vector.end(), CellType::Quadrangle);
+  descriptor.setNodeNumberVector([&] {
+    Array<int> node_number_vector(number_of_nodes);
+    parallel_for(
+      number_of_nodes, PUGS_LAMBDA(const size_t i) { node_number_vector[i] = i; });
+    return node_number_vector;
+  }());
+
+  descriptor.setCellNumberVector([&] {
+    Array<int> cell_number_vector(number_of_cells);
+    parallel_for(
+      number_of_cells, PUGS_LAMBDA(size_t i) { cell_number_vector[i] = i; });
+    return cell_number_vector;
+  }());
+
+  Array<CellType> cell_type_vector(number_of_cells);
+  cell_type_vector.fill(CellType::Quadrangle);
+  descriptor.setCellTypeVector(cell_type_vector);
 
   const auto node_number = [&](const TinyVector<Dimension, uint64_t> node_logic_id) {
     return node_logic_id[0] * node_size[1] + node_logic_id[1];
@@ -501,32 +525,44 @@ LogicalConnectivityBuilder::_buildConnectivity(
     return TinyVector<Dimension, uint64_t>{j0, j1};
   };
 
-  descriptor.cell_to_node_vector.resize(number_of_cells);
-  constexpr size_t nb_node_per_cell = 1 << Dimension;
+  Array<unsigned int> cell_to_node_row_map(number_of_cells + 1);
+  for (size_t i = 0; i < cell_to_node_row_map.size(); ++i) {
+    cell_to_node_row_map[i] = 4 * i;
+  }
+  Array<unsigned int> cell_to_node_list(4 * number_of_cells);
   for (size_t j = 0; j < number_of_cells; ++j) {
     TinyVector<Dimension, size_t> cell_index = cell_logic_id(j);
-    descriptor.cell_to_node_vector[j].resize(nb_node_per_cell);
-    for (size_t r = 0; r < nb_node_per_cell; ++r) {
-      descriptor.cell_to_node_vector[j][0] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 0});
-      descriptor.cell_to_node_vector[j][1] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 0});
-      descriptor.cell_to_node_vector[j][2] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 1});
-      descriptor.cell_to_node_vector[j][3] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1});
-    }
+
+    cell_to_node_list[4 * j]     = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 0});
+    cell_to_node_list[4 * j + 1] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 0});
+    cell_to_node_list[4 * j + 2] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 1});
+    cell_to_node_list[4 * j + 3] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1});
   }
 
+  descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row_map, cell_to_node_list));
+
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<Dimension>(descriptor);
 
   this->_buildBoundaryNodeList(cell_size, descriptor);
   this->_buildBoundaryFaceList(cell_size, descriptor);
 
-  descriptor.cell_owner_vector.resize(number_of_cells);
-  std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
+  descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(number_of_cells);
+    cell_owner_vector.fill(parallel::rank());
+    return cell_owner_vector;
+  }());
 
-  descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
-  std::fill(descriptor.face_owner_vector.begin(), descriptor.face_owner_vector.end(), parallel::rank());
+  descriptor.setFaceOwnerVector([&] {
+    Array<int> face_owner_vector(descriptor.faceNumberVector().size());
+    face_owner_vector.fill(parallel::rank());
+    return face_owner_vector;
+  }());
 
-  descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-  std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
+  descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(descriptor.nodeNumberVector().size());
+    node_owner_vector.fill(parallel::rank());
+    return node_owner_vector;
+  }());
 
   m_connectivity = Connectivity<Dimension>::build(descriptor);
 }
@@ -560,18 +596,23 @@ LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& ce
   const size_t number_of_nodes = count_items(node_size);
 
   ConnectivityDescriptor descriptor;
-  descriptor.node_number_vector.resize(number_of_nodes);
-  for (size_t i = 0; i < number_of_nodes; ++i) {
-    descriptor.node_number_vector[i] = i;
-  }
-
-  descriptor.cell_number_vector.resize(number_of_cells);
-  for (size_t i = 0; i < number_of_cells; ++i) {
-    descriptor.cell_number_vector[i] = i;
-  }
-
-  descriptor.cell_type_vector.resize(number_of_cells);
-  std::fill(descriptor.cell_type_vector.begin(), descriptor.cell_type_vector.end(), CellType::Hexahedron);
+  descriptor.setNodeNumberVector([&] {
+    Array<int> node_number_vector(number_of_nodes);
+    parallel_for(
+      number_of_nodes, PUGS_LAMBDA(const size_t i) { node_number_vector[i] = i; });
+    return node_number_vector;
+  }());
+
+  descriptor.setCellNumberVector([&] {
+    Array<int> cell_number_vector(number_of_cells);
+    parallel_for(
+      number_of_cells, PUGS_LAMBDA(size_t i) { cell_number_vector[i] = i; });
+    return cell_number_vector;
+  }());
+
+  Array<CellType> cell_type_vector(number_of_cells);
+  cell_type_vector.fill(CellType::Hexahedron);
+  descriptor.setCellTypeVector(cell_type_vector);
 
   const auto cell_logic_id = [&](size_t j) {
     const size_t slice1  = cell_size[1] * cell_size[2];
@@ -586,24 +627,26 @@ LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& ce
     return (node_logic_id[0] * node_size[1] + node_logic_id[1]) * node_size[2] + node_logic_id[2];
   };
 
-  descriptor.cell_to_node_vector.resize(number_of_cells);
-  constexpr size_t nb_node_per_cell = 1 << Dimension;
+  Array<unsigned int> cell_to_node_row_map(number_of_cells + 1);
+  for (size_t i = 0; i < cell_to_node_row_map.size(); ++i) {
+    cell_to_node_row_map[i] = 8 * i;
+  }
+  Array<unsigned int> cell_to_node_list(8 * number_of_cells);
   for (size_t j = 0; j < number_of_cells; ++j) {
     TinyVector<Dimension, size_t> cell_index = cell_logic_id(j);
-    descriptor.cell_to_node_vector[j].resize(nb_node_per_cell);
-    for (size_t r = 0; r < nb_node_per_cell; ++r) {
-      static_assert(Dimension == 3, "unexpected dimension");
-      descriptor.cell_to_node_vector[j][0] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 0, 0});
-      descriptor.cell_to_node_vector[j][1] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 0, 0});
-      descriptor.cell_to_node_vector[j][2] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 1, 0});
-      descriptor.cell_to_node_vector[j][3] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1, 0});
-      descriptor.cell_to_node_vector[j][4] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 0, 1});
-      descriptor.cell_to_node_vector[j][5] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 0, 1});
-      descriptor.cell_to_node_vector[j][6] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 1, 1});
-      descriptor.cell_to_node_vector[j][7] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1, 1});
-    }
+
+    cell_to_node_list[8 * j]     = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 0, 0});
+    cell_to_node_list[8 * j + 1] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 0, 0});
+    cell_to_node_list[8 * j + 2] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 1, 0});
+    cell_to_node_list[8 * j + 3] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1, 0});
+    cell_to_node_list[8 * j + 4] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 0, 1});
+    cell_to_node_list[8 * j + 5] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 0, 1});
+    cell_to_node_list[8 * j + 6] = node_number(cell_index + TinyVector<Dimension, uint64_t>{1, 1, 1});
+    cell_to_node_list[8 * j + 7] = node_number(cell_index + TinyVector<Dimension, uint64_t>{0, 1, 1});
   }
 
+  descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row_map, cell_to_node_list));
+
   ConnectivityBuilderBase::_computeCellFaceAndFaceNodeConnectivities<Dimension>(descriptor);
   ConnectivityBuilderBase::_computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities<Dimension>(descriptor);
 
@@ -611,17 +654,29 @@ LogicalConnectivityBuilder::_buildConnectivity(const TinyVector<3, uint64_t>& ce
   this->_buildBoundaryEdgeList(cell_size, descriptor);
   this->_buildBoundaryFaceList(cell_size, descriptor);
 
-  descriptor.cell_owner_vector.resize(number_of_cells);
-  std::fill(descriptor.cell_owner_vector.begin(), descriptor.cell_owner_vector.end(), parallel::rank());
-
-  descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
-  std::fill(descriptor.face_owner_vector.begin(), descriptor.face_owner_vector.end(), parallel::rank());
-
-  descriptor.edge_owner_vector.resize(descriptor.edge_number_vector.size());
-  std::fill(descriptor.edge_owner_vector.begin(), descriptor.edge_owner_vector.end(), parallel::rank());
-
-  descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
-  std::fill(descriptor.node_owner_vector.begin(), descriptor.node_owner_vector.end(), parallel::rank());
+  descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(number_of_cells);
+    cell_owner_vector.fill(parallel::rank());
+    return cell_owner_vector;
+  }());
+
+  descriptor.setFaceOwnerVector([&] {
+    Array<int> face_owner_vector(descriptor.faceNumberVector().size());
+    face_owner_vector.fill(parallel::rank());
+    return face_owner_vector;
+  }());
+
+  descriptor.setEdgeOwnerVector([&] {
+    Array<int> edge_owner_vector(descriptor.edgeNumberVector().size());
+    edge_owner_vector.fill(parallel::rank());
+    return edge_owner_vector;
+  }());
+
+  descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(descriptor.nodeNumberVector().size());
+    node_owner_vector.fill(parallel::rank());
+    return node_owner_vector;
+  }());
 
   m_connectivity = Connectivity<Dimension>::build(descriptor);
 }
diff --git a/src/mesh/MedianDualConnectivityBuilder.cpp b/src/mesh/MedianDualConnectivityBuilder.cpp
index c6770d05ee1d93891d560bd1b1373fd2e68da5eb..1986c4a826a0a3ba2754e6204dafb4f9151bd5f7 100644
--- a/src/mesh/MedianDualConnectivityBuilder.cpp
+++ b/src/mesh/MedianDualConnectivityBuilder.cpp
@@ -92,33 +92,37 @@ MedianDualConnectivityBuilder::_buildConnectivityDescriptor<2>(const Connectivit
     Assert(i_boundary_node == primal_number_of_boundary_nodes);
   }
 
-  dual_descriptor.node_number_vector.resize(dual_number_of_nodes);
+  Array<int> node_number_vector(dual_number_of_nodes);
   {
     parallel_for(m_primal_cell_to_dual_node_map.size(), [&](size_t i) {
-      const auto [primal_cell_id, dual_node_id]        = m_primal_cell_to_dual_node_map[i];
-      dual_descriptor.node_number_vector[dual_node_id] = primal_cell_number[primal_cell_id];
+      const auto [primal_cell_id, dual_node_id] = m_primal_cell_to_dual_node_map[i];
+      node_number_vector[dual_node_id]          = primal_cell_number[primal_cell_id];
     });
 
     const size_t face_number_shift = max(primal_cell_number) + 1;
     parallel_for(primal_number_of_faces, [&](size_t i) {
-      const auto [primal_face_id, dual_node_id]        = m_primal_face_to_dual_node_map[i];
-      dual_descriptor.node_number_vector[dual_node_id] = primal_face_number[primal_face_id] + face_number_shift;
+      const auto [primal_face_id, dual_node_id] = m_primal_face_to_dual_node_map[i];
+      node_number_vector[dual_node_id]          = primal_face_number[primal_face_id] + face_number_shift;
     });
 
     const size_t node_number_shift = face_number_shift + max(primal_face_number) + 1;
     parallel_for(m_primal_boundary_node_to_dual_node_map.size(), [&](size_t i) {
-      const auto [primal_node_id, dual_node_id]        = m_primal_boundary_node_to_dual_node_map[i];
-      dual_descriptor.node_number_vector[dual_node_id] = primal_node_number[primal_node_id] + node_number_shift;
+      const auto [primal_node_id, dual_node_id] = m_primal_boundary_node_to_dual_node_map[i];
+      node_number_vector[dual_node_id]          = primal_node_number[primal_node_id] + node_number_shift;
     });
   }
+  dual_descriptor.setNodeNumberVector(node_number_vector);
 
-  dual_descriptor.cell_number_vector.resize(dual_number_of_cells);
-  parallel_for(dual_number_of_cells, [&](size_t i) {
-    const auto [primal_node_id, dual_cell_id]        = m_primal_node_to_dual_cell_map[i];
-    dual_descriptor.cell_number_vector[dual_cell_id] = primal_node_number[primal_node_id];
-  });
+  {
+    Array<int> cell_number_vector(dual_number_of_cells);
+    parallel_for(dual_number_of_cells, [&](size_t i) {
+      const auto [primal_node_id, dual_cell_id] = m_primal_node_to_dual_cell_map[i];
+      cell_number_vector[dual_cell_id]          = primal_node_number[primal_node_id];
+    });
+    dual_descriptor.setCellNumberVector(cell_number_vector);
+  }
 
-  dual_descriptor.cell_type_vector.resize(dual_number_of_cells);
+  Array<CellType> cell_type_vector(dual_number_of_cells);
 
   const auto& primal_node_to_cell_matrix = primal_connectivity.nodeToCellMatrix();
 
@@ -127,13 +131,13 @@ MedianDualConnectivityBuilder::_buildConnectivityDescriptor<2>(const Connectivit
     const auto& primal_node_cell_list = primal_node_to_cell_matrix[node_id];
 
     if (primal_node_cell_list.size() == 1) {
-      dual_descriptor.cell_type_vector[i_dual_cell] = CellType::Quadrangle;
+      cell_type_vector[i_dual_cell] = CellType::Quadrangle;
     } else {
-      dual_descriptor.cell_type_vector[i_dual_cell] = CellType::Polygon;
+      cell_type_vector[i_dual_cell] = CellType::Polygon;
     }
   });
+  dual_descriptor.setCellTypeVector(cell_type_vector);
 
-  dual_descriptor.cell_to_node_vector.resize(dual_number_of_cells);
   const auto& primal_cell_to_face_matrix               = primal_connectivity.cellToFaceMatrix();
   const auto& primal_node_to_face_matrix               = primal_connectivity.nodeToFaceMatrix();
   const auto& primal_face_to_cell_matrix               = primal_connectivity.faceToCellMatrix();
@@ -170,16 +174,35 @@ MedianDualConnectivityBuilder::_buildConnectivityDescriptor<2>(const Connectivit
     // LCOV_EXCL_STOP
   };
 
+  Array<unsigned int> cell_to_node_row(dual_number_of_cells + 1);
+  cell_to_node_row[0] = 0;
+  {
+    for (NodeId node_id = 0; node_id < primal_number_of_nodes; ++node_id) {
+      // const size_t i_dual_cell             = node_id;
+      const auto& primal_node_to_cell_list = primal_node_to_cell_matrix[node_id];
+      const auto& primal_node_to_face_list = primal_node_to_face_matrix[node_id];
+
+      if (primal_node_to_cell_list.size() != primal_node_to_face_list.size()) {
+        // boundary cell
+        cell_to_node_row[node_id + 1] =
+          cell_to_node_row[node_id] + 1 + primal_node_to_cell_list.size() + primal_node_to_face_list.size();
+      } else {
+        // inner cell
+        cell_to_node_row[node_id + 1] =
+          cell_to_node_row[node_id] + primal_node_to_cell_list.size() + primal_node_to_face_list.size();
+      }
+    }
+  }
+
+  Array<unsigned int> cell_to_node_list(cell_to_node_row[cell_to_node_row.size() - 1]);
   parallel_for(primal_number_of_nodes, [&](NodeId node_id) {
-    const size_t i_dual_cell             = node_id;
     const auto& primal_node_to_cell_list = primal_node_to_cell_matrix[node_id];
     const auto& primal_node_to_face_list = primal_node_to_face_matrix[node_id];
 
-    auto& dual_cell_node_list = dual_descriptor.cell_to_node_vector[i_dual_cell];
+    size_t i_dual_cell_node = cell_to_node_row[node_id];
 
     if (primal_node_to_cell_list.size() != primal_node_to_face_list.size()) {
       // boundary cell
-      dual_cell_node_list.reserve(1 + primal_node_to_cell_list.size() + primal_node_to_face_list.size());
 
       auto [face_id, cell_id] = [&]() -> std::pair<FaceId, CellId> {
         for (size_t i_face = 0; i_face < primal_node_to_face_list.size(); ++i_face) {
@@ -200,24 +223,24 @@ MedianDualConnectivityBuilder::_buildConnectivityDescriptor<2>(const Connectivit
         // LCOV_EXCL_STOP
       }();
 
-      dual_cell_node_list.push_back(m_primal_face_to_dual_node_map[face_id].second);
-      dual_cell_node_list.push_back(m_primal_cell_to_dual_node_map[cell_id].second);
+      cell_to_node_list[i_dual_cell_node++] = m_primal_face_to_dual_node_map[face_id].second;
+      cell_to_node_list[i_dual_cell_node++] = m_primal_cell_to_dual_node_map[cell_id].second;
 
       face_id = next_face(cell_id, face_id, node_id);
 
       while (primal_face_to_cell_matrix[face_id].size() > 1) {
-        dual_cell_node_list.push_back(m_primal_face_to_dual_node_map[face_id].second);
-        cell_id = next_cell(cell_id, face_id);
-        dual_cell_node_list.push_back(m_primal_cell_to_dual_node_map[cell_id].second);
+        cell_to_node_list[i_dual_cell_node++] = m_primal_face_to_dual_node_map[face_id].second;
+
+        cell_id                               = next_cell(cell_id, face_id);
+        cell_to_node_list[i_dual_cell_node++] = m_primal_cell_to_dual_node_map[cell_id].second;
+
         face_id = next_face(cell_id, face_id, node_id);
       }
-      dual_cell_node_list.push_back(m_primal_face_to_dual_node_map[face_id].second);
-      dual_cell_node_list.push_back(node_to_dual_node_correpondance[node_id]);
+      cell_to_node_list[i_dual_cell_node++] = m_primal_face_to_dual_node_map[face_id].second;
+      cell_to_node_list[i_dual_cell_node++] = node_to_dual_node_correpondance[node_id];
 
-      Assert(dual_cell_node_list.size() == 1 + primal_node_to_cell_list.size() + primal_node_to_face_list.size());
     } else {
       // inner cell
-      dual_cell_node_list.reserve(primal_node_to_cell_list.size() + primal_node_to_face_list.size());
 
       auto [face_id, cell_id] = [&]() -> std::pair<FaceId, CellId> {
         const FaceId face_id = primal_node_to_face_list[0];
@@ -237,14 +260,16 @@ MedianDualConnectivityBuilder::_buildConnectivityDescriptor<2>(const Connectivit
 
       const FaceId first_face_id = face_id;
       do {
-        dual_cell_node_list.push_back(m_primal_face_to_dual_node_map[face_id].second);
-        dual_cell_node_list.push_back(m_primal_cell_to_dual_node_map[cell_id].second);
+        cell_to_node_list[i_dual_cell_node++] = m_primal_face_to_dual_node_map[face_id].second;
+        cell_to_node_list[i_dual_cell_node++] = m_primal_cell_to_dual_node_map[cell_id].second;
 
         face_id = next_face(cell_id, face_id, node_id);
         cell_id = next_cell(cell_id, face_id);
       } while (face_id != first_face_id);
     }
   });
+
+  dual_descriptor.setCellToNodeMatrix(ConnectivityMatrix(cell_to_node_row, cell_to_node_list));
 }
 
 template <>
@@ -277,39 +302,28 @@ MedianDualConnectivityBuilder::_buildConnectivityFrom<2>(const IConnectivity& i_
       const auto& primal_node_list     = primal_ref_node_list.list();
 
       const std::vector<NodeId> dual_node_list = [&]() {
-        std::vector<NodeId> dual_node_list;
+        std::vector<NodeId> tmp_dual_node_list;
 
         for (size_t i_primal_node = 0; i_primal_node < primal_node_list.size(); ++i_primal_node) {
           auto primal_node_id = primal_node_list[i_primal_node];
           const auto i_dual_node =
             primal_boundary_node_id_to_dual_node_id_map.find(primal_connectivity.nodeNumber()[primal_node_id]);
           if (i_dual_node != primal_boundary_node_id_to_dual_node_id_map.end()) {
-            dual_node_list.push_back(i_dual_node->second);
+            tmp_dual_node_list.push_back(i_dual_node->second);
           }
         }
 
-        return dual_node_list;
+        return tmp_dual_node_list;
       }();
 
       if (parallel::allReduceOr(dual_node_list.size() > 0)) {
-        dual_descriptor.addRefItemList(RefNodeList{primal_ref_node_list.refId(), convert_to_array(dual_node_list),
-                                                   primal_ref_node_list.isBoundary()});
+        auto dual_node_array = convert_to_array(dual_node_list);
+        dual_descriptor.addRefItemList(
+          RefNodeList{primal_ref_node_list.refId(), dual_node_array, primal_ref_node_list.isBoundary()});
       }
     }
   }
 
-  using Face = ConnectivityFace<2>;
-
-  const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map = [&] {
-    std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
-    for (FaceId l = 0; l < dual_descriptor.face_to_node_vector.size(); ++l) {
-      const auto& node_vector = dual_descriptor.face_to_node_vector[l];
-
-      face_to_id_map[Face(node_vector, dual_descriptor.node_number_vector)] = l;
-    }
-    return face_to_id_map;
-  }();
-
   for (size_t i_face_list = 0; i_face_list < primal_connectivity.template numberOfRefItemList<ItemType::face>();
        ++i_face_list) {
     const auto& primal_ref_face_list = primal_connectivity.template refItemList<ItemType::face>(i_face_list);
@@ -321,21 +335,23 @@ MedianDualConnectivityBuilder::_buildConnectivityFrom<2>(const IConnectivity& i_
         bounday_face_dual_node_id_list[i_face] = m_primal_face_to_dual_node_map[primal_face_list[i_face]].second;
       }
 
-      std::vector<bool> is_dual_node_from_boundary_face(dual_descriptor.node_number_vector.size(), false);
+      std::vector<bool> is_dual_node_from_boundary_face(dual_descriptor.nodeNumberVector().size(), false);
       for (size_t i_face = 0; i_face < bounday_face_dual_node_id_list.size(); ++i_face) {
         is_dual_node_from_boundary_face[bounday_face_dual_node_id_list[i_face]] = true;
       }
 
-      std::vector<bool> is_dual_node_from_boundary_node(dual_descriptor.node_number_vector.size(), false);
+      std::vector<bool> is_dual_node_from_boundary_node(dual_descriptor.nodeNumberVector().size(), false);
       for (size_t i_node = 0; i_node < m_primal_boundary_node_to_dual_node_map.size(); ++i_node) {
         is_dual_node_from_boundary_node[m_primal_boundary_node_to_dual_node_map[i_node].second] = true;
       }
 
+      const auto& dual_face_to_node_matrix = dual_descriptor.faceToNodeMatrix();
+
       std::vector<FaceId> dual_face_list;
       dual_face_list.reserve(2 * primal_face_list.size());
-      for (size_t i_dual_face = 0; i_dual_face < dual_descriptor.face_to_node_vector.size(); ++i_dual_face) {
-        const NodeId dual_node_0 = dual_descriptor.face_to_node_vector[i_dual_face][0];
-        const NodeId dual_node_1 = dual_descriptor.face_to_node_vector[i_dual_face][1];
+      for (size_t i_dual_face = 0; i_dual_face < dual_face_to_node_matrix.numberOfRows(); ++i_dual_face) {
+        const NodeId dual_node_0 = dual_face_to_node_matrix[i_dual_face][0];
+        const NodeId dual_node_1 = dual_face_to_node_matrix[i_dual_face][1];
 
         if ((is_dual_node_from_boundary_face[dual_node_0] and is_dual_node_from_boundary_node[dual_node_1]) or
             (is_dual_node_from_boundary_node[dual_node_0] and is_dual_node_from_boundary_face[dual_node_1])) {
@@ -352,42 +368,61 @@ MedianDualConnectivityBuilder::_buildConnectivityFrom<2>(const IConnectivity& i_
     }
   }
 
-  const size_t primal_number_of_nodes = primal_connectivity.numberOfNodes();
-  const size_t primal_number_of_cells = primal_connectivity.numberOfCells();
+  const auto& primal_node_owner = primal_connectivity.nodeOwner();
 
-  dual_descriptor.node_owner_vector.resize(dual_descriptor.node_number_vector.size());
+  dual_descriptor.setNodeOwnerVector([&] {
+    Array<int> node_owner_vector(dual_descriptor.nodeNumberVector().size());
 
-  const auto& primal_node_owner = primal_connectivity.nodeOwner();
-  for (NodeId primal_node_id = 0; primal_node_id < primal_connectivity.numberOfNodes(); ++primal_node_id) {
-    dual_descriptor.node_owner_vector[primal_node_id] = primal_node_owner[primal_node_id];
-  }
-  const auto& primal_cell_owner = primal_connectivity.cellOwner();
-  for (CellId primal_cell_id = 0; primal_cell_id < primal_number_of_cells; ++primal_cell_id) {
-    dual_descriptor.node_owner_vector[primal_number_of_nodes + primal_cell_id] = primal_cell_owner[primal_cell_id];
-  }
+    node_owner_vector.fill(-1);
+    for (size_t i_primal_node_to_dual_node = 0;
+         i_primal_node_to_dual_node < m_primal_boundary_node_to_dual_node_map.size(); ++i_primal_node_to_dual_node) {
+      const auto& [primal_node_id, dual_node_id] = m_primal_boundary_node_to_dual_node_map[i_primal_node_to_dual_node];
+      node_owner_vector[dual_node_id]            = primal_node_owner[primal_node_id];
+    }
 
-  dual_descriptor.cell_owner_vector.resize(dual_descriptor.cell_number_vector.size());
-  for (NodeId primal_node_id = 0; primal_node_id < primal_number_of_nodes; ++primal_node_id) {
-    dual_descriptor.cell_owner_vector[primal_node_id] = primal_node_owner[primal_node_id];
-  }
+    const auto& primal_face_owner = primal_connectivity.faceOwner();
+    for (size_t i_primal_face_to_dual_node = 0; i_primal_face_to_dual_node < m_primal_face_to_dual_node_map.size();
+         ++i_primal_face_to_dual_node) {
+      const auto& [primal_face_id, dual_node_id] = m_primal_face_to_dual_node_map[i_primal_face_to_dual_node];
+      node_owner_vector[dual_node_id]            = primal_face_owner[primal_face_id];
+    }
 
-  {
-    std::vector<int> face_cell_owner(dual_descriptor.face_number_vector.size());
-    std::fill(std::begin(face_cell_owner), std::end(face_cell_owner), parallel::size());
+    const auto& primal_cell_owner = primal_connectivity.cellOwner();
+    for (size_t i_primal_cell_to_dual_node = 0; i_primal_cell_to_dual_node < m_primal_cell_to_dual_node_map.size();
+         ++i_primal_cell_to_dual_node) {
+      const auto& [primal_cell_id, dual_node_id] = m_primal_cell_to_dual_node_map[i_primal_cell_to_dual_node];
+      node_owner_vector[dual_node_id]            = primal_cell_owner[primal_cell_id];
+    }
 
-    for (size_t i_cell = 0; i_cell < dual_descriptor.cell_to_face_vector.size(); ++i_cell) {
-      const auto& cell_face_list = dual_descriptor.cell_to_face_vector[i_cell];
-      for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) {
-        const size_t face_id     = cell_face_list[i_face];
-        face_cell_owner[face_id] = std::min(face_cell_owner[face_id], dual_descriptor.cell_number_vector[i_cell]);
-      }
+    return node_owner_vector;
+  }());
+
+  dual_descriptor.setCellOwnerVector([&] {
+    Array<int> cell_owner_vector(dual_descriptor.cellNumberVector().size());
+    cell_owner_vector.fill(-1);
+    for (size_t i_primal_cell_to_dual_node = 0; i_primal_cell_to_dual_node < m_primal_node_to_dual_cell_map.size();
+         ++i_primal_cell_to_dual_node) {
+      const auto& [primal_node_id, dual_cell_id] = m_primal_node_to_dual_cell_map[i_primal_cell_to_dual_node];
+      cell_owner_vector[dual_cell_id]            = primal_node_owner[primal_node_id];
     }
+    return cell_owner_vector;
+  }());
 
-    dual_descriptor.face_owner_vector.resize(face_cell_owner.size());
-    for (size_t i_face = 0; i_face < face_cell_owner.size(); ++i_face) {
-      dual_descriptor.face_owner_vector[i_face] = dual_descriptor.cell_owner_vector[face_cell_owner[i_face]];
+  dual_descriptor.setFaceOwnerVector([&] {
+    const auto& dual_cell_to_face_matrix = dual_descriptor.cellToFaceMatrix();
+    const auto& dual_cell_owner_vector   = dual_descriptor.cellOwnerVector();
+
+    Array<int> face_owner_vector(dual_descriptor.faceNumberVector().size());
+    face_owner_vector.fill(parallel::size());
+    for (size_t i_cell = 0; i_cell < dual_cell_to_face_matrix.numberOfRows(); ++i_cell) {
+      const auto& cell_face_list = dual_cell_to_face_matrix[i_cell];
+      for (size_t i_face = 0; i_face < cell_face_list.size(); ++i_face) {
+        const size_t face_id       = cell_face_list[i_face];
+        face_owner_vector[face_id] = std::min(face_owner_vector[face_id], dual_cell_owner_vector[i_cell]);
+      }
     }
-  }
+    return face_owner_vector;
+  }());
 
   m_connectivity = ConnectivityType::build(dual_descriptor);
 
diff --git a/src/mesh/MeshBuilderBase.cpp b/src/mesh/MeshBuilderBase.cpp
index a3a8440bd25499d8aea34a08e7c836b66a4c3a32..973a6099afb49595d02403c697bbb927d2a7ba63 100644
--- a/src/mesh/MeshBuilderBase.cpp
+++ b/src/mesh/MeshBuilderBase.cpp
@@ -132,8 +132,14 @@ MeshBuilderBase::_checkMesh() const
       if (intersection.size() > 1) {
         std::ostringstream error_msg;
         error_msg << "invalid mesh.\n\tFollowing faces\n";
-        for (FaceId face_id : intersection) {
-          error_msg << "\t - id=" << face_id << " number=" << connectivity.faceNumber()[face_id] << '\n';
+        for (FaceId intersection_face_id : intersection) {
+          error_msg << "\t - id=" << intersection_face_id
+                    << " number=" << connectivity.faceNumber()[intersection_face_id] << '\n';
+          error_msg << "\t   nodes:";
+          for (size_t i = 0; i < face_to_node_matrix[intersection_face_id].size(); ++i) {
+            error_msg << ' ' << face_to_node_matrix[intersection_face_id][i];
+          }
+          error_msg << '\n';
         }
         error_msg << "\tare duplicated";
         throw NormalError(error_msg.str());
diff --git a/src/output/GnuplotWriter.cpp b/src/output/GnuplotWriter.cpp
index 4a346cf33e4c7f475a0abb8f0c3136ed88a72cc0..50969799eeb4250b28d21722e8476ab86a88d9eb 100644
--- a/src/output/GnuplotWriter.cpp
+++ b/src/output/GnuplotWriter.cpp
@@ -162,7 +162,7 @@ GnuplotWriter::_writeDataAtNodes(const MeshType& mesh,
           const NodeId& node_id                     = cell_nodes[i_node];
           const TinyVector<MeshType::Dimension>& xr = mesh.xr()[node_id];
           fout << xr[0];
-          for (auto [name, item_data_variant] : output_named_item_data_set) {
+          for (const auto& [name, item_data_variant] : output_named_item_data_set) {
             std::visit([&](auto&& item_data) { _writeData(item_data, cell_id, node_id, fout); }, item_data_variant);
           }
           fout << '\n';
@@ -182,7 +182,7 @@ GnuplotWriter::_writeDataAtNodes(const MeshType& mesh,
           const NodeId& node_id                     = cell_nodes[i_node];
           const TinyVector<MeshType::Dimension>& xr = mesh.xr()[node_id];
           fout << xr[0] << ' ' << xr[1];
-          for (auto [name, item_data_variant] : output_named_item_data_set) {
+          for (const auto& [name, item_data_variant] : output_named_item_data_set) {
             std::visit([&](auto&& item_data) { _writeData(item_data, cell_id, node_id, fout); }, item_data_variant);
           }
           fout << '\n';
@@ -190,7 +190,7 @@ GnuplotWriter::_writeDataAtNodes(const MeshType& mesh,
         const NodeId& node_id                     = cell_nodes[0];
         const TinyVector<MeshType::Dimension>& xr = mesh.xr()[node_id];
         fout << xr[0] << ' ' << xr[1];
-        for (auto [name, item_data_variant] : output_named_item_data_set) {
+        for (const auto& [name, item_data_variant] : output_named_item_data_set) {
           std::visit([&](auto&& item_data) { _writeData(item_data, cell_id, node_id, fout); }, item_data_variant);
         }
         fout << "\n\n\n";
diff --git a/src/output/GnuplotWriter1D.cpp b/src/output/GnuplotWriter1D.cpp
index ad398126c4f075bf8c092ebb0aaac6ed1e4c0fd9..a3156904843bdeb77be718de6a10614a3333afae 100644
--- a/src/output/GnuplotWriter1D.cpp
+++ b/src/output/GnuplotWriter1D.cpp
@@ -149,7 +149,7 @@ GnuplotWriter1D::_writeItemDatas(const MeshType& mesh,
 
   const size_t& number_of_columns = [&] {
     size_t number_of_columns = 1;
-    for (auto [name, item_data] : output_named_item_data_set) {
+    for (const auto& [name, item_data] : output_named_item_data_set) {
       std::visit([&](auto&& value) { number_of_columns += _itemDataNbRow(value); }, item_data);
     }
     return number_of_columns;
@@ -198,7 +198,7 @@ GnuplotWriter1D::_writeItemDatas(const MeshType& mesh,
   }
 
   size_t column_number = 1;
-  for (auto [name, output_item_data] : output_named_item_data_set) {
+  for (const auto& [name, output_item_data] : output_named_item_data_set) {
     std::visit(
       [&](auto&& item_data) {
         using ItemDataT = std::decay_t<decltype(item_data)>;
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/VTKWriter.cpp b/src/output/VTKWriter.cpp
index f68607d7da6daf5afe8980a36e669e87b013dd3e..0a40c7265e05a17d31e59bd41eeb10cb63935821 100644
--- a/src/output/VTKWriter.cpp
+++ b/src/output/VTKWriter.cpp
@@ -116,7 +116,7 @@ class VTKWriter::SerializedDataList
   void
   write(std::ostream& os) const
   {
-    for (auto serialized_data : m_serialized_data_list) {
+    for (const auto& serialized_data : m_serialized_data_list) {
       serialized_data->write(os);
     }
   }
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..f61f27cb46c4ed9dbee0481c433e6f9aa3ea4da0 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<3, 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/CMakeLists.txt b/src/scheme/CMakeLists.txt
index 25e1405a5e9d2f9603a5f50a6abf04350596c58b..88fb5d99cebb75af47c515126165d91a5370b23f 100644
--- a/src/scheme/CMakeLists.txt
+++ b/src/scheme/CMakeLists.txt
@@ -3,6 +3,7 @@
 add_library(
   PugsScheme
   AcousticSolver.cpp
+  HyperelasticSolver.cpp
   DiscreteFunctionIntegrator.cpp
   DiscreteFunctionInterpoler.cpp
   DiscreteFunctionUtils.cpp
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 bb6b484a6bc10bd803e16b8bb54a7cba25a349de..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>);
@@ -524,11 +522,62 @@ class DiscreteFunctionP0 : public IDiscreteFunction
     return result;
   }
 
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  det(const DiscreteFunctionP0& A)
+  {
+    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(
+      A.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = det(A[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  trace(const DiscreteFunctionP0& A)
+  {
+    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(
+      A.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = trace(A[cell_id]); });
+
+    return result;
+  }
+
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, std::remove_const_t<DataType>>
+  inverse(const DiscreteFunctionP0& A)
+  {
+    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<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<Dimension, std::remove_const_t<DataType>>
+  transpose(const DiscreteFunctionP0& A)
+  {
+    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<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]); });
+
+    return result;
+  }
+
   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};
     parallel_for(
       f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { result[cell_id] = dot(f[cell_id], g[cell_id]); });
@@ -539,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(
@@ -551,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(
@@ -569,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>);
@@ -582,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>);
@@ -594,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>);
@@ -615,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>);
@@ -628,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>);
@@ -640,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>);
@@ -669,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];
@@ -687,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 8eaa5d0a7580ddc65cb17e82d6f5551f86114e1c..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;
   }
@@ -195,6 +191,26 @@ class DiscreteFunctionP0Vector : public IDiscreteFunction
     return product;
   }
 
+  PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
+  sumOfComponents(const DiscreteFunctionP0Vector& f)
+  {
+    DiscreteFunctionP0<Dimension, double> result{f.m_mesh};
+
+    parallel_for(
+      f.m_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+        const auto& f_cell_id = f[cell_id];
+
+        double sum = 0;
+        for (size_t i = 0; i < f.size(); ++i) {
+          sum += f_cell_id[i];
+        }
+
+        result[cell_id] = sum;
+      });
+
+    return result;
+  }
+
   PUGS_INLINE friend DiscreteFunctionP0<Dimension, double>
   dot(const DiscreteFunctionP0Vector& f, const DiscreteFunctionP0Vector& g)
   {
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 1eea93a7eae8db4c592b6bbec35341ad33e35bdc..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 (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 (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/HyperelasticSolver.cpp b/src/scheme/HyperelasticSolver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2f683fa805aa2741b003acff8cb5f39040dafd29
--- /dev/null
+++ b/src/scheme/HyperelasticSolver.cpp
@@ -0,0 +1,982 @@
+#include <scheme/HyperelasticSolver.hpp>
+
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/ExternalBoundaryConditionDescriptor.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/Socket.hpp>
+
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+double
+hyperelastic_dt(const DiscreteFunctionP0<Dimension, const double>& c)
+{
+  const Mesh<Connectivity<Dimension>>& mesh = dynamic_cast<const Mesh<Connectivity<Dimension>>&>(*c.mesh());
+
+  const auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj();
+  const auto Sj = MeshDataManager::instance().getMeshData(mesh).sumOverRLjr();
+
+  CellValue<double> local_dt{mesh.connectivity()};
+  parallel_for(
+    mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { local_dt[j] = 2 * Vj[j] / (Sj[j] * c[j]); });
+
+  return min(local_dt);
+}
+
+double
+hyperelastic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c)
+{
+  std::shared_ptr mesh = getCommonMesh({c});
+
+  switch (mesh->dimension()) {
+  case 1: {
+    return hyperelastic_dt(c->get<DiscreteFunctionP0<1, const double>>());
+  }
+  case 2: {
+    return hyperelastic_dt(c->get<DiscreteFunctionP0<2, const double>>());
+  }
+  case 3: {
+    return hyperelastic_dt(c->get<DiscreteFunctionP0<3, const double>>());
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+template <size_t Dimension>
+class HyperelasticSolverHandler::HyperelasticSolver final : public HyperelasticSolverHandler::IHyperelasticSolver
+{
+ private:
+  using Rdxd = TinyMatrix<Dimension>;
+  using Rd   = TinyVector<Dimension>;
+
+  using MeshType     = Mesh<Connectivity<Dimension>>;
+  using MeshDataType = MeshData<Dimension>;
+
+  using DiscreteScalarFunction = DiscreteFunctionP0<Dimension, const double>;
+  using DiscreteVectorFunction = DiscreteFunctionP0<Dimension, const Rd>;
+  using DiscreteTensorFunction = DiscreteFunctionP0<Dimension, const Rdxd>;
+
+  class FixedBoundaryCondition;
+  class PressureBoundaryCondition;
+  class NormalStressBoundaryCondition;
+  class SymmetryBoundaryCondition;
+  class VelocityBoundaryCondition;
+
+  using BoundaryCondition = std::variant<FixedBoundaryCondition,
+                                         PressureBoundaryCondition,
+                                         NormalStressBoundaryCondition,
+                                         SymmetryBoundaryCondition,
+                                         VelocityBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+  NodeValuePerCell<const Rdxd>
+  _computeGlaceAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoaL, const DiscreteScalarFunction& rhoaT) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+    const NodeValuePerCell<const Rd> njr = mesh_data.njr();
+
+    NodeValuePerCell<Rdxd> Ajr{mesh.connectivity()};
+    const Rdxd I = identity;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const size_t& nb_nodes = Ajr.numberOfSubValues(j);
+        const double& rhoaL_j  = rhoaL[j];
+        const double& rhoaT_j  = rhoaT[j];
+        for (size_t r = 0; r < nb_nodes; ++r) {
+          const Rdxd& M = tensorProduct(Cjr(j, r), njr(j, r));
+          Ajr(j, r)     = rhoaL_j * M + rhoaT_j * (l2Norm(Cjr(j, r)) * I - M);
+        }
+      });
+
+    return Ajr;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeEucclhydAjr(const MeshType& mesh,
+                      const DiscreteScalarFunction& rhoaL,
+                      const DiscreteScalarFunction& rhoaT) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+    const NodeValuePerFace<const Rd> nlr = mesh_data.nlr();
+
+    const auto& face_to_node_matrix = mesh.connectivity().faceToNodeMatrix();
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+    const auto& cell_to_face_matrix = mesh.connectivity().cellToFaceMatrix();
+
+    NodeValuePerCell<Rdxd> Ajr{mesh.connectivity()};
+
+    parallel_for(
+      Ajr.numberOfValues(), PUGS_LAMBDA(size_t jr) { Ajr[jr] = zero; });
+    const Rdxd I = identity;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        const auto& cell_faces = cell_to_face_matrix[j];
+
+        const double& rho_aL = rhoaL[j];
+        const double& rho_aT = rhoaT[j];
+
+        for (size_t L = 0; L < cell_faces.size(); ++L) {
+          const FaceId& l        = cell_faces[L];
+          const auto& face_nodes = face_to_node_matrix[l];
+
+          auto local_node_number_in_cell = [&](NodeId node_number) {
+            for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
+              if (node_number == cell_nodes[i_node]) {
+                return i_node;
+              }
+            }
+            return std::numeric_limits<size_t>::max();
+          };
+
+          for (size_t rl = 0; rl < face_nodes.size(); ++rl) {
+            const size_t R = local_node_number_in_cell(face_nodes[rl]);
+            const Rdxd& M  = tensorProduct(Nlr(l, rl), nlr(l, rl));
+            Ajr(j, R) += rho_aL * M + rho_aT * (l2Norm(Nlr(l, rl)) * I - M);
+          }
+        }
+      });
+
+    return Ajr;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeAjr(const SolverType& solver_type,
+              const MeshType& mesh,
+              const DiscreteScalarFunction& rhoaL,
+              const DiscreteScalarFunction& rhoaT) const
+  {
+    if constexpr (Dimension == 1) {
+      return _computeGlaceAjr(mesh, rhoaL, rhoaT);
+    } else {
+      switch (solver_type) {
+      case SolverType::Glace: {
+        return _computeGlaceAjr(mesh, rhoaL, rhoaT);
+      }
+      case SolverType::Eucclhyd: {
+        return _computeEucclhydAjr(mesh, rhoaL, rhoaT);
+      }
+      default: {
+        throw UnexpectedError("invalid solver type");
+      }
+      }
+    }
+  }
+
+  NodeValue<Rdxd>
+  _computeAr(const MeshType& mesh, const NodeValuePerCell<const Rdxd>& Ajr) const
+  {
+    const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+    const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+    NodeValue<Rdxd> Ar{mesh.connectivity()};
+
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
+        Rdxd sum                                   = zero;
+        const auto& node_to_cell                   = node_to_cell_matrix[r];
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
+
+        for (size_t j = 0; j < node_to_cell.size(); ++j) {
+          const CellId J       = node_to_cell[j];
+          const unsigned int R = node_local_number_in_its_cells[j];
+          sum += Ajr(J, R);
+        }
+        Ar[r] = sum;
+      });
+
+    return Ar;
+  }
+
+  NodeValue<Rd>
+  _computeBr(const Mesh<Connectivity<Dimension>>& mesh,
+             const NodeValuePerCell<const Rdxd>& Ajr,
+             const DiscreteVectorFunction& u,
+             const DiscreteTensorFunction& sigma) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd>& Cjr = mesh_data.Cjr();
+
+    const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+    const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+    NodeValue<Rd> b{mesh.connectivity()};
+
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
+        const auto& node_to_cell                   = node_to_cell_matrix[r];
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
+
+        Rd br = zero;
+        for (size_t j = 0; j < node_to_cell.size(); ++j) {
+          const CellId J       = node_to_cell[j];
+          const unsigned int R = node_local_number_in_its_cells[j];
+          br += Ajr(J, R) * u[J] - sigma[J] * Cjr(J, R);
+        }
+
+        b[r] = br;
+      });
+
+    return b;
+  }
+
+  BoundaryConditionList
+  _getBCList(const MeshType& mesh,
+             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    BoundaryConditionList bc_list;
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        bc_list.emplace_back(
+          SymmetryBoundaryCondition(getMeshFlatNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())));
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fixed: {
+        bc_list.emplace_back(FixedBoundaryCondition(getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())));
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if (dirichlet_bc_descriptor.name() == "velocity") {
+          MeshNodeBoundary<Dimension> mesh_node_boundary =
+            getMeshNodeBoundary(mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+          Array<const Rd> value_list =
+            InterpolateItemValue<Rd(Rd)>::template interpolate<ItemType::node>(dirichlet_bc_descriptor.rhsSymbolId(),
+                                                                               mesh.xr(),
+                                                                               mesh_node_boundary.nodeList());
+
+          bc_list.emplace_back(VelocityBoundaryCondition{mesh_node_boundary, value_list});
+        } else if (dirichlet_bc_descriptor.name() == "pressure") {
+          const FunctionSymbolId pressure_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          if constexpr (Dimension == 1) {
+            MeshNodeBoundary<Dimension> mesh_node_boundary =
+              getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            Array<const double> node_values =
+              InterpolateItemValue<double(Rd)>::template interpolate<ItemType::node>(pressure_id, mesh.xr(),
+                                                                                     mesh_node_boundary.nodeList());
+
+            bc_list.emplace_back(PressureBoundaryCondition{mesh_node_boundary, node_values});
+          } else {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+            Array<const double> face_values =
+              InterpolateItemValue<double(Rd)>::template interpolate<ItemType::face>(pressure_id, mesh_data.xl(),
+                                                                                     mesh_face_boundary.faceList());
+            bc_list.emplace_back(PressureBoundaryCondition{mesh_face_boundary, face_values});
+          }
+
+        } else if (dirichlet_bc_descriptor.name() == "normal-stress") {
+          const FunctionSymbolId normal_stress_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          if constexpr (Dimension == 1) {
+            MeshNodeBoundary<Dimension> mesh_node_boundary =
+              getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            Array<const Rdxd> node_values =
+              InterpolateItemValue<Rdxd(Rd)>::template interpolate<ItemType::node>(normal_stress_id, mesh.xr(),
+                                                                                   mesh_node_boundary.nodeList());
+
+            bc_list.emplace_back(NormalStressBoundaryCondition{mesh_node_boundary, node_values});
+          } else {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+            Array<const Rdxd> face_values =
+              InterpolateItemValue<Rdxd(Rd)>::template interpolate<ItemType::face>(normal_stress_id, mesh_data.xl(),
+                                                                                   mesh_face_boundary.faceList());
+            bc_list.emplace_back(NormalStressBoundaryCondition{mesh_face_boundary, face_values});
+          }
+
+        } else {
+          is_valid_boundary_condition = false;
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for hyperelastic solver";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    return bc_list;
+  }
+
+  void _applyPressureBC(const BoundaryConditionList& bc_list, const MeshType& mesh, NodeValue<Rd>& br) const;
+  void _applyNormalStressBC(const BoundaryConditionList& bc_list, const MeshType& mesh, NodeValue<Rd>& br) const;
+  void _applySymmetryBC(const BoundaryConditionList& bc_list, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br) const;
+  void _applyVelocityBC(const BoundaryConditionList& bc_list, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br) const;
+  void
+  _applyBoundaryConditions(const BoundaryConditionList& bc_list,
+                           const MeshType& mesh,
+                           NodeValue<Rdxd>& Ar,
+                           NodeValue<Rd>& br) const
+  {
+    this->_applyPressureBC(bc_list, mesh, br);
+    this->_applyNormalStressBC(bc_list, mesh, br);
+    this->_applySymmetryBC(bc_list, Ar, br);
+    this->_applyVelocityBC(bc_list, Ar, br);
+  }
+
+  NodeValue<const Rd>
+  _computeUr(const MeshType& mesh, const NodeValue<Rdxd>& Ar, const NodeValue<Rd>& br) const
+  {
+    NodeValue<Rd> u{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { u[r] = inverse(Ar[r]) * br[r]; });
+
+    return u;
+  }
+
+  NodeValuePerCell<Rd>
+  _computeFjr(const MeshType& mesh,
+              const NodeValuePerCell<const Rdxd>& Ajr,
+              const NodeValue<const Rd>& ur,
+              const DiscreteVectorFunction& u,
+              const DiscreteTensorFunction& sigma) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    NodeValuePerCell<Rd> F{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        for (size_t r = 0; r < cell_nodes.size(); ++r) {
+          F(j, r) = -Ajr(j, r) * (u[j] - ur[cell_nodes[r]]) + sigma[j] * Cjr(j, r);
+        }
+      });
+
+    return F;
+  }
+
+ 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 DiscreteFunctionVariant>& rho_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& aL_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& aT_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma_v,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    std::shared_ptr i_mesh = getCommonMesh({rho_v, aL_v, aT_v, u_v, sigma_v});
+    if (not i_mesh) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho_v, aL_v, u_v, sigma_v}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperelastic solver expects P0 functions");
+    }
+
+    const MeshType& mesh                = dynamic_cast<const MeshType&>(*i_mesh);
+    const DiscreteScalarFunction& rho   = rho_v->get<DiscreteScalarFunction>();
+    const DiscreteVectorFunction& u     = u_v->get<DiscreteVectorFunction>();
+    const DiscreteScalarFunction& aL    = aL_v->get<DiscreteScalarFunction>();
+    const DiscreteScalarFunction& aT    = aT_v->get<DiscreteScalarFunction>();
+    const DiscreteTensorFunction& sigma = sigma_v->get<DiscreteTensorFunction>();
+
+    NodeValuePerCell<const Rdxd> Ajr = this->_computeAjr(solver_type, mesh, rho * aL, rho * aT);
+
+    NodeValue<Rdxd> Ar = this->_computeAr(mesh, Ajr);
+    NodeValue<Rd> br   = this->_computeBr(mesh, Ajr, u, sigma);
+
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_applyBoundaryConditions(bc_list, mesh, Ar, br);
+
+    synchronize(Ar);
+    synchronize(br);
+
+    NodeValue<const Rd> ur         = this->_computeUr(mesh, Ar, br);
+    NodeValuePerCell<const Rd> Fjr = this->_computeFjr(mesh, Ajr, ur, u, sigma);
+
+    return std::make_tuple(std::make_shared<const ItemValueVariant>(ur),
+                           std::make_shared<const SubItemValuePerItemVariant>(Fjr));
+  }
+
+  std::tuple<std::shared_ptr<const IMesh>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes(const double& dt,
+               const MeshType& mesh,
+               const DiscreteScalarFunction& rho,
+               const DiscreteVectorFunction& u,
+               const DiscreteScalarFunction& E,
+               const DiscreteTensorFunction& CG,
+               const NodeValue<const Rd>& ur,
+               const NodeValuePerCell<const Rd>& Fjr) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho = copy(rho.cellValues());
+    CellValue<Rd> new_u       = copy(u.cellValues());
+    CellValue<double> new_E   = copy(E.cellValues());
+    CellValue<Rdxd> new_CG    = copy(CG.cellValues());
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        const Rdxd cauchy_green_fluxes = gradv * CG[j] + CG[j] * transpose(gradv);
+        const double dt_over_Mj        = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj        = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_CG[j] += dt_over_Vj * cauchy_green_fluxes;
+        new_CG[j] += transpose(new_CG[j]);
+        new_CG[j] *= 0.5;
+      });
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    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::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction(new_mesh, new_CG))};
+  }
+
+  std::tuple<std::shared_ptr<const IMesh>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             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 DiscreteFunctionVariant>& rho,
+               const std::shared_ptr<const DiscreteFunctionVariant>& u,
+               const std::shared_ptr<const DiscreteFunctionVariant>& E,
+               const std::shared_ptr<const DiscreteFunctionVariant>& CG,
+               const std::shared_ptr<const ItemValueVariant>& ur,
+               const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr) const
+  {
+    std::shared_ptr i_mesh = getCommonMesh({rho, u, E});
+    if (not i_mesh) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho, u, E}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperelastic solver expects P0 functions");
+    }
+
+    return this->apply_fluxes(dt,                                       //
+                              dynamic_cast<const MeshType&>(*i_mesh),   //
+                              rho->get<DiscreteScalarFunction>(),       //
+                              u->get<DiscreteVectorFunction>(),         //
+                              E->get<DiscreteScalarFunction>(),         //
+                              CG->get<DiscreteTensorFunction>(),        //
+                              ur->get<NodeValue<const Rd>>(),           //
+                              Fjr->get<NodeValuePerCell<const Rd>>());
+  }
+
+  std::tuple<std::shared_ptr<const IMesh>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             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 DiscreteFunctionVariant>& rho,
+        const std::shared_ptr<const DiscreteFunctionVariant>& u,
+        const std::shared_ptr<const DiscreteFunctionVariant>& E,
+        const std::shared_ptr<const DiscreteFunctionVariant>& CG,
+        const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+        const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+        const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+        const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    auto [ur, Fjr] = compute_fluxes(solver_type, rho, aL, aT, u, sigma, bc_descriptor_list);
+    return apply_fluxes(dt, rho, u, E, CG, ur, Fjr);
+  }
+
+  HyperelasticSolver()                     = default;
+  HyperelasticSolver(HyperelasticSolver&&) = default;
+  ~HyperelasticSolver()                    = default;
+};
+
+template <size_t Dimension>
+void
+HyperelasticSolverHandler::HyperelasticSolver<Dimension>::_applyPressureBC(const BoundaryConditionList& bc_list,
+                                                                           const MeshType& mesh,
+                                                                           NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<PressureBoundaryCondition, T>) {
+          MeshData<Dimension>& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+          if constexpr (Dimension == 1) {
+            const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+            const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+            const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+            const auto& node_list  = bc.nodeList();
+            const auto& value_list = bc.valueList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(size_t i_node) {
+                const NodeId node_id       = node_list[i_node];
+                const auto& node_cell_list = node_to_cell_matrix[node_id];
+                Assert(node_cell_list.size() == 1);
+
+                CellId node_cell_id              = node_cell_list[0];
+                size_t node_local_number_in_cell = node_local_numbers_in_their_cells(node_id, 0);
+
+                br[node_id] -= value_list[i_node] * Cjr(node_cell_id, node_local_number_in_cell);
+              });
+          } else {
+            const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+
+            const auto& face_to_cell_matrix               = mesh.connectivity().faceToCellMatrix();
+            const auto& face_to_node_matrix               = mesh.connectivity().faceToNodeMatrix();
+            const auto& face_local_numbers_in_their_cells = mesh.connectivity().faceLocalNumbersInTheirCells();
+            const auto& face_cell_is_reversed             = mesh.connectivity().cellFaceIsReversed();
+
+            const auto& face_list  = bc.faceList();
+            const auto& value_list = bc.valueList();
+            for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+              const FaceId face_id       = face_list[i_face];
+              const auto& face_cell_list = face_to_cell_matrix[face_id];
+              Assert(face_cell_list.size() == 1);
+
+              CellId face_cell_id              = face_cell_list[0];
+              size_t face_local_number_in_cell = face_local_numbers_in_their_cells(face_id, 0);
+
+              const double sign = face_cell_is_reversed(face_cell_id, face_local_number_in_cell) ? -1 : 1;
+
+              const auto& face_nodes = face_to_node_matrix[face_id];
+
+              for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
+                NodeId node_id = face_nodes[i_node];
+                br[node_id] -= sign * value_list[i_face] * Nlr(face_id, i_node);
+              }
+            }
+          }
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <size_t Dimension>
+void
+HyperelasticSolverHandler::HyperelasticSolver<Dimension>::_applyNormalStressBC(const BoundaryConditionList& bc_list,
+                                                                               const MeshType& mesh,
+                                                                               NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<NormalStressBoundaryCondition, T>) {
+          MeshData<Dimension>& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+          if constexpr (Dimension == 1) {
+            const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+            const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+            const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+            const auto& node_list  = bc.nodeList();
+            const auto& value_list = bc.valueList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(size_t i_node) {
+                const NodeId node_id       = node_list[i_node];
+                const auto& node_cell_list = node_to_cell_matrix[node_id];
+                Assert(node_cell_list.size() == 1);
+
+                CellId node_cell_id              = node_cell_list[0];
+                size_t node_local_number_in_cell = node_local_numbers_in_their_cells(node_id, 0);
+
+                br[node_id] += value_list[i_node] * Cjr(node_cell_id, node_local_number_in_cell);
+              });
+          } else {
+            const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+
+            const auto& face_to_cell_matrix               = mesh.connectivity().faceToCellMatrix();
+            const auto& face_to_node_matrix               = mesh.connectivity().faceToNodeMatrix();
+            const auto& face_local_numbers_in_their_cells = mesh.connectivity().faceLocalNumbersInTheirCells();
+            const auto& face_cell_is_reversed             = mesh.connectivity().cellFaceIsReversed();
+
+            const auto& face_list  = bc.faceList();
+            const auto& value_list = bc.valueList();
+            for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+              const FaceId face_id       = face_list[i_face];
+              const auto& face_cell_list = face_to_cell_matrix[face_id];
+              Assert(face_cell_list.size() == 1);
+
+              CellId face_cell_id              = face_cell_list[0];
+              size_t face_local_number_in_cell = face_local_numbers_in_their_cells(face_id, 0);
+
+              const double sign = face_cell_is_reversed(face_cell_id, face_local_number_in_cell) ? -1 : 1;
+
+              const auto& face_nodes = face_to_node_matrix[face_id];
+
+              for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
+                NodeId node_id = face_nodes[i_node];
+                br[node_id] += sign * value_list[i_face] * Nlr(face_id, i_node);
+              }
+            }
+          }
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <size_t Dimension>
+void
+HyperelasticSolverHandler::HyperelasticSolver<Dimension>::_applySymmetryBC(const BoundaryConditionList& bc_list,
+                                                                           NodeValue<Rdxd>& Ar,
+                                                                           NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<SymmetryBoundaryCondition, T>) {
+          const Rd& n = bc.outgoingNormal();
+
+          const Rdxd I   = identity;
+          const Rdxd nxn = tensorProduct(n, n);
+          const Rdxd P   = I - nxn;
+
+          const Array<const NodeId>& node_list = bc.nodeList();
+          parallel_for(
+            bc.numberOfNodes(), PUGS_LAMBDA(int r_number) {
+              const NodeId r = node_list[r_number];
+
+              Ar[r] = P * Ar[r] * P + nxn;
+              br[r] = P * br[r];
+            });
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <size_t Dimension>
+void
+HyperelasticSolverHandler::HyperelasticSolver<Dimension>::_applyVelocityBC(const BoundaryConditionList& bc_list,
+                                                                           NodeValue<Rdxd>& Ar,
+                                                                           NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<VelocityBoundaryCondition, T>) {
+          const auto& node_list  = bc.nodeList();
+          const auto& value_list = bc.valueList();
+
+          parallel_for(
+            node_list.size(), PUGS_LAMBDA(size_t i_node) {
+              NodeId node_id    = node_list[i_node];
+              const auto& value = value_list[i_node];
+
+              Ar[node_id] = identity;
+              br[node_id] = value;
+            });
+        } else if constexpr (std::is_same_v<FixedBoundaryCondition, T>) {
+          const auto& node_list = bc.nodeList();
+          parallel_for(
+            node_list.size(), PUGS_LAMBDA(size_t i_node) {
+              NodeId node_id = node_list[i_node];
+
+              Ar[node_id] = identity;
+              br[node_id] = zero;
+            });
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <size_t Dimension>
+class HyperelasticSolverHandler::HyperelasticSolver<Dimension>::FixedBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary<Dimension> m_mesh_node_boundary;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  FixedBoundaryCondition(const MeshNodeBoundary<Dimension> mesh_node_boundary)
+    : m_mesh_node_boundary{mesh_node_boundary}
+  {}
+
+  ~FixedBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HyperelasticSolverHandler::HyperelasticSolver<Dimension>::PressureBoundaryCondition
+{
+ private:
+  const MeshFaceBoundary<Dimension> m_mesh_face_boundary;
+  const Array<const double> m_value_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_mesh_face_boundary.faceList();
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  PressureBoundaryCondition(const MeshFaceBoundary<Dimension>& mesh_face_boundary,
+                            const Array<const double>& value_list)
+    : m_mesh_face_boundary{mesh_face_boundary}, m_value_list{value_list}
+  {}
+
+  ~PressureBoundaryCondition() = default;
+};
+
+template <>
+class HyperelasticSolverHandler::HyperelasticSolver<1>::PressureBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary<1> m_mesh_node_boundary;
+  const Array<const double> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  PressureBoundaryCondition(const MeshNodeBoundary<1>& mesh_node_boundary, const Array<const double>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~PressureBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HyperelasticSolverHandler::HyperelasticSolver<Dimension>::NormalStressBoundaryCondition
+{
+ private:
+  const MeshFaceBoundary<Dimension> m_mesh_face_boundary;
+  const Array<const Rdxd> m_value_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_mesh_face_boundary.faceList();
+  }
+
+  const Array<const Rdxd>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStressBoundaryCondition(const MeshFaceBoundary<Dimension>& mesh_face_boundary,
+                                const Array<const Rdxd>& value_list)
+    : m_mesh_face_boundary{mesh_face_boundary}, m_value_list{value_list}
+  {}
+
+  ~NormalStressBoundaryCondition() = default;
+};
+
+template <>
+class HyperelasticSolverHandler::HyperelasticSolver<1>::NormalStressBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary<1> m_mesh_node_boundary;
+  const Array<const Rdxd> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const Rdxd>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStressBoundaryCondition(const MeshNodeBoundary<1>& mesh_node_boundary, const Array<const Rdxd>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~NormalStressBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HyperelasticSolverHandler::HyperelasticSolver<Dimension>::VelocityBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary<Dimension> m_mesh_node_boundary;
+
+  const Array<const TinyVector<Dimension>> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  VelocityBoundaryCondition(const MeshNodeBoundary<Dimension>& mesh_node_boundary,
+                            const Array<const TinyVector<Dimension>>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~VelocityBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HyperelasticSolverHandler::HyperelasticSolver<Dimension>::SymmetryBoundaryCondition
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const MeshFlatNodeBoundary<Dimension> m_mesh_flat_node_boundary;
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_mesh_flat_node_boundary.outgoingNormal();
+  }
+
+  size_t
+  numberOfNodes() const
+  {
+    return m_mesh_flat_node_boundary.nodeList().size();
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_flat_node_boundary.nodeList();
+  }
+
+  SymmetryBoundaryCondition(const MeshFlatNodeBoundary<Dimension>& mesh_flat_node_boundary)
+    : m_mesh_flat_node_boundary(mesh_flat_node_boundary)
+  {
+    ;
+  }
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+HyperelasticSolverHandler::HyperelasticSolverHandler(const std::shared_ptr<const IMesh>& i_mesh)
+{
+  if (not i_mesh) {
+    throw NormalError("discrete functions are not defined on the same mesh");
+  }
+
+  switch (i_mesh->dimension()) {
+  case 1: {
+    m_hyperelastic_solver = std::make_unique<HyperelasticSolver<1>>();
+    break;
+  }
+  case 2: {
+    m_hyperelastic_solver = std::make_unique<HyperelasticSolver<2>>();
+    break;
+  }
+  case 3: {
+    m_hyperelastic_solver = std::make_unique<HyperelasticSolver<3>>();
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
diff --git a/src/scheme/HyperelasticSolver.hpp b/src/scheme/HyperelasticSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f82cbe361051efc64a3e413d60c830febd62d907
--- /dev/null
+++ b/src/scheme/HyperelasticSolver.hpp
@@ -0,0 +1,90 @@
+#ifndef HYPERELASTIC_SOLVER_HPP
+#define HYPERELASTIC_SOLVER_HPP
+
+#include <memory>
+#include <tuple>
+#include <vector>
+
+class IBoundaryConditionDescriptor;
+class IMesh;
+class ItemValueVariant;
+class SubItemValuePerItemVariant;
+class DiscreteFunctionVariant;
+
+double hyperelastic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c);
+
+class HyperelasticSolverHandler
+{
+ public:
+  enum class SolverType
+  {
+    Glace,
+    Eucclhyd
+  };
+
+ private:
+  struct IHyperelasticSolver
+  {
+    virtual 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 DiscreteFunctionVariant>& rho,
+      const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+      const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+      const std::shared_ptr<const DiscreteFunctionVariant>& u,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+      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 DiscreteFunctionVariant>,
+                       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 DiscreteFunctionVariant>& rho,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& CG,
+                 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 DiscreteFunctionVariant>,
+                       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 DiscreteFunctionVariant>& rho,
+          const std::shared_ptr<const DiscreteFunctionVariant>& u,
+          const std::shared_ptr<const DiscreteFunctionVariant>& E,
+          const std::shared_ptr<const DiscreteFunctionVariant>& CG,
+          const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+          const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+          const std::shared_ptr<const DiscreteFunctionVariant>& p,
+          const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    IHyperelasticSolver()                                 = default;
+    IHyperelasticSolver(IHyperelasticSolver&&)            = default;
+    IHyperelasticSolver& operator=(IHyperelasticSolver&&) = default;
+
+    virtual ~IHyperelasticSolver() = default;
+  };
+
+  template <size_t Dimension>
+  class HyperelasticSolver;
+
+  std::unique_ptr<IHyperelasticSolver> m_hyperelastic_solver;
+
+ public:
+  const IHyperelasticSolver&
+  solver() const
+  {
+    return *m_hyperelastic_solver;
+  }
+
+  HyperelasticSolverHandler(const std::shared_ptr<const IMesh>& mesh);
+};
+
+#endif   // HYPERELASTIC_SOLVER_HPP
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/scheme/ScalarDiamondScheme.cpp b/src/scheme/ScalarDiamondScheme.cpp
index 07561e6f5abfee4461cd72d2e634bce94f7f12e8..d0a9f7e48f7f93b9224a197312dbb2a0b2829847 100644
--- a/src/scheme/ScalarDiamondScheme.cpp
+++ b/src/scheme/ScalarDiamondScheme.cpp
@@ -138,7 +138,7 @@ class ScalarDiamondSchemeHandler::InterpolationWeightsManager
 class ScalarDiamondSchemeHandler::IScalarDiamondScheme
 {
  public:
-  virtual std::shared_ptr<const IDiscreteFunction> getSolution() const = 0;
+  virtual std::shared_ptr<const DiscreteFunctionVariant> getSolution() const = 0;
 
   IScalarDiamondScheme()          = default;
   virtual ~IScalarDiamondScheme() = default;
@@ -152,7 +152,7 @@ class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSche
   using MeshType         = Mesh<ConnectivityType>;
   using MeshDataType     = MeshData<Dimension>;
 
-  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_solution;
+  DiscreteFunctionP0<Dimension, double> m_solution;
 
   class DirichletBoundaryCondition
   {
@@ -268,22 +268,23 @@ class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSche
   };
 
  public:
-  std::shared_ptr<const IDiscreteFunction>
+  std::shared_ptr<const DiscreteFunctionVariant>
   getSolution() const final
   {
-    return m_solution;
+    return std::make_shared<DiscreteFunctionVariant>(m_solution);
   }
 
   ScalarDiamondScheme(const std::shared_ptr<const MeshType>& mesh,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& alpha,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mub,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mu,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& f,
+                      const DiscreteFunctionP0<Dimension, const double>& alpha,
+                      const DiscreteFunctionP0<Dimension, const double>& dual_mub,
+                      const DiscreteFunctionP0<Dimension, const double>& dual_mu,
+                      const DiscreteFunctionP0<Dimension, const double>& f,
                       const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+    : m_solution(mesh)
   {
-    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mu->mesh(),
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mu.mesh(),
            "diffusion coefficient is not defined on the dual mesh!");
-    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mub->mesh(),
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mub.mesh(),
            "boundary diffusion coefficient is not defined on the dual mesh!");
 
     using BoundaryCondition = std::variant<DirichletBoundaryCondition, FourierBoundaryCondition,
@@ -485,8 +486,8 @@ class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSche
         std::shared_ptr mapper =
           DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
 
-        CellValue<const double> dual_kappaj  = dual_mu->cellValues();
-        CellValue<const double> dual_kappajb = dual_mub->cellValues();
+        CellValue<const double> dual_kappaj  = dual_mu.cellValues();
+        CellValue<const double> dual_kappajb = dual_mub.cellValues();
 
         const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
 
@@ -641,7 +642,7 @@ class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSche
 
         for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
           const size_t j = cell_dof_number[cell_id];
-          S(j, j) += (*alpha)[cell_id] * primal_Vj[cell_id];
+          S(j, j) += alpha[cell_id] * primal_Vj[cell_id];
         }
 
         const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
@@ -705,7 +706,7 @@ class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSche
           }
         }
 
-        CellValue<const double> fj = f->cellValues();
+        CellValue<const double> fj = f.cellValues();
 
         CRSMatrix A{S.getCRSMatrix()};
         Vector<double> b{number_of_dof};
@@ -755,26 +756,24 @@ class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSche
         LinearSolver solver;
         solver.solveLocalSystem(A, T, b);
 
-        m_solution     = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-        auto& solution = *m_solution;
         parallel_for(
-          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { solution[cell_id] = T[cell_dof_number[cell_id]]; });
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { m_solution[cell_id] = T[cell_dof_number[cell_id]]; });
       }
     }
   }
 };
 
-std::shared_ptr<const IDiscreteFunction>
+std::shared_ptr<const DiscreteFunctionVariant>
 ScalarDiamondSchemeHandler::solution() const
 {
   return m_scheme->getSolution();
 }
 
 ScalarDiamondSchemeHandler::ScalarDiamondSchemeHandler(
-  const std::shared_ptr<const IDiscreteFunction>& alpha,
-  const std::shared_ptr<const IDiscreteFunction>& dual_mub,
-  const std::shared_ptr<const IDiscreteFunction>& dual_mu,
-  const std::shared_ptr<const IDiscreteFunction>& f,
+  const std::shared_ptr<const DiscreteFunctionVariant>& alpha,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_mub,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_mu,
+  const std::shared_ptr<const DiscreteFunctionVariant>& f,
   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
 {
   const std::shared_ptr i_mesh = getCommonMesh({alpha, f});
@@ -790,7 +789,7 @@ ScalarDiamondSchemeHandler::ScalarDiamondSchemeHandler(
   switch (i_mesh->dimension()) {
   case 1: {
     using MeshType                   = Mesh<Connectivity<1>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, double>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, const double>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -798,17 +797,15 @@ ScalarDiamondSchemeHandler::ScalarDiamondSchemeHandler(
       throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
     }
 
-    m_scheme =
-      std::make_unique<ScalarDiamondScheme<1>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
-                                               bc_descriptor_list);
+    m_scheme = std::make_unique<ScalarDiamondScheme<1>>(mesh, alpha->get<DiscreteScalarFunctionType>(),
+                                                        dual_mub->get<DiscreteScalarFunctionType>(),
+                                                        dual_mu->get<DiscreteScalarFunctionType>(),
+                                                        f->get<DiscreteScalarFunctionType>(), bc_descriptor_list);
     break;
   }
   case 2: {
     using MeshType                   = Mesh<Connectivity<2>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, const double>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -816,17 +813,15 @@ ScalarDiamondSchemeHandler::ScalarDiamondSchemeHandler(
       throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
     }
 
-    m_scheme =
-      std::make_unique<ScalarDiamondScheme<2>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
-                                               bc_descriptor_list);
+    m_scheme = std::make_unique<ScalarDiamondScheme<2>>(mesh, alpha->get<DiscreteScalarFunctionType>(),
+                                                        dual_mub->get<DiscreteScalarFunctionType>(),
+                                                        dual_mu->get<DiscreteScalarFunctionType>(),
+                                                        f->get<DiscreteScalarFunctionType>(), bc_descriptor_list);
     break;
   }
   case 3: {
     using MeshType                   = Mesh<Connectivity<3>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, double>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, const double>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -834,12 +829,10 @@ ScalarDiamondSchemeHandler::ScalarDiamondSchemeHandler(
       throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
     }
 
-    m_scheme =
-      std::make_unique<ScalarDiamondScheme<3>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
-                                               bc_descriptor_list);
+    m_scheme = std::make_unique<ScalarDiamondScheme<3>>(mesh, alpha->get<DiscreteScalarFunctionType>(),
+                                                        dual_mub->get<DiscreteScalarFunctionType>(),
+                                                        dual_mu->get<DiscreteScalarFunctionType>(),
+                                                        f->get<DiscreteScalarFunctionType>(), bc_descriptor_list);
     break;
   }
   default: {
diff --git a/src/scheme/ScalarDiamondScheme.hpp b/src/scheme/ScalarDiamondScheme.hpp
index 47b66177b5628bb8b13a0ea2d92ede5e41a84950..4040fe1ade92e3b599dcdf724e924057feacaf63 100644
--- a/src/scheme/ScalarDiamondScheme.hpp
+++ b/src/scheme/ScalarDiamondScheme.hpp
@@ -19,8 +19,8 @@
 #include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
 #include <mesh/SubItemValuePerItem.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
-#include <scheme/IDiscreteFunction.hpp>
 #include <scheme/NeumannBoundaryConditionDescriptor.hpp>
 #include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
 
@@ -38,13 +38,13 @@ class ScalarDiamondSchemeHandler
  public:
   std::unique_ptr<IScalarDiamondScheme> m_scheme;
 
-  std::shared_ptr<const IDiscreteFunction> solution() const;
+  std::shared_ptr<const DiscreteFunctionVariant> solution() const;
 
   ScalarDiamondSchemeHandler(
-    const std::shared_ptr<const IDiscreteFunction>& alpha,
-    const std::shared_ptr<const IDiscreteFunction>& mu_dualb,
-    const std::shared_ptr<const IDiscreteFunction>& mu_dual,
-    const std::shared_ptr<const IDiscreteFunction>& f,
+    const std::shared_ptr<const DiscreteFunctionVariant>& alpha,
+    const std::shared_ptr<const DiscreteFunctionVariant>& mu_dualb,
+    const std::shared_ptr<const DiscreteFunctionVariant>& mu_dual,
+    const std::shared_ptr<const DiscreteFunctionVariant>& f,
     const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
 
   ~ScalarDiamondSchemeHandler();
diff --git a/src/scheme/VectorDiamondScheme.cpp b/src/scheme/VectorDiamondScheme.cpp
index ac1345a0818e50dd6275d5380363522b2160a4a3..95177642047f849456193cfccb97a50276bf525c 100644
--- a/src/scheme/VectorDiamondScheme.cpp
+++ b/src/scheme/VectorDiamondScheme.cpp
@@ -13,7 +13,7 @@ class VectorDiamondSchemeHandler::InterpolationWeightsManager
   FaceValue<const bool> m_primal_face_is_symmetry;
   CellValuePerNode<double> m_w_rj;
   FaceValuePerNode<double> m_w_rl;
- 
+
  public:
   InterpolationWeightsManager(std::shared_ptr<const Mesh<Connectivity<Dimension>>> mesh,
                               FaceValue<const bool> primal_face_is_on_boundary,
@@ -156,12 +156,10 @@ class VectorDiamondSchemeHandler::InterpolationWeightsManager
 class VectorDiamondSchemeHandler::IVectorDiamondScheme
 {
  public:
-  virtual std::shared_ptr<const IDiscreteFunction> getSolution() const     = 0;
-  virtual std::shared_ptr<const IDiscreteFunction> getDualSolution() const = 0;
-  virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> apply()
-    const = 0;
-  // virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
-  // computeEnergyUpdate() const = 0;
+  virtual std::shared_ptr<const DiscreteFunctionVariant> getSolution() const     = 0;
+  virtual std::shared_ptr<const DiscreteFunctionVariant> getDualSolution() const = 0;
+  virtual std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
+  apply() const = 0;
 
   IVectorDiamondScheme()          = default;
   virtual ~IVectorDiamondScheme() = default;
@@ -175,9 +173,8 @@ class VectorDiamondSchemeHandler::VectorDiamondScheme : public VectorDiamondSche
   using MeshType         = Mesh<ConnectivityType>;
   using MeshDataType     = MeshData<Dimension>;
 
-  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_solution;
-  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_dual_solution;
-  //  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_energy_delta;
+  DiscreteFunctionP0<Dimension, TinyVector<Dimension>> m_solution;
+  DiscreteFunctionP0<Dimension, TinyVector<Dimension>> m_dual_solution;
 
   class DirichletBoundaryCondition
   {
@@ -257,39 +254,41 @@ class VectorDiamondSchemeHandler::VectorDiamondScheme : public VectorDiamondSche
   };
 
  public:
-  std::shared_ptr<const IDiscreteFunction>
+  std::shared_ptr<const DiscreteFunctionVariant>
   getSolution() const final
   {
-    return m_solution;
+    return std::make_shared<DiscreteFunctionVariant>(m_solution);
   }
 
-  std::shared_ptr<const IDiscreteFunction>
+  std::shared_ptr<const DiscreteFunctionVariant>
   getDualSolution() const final
   {
-    return m_dual_solution;
+    return std::make_shared<DiscreteFunctionVariant>(m_dual_solution);
   }
 
-  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+  std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
   apply() const final
   {
-    return {m_solution, m_dual_solution};
+    return {std::make_shared<DiscreteFunctionVariant>(m_solution),
+            std::make_shared<DiscreteFunctionVariant>(m_dual_solution)};
   }
 
   VectorDiamondScheme(const std::shared_ptr<const MeshType>& mesh,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& alpha,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambdab,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mub,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambda,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mu,
-                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& source,
+                      const DiscreteFunctionP0<Dimension, const double>& alpha,
+                      const DiscreteFunctionP0<Dimension, const double>& dual_lambdab,
+                      const DiscreteFunctionP0<Dimension, const double>& dual_mub,
+                      const DiscreteFunctionP0<Dimension, const double>& dual_lambda,
+                      const DiscreteFunctionP0<Dimension, const double>& dual_mu,
+                      const DiscreteFunctionP0<Dimension, const TinyVector<Dimension>>& source,
                       const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+    : m_solution(mesh), m_dual_solution(DualMeshManager::instance().getDiamondDualMesh(*mesh))
   {
-    Assert(mesh == alpha->mesh());
-    Assert(mesh == source->mesh());
-    Assert(dual_lambda->mesh() == dual_mu->mesh());
-    Assert(dual_lambdab->mesh() == dual_mu->mesh());
-    Assert(dual_mub->mesh() == dual_mu->mesh());
-    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mu->mesh(),
+    Assert(mesh == alpha.mesh());
+    Assert(mesh == source.mesh());
+    Assert(dual_lambda.mesh() == dual_mu.mesh());
+    Assert(dual_lambdab.mesh() == dual_mu.mesh());
+    Assert(dual_mub.mesh() == dual_mu.mesh());
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mu.mesh(),
            "diffusion coefficient is not defined on the dual mesh!");
 
     using MeshDataType = MeshData<Dimension>;
@@ -526,15 +525,12 @@ class VectorDiamondSchemeHandler::VectorDiamondScheme : public VectorDiamondSche
         std::shared_ptr mapper =
           DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
 
-        CellValue<const double> dual_muj      = dual_mu->cellValues();
-        CellValue<const double> dual_lambdaj  = dual_lambda->cellValues();
-        CellValue<const double> dual_mubj     = dual_mub->cellValues();
-        CellValue<const double> dual_lambdabj = dual_lambdab->cellValues();
+        CellValue<const double> dual_muj      = dual_mu.cellValues();
+        CellValue<const double> dual_lambdaj  = dual_lambda.cellValues();
+        CellValue<const double> dual_mubj     = dual_mub.cellValues();
+        CellValue<const double> dual_lambdabj = dual_lambdab.cellValues();
 
-        CellValue<const TinyVector<Dimension>> fj = source->cellValues();
-        // for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-        //   std::cout << xj[cell_id] << "-> fj[" << cell_id << "]=" << fj[cell_id] << '\n';
-        // }
+        CellValue<const TinyVector<Dimension>> fj = source.cellValues();
 
         const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
 
@@ -763,7 +759,7 @@ class VectorDiamondSchemeHandler::VectorDiamondScheme : public VectorDiamondSche
         for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
           for (size_t i = 0; i < Dimension; ++i) {
             const size_t j = cell_dof_number[cell_id] * Dimension + i;
-            S(j, j) += (*alpha)[cell_id] * primal_Vj[cell_id];
+            S(j, j) += alpha[cell_id] * primal_Vj[cell_id];
           }
         }
 
@@ -915,30 +911,26 @@ class VectorDiamondSchemeHandler::VectorDiamondScheme : public VectorDiamondSche
 
         std::cout << "final (real) residu = " << std::sqrt(dot(r, r)) << '\n';
 
-        m_solution     = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(mesh);
-        auto& solution = *m_solution;
         parallel_for(
           mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
             for (size_t i = 0; i < Dimension; ++i) {
-              solution[cell_id][i] = U[(cell_dof_number[cell_id] * Dimension) + i];
+              m_solution[cell_id][i] = U[(cell_dof_number[cell_id] * Dimension) + i];
             }
           });
 
-        m_dual_solution     = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(diamond_mesh);
-        auto& dual_solution = *m_dual_solution;
-        dual_solution.fill(zero);
+        m_dual_solution.fill(zero);
         const auto& face_to_cell_matrix = mesh->connectivity().faceToCellMatrix();
         for (CellId cell_id = 0; cell_id < diamond_mesh->numberOfCells(); ++cell_id) {
           const FaceId face_id = dual_cell_face_id[cell_id];
           if (primal_face_is_on_boundary[face_id]) {
             for (size_t i = 0; i < Dimension; ++i) {
-              dual_solution[cell_id][i] = U[(face_dof_number[face_id] * Dimension) + i];
+              m_dual_solution[cell_id][i] = U[(face_dof_number[face_id] * Dimension) + i];
             }
           } else {
             CellId cell_id1 = face_to_cell_matrix[face_id][0];
             CellId cell_id2 = face_to_cell_matrix[face_id][1];
             for (size_t i = 0; i < Dimension; ++i) {
-              dual_solution[cell_id][i] =
+              m_dual_solution[cell_id][i] =
                 0.5 * (U[(cell_dof_number[cell_id1] * Dimension) + i] + U[(cell_dof_number[cell_id2] * Dimension) + i]);
             }
           }
@@ -1109,11 +1101,8 @@ class EnergyComputerHandler::InterpolationWeightsManager
 class EnergyComputerHandler::IEnergyComputer
 {
  public:
-  // virtual std::shared_ptr<const IDiscreteFunction> getSolution() const     = 0;
-  // virtual std::shared_ptr<const IDiscreteFunction> getDualSolution() const = 0;
-  // virtual std::shared_ptr<const IDiscreteFunction> apply() const           = 0;
-  virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> apply()
-    const = 0;
+  virtual std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
+  apply() const = 0;
 
   IEnergyComputer()          = default;
   virtual ~IEnergyComputer() = default;
@@ -1127,9 +1116,9 @@ class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEne
   using MeshType         = Mesh<ConnectivityType>;
   using MeshDataType     = MeshData<Dimension>;
 
-  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_solution;
-  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_dual_solution;
-  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_energy_delta;
+  DiscreteFunctionP0<Dimension, TinyVector<Dimension>> m_solution;
+  DiscreteFunctionP0<Dimension, TinyVector<Dimension>> m_dual_solution;
+  DiscreteFunctionP0<Dimension, double> m_energy_delta;
 
   class DirichletBoundaryCondition
   {
@@ -1209,41 +1198,28 @@ class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEne
   };
 
  public:
-  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+  std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
   apply() const final
   {
-    return {m_energy_delta, m_dual_solution};
+    return {std::make_shared<DiscreteFunctionVariant>(m_energy_delta),
+            std::make_shared<DiscreteFunctionVariant>(m_dual_solution)};
   }
 
-  // std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
-  // computeEnergyUpdate() const final
-  // {
-  //   m_scheme->computeEnergyUpdate(mesh, dual_lambdab, dual_mub, m_solution,
-  //                                 m_dual_solution,   // f,
-  //                                 bc_descriptor_list);
-
-  //   return {m_dual_solution, m_energy_delta};
-  // }
-
   // compute the fluxes
-  // std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>
   EnergyComputer(const std::shared_ptr<const MeshType>& mesh,
-                 // const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& alpha,
-                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambdab,
-                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mub,
-                 // const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambda,
-                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& U,
-                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& dual_U,
-                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& source,
+                 const DiscreteFunctionP0<Dimension, const double>& dual_lambdab,
+                 const DiscreteFunctionP0<Dimension, const double>& dual_mub,
+                 const DiscreteFunctionP0<Dimension, const TinyVector<Dimension>>& U,
+                 const DiscreteFunctionP0<Dimension, const TinyVector<Dimension>>& dual_U,
+                 const DiscreteFunctionP0<Dimension, const TinyVector<Dimension>>& source,
                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+    : m_solution(mesh), m_dual_solution(DualMeshManager::instance().getDiamondDualMesh(*mesh)), m_energy_delta(mesh)
   {
-    // Assert(mesh == alpha->mesh());
-    Assert(mesh == U->mesh());
-    // Assert(dual_lambda->mesh() == dual_mu->mesh());
-    Assert(dual_lambdab->mesh() == dual_U->mesh());
-    Assert(U->mesh() == source->mesh());
-    Assert(dual_lambdab->mesh() == dual_mub->mesh());
-    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mub->mesh(),
+    Assert(mesh == U.mesh());
+    Assert(dual_lambdab.mesh() == dual_U.mesh());
+    Assert(U.mesh() == source.mesh());
+    Assert(dual_lambdab.mesh() == dual_mub.mesh());
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mub.mesh(),
            "diffusion coefficient is not defined on the dual mesh!");
 
     using MeshDataType = MeshData<Dimension>;
@@ -1480,11 +1456,10 @@ class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEne
         std::shared_ptr mapper =
           DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
 
-        CellValue<const double> dual_mubj     = dual_mub->cellValues();
-        CellValue<const double> dual_lambdabj = dual_lambdab->cellValues();
-        // attention, fj not in this context
-        CellValue<const TinyVector<Dimension>> velocity = U->cellValues();
-        // CellValue<const TinyVector<Dimension>> dual_velocity = dual_U->cellValues();
+        CellValue<const double> dual_mubj     = dual_mub.cellValues();
+        CellValue<const double> dual_lambdabj = dual_lambdab.cellValues();
+
+        CellValue<const TinyVector<Dimension>> velocity = U.cellValues();
 
         const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
 
@@ -1650,40 +1625,13 @@ class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEne
           return computed_dual_cell_face_id;
         }();
 
-        m_dual_solution = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(diamond_mesh);
-        // m_solution           = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(mesh);
-        // const auto& solution = *U;
-        auto& dual_solution = *dual_U;
-        //  dual_solution.fill(zero);
-        // const auto& face_to_cell_matrix = mesh->connectivity().faceToCellMatrix();
-        // for (CellId cell_id = 0; cell_id < diamond_mesh->numberOfCells(); ++cell_id) {
-        //   const FaceId face_id = dual_cell_face_id[cell_id];
-        //   CellId cell_id1      = face_to_cell_matrix[face_id][0];
-        //   if (primal_face_is_on_boundary[face_id]) {
-        //     for (size_t i = 0; i < Dimension; ++i) {
-        //       // A revoir!!
-        //       dual_solution[cell_id][i] = solution[cell_id1][i];
-        //     }
-        //   } else {
-        //     CellId cell_id1 = face_to_cell_matrix[face_id][0];
-        //     CellId cell_id2 = face_to_cell_matrix[face_id][1];
-        //     for (size_t i = 0; i < Dimension; ++i) {
-        //       dual_solution[cell_id][i] = 0.5 * (solution[cell_id1][i] + solution[cell_id2][i]);
-        //     }
-        //   }
-        // }
+        auto& dual_solution = dual_U;
 
-        // const Array<int> non_zeros{number_of_dof * Dimension};
-        // non_zeros.fill(Dimension * Dimension);
-        // CRSMatrixDescriptor<double> S(number_of_dof * Dimension, number_of_dof * Dimension, non_zeros);
-        // Begining of main
         CellValuePerFace<double> flux{mesh->connectivity()};
         parallel_for(
           flux.numberOfValues(), PUGS_LAMBDA(size_t jl) { flux[jl] = 0; });
 
         for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
-          // const double beta_mu_l     = l2Norm(dualClj[face_id]) * alpha_mu_l[face_id] * mes_l[face_id];
-          // const double beta_lambda_l = l2Norm(dualClj[face_id]) * alpha_lambda_l[face_id] * mes_l[face_id];
           const double beta_mub_l         = l2Norm(dualClj[face_id]) * alpha_mub_l[face_id] * mes_l[face_id];
           const double beta_lambdab_l     = l2Norm(dualClj[face_id]) * alpha_lambdab_l[face_id] * mes_l[face_id];
           const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
@@ -1802,20 +1750,18 @@ class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEne
         //   // exit(0);
         // }
         // Assemble
-        m_energy_delta     = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
-        auto& energy_delta = *m_energy_delta;
         // CellValue<const TinyVector<Dimension>> fj = source->cellValues();
 
         double sum_deltae = 0.;
         for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
-          energy_delta[cell_id] = 0.;   // dot(fj[cell_id], velocity[cell_id]);
-          sum_deltae += energy_delta[cell_id];
+          m_energy_delta[cell_id] = 0.;   // dot(fj[cell_id], velocity[cell_id]);
+          sum_deltae += m_energy_delta[cell_id];
         }
         // CellValue<double>& deltae = m_energy_delta->cellValues();
         for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
           for (size_t j = 0; j < face_to_cell_matrix[face_id].size(); j++) {
             CellId i_id = face_to_cell_matrix[face_id][j];
-            energy_delta[i_id] -= flux(face_id, j) / primal_Vj[i_id];
+            m_energy_delta[i_id] -= flux(face_id, j) / primal_Vj[i_id];
             sum_deltae -= flux(face_id, j);
           }
           // exit(0);
@@ -1830,37 +1776,37 @@ class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEne
   }
 };
 
-std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
 VectorDiamondSchemeHandler::apply() const
 {
   return m_scheme->apply();
 }
 
-std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
 EnergyComputerHandler::computeEnergyUpdate() const
 {
   return m_energy_computer->apply();
 }
 
-std::shared_ptr<const IDiscreteFunction>
+std::shared_ptr<const DiscreteFunctionVariant>
 VectorDiamondSchemeHandler::solution() const
 {
   return m_scheme->getSolution();
 }
 
-std::shared_ptr<const IDiscreteFunction>
+std::shared_ptr<const DiscreteFunctionVariant>
 VectorDiamondSchemeHandler::dual_solution() const
 {
   return m_scheme->getDualSolution();
 }
 
 VectorDiamondSchemeHandler::VectorDiamondSchemeHandler(
-  const std::shared_ptr<const IDiscreteFunction>& alpha,
-  const std::shared_ptr<const IDiscreteFunction>& dual_lambdab,
-  const std::shared_ptr<const IDiscreteFunction>& dual_mub,
-  const std::shared_ptr<const IDiscreteFunction>& dual_lambda,
-  const std::shared_ptr<const IDiscreteFunction>& dual_mu,
-  const std::shared_ptr<const IDiscreteFunction>& f,
+  const std::shared_ptr<const DiscreteFunctionVariant>& alpha,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_lambdab,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_mub,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_lambda,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_mu,
+  const std::shared_ptr<const DiscreteFunctionVariant>& f,
   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
 {
   const std::shared_ptr i_mesh = getCommonMesh({alpha, f});
@@ -1876,26 +1822,23 @@ VectorDiamondSchemeHandler::VectorDiamondSchemeHandler(
   switch (i_mesh->dimension()) {
   case 1: {
     using MeshType                   = Mesh<Connectivity<1>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, double>;
-    using DiscreteVectorFunctionType = DiscreteFunctionP0<1, TinyVector<1>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, const double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<1, const TinyVector<1>>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
-    m_scheme =
-      std::make_unique<VectorDiamondScheme<1>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(
-                                                 dual_lambdab),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambda),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
-                                               std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(f),
-                                               bc_descriptor_list);
+    m_scheme = std::make_unique<VectorDiamondScheme<1>>(mesh, alpha->get<DiscreteScalarFunctionType>(),
+                                                        dual_lambdab->get<DiscreteScalarFunctionType>(),
+                                                        dual_mub->get<DiscreteScalarFunctionType>(),
+                                                        dual_lambda->get<DiscreteScalarFunctionType>(),
+                                                        dual_mu->get<DiscreteScalarFunctionType>(),
+                                                        f->get<DiscreteVectorFunctionType>(), bc_descriptor_list);
     break;
   }
   case 2: {
     using MeshType                   = Mesh<Connectivity<2>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
-    using DiscreteVectorFunctionType = DiscreteFunctionP0<2, TinyVector<2>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, const double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<2, const TinyVector<2>>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -1903,21 +1846,18 @@ VectorDiamondSchemeHandler::VectorDiamondSchemeHandler(
       throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
     }
 
-    m_scheme =
-      std::make_unique<VectorDiamondScheme<2>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(
-                                                 dual_lambdab),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambda),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
-                                               std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(f),
-                                               bc_descriptor_list);
+    m_scheme = std::make_unique<VectorDiamondScheme<2>>(mesh, alpha->get<DiscreteScalarFunctionType>(),
+                                                        dual_lambdab->get<DiscreteScalarFunctionType>(),
+                                                        dual_mub->get<DiscreteScalarFunctionType>(),
+                                                        dual_lambda->get<DiscreteScalarFunctionType>(),
+                                                        dual_mu->get<DiscreteScalarFunctionType>(),
+                                                        f->get<DiscreteVectorFunctionType>(), bc_descriptor_list);
     break;
   }
   case 3: {
     using MeshType                   = Mesh<Connectivity<3>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, double>;
-    using DiscreteVectorFunctionType = DiscreteFunctionP0<3, TinyVector<3>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, const double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<3, const TinyVector<3>>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -1925,15 +1865,12 @@ VectorDiamondSchemeHandler::VectorDiamondSchemeHandler(
       throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
     }
 
-    m_scheme =
-      std::make_unique<VectorDiamondScheme<3>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(
-                                                 dual_lambdab),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambda),
-                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
-                                               std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(f),
-                                               bc_descriptor_list);
+    m_scheme = std::make_unique<VectorDiamondScheme<3>>(mesh, alpha->get<DiscreteScalarFunctionType>(),
+                                                        dual_lambdab->get<DiscreteScalarFunctionType>(),
+                                                        dual_mub->get<DiscreteScalarFunctionType>(),
+                                                        dual_lambda->get<DiscreteScalarFunctionType>(),
+                                                        dual_mu->get<DiscreteScalarFunctionType>(),
+                                                        f->get<DiscreteVectorFunctionType>(), bc_descriptor_list);
     break;
   }
   default: {
@@ -1945,11 +1882,11 @@ VectorDiamondSchemeHandler::VectorDiamondSchemeHandler(
 VectorDiamondSchemeHandler::~VectorDiamondSchemeHandler() = default;
 
 EnergyComputerHandler::EnergyComputerHandler(
-  const std::shared_ptr<const IDiscreteFunction>& dual_lambdab,
-  const std::shared_ptr<const IDiscreteFunction>& dual_mub,
-  const std::shared_ptr<const IDiscreteFunction>& U,
-  const std::shared_ptr<const IDiscreteFunction>& dual_U,
-  const std::shared_ptr<const IDiscreteFunction>& source,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_lambdab,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_mub,
+  const std::shared_ptr<const DiscreteFunctionVariant>& U,
+  const std::shared_ptr<const DiscreteFunctionVariant>& dual_U,
+  const std::shared_ptr<const DiscreteFunctionVariant>& source,
   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
 {
   const std::shared_ptr i_mesh = getCommonMesh({U, source});
@@ -1965,8 +1902,8 @@ EnergyComputerHandler::EnergyComputerHandler(
   switch (i_mesh->dimension()) {
   case 1: {
     using MeshType                   = Mesh<Connectivity<1>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, double>;
-    using DiscreteVectorFunctionType = DiscreteFunctionP0<1, TinyVector<1>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, const double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<1, const TinyVector<1>>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -1975,19 +1912,17 @@ EnergyComputerHandler::EnergyComputerHandler(
     }
 
     m_energy_computer =
-      std::make_unique<EnergyComputer<1>>(mesh,
-                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambdab),
-                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(U),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(dual_U),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(source),
-                                          bc_descriptor_list);
+      std::make_unique<EnergyComputer<1>>(mesh, dual_lambdab->get<DiscreteScalarFunctionType>(),
+                                          dual_mub->get<DiscreteScalarFunctionType>(),
+                                          U->get<DiscreteVectorFunctionType>(),
+                                          dual_U->get<DiscreteVectorFunctionType>(),
+                                          source->get<DiscreteVectorFunctionType>(), bc_descriptor_list);
     break;
   }
   case 2: {
     using MeshType                   = Mesh<Connectivity<2>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
-    using DiscreteVectorFunctionType = DiscreteFunctionP0<2, TinyVector<2>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, const double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<2, const TinyVector<2>>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -1996,19 +1931,18 @@ EnergyComputerHandler::EnergyComputerHandler(
     }
 
     m_energy_computer =
-      std::make_unique<EnergyComputer<2>>(mesh,
-                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambdab),
-                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(U),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(dual_U),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(source),
-                                          bc_descriptor_list);
+      std::make_unique<EnergyComputer<2>>(mesh, dual_lambdab->get<DiscreteScalarFunctionType>(),
+                                          dual_mub->get<DiscreteScalarFunctionType>(),
+                                          U->get<DiscreteVectorFunctionType>(),
+                                          dual_U->get<DiscreteVectorFunctionType>(),
+                                          source->get<DiscreteVectorFunctionType>(), bc_descriptor_list);
+
     break;
   }
   case 3: {
     using MeshType                   = Mesh<Connectivity<3>>;
-    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, double>;
-    using DiscreteVectorFunctionType = DiscreteFunctionP0<3, TinyVector<3>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, const double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<3, const TinyVector<3>>;
 
     std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
 
@@ -2017,13 +1951,12 @@ EnergyComputerHandler::EnergyComputerHandler(
     }
 
     m_energy_computer =
-      std::make_unique<EnergyComputer<3>>(mesh,
-                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambdab),
-                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(U),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(dual_U),
-                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(source),
-                                          bc_descriptor_list);
+      std::make_unique<EnergyComputer<3>>(mesh, dual_lambdab->get<DiscreteScalarFunctionType>(),
+                                          dual_mub->get<DiscreteScalarFunctionType>(),
+                                          U->get<DiscreteVectorFunctionType>(),
+                                          dual_U->get<DiscreteVectorFunctionType>(),
+                                          source->get<DiscreteVectorFunctionType>(), bc_descriptor_list);
+
     break;
   }
   default: {
diff --git a/src/scheme/VectorDiamondScheme.hpp b/src/scheme/VectorDiamondScheme.hpp
index 20bbadfa3934cabf213d201e47397c97d56047e0..16bee0ffbf1493e3b56b1a260189fe95157803cf 100644
--- a/src/scheme/VectorDiamondScheme.hpp
+++ b/src/scheme/VectorDiamondScheme.hpp
@@ -18,6 +18,7 @@
 #include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
 #include <output/VTKWriter.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
 #include <scheme/NeumannBoundaryConditionDescriptor.hpp>
 #include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
 
@@ -34,19 +35,20 @@ class VectorDiamondSchemeHandler
  public:
   std::unique_ptr<IVectorDiamondScheme> m_scheme;
 
-  std::shared_ptr<const IDiscreteFunction> solution() const;
+  std::shared_ptr<const DiscreteFunctionVariant> solution() const;
 
-  std::shared_ptr<const IDiscreteFunction> dual_solution() const;
+  std::shared_ptr<const DiscreteFunctionVariant> dual_solution() const;
 
-  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> apply() const;
+  std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>> apply()
+    const;
 
   VectorDiamondSchemeHandler(
-    const std::shared_ptr<const IDiscreteFunction>& alphab,
-    const std::shared_ptr<const IDiscreteFunction>& lambdab,
-    const std::shared_ptr<const IDiscreteFunction>& alpha,
-    const std::shared_ptr<const IDiscreteFunction>& lambda,
-    const std::shared_ptr<const IDiscreteFunction>& mu,
-    const std::shared_ptr<const IDiscreteFunction>& f,
+    const std::shared_ptr<const DiscreteFunctionVariant>& alphab,
+    const std::shared_ptr<const DiscreteFunctionVariant>& lambdab,
+    const std::shared_ptr<const DiscreteFunctionVariant>& alpha,
+    const std::shared_ptr<const DiscreteFunctionVariant>& lambda,
+    const std::shared_ptr<const DiscreteFunctionVariant>& mu,
+    const std::shared_ptr<const DiscreteFunctionVariant>& f,
     const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
 
   ~VectorDiamondSchemeHandler();
@@ -64,16 +66,16 @@ class EnergyComputerHandler
 
  public:
   std::unique_ptr<IEnergyComputer> m_energy_computer;
-  std::shared_ptr<const IDiscreteFunction> dual_solution() const;
+  std::shared_ptr<const DiscreteFunctionVariant> dual_solution() const;
 
-  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> computeEnergyUpdate()
-    const;
+  std::tuple<std::shared_ptr<const DiscreteFunctionVariant>, std::shared_ptr<const DiscreteFunctionVariant>>
+  computeEnergyUpdate() const;
 
-  EnergyComputerHandler(const std::shared_ptr<const IDiscreteFunction>& lambdab,
-                        const std::shared_ptr<const IDiscreteFunction>& mub,
-                        const std::shared_ptr<const IDiscreteFunction>& U,
-                        const std::shared_ptr<const IDiscreteFunction>& dual_U,
-                        const std::shared_ptr<const IDiscreteFunction>& source,
+  EnergyComputerHandler(const std::shared_ptr<const DiscreteFunctionVariant>& lambdab,
+                        const std::shared_ptr<const DiscreteFunctionVariant>& mub,
+                        const std::shared_ptr<const DiscreteFunctionVariant>& U,
+                        const std::shared_ptr<const DiscreteFunctionVariant>& dual_U,
+                        const std::shared_ptr<const DiscreteFunctionVariant>& source,
                         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
 
   ~EnergyComputerHandler();
diff --git a/src/utils/Array.hpp b/src/utils/Array.hpp
index 7535b673565fa04aadba3935420f6946f5ad5544..3ee64824a650e18714a9b4b8523afb78cc89f643 100644
--- a/src/utils/Array.hpp
+++ b/src/utils/Array.hpp
@@ -24,6 +24,18 @@ class [[nodiscard]] Array
     const size_t m_size;
 
    public:
+    friend std::ostream&
+    operator<<(std::ostream& os, const UnsafeArrayView& x)
+    {
+      if (x.size() > 0) {
+        os << 0 << ':' << NaNHelper(x[0]);
+      }
+      for (size_t i = 1; i < x.size(); ++i) {
+        os << ' ' << i << ':' << NaNHelper(x[i]);
+      }
+      return os;
+    }
+
     [[nodiscard]] PUGS_INLINE size_t
     size() const
     {
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/src/utils/Table.hpp b/src/utils/Table.hpp
index 6972ea1030dedbdd30c039e0880a3773024603dc..a4a134dfd1d5838766b062eb429c725eb66bbb33 100644
--- a/src/utils/Table.hpp
+++ b/src/utils/Table.hpp
@@ -63,6 +63,19 @@ class [[nodiscard]] Table
       Assert(row < table.numberOfRows(), "required row view is not contained in the Table");
     }
 
+    friend std::ostream&
+    operator<<(std::ostream& os, const UnsafeRowView& x)
+    {
+      if (x.size() > 0) {
+        os << 0 << ':' << NaNHelper(x[0]);
+      }
+      for (size_t i = 1; i < x.size(); ++i) {
+        os << ' ' << i << ':' << NaNHelper(x[i]);
+      }
+
+      return os;
+    }
+
     // To try to keep these views close to the initial array one
     // forbids copy constructor and take benefits of C++-17 copy
     // elisions.
@@ -113,6 +126,19 @@ class [[nodiscard]] Table
         }
       }
 
+      friend std::ostream&
+      operator<<(std::ostream& os, const RowView& x)
+      {
+        if (x.size() > 0) {
+          os << 0 << ':' << NaNHelper(x[0]);
+        }
+        for (size_t i = 1; i < x.size(); ++i) {
+          os << ' ' << i << ':' << NaNHelper(x[i]);
+        }
+
+        return os;
+      }
+
       RowView(const UnsafeTableView& table_view, index_type row) : m_table_view{table_view}, m_row{row}
       {
         Assert(row < m_table_view.numberOfRows(), "required row view is not contained in the Table view");
@@ -155,6 +181,19 @@ class [[nodiscard]] Table
       return m_table(m_row_begin + i, m_column_begin + j);
     }
 
+    friend std::ostream&
+    operator<<(std::ostream& os, const UnsafeTableView& t)
+    {
+      for (size_t i = 0; i < t.numberOfRows(); ++i) {
+        os << i << '|';
+        for (size_t j = 0; j < t.numberOfColumns(); ++j) {
+          os << ' ' << j << ':' << NaNHelper(t(i, j));
+        }
+        os << '\n';
+      }
+      return os;
+    }
+
     PUGS_INLINE void
     fill(const DataType& data) const
     {
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/FixturesForBuiltinT.hpp b/tests/FixturesForBuiltinT.hpp
index d2ff0fcb3b0db1e37a9a5f00487eb8288f1aec3d..52348a23daf00350de702f7978b46639a8bef41d 100644
--- a/tests/FixturesForBuiltinT.hpp
+++ b/tests/FixturesForBuiltinT.hpp
@@ -9,7 +9,7 @@
 template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const double>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("builtin_t");
-const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const double>>;
+inline const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const double>>;
 
 inline std::shared_ptr<const double>
 operator-(const std::shared_ptr<const double>& p_a)
diff --git a/tests/test_ASTNodeFunctionExpressionBuilder.cpp b/tests/test_ASTNodeFunctionExpressionBuilder.cpp
index f19b6585613db943b77d684008974e737140686d..762202a671b1f7708b193ca8cd6c7aa599bfc780 100644
--- a/tests/test_ASTNodeFunctionExpressionBuilder.cpp
+++ b/tests/test_ASTNodeFunctionExpressionBuilder.cpp
@@ -370,13 +370,30 @@ cat("foo", 2.5e-3);
 let f : R^1 -> R^1, x -> x+x;
 let x : R^1, x = 1;
 f(x);
+let n:N, n=1;
+f(true);
+f(n);
+f(2);
+f(1.4);
 )";
 
       std::string_view result = R"(
 (root:ASTNodeListProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::name:x:NameProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::true_kw:ValueProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::name:n:NameProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::integer:2:ValueProcessor)
  `-(language::function_evaluation:FunctionProcessor)
      +-(language::name:f:NameProcessor)
-     `-(language::name:x:NameProcessor)
+     `-(language::real:1.4:ValueProcessor)
 )";
 
       CHECK_AST(data, result);
@@ -424,13 +441,30 @@ f(x);
 let f : R^1x1 -> R^1x1, x -> x+x;
 let x : R^1x1, x = 1;
 f(x);
+let n:N, n=1;
+f(true);
+f(n);
+f(2);
+f(1.4);
 )";
 
       std::string_view result = R"(
 (root:ASTNodeListProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::name:x:NameProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::true_kw:ValueProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::name:n:NameProcessor)
+ +-(language::function_evaluation:FunctionProcessor)
+ |   +-(language::name:f:NameProcessor)
+ |   `-(language::integer:2:ValueProcessor)
  `-(language::function_evaluation:FunctionProcessor)
      +-(language::name:f:NameProcessor)
-     `-(language::name:x:NameProcessor)
+     `-(language::real:1.4:ValueProcessor)
 )";
 
       CHECK_AST(data, result);
@@ -1115,6 +1149,170 @@ prev(3 + .24);
 
         CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> Z"});
       }
+
+      SECTION("B -> R^2")
+      {
+        std::string_view data = R"(
+let f : R^2 -> R^2, x -> x;
+f(true);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: B -> R^2"});
+      }
+
+      SECTION("N -> R^2")
+      {
+        std::string_view data = R"(
+let f : R^2 -> R^2, x -> x;
+let n : N, n = 2;
+f(n);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: N -> R^2"});
+      }
+
+      SECTION("Z -> R^2")
+      {
+        std::string_view data = R"(
+let f : R^2 -> R^2, x -> x;
+f(-2);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: Z -> R^2"});
+      }
+
+      SECTION("R -> R^2")
+      {
+        std::string_view data = R"(
+let f : R^2 -> R^2, x -> x;
+f(1.3);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> R^2"});
+      }
+
+      SECTION("B -> R^3")
+      {
+        std::string_view data = R"(
+let f : R^3 -> R^3, x -> x;
+f(true);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: B -> R^3"});
+      }
+
+      SECTION("N -> R^3")
+      {
+        std::string_view data = R"(
+let f : R^3 -> R^3, x -> x;
+let n : N, n = 2;
+f(n);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: N -> R^3"});
+      }
+
+      SECTION("Z -> R^3")
+      {
+        std::string_view data = R"(
+let f : R^3 -> R^3, x -> x;
+f(-2);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: Z -> R^3"});
+      }
+
+      SECTION("R -> R^3")
+      {
+        std::string_view data = R"(
+let f : R^3 -> R^3, x -> x;
+f(1.3);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> R^3"});
+      }
+
+      SECTION("B -> R^2x2")
+      {
+        std::string_view data = R"(
+let f : R^2x2 -> R^2x2, x -> x;
+f(true);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: B -> R^2x2"});
+      }
+
+      SECTION("N -> R^2x2")
+      {
+        std::string_view data = R"(
+let f : R^2x2 -> R^2x2, x -> x;
+let n : N, n = 2;
+f(n);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: N -> R^2x2"});
+      }
+
+      SECTION("Z -> R^2x2")
+      {
+        std::string_view data = R"(
+let f : R^2x2 -> R^2x2, x -> x;
+f(-2);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: Z -> R^2x2"});
+      }
+
+      SECTION("R -> R^2x2")
+      {
+        std::string_view data = R"(
+let f : R^2x2 -> R^2x2, x -> x;
+f(1.3);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> R^2x2"});
+      }
+
+      SECTION("B -> R^3x3")
+      {
+        std::string_view data = R"(
+let f : R^3x3 -> R^3x3, x -> x;
+f(true);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: B -> R^3x3"});
+      }
+
+      SECTION("N -> R^3x3")
+      {
+        std::string_view data = R"(
+let f : R^3x3 -> R^3x3, x -> x;
+let n : N, n = 2;
+f(n);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: N -> R^3x3"});
+      }
+
+      SECTION("Z -> R^3x3")
+      {
+        std::string_view data = R"(
+let f : R^3x3 -> R^3x3, x -> x;
+f(-2);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: Z -> R^3x3"});
+      }
+
+      SECTION("R -> R^3x3")
+      {
+        std::string_view data = R"(
+let f : R^3x3 -> R^3x3, x -> x;
+f(1.3);
+)";
+
+        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> R^3x3"});
+      }
     }
 
     SECTION("arguments invalid tuple -> R^d conversion")
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_BuiltinFunctionProcessor.cpp b/tests/test_BuiltinFunctionProcessor.cpp
index 60a4bd491f392b7cc0c303d94531edcf8634edbc..8de9ec2c2ed9e61479aa2d7165bf6e506fd6caf6 100644
--- a/tests/test_BuiltinFunctionProcessor.cpp
+++ b/tests/test_BuiltinFunctionProcessor.cpp
@@ -358,6 +358,158 @@ let s:R, s = dot(x,y);
       CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "s", dot(TinyVector<3>{-2, 3, 4}, TinyVector<3>{4, 3, 5}));
     }
 
+    {   // det
+      tested_function_set.insert("det:R^1x1");
+      std::string_view data = R"(
+import math;
+let A:R^1x1, A = -2;
+let d:R, d = det(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "d", det(TinyMatrix<1>{-2}));
+    }
+
+    {   // det
+      tested_function_set.insert("det:R^2x2");
+      std::string_view data = R"(
+import math;
+let A:R^2x2, A = [[-2.5, 3.2],
+                  [ 3.2, 2.3]];
+let d:R, d = det(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "d",
+                                               det(TinyMatrix<2>{-2.5, 3.2,   //
+                                                                 +3.2, 2.3}));
+    }
+
+    {   // det
+      tested_function_set.insert("det:R^3x3");
+      std::string_view data = R"(
+import math;
+let A:R^3x3, A = [[-2, 2,-1],
+                  [ 3, 2, 2],
+                  [-2, 5,-3]];
+let d:R, d = det(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "d",
+                                               det(TinyMatrix<3>{-2, 2, -1,   //
+                                                                 +3, 2, +2,   //
+                                                                 -2, 5, -3}));
+    }
+
+    {   // trace
+      tested_function_set.insert("trace:R^1x1");
+      std::string_view data = R"(
+import math;
+let A:R^1x1, A = -2;
+let t:R, t = trace(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "t", trace(TinyMatrix<1>{-2}));
+    }
+
+    {   // trace
+      tested_function_set.insert("trace:R^2x2");
+      std::string_view data = R"(
+import math;
+let A:R^2x2, A = [[-2.5, 3.2],
+                  [ 3.2, 2.3]];
+let t:R, t = trace(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "t",
+                                               trace(TinyMatrix<2>{-2.5, 3.2,   //
+                                                                   +3.2, 2.3}));
+    }
+
+    {   // trace
+      tested_function_set.insert("trace:R^3x3");
+      std::string_view data = R"(
+import math;
+let A:R^3x3, A = [[-2.5, 2.9,-1.3],
+                  [ 3.2, 2.3, 2.7],
+                  [-2.6, 5.2,-3.5]];
+let t:R, t = trace(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "t",
+                                               trace(TinyMatrix<3>{-2.5, 2.9, -1.3,   //
+                                                                   +3.2, 2.3, +2.7,   //
+                                                                   -2.6, 5.2, -3.5}));
+    }
+
+    {   // inverse
+      tested_function_set.insert("inverse:R^1x1");
+      std::string_view data = R"(
+import math;
+let A:R^1x1, A = -2;
+let invA:R^1x1, invA = inverse(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "invA", inverse(TinyMatrix<1>{-2}));
+    }
+
+    {   // inverse
+      tested_function_set.insert("inverse:R^2x2");
+      std::string_view data = R"(
+import math;
+let A:R^2x2, A = [[-2.5, 3.2],
+                  [ 3.2, 2.3]];
+let invA:R^2x2, invA = inverse(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "invA",
+                                               inverse(TinyMatrix<2>{-2.5, 3.2,   //
+                                                                     +3.2, 2.3}));
+    }
+
+    {   // inverse
+      tested_function_set.insert("inverse:R^3x3");
+      std::string_view data = R"(
+import math;
+let A:R^3x3, A = [[-2, 2,-1],
+                  [ 3, 2, 2],
+                  [-2, 5,-3]];
+let invA:R^3x3, invA = inverse(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "invA",
+                                               inverse(TinyMatrix<3>{-2, 2, -1,   //
+                                                                     +3, 2, +2,   //
+                                                                     -2, 5, -3}));
+    }
+
+    {   // transpose
+      tested_function_set.insert("transpose:R^1x1");
+      std::string_view data = R"(
+import math;
+let A:R^1x1, A = -2;
+let tA:R^1x1, tA = transpose(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "tA", transpose(TinyMatrix<1>{-2}));
+    }
+
+    {   // transpose
+      tested_function_set.insert("transpose:R^2x2");
+      std::string_view data = R"(
+import math;
+let A:R^2x2, A = [[-2.5, 3.2],
+                  [ 3.2, 2.3]];
+let tA:R^2x2, tA = transpose(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "tA",
+                                               transpose(TinyMatrix<2>{-2.5, 3.2,   //
+                                                                       +3.2, 2.3}));
+    }
+
+    {   // transpose
+      tested_function_set.insert("transpose:R^3x3");
+      std::string_view data = R"(
+import math;
+let A:R^3x3, A = [[-2.5, 2.9,-1.3],
+                  [ 3.2, 2.3, 2.7],
+                  [-2.6, 5.2,-3.5]];
+let tA:R^3x3, tA = transpose(A);
+)";
+      CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "tA",
+                                               transpose(TinyMatrix<3>{-2.5, 2.9, -1.3,   //
+                                                                       +3.2, 2.3, +2.7,   //
+                                                                       -2.6, 5.2, -3.5}));
+    }
+
     MathModule math_module;
 
     bool missing_test = false;
diff --git a/tests/test_BuiltinFunctionRegister.hpp b/tests/test_BuiltinFunctionRegister.hpp
index 03a847bc2f805d4d883a060709b232c6f64e4a3a..dfc7da319d00260c22a23fdba6b66e9ecb0ca437 100644
--- a/tests/test_BuiltinFunctionRegister.hpp
+++ b/tests/test_BuiltinFunctionRegister.hpp
@@ -4,6 +4,7 @@
 #include <language/utils/ASTNodeDataTypeTraits.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/ParseError.hpp>
+#include <language/utils/SymbolTable.hpp>
 #include <language/utils/TypeDescriptor.hpp>
 #include <utils/Exceptions.hpp>
 
@@ -12,7 +13,7 @@
 template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const double>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("builtin_t");
-const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const double>>;
+inline const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const double>>;
 
 namespace test_only
 {
@@ -116,25 +117,25 @@ class test_BuiltinFunctionRegister
 
     m_name_builtin_function_map.insert(
       std::make_pair("tuple_BtoR:(B)", std::make_shared<BuiltinFunctionEmbedder<double(std::vector<bool>)>>(
-                                            [](const std::vector<bool>&) -> double { return 0.5; })));
+                                         [](const std::vector<bool>&) -> double { return 0.5; })));
 
     m_name_builtin_function_map.insert(
       std::make_pair("tuple_NtoR:(N)", std::make_shared<BuiltinFunctionEmbedder<double(std::vector<uint64_t>)>>(
-                                            [](const std::vector<uint64_t>&) -> double { return 0.5; })));
+                                         [](const std::vector<uint64_t>&) -> double { return 0.5; })));
 
     m_name_builtin_function_map.insert(
       std::make_pair("tuple_ZtoR:(Z)", std::make_shared<BuiltinFunctionEmbedder<double(std::vector<int64_t>)>>(
-                                            [](const std::vector<int64_t>& v) -> double {
-                                              int64_t sum = 0;
-                                              for (auto vi : v) {
-                                                sum += vi;
-                                              }
-                                              return 0.5 * sum;
-                                            })));
+                                         [](const std::vector<int64_t>& v) -> double {
+                                           int64_t sum = 0;
+                                           for (auto vi : v) {
+                                             sum += vi;
+                                           }
+                                           return 0.5 * sum;
+                                         })));
 
     m_name_builtin_function_map.insert(
       std::make_pair("tuple_RtoB:(R)", std::make_shared<BuiltinFunctionEmbedder<bool(std::vector<double>)>>(
-                                            [](const std::vector<double>&) -> bool { return false; })));
+                                         [](const std::vector<double>&) -> bool { return false; })));
 
     m_name_builtin_function_map.insert(
       std::make_pair("tuple_stringtoB:(string)",
diff --git a/tests/test_CellIntegrator.cpp b/tests/test_CellIntegrator.cpp
index ebd322d14c8ea5df4053163dcae9b60d02318a53..7c70d19fd2a538f831507d7eeb41392b0686f158 100644
--- a/tests/test_CellIntegrator.cpp
+++ b/tests/test_CellIntegrator.cpp
@@ -497,7 +497,7 @@ TEST_CASE("CellIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
@@ -1418,7 +1418,7 @@ TEST_CASE("CellIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
@@ -2345,7 +2345,7 @@ TEST_CASE("CellIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
diff --git a/tests/test_Connectivity.cpp b/tests/test_Connectivity.cpp
index f7d2ab7de77a04e3f5eb46efd458d5299740fc36..b0465d08489c644685500d59b145e12594966522 100644
--- a/tests/test_Connectivity.cpp
+++ b/tests/test_Connectivity.cpp
@@ -3,6 +3,7 @@
 
 #include <MeshDataBaseForTests.hpp>
 #include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
 #include <mesh/ItemValue.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
@@ -132,7 +133,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d                        = named_mesh.mesh();
@@ -223,7 +224,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d                        = named_mesh.mesh();
@@ -326,7 +327,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -443,7 +444,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d                        = named_mesh.mesh();
@@ -492,7 +493,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d                        = named_mesh.mesh();
@@ -570,7 +571,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -670,6 +671,318 @@ TEST_CASE("Connectivity", "[mesh]")
     }
   }
 
+  SECTION("item ordering")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          SECTION("cell -> nodes")
+          {
+            auto mesh = named_mesh.mesh();
+            auto xr   = mesh->xr();
+
+            const Connectivity<1>& connectivity = mesh->connectivity();
+
+            auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+
+            bool is_correct = true;
+
+            for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+              if (xr[cell_to_node_matrix[cell_id][1]][0] < xr[cell_to_node_matrix[cell_id][0]][0]) {
+                is_correct = false;
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("node -> cells")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<1>& connectivity = mesh->connectivity();
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            auto cell_number         = connectivity.cellNumber();
+
+            bool is_correct = true;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              auto cell_node_list = node_to_cell_matrix[node_id];
+              for (size_t i_node = 0; i_node < cell_node_list.size() - 1; ++i_node) {
+                is_correct &= (cell_number[cell_node_list[i_node]] < cell_number[cell_node_list[i_node + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          SECTION("face -> nodes")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<2>& connectivity = mesh->connectivity();
+
+            auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+            auto node_number         = connectivity.nodeNumber();
+
+            bool is_correct = true;
+
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              auto face_node_list = face_to_node_matrix[face_id];
+              if (node_number[face_node_list[1]] < node_number[face_node_list[0]]) {
+                is_correct = false;
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("node -> faces")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<2>& connectivity = mesh->connectivity();
+
+            auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+            auto face_number         = connectivity.faceNumber();
+
+            bool is_correct = true;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              auto face_node_list = node_to_face_matrix[node_id];
+              for (size_t i_node = 0; i_node < face_node_list.size() - 1; ++i_node) {
+                is_correct &= (face_number[face_node_list[i_node]] < face_number[face_node_list[i_node + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("node -> cells")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<2>& connectivity = mesh->connectivity();
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            auto cell_number         = connectivity.cellNumber();
+
+            bool is_correct = true;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              auto cell_node_list = node_to_cell_matrix[node_id];
+              for (size_t i_node = 0; i_node < cell_node_list.size() - 1; ++i_node) {
+                is_correct &= (cell_number[cell_node_list[i_node]] < cell_number[cell_node_list[i_node + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("face -> cells")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<2>& connectivity = mesh->connectivity();
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            auto cell_number         = connectivity.cellNumber();
+
+            bool is_correct = true;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              auto cell_face_list = face_to_cell_matrix[face_id];
+              for (size_t i_face = 0; i_face < cell_face_list.size() - 1; ++i_face) {
+                is_correct &= (cell_number[cell_face_list[i_face]] < cell_number[cell_face_list[i_face + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          SECTION("edge -> nodes")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+            auto node_number         = connectivity.nodeNumber();
+
+            bool is_correct = true;
+
+            for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+              auto edge_node_list = edge_to_node_matrix[edge_id];
+              if (node_number[edge_node_list[1]] < node_number[edge_node_list[0]]) {
+                is_correct = false;
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("face -> nodes")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+            auto node_number         = connectivity.nodeNumber();
+
+            bool is_correct = true;
+
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              auto face_node_list = face_to_node_matrix[face_id];
+              for (size_t i = 1; i < face_node_list.size() - 1; ++i) {
+                if (node_number[face_node_list[i]] < node_number[face_node_list[0]]) {
+                  is_correct = false;
+                }
+              }
+              for (size_t i = 2; i < face_node_list.size() - 1; ++i) {
+                if (node_number[face_node_list[i]] < node_number[face_node_list[1]]) {
+                  is_correct = false;
+                }
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("node -> edges")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            REQUIRE(checkConnectivityOrdering(connectivity));
+
+            auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+            auto edge_number         = connectivity.edgeNumber();
+
+            bool is_correct = true;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              auto edge_node_list = node_to_edge_matrix[node_id];
+              for (size_t i_node = 0; i_node < edge_node_list.size() - 1; ++i_node) {
+                is_correct &= (edge_number[edge_node_list[i_node]] < edge_number[edge_node_list[i_node + 1]]);
+              }
+            }
+
+            REQUIRE(is_correct);
+          }
+
+          SECTION("node -> faces")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+            auto face_number         = connectivity.faceNumber();
+
+            bool is_correct = true;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              auto face_node_list = node_to_face_matrix[node_id];
+              for (size_t i_node = 0; i_node < face_node_list.size() - 1; ++i_node) {
+                is_correct &= (face_number[face_node_list[i_node]] < face_number[face_node_list[i_node + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("node -> cells")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+            auto cell_number         = connectivity.cellNumber();
+
+            bool is_correct = true;
+            for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+              auto cell_node_list = node_to_cell_matrix[node_id];
+              for (size_t i_node = 0; i_node < cell_node_list.size() - 1; ++i_node) {
+                is_correct &= (cell_number[cell_node_list[i_node]] < cell_number[cell_node_list[i_node + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("edge -> faces")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
+            auto face_number         = connectivity.faceNumber();
+
+            bool is_correct = true;
+            for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+              auto face_edge_list = edge_to_face_matrix[edge_id];
+              for (size_t i_edge = 0; i_edge < face_edge_list.size() - 1; ++i_edge) {
+                is_correct &= (face_number[face_edge_list[i_edge]] < face_number[face_edge_list[i_edge + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("edge -> cells")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+            auto cell_number         = connectivity.cellNumber();
+
+            bool is_correct = true;
+            for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+              auto cell_edge_list = edge_to_cell_matrix[edge_id];
+              for (size_t i_edge = 0; i_edge < cell_edge_list.size() - 1; ++i_edge) {
+                is_correct &= (cell_number[cell_edge_list[i_edge]] < cell_number[cell_edge_list[i_edge + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+
+          SECTION("face -> cells")
+          {
+            auto mesh = named_mesh.mesh();
+
+            const Connectivity<3>& connectivity = mesh->connectivity();
+
+            auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+            auto cell_number         = connectivity.cellNumber();
+
+            bool is_correct = true;
+            for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+              auto cell_face_list = face_to_cell_matrix[face_id];
+              for (size_t i_face = 0; i_face < cell_face_list.size() - 1; ++i_face) {
+                is_correct &= (cell_number[cell_face_list[i_face]] < cell_number[cell_face_list[i_face + 1]]);
+              }
+            }
+            REQUIRE(is_correct);
+          }
+        }
+      }
+    }
+  }
+
   SECTION("ItemLocalNumbersInTheirSubItems")
   {
     auto check_item_local_numbers_in_their_subitems = [](auto item_to_subitem_matrix, auto subitem_to_item_matrix,
@@ -703,7 +1016,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh                           = named_mesh.mesh();
@@ -775,7 +1088,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh                           = named_mesh.mesh();
@@ -877,7 +1190,7 @@ TEST_CASE("Connectivity", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh                           = named_mesh.mesh();
diff --git a/tests/test_DiamondDualConnectivityBuilder.cpp b/tests/test_DiamondDualConnectivityBuilder.cpp
index 70b8f75440061530f386654a4bbf9e3f5342d33e..45d9cd700ca7b5ae73e6c6fd22db024e7de3126c 100644
--- a/tests/test_DiamondDualConnectivityBuilder.cpp
+++ b/tests/test_DiamondDualConnectivityBuilder.cpp
@@ -5,6 +5,7 @@
 #include <mesh/DualConnectivityManager.hpp>
 
 #include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
 
@@ -54,6 +55,8 @@ TEST_CASE("DiamondDualConnectivityBuilder", "[mesh]")
     REQUIRE(dual_connectivity.numberOfFaces() == 220);
     REQUIRE(dual_connectivity.numberOfCells() == 110);
 
+    REQUIRE(checkConnectivityOrdering(dual_connectivity));
+
     SECTION("ref node list")
     {
       REQUIRE(primal_connectivity.numberOfRefItemList<ItemType::node>() == 4);
@@ -159,6 +162,8 @@ TEST_CASE("DiamondDualConnectivityBuilder", "[mesh]")
       DualConnectivityManager::instance().getDiamondDualConnectivity(primal_connectivity);
     const ConnectivityType& dual_connectivity = *p_diamond_dual_connectivity;
 
+    REQUIRE(checkConnectivityOrdering(dual_connectivity));
+
     REQUIRE(dual_connectivity.numberOfNodes() == 331);
     REQUIRE(dual_connectivity.numberOfEdges() == 1461);
     REQUIRE(dual_connectivity.numberOfFaces() == 1651);
diff --git a/tests/test_DiscreteFunctionIntegrator.cpp b/tests/test_DiscreteFunctionIntegrator.cpp
index 031f6c62bd4f123e0a001eacdea0721716325978..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>
 
@@ -49,7 +50,7 @@ TEST_CASE("DiscreteFunctionIntegrator", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -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>>()));
         }
       }
     }
@@ -295,7 +286,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -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>>()));
         }
       }
     }
@@ -541,7 +522,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -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 f9d870d29dbd53f6eef9f49bf114be2566475d0d..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>
 
@@ -45,7 +46,7 @@ TEST_CASE("DiscreteFunctionInterpoler", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -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>>()));
         }
       }
     }
@@ -344,7 +335,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -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>>()));
         }
       }
     }
@@ -644,7 +625,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -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_DiscreteFunctionP0.cpp b/tests/test_DiscreteFunctionP0.cpp
index 7073def1e438a3a19df04723ba88349d3bb7d7b1..48c107e4ad2f07391a49270a04c9a3dc20d2db4a 100644
--- a/tests/test_DiscreteFunctionP0.cpp
+++ b/tests/test_DiscreteFunctionP0.cpp
@@ -26,7 +26,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -74,7 +74,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -122,7 +122,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -184,7 +184,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -213,7 +213,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -242,7 +242,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -284,7 +284,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -386,7 +386,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -488,7 +488,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -592,7 +592,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
       constexpr size_t Dimension = 1;
       std::array mesh_list       = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -677,7 +677,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -1258,7 +1258,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -1844,7 +1844,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -2493,7 +2493,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
       constexpr size_t Dimension = 1;
       std::array mesh_list       = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -2837,7 +2837,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -3181,7 +3181,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -3428,6 +3428,94 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
             CHECK_STD_BINARY_MATH_FUNCTION_WITH_LHS_VALUE(u, vh, dot);
           }
 
+          SECTION("det(Ah)")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> Ah{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                Ah[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3,   //
+                                               -0.2 * x - 1, 2 + x};
+              });
+
+            {
+              DiscreteFunctionP0 result = det(Ah);
+              bool is_same              = true;
+              parallel_for(Ah.cellValues().numberOfItems(), [&](const CellId cell_id) {
+                if (result[cell_id] != det(Ah[cell_id])) {
+                  is_same = false;
+                }
+              });
+              REQUIRE(is_same);
+            }
+          }
+
+          SECTION("trace(Ah)")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> Ah{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                Ah[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3,   //
+                                               -0.2 * x - 1, 2 + x};
+              });
+
+            {
+              DiscreteFunctionP0 result = trace(Ah);
+              bool is_same              = true;
+              parallel_for(Ah.cellValues().numberOfItems(), [&](const CellId cell_id) {
+                if (result[cell_id] != trace(Ah[cell_id])) {
+                  is_same = false;
+                }
+              });
+              REQUIRE(is_same);
+            }
+          }
+
+          SECTION("inverse(Ah)")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> Ah{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                Ah[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3,   //
+                                               -0.2 * x - 1, 2 + x};
+              });
+
+            {
+              DiscreteFunctionP0 result = inverse(Ah);
+              bool is_same              = true;
+              parallel_for(Ah.cellValues().numberOfItems(), [&](const CellId cell_id) {
+                if (result[cell_id] != inverse(Ah[cell_id])) {
+                  is_same = false;
+                }
+              });
+              REQUIRE(is_same);
+            }
+          }
+
+          SECTION("transpose(Ah)")
+          {
+            DiscreteFunctionP0<Dimension, TinyMatrix<2>> Ah{mesh};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+                const double x = xj[cell_id][0];
+                Ah[cell_id]    = TinyMatrix<2>{x + 1, 2 * x - 3,   //
+                                               -0.2 * x - 1, 2 + x};
+              });
+
+            {
+              DiscreteFunctionP0 result = transpose(Ah);
+              bool is_same              = true;
+              parallel_for(Ah.cellValues().numberOfItems(), [&](const CellId cell_id) {
+                if (result[cell_id] != transpose(Ah[cell_id])) {
+                  is_same = false;
+                }
+              });
+              REQUIRE(is_same);
+            }
+          }
+
           SECTION("scalar sum")
           {
             const CellValue<const double> cell_value = positive_function.cellValues();
@@ -3531,7 +3619,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1 = named_mesh.mesh();
@@ -3558,7 +3646,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1 = named_mesh.mesh();
@@ -3585,7 +3673,7 @@ TEST_CASE("DiscreteFunctionP0", "[scheme]")
 
         std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1 = named_mesh.mesh();
diff --git a/tests/test_DiscreteFunctionP0Vector.cpp b/tests/test_DiscreteFunctionP0Vector.cpp
index 6e351c404a7b5f57d7b39e5a3f70d43cf62a5635..d2da3d84ef517f28dc0bd0ba60149be7c4fba725 100644
--- a/tests/test_DiscreteFunctionP0Vector.cpp
+++ b/tests/test_DiscreteFunctionP0Vector.cpp
@@ -31,7 +31,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -79,7 +79,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -127,7 +127,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -189,7 +189,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh                  = named_mesh.mesh();
@@ -211,7 +211,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -231,7 +231,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -266,7 +266,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -312,7 +312,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
       constexpr size_t Dimension = 2;
       std::array mesh_list       = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -359,7 +359,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -410,7 +410,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -453,7 +453,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -497,7 +497,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -535,6 +535,258 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
     }
   }
 
+  SECTION("functions")
+  {
+    SECTION("1D")
+    {
+      const size_t size = 3;
+
+      constexpr size_t Dimension = 1;
+
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id][0]  = 2 * x + 1;
+              f[cell_id][1]  = x * x - 1;
+              f[cell_id][2]  = 2 + x;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              g[cell_id][0]  = (x + 1) * (x - 2) + 1;
+              g[cell_id][1]  = 3 * (x + 2) - 1;
+              g[cell_id][2]  = (x + 3) * 5;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+          auto same_data = [](const auto& f, const auto& g) {
+            const size_t number_of_cells = g.size();
+            for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+              if (f[cell_id] != g[cell_id]) {
+                return false;
+              }
+            }
+            return true;
+          };
+
+          SECTION("dot")
+          {
+            Array<double> dot_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                double sum = 0;
+                for (size_t i = 0; i < size; ++i) {
+                  sum += f[cell_id][i] * g[cell_id][i];
+                }
+                dot_values[cell_id] = sum;
+              });
+
+            REQUIRE(same_data(dot(f, g), dot_values));
+            REQUIRE(same_data(dot(const_f, g), dot_values));
+            REQUIRE(same_data(dot(f, const_g), dot_values));
+            REQUIRE(same_data(dot(const_f, const_g), dot_values));
+          }
+
+          SECTION("sumOfComponents")
+          {
+            Array<double> sum_of_components{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                double sum = 0;
+                for (size_t i = 0; i < size; ++i) {
+                  sum += f[cell_id][i];
+                }
+                sum_of_components[cell_id] = sum;
+              });
+
+            REQUIRE(same_data(sumOfComponents(f), sum_of_components));
+            REQUIRE(same_data(sumOfComponents(const_f), sum_of_components));
+          }
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      const size_t size = 3;
+
+      constexpr size_t Dimension = 2;
+
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id][0]  = 2 * x + 1;
+              f[cell_id][1]  = x * x - 1;
+              f[cell_id][2]  = 2 + x;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              g[cell_id][0]  = (x + 1) * (x - 2) + 1;
+              g[cell_id][1]  = 3 * (x + 2) - 1;
+              g[cell_id][2]  = (x + 3) * 5;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+          auto same_data = [](const auto& f, const auto& g) {
+            const size_t number_of_cells = g.size();
+            for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+              if (f[cell_id] != g[cell_id]) {
+                return false;
+              }
+            }
+            return true;
+          };
+
+          SECTION("dot")
+          {
+            Array<double> dot_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                double sum = 0;
+                for (size_t i = 0; i < size; ++i) {
+                  sum += f[cell_id][i] * g[cell_id][i];
+                }
+                dot_values[cell_id] = sum;
+              });
+
+            REQUIRE(same_data(dot(f, g), dot_values));
+            REQUIRE(same_data(dot(const_f, g), dot_values));
+            REQUIRE(same_data(dot(f, const_g), dot_values));
+            REQUIRE(same_data(dot(const_f, const_g), dot_values));
+          }
+
+          SECTION("sumOfComponents")
+          {
+            Array<double> sum_of_components{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                double sum = 0;
+                for (size_t i = 0; i < size; ++i) {
+                  sum += f[cell_id][i];
+                }
+                sum_of_components[cell_id] = sum;
+              });
+
+            REQUIRE(same_data(sumOfComponents(f), sum_of_components));
+            REQUIRE(same_data(sumOfComponents(const_f), sum_of_components));
+          }
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      const size_t size = 3;
+
+      constexpr size_t Dimension = 3;
+
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (const auto& named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh = named_mesh.mesh();
+
+          auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();
+
+          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              f[cell_id][0]  = 2 * x + 1;
+              f[cell_id][1]  = x * x - 1;
+              f[cell_id][2]  = 2 + x;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
+          parallel_for(
+            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              const double x = xj[cell_id][0];
+              g[cell_id][0]  = (x + 1) * (x - 2) + 1;
+              g[cell_id][1]  = 3 * (x + 2) - 1;
+              g[cell_id][2]  = (x + 3) * 5;
+            });
+
+          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
+          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};
+
+          auto same_data = [](const auto& f, const auto& g) {
+            const size_t number_of_cells = g.size();
+            for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
+              if (f[cell_id] != g[cell_id]) {
+                return false;
+              }
+            }
+            return true;
+          };
+
+          SECTION("dot")
+          {
+            Array<double> dot_values{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                double sum = 0;
+                for (size_t i = 0; i < size; ++i) {
+                  sum += f[cell_id][i] * g[cell_id][i];
+                }
+                dot_values[cell_id] = sum;
+              });
+
+            REQUIRE(same_data(dot(f, g), dot_values));
+            REQUIRE(same_data(dot(const_f, g), dot_values));
+            REQUIRE(same_data(dot(f, const_g), dot_values));
+            REQUIRE(same_data(dot(const_f, const_g), dot_values));
+          }
+
+          SECTION("sumOfComponents")
+          {
+            Array<double> sum_of_components{mesh->numberOfCells()};
+            parallel_for(
+              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+                double sum = 0;
+                for (size_t i = 0; i < size; ++i) {
+                  sum += f[cell_id][i];
+                }
+                sum_of_components[cell_id] = sum;
+              });
+
+            REQUIRE(same_data(sumOfComponents(f), sum_of_components));
+            REQUIRE(same_data(sumOfComponents(const_f), sum_of_components));
+          }
+        }
+      }
+    }
+  }
+
   SECTION("binary operators")
   {
     SECTION("1D")
@@ -545,7 +797,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -679,7 +931,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -817,7 +1069,7 @@ TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
diff --git a/tests/test_DiscreteFunctionUtils.cpp b/tests/test_DiscreteFunctionUtils.cpp
index 3783a19c99cb67066565c507abfeca6ce0f5d61f..85c916c4cf559ca4c2ceef612d1da03db779715e 100644
--- a/tests/test_DiscreteFunctionUtils.cpp
+++ b/tests/test_DiscreteFunctionUtils.cpp
@@ -18,7 +18,7 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh = named_mesh.mesh();
@@ -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]));
         }
       }
     }
@@ -171,7 +204,7 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh = named_mesh.mesh();
@@ -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]));
         }
       }
     }
@@ -324,7 +390,7 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh = named_mesh.mesh();
@@ -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]));
         }
       }
     }
@@ -479,7 +578,7 @@ TEST_CASE("DiscreteFunctionUtils", "[scheme]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh = named_mesh.mesh();
@@ -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 31fd447f525f2b002ead1246219319454c792588..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>
@@ -60,7 +61,7 @@ TEST_CASE("DiscreteFunctionVectorIntegrator", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -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());
@@ -153,7 +150,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -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());
@@ -246,7 +239,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -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());
@@ -337,7 +326,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
 
     std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor = std::make_shared<GaussQuadratureDescriptor>(3);
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
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 3c3fefc38aba5b7377a86eb28ce4c6439ed82164..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>
@@ -55,7 +56,7 @@ TEST_CASE("DiscreteFunctionVectorInterpoler", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -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());
@@ -163,7 +160,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -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());
@@ -271,7 +264,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -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());
@@ -377,7 +366,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp b/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
index c3605a232c7b032ad4215db72cdf711695818c6e..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());
@@ -430,7 +431,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_Dual1DConnectivityBuilder.cpp b/tests/test_Dual1DConnectivityBuilder.cpp
index 2b194a92baa821d6da4bac1e6277aaea6c1ddb1b..b7a35abfc577d93f729a0befc166c16c04f47fd3 100644
--- a/tests/test_Dual1DConnectivityBuilder.cpp
+++ b/tests/test_Dual1DConnectivityBuilder.cpp
@@ -5,6 +5,7 @@
 #include <mesh/DualConnectivityManager.hpp>
 
 #include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
 
@@ -48,6 +49,8 @@ TEST_CASE("Dual1DConnectivityBuilder", "[mesh]")
     DualConnectivityManager::instance().getDual1DConnectivity(primal_connectivity);
   const ConnectivityType& dual_connectivity = *p_dual_1d_connectivity;
 
+  REQUIRE(checkConnectivityOrdering(dual_connectivity));
+
   REQUIRE(dual_connectivity.numberOfNodes() == 36);
   REQUIRE(dual_connectivity.numberOfCells() == 35);
 
diff --git a/tests/test_EdgeIntegrator.cpp b/tests/test_EdgeIntegrator.cpp
index 5dfccbc6c7427a2bd27634c49957209c61c6f3ce..46f4d423d933a97faeffcaa762cdd210e36a74d0 100644
--- a/tests/test_EdgeIntegrator.cpp
+++ b/tests/test_EdgeIntegrator.cpp
@@ -35,7 +35,7 @@ TEST_CASE("EdgeIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
@@ -284,7 +284,7 @@ TEST_CASE("EdgeIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
@@ -535,7 +535,7 @@ TEST_CASE("EdgeIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
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 a7f0cc0d74eb79f0897e2ea2a2a4b7212dbc306e..0000000000000000000000000000000000000000
--- a/tests/test_EmbeddedIDiscreteFunctionMathFunctions.cpp
+++ /dev/null
@@ -1,1635 +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 (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("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 (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("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 (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("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 b955ba24b785a83c3be6f73360fa9ea0026cc56e..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 (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 (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 (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 (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 (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 (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 e7d0e8eea200cb115be60a2ba789999155cf5fab..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 (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 (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 (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 (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 (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
-}
diff --git a/tests/test_FaceIntegrator.cpp b/tests/test_FaceIntegrator.cpp
index 945d765a6d0a530c7c7babb5d8b5800d5af033d3..916dcf497b7dfdd9dba2200092286b2a6c6c2522 100644
--- a/tests/test_FaceIntegrator.cpp
+++ b/tests/test_FaceIntegrator.cpp
@@ -262,7 +262,7 @@ TEST_CASE("FaceIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
@@ -754,7 +754,7 @@ TEST_CASE("FaceIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
@@ -1249,7 +1249,7 @@ TEST_CASE("FaceIntegrator", "[scheme]")
       mesh_list.push_back(std::make_pair("hybrid mesh", hybrid_mesh));
       mesh_list.push_back(std::make_pair("diamond mesh", DualMeshManager::instance().getDiamondDualMesh(*hybrid_mesh)));
 
-      for (auto mesh_info : mesh_list) {
+      for (const auto& mesh_info : mesh_list) {
         auto mesh_name = mesh_info.first;
         auto mesh      = mesh_info.second;
 
diff --git a/tests/test_FunctionProcessor.cpp b/tests/test_FunctionProcessor.cpp
index 03c8baa8ed5509c19520bb04d2860402e7308984..97cc8d6be42199f684739dba21b1b3f9a2cab644 100644
--- a/tests/test_FunctionProcessor.cpp
+++ b/tests/test_FunctionProcessor.cpp
@@ -374,6 +374,43 @@ let fx:R^1, fx = f(x);
       CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyVector<1>{3}));
     }
 
+    SECTION("  R^1 -> R^1 called with B argument")
+    {
+      std::string_view data = R"(
+let f : R^1 -> R^1, x -> 2*x;
+let fx:R^1, fx = f(true);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyVector<1>{true}));
+    }
+
+    SECTION("  R^1 -> R^1 called with N argument")
+    {
+      std::string_view data = R"(
+let f : R^1 -> R^1, x -> 2*x;
+let n:N, n = 3;
+let fx:R^1, fx = f(n);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyVector<1>{3}));
+    }
+
+    SECTION("  R^1 -> R^1 called with Z argument")
+    {
+      std::string_view data = R"(
+let f : R^1 -> R^1, x -> 2*x;
+let fx:R^1, fx = f(-2);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyVector<1>{-2}));
+    }
+
+    SECTION("  R^1 -> R^1 called with R argument")
+    {
+      std::string_view data = R"(
+let f : R^1 -> R^1, x -> 2*x;
+let fx:R^1, fx = f(1.3);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyVector<1>{1.3}));
+    }
+
     SECTION("  R^2 -> R^2")
     {
       std::string_view data = R"(
@@ -439,6 +476,43 @@ let fx:R^1x1, fx = f(x);
       CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyMatrix<1>{3}));
     }
 
+    SECTION("  R^1x1 -> R^1x1 called with B argument")
+    {
+      std::string_view data = R"(
+let f : R^1x1 -> R^1x1, x -> 2*x;
+let fx:R^1x1, fx = f(true);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyMatrix<1>{true}));
+    }
+
+    SECTION("  R^1x1 -> R^1x1 called with N argument")
+    {
+      std::string_view data = R"(
+let f : R^1x1 -> R^1x1, x -> 2*x;
+let n:N, n = 3;
+let fx:R^1x1, fx = f(n);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyMatrix<1>{3}));
+    }
+
+    SECTION("  R^1x1 -> R^1x1 called with Z argument")
+    {
+      std::string_view data = R"(
+let f : R^1x1 -> R^1x1, x -> 2*x;
+let fx:R^1x1, fx = f(-4);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyMatrix<1>{-4}));
+    }
+
+    SECTION("  R^1x1 -> R^1x1 called with R argument")
+    {
+      std::string_view data = R"(
+let f : R^1x1 -> R^1x1, x -> 2*x;
+let fx:R^1x1, fx = f(-2.3);
+)";
+      CHECK_FUNCTION_EVALUATION_RESULT(data, "fx", (2 * TinyMatrix<1>{-2.3}));
+    }
+
     SECTION("  R^2x2 -> R^2x2")
     {
       std::string_view data = R"(
diff --git a/tests/test_IntegrateCellArray.cpp b/tests/test_IntegrateCellArray.cpp
index b6b8db8c8ca7bc0aa895f8be6b7d5f8dbccd89ff..0b64ca10e057803c4b8bd0befd06a27277239c9c 100644
--- a/tests/test_IntegrateCellArray.cpp
+++ b/tests/test_IntegrateCellArray.cpp
@@ -51,7 +51,7 @@ TEST_CASE("IntegrateCellArray", "[language]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -137,7 +137,7 @@ let g: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -234,7 +234,7 @@ let g: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
         return extended_mesh_list;
       }();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -336,7 +336,7 @@ let g: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -429,7 +429,7 @@ let g: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -535,7 +535,7 @@ let g: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
         return extended_mesh_list;
       }();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_IntegrateCellValue.cpp b/tests/test_IntegrateCellValue.cpp
index 5df4d0c447c50751db43c0ce2db5345612fc514f..c109c227c35eeb10362a339dd9aaa5e8a947114f 100644
--- a/tests/test_IntegrateCellValue.cpp
+++ b/tests/test_IntegrateCellValue.cpp
@@ -49,7 +49,7 @@ TEST_CASE("IntegrateCellValue", "[language]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -106,7 +106,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -175,7 +175,7 @@ let R3_2d: R^2 -> R^3, x -> [2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3];
         return extended_mesh_list;
       }();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -244,7 +244,7 @@ let scalar_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -307,7 +307,7 @@ let scalar_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -386,7 +386,7 @@ let R3_2d: R^2 -> R^3, x -> [2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3];
         return extended_mesh_list;
       }();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_IntegrateCellValue.hpp b/tests/test_IntegrateCellValue.hpp
deleted file mode 100644
index f9a9e32a9a0bb69e61626eab69334c3248ee2b74..0000000000000000000000000000000000000000
--- a/tests/test_IntegrateCellValue.hpp
+++ /dev/null
@@ -1 +0,0 @@
-i_f_symboli_f_symboli_f_symbol
diff --git a/tests/test_IntegrateOnCells.cpp b/tests/test_IntegrateOnCells.cpp
index 80463f0831bc21c3c172bb62b0564c261629b7f0..5207325e8a4d36c1b8dd66ea24d3ddfa3be3af9f 100644
--- a/tests/test_IntegrateOnCells.cpp
+++ b/tests/test_IntegrateOnCells.cpp
@@ -51,7 +51,7 @@ TEST_CASE("IntegrateOnCells", "[language]")
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1d = named_mesh.mesh();
@@ -151,7 +151,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_2d = named_mesh.mesh();
@@ -264,7 +264,7 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
           return extended_mesh_list;
         }();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_3d = named_mesh.mesh();
@@ -382,7 +382,7 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1d = named_mesh.mesh();
@@ -491,7 +491,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_2d = named_mesh.mesh();
@@ -614,7 +614,7 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
           return extended_mesh_list;
         }();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_3d = named_mesh.mesh();
@@ -747,7 +747,7 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1d = named_mesh.mesh();
@@ -847,7 +847,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_2d = named_mesh.mesh();
@@ -960,7 +960,7 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
           return extended_mesh_list;
         }();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_3d = named_mesh.mesh();
@@ -1078,7 +1078,7 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1d = named_mesh.mesh();
@@ -1187,7 +1187,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_2d = named_mesh.mesh();
@@ -1310,7 +1310,7 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
           return extended_mesh_list;
         }();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_3d = named_mesh.mesh();
@@ -1443,7 +1443,7 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1d = named_mesh.mesh();
@@ -1543,7 +1543,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_2d = named_mesh.mesh();
@@ -1656,7 +1656,7 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
           return extended_mesh_list;
         }();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_3d = named_mesh.mesh();
@@ -1774,7 +1774,7 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
         std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_1d = named_mesh.mesh();
@@ -1883,7 +1883,7 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
         std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_2d = named_mesh.mesh();
@@ -2006,7 +2006,7 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
           return extended_mesh_list;
         }();
 
-        for (auto named_mesh : mesh_list) {
+        for (const auto& named_mesh : mesh_list) {
           SECTION(named_mesh.name())
           {
             auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_InterpolateItemArray.cpp b/tests/test_InterpolateItemArray.cpp
index 7fd968be9aaeed6cd7774353df69b2153eb68d51..ccee6e77877eadc359cc2c11313a7114081cc8c0 100644
--- a/tests/test_InterpolateItemArray.cpp
+++ b/tests/test_InterpolateItemArray.cpp
@@ -48,7 +48,7 @@ TEST_CASE("InterpolateItemArray", "[language]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -121,7 +121,7 @@ let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -194,7 +194,7 @@ let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -281,7 +281,7 @@ let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -362,7 +362,7 @@ let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -440,7 +440,7 @@ let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_InterpolateItemValue.cpp b/tests/test_InterpolateItemValue.cpp
index 45832349fd50410aa70cadfb6df74ad59156c045..f53921ed19cfdd35549b2c032dd0ce8d27620ff3 100644
--- a/tests/test_InterpolateItemValue.cpp
+++ b/tests/test_InterpolateItemValue.cpp
@@ -45,7 +45,7 @@ TEST_CASE("InterpolateItemValue", "[language]")
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -216,7 +216,7 @@ let R2x2_non_linear_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -381,7 +381,7 @@ let R2x2_non_linear_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -530,7 +530,7 @@ let R2x2_non_linear_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos
               cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
                 const TinyVector<Dimension>& x = xj[cell_id];
                 cell_value[cell_id]            = TinyMatrix<2>{2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]),
-                                                    sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]};
+                                                               sin(x[0] - 2 * x[1] * x[2]), 3, x[0] * x[1] * x[2]};
               });
             CellValue<const TinyMatrix<2>> interpolate_value =
               InterpolateItemValue<TinyMatrix<2>(TinyVector<Dimension>)>::interpolate(function_symbol_id, xj);
@@ -560,7 +560,7 @@ let R2x2_non_linear_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos
 
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -742,7 +742,7 @@ let R2x2_non_linear_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -916,7 +916,7 @@ let R2x2_non_linear_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2
 
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_ItemArray.cpp b/tests/test_ItemArray.cpp
index 997e327a1237752463e5b8ace7ef9bb1a167fcf7..2dbe964f3cdd5733aa77ddf5e1bc9204e9731998 100644
--- a/tests/test_ItemArray.cpp
+++ b/tests/test_ItemArray.cpp
@@ -30,7 +30,7 @@ TEST_CASE("ItemArray", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -68,7 +68,7 @@ TEST_CASE("ItemArray", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -104,7 +104,7 @@ TEST_CASE("ItemArray", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -138,7 +138,7 @@ TEST_CASE("ItemArray", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -189,7 +189,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -224,7 +224,7 @@ TEST_CASE("ItemArray", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -313,7 +313,7 @@ TEST_CASE("ItemArray", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -343,7 +343,7 @@ TEST_CASE("ItemArray", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -360,7 +360,7 @@ TEST_CASE("ItemArray", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -369,7 +369,7 @@ TEST_CASE("ItemArray", "[mesh]")
 
           std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-          for (auto named_mesh : mesh_list) {
+          for (const auto& named_mesh : mesh_list) {
             SECTION(named_mesh.name())
             {
               auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_ItemArrayUtils.cpp b/tests/test_ItemArrayUtils.cpp
index c08167d7ecef9bbb0592838c075624464c5c4a4a..8c832d16c1dac5ed7a84828303f68bceaf25c3d8 100644
--- a/tests/test_ItemArrayUtils.cpp
+++ b/tests/test_ItemArrayUtils.cpp
@@ -20,7 +20,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -294,7 +294,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -568,7 +568,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -845,7 +845,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -874,7 +874,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -903,7 +903,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -935,7 +935,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -964,7 +964,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -993,7 +993,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -1025,7 +1025,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -1054,7 +1054,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_2d = named_mesh.mesh();
@@ -1108,7 +1108,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_ItemValue.cpp b/tests/test_ItemValue.cpp
index 702110f7a906654bb5e60676e1e0e2d4c7a7fc03..4271db6c25f21dc7aa7db61d93d9280a22aaf561 100644
--- a/tests/test_ItemValue.cpp
+++ b/tests/test_ItemValue.cpp
@@ -28,7 +28,7 @@ TEST_CASE("ItemValue", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -61,7 +61,7 @@ TEST_CASE("ItemValue", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -90,7 +90,7 @@ TEST_CASE("ItemValue", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -114,7 +114,7 @@ TEST_CASE("ItemValue", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -144,7 +144,7 @@ TEST_CASE("ItemValue", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -182,7 +182,7 @@ TEST_CASE("ItemValue", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -257,7 +257,7 @@ TEST_CASE("ItemValue", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -287,7 +287,7 @@ TEST_CASE("ItemValue", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -304,7 +304,7 @@ TEST_CASE("ItemValue", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -313,7 +313,7 @@ TEST_CASE("ItemValue", "[mesh]")
 
           std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-          for (auto named_mesh : mesh_list) {
+          for (const auto& named_mesh : mesh_list) {
             SECTION(named_mesh.name())
             {
               auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_ItemValueUtils.cpp b/tests/test_ItemValueUtils.cpp
index 42ed106fde72ca0a04c05ab25956c5807475f51c..755392515ae3078421375f71af8727aa8bff144e 100644
--- a/tests/test_ItemValueUtils.cpp
+++ b/tests/test_ItemValueUtils.cpp
@@ -18,7 +18,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -71,7 +71,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -98,7 +98,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -125,7 +125,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -155,7 +155,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -182,7 +182,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -209,7 +209,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -239,7 +239,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -268,7 +268,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + "for size_t data")
         {
           auto mesh_2d = named_mesh.mesh();
@@ -321,7 +321,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_ItemValueVariantFunctionInterpoler.cpp b/tests/test_ItemValueVariantFunctionInterpoler.cpp
index 8689b1086a816b10552b4b8c6f6ae78bcced7670..19f53e1085fb058960737b2543c2892e8d6d9fb2 100644
--- a/tests/test_ItemValueVariantFunctionInterpoler.cpp
+++ b/tests/test_ItemValueVariantFunctionInterpoler.cpp
@@ -43,7 +43,7 @@ TEST_CASE("ItemValueVariantFunctionInterpoler", "[scheme]")
 
     std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_1d = named_mesh.mesh();
@@ -323,7 +323,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -442,7 +442,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_MathModule.cpp b/tests/test_MathModule.cpp
index 99966d4bacebb938fce5bc686661b5c53e8d708e..2cb3481b1e6cde7561733d8ea03decd46153db9d 100644
--- a/tests/test_MathModule.cpp
+++ b/tests/test_MathModule.cpp
@@ -13,7 +13,7 @@ TEST_CASE("MathModule", "[language]")
   MathModule math_module;
   const auto& name_builtin_function = math_module.getNameBuiltinFunctionMap();
 
-  REQUIRE(name_builtin_function.size() == 30);
+  REQUIRE(name_builtin_function.size() == 42);
 
   SECTION("Z -> N")
   {
@@ -457,4 +457,210 @@ TEST_CASE("MathModule", "[language]")
       REQUIRE(std::get<double>(result_variant) == result);
     }
   }
+
+  SECTION("R^dxd -> double")
+  {
+    SECTION("det:R^1x1")
+    {
+      TinyMatrix<1> arg{3};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("det:R^1x1");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const double result = det(arg);
+      REQUIRE(std::get<double>(result_variant) == result);
+    }
+
+    SECTION("det:R^2x2")
+    {
+      TinyMatrix<2> arg{3, 2, 1, -2};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("det:R^2x2");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const double result = det(arg);
+      REQUIRE(std::get<double>(result_variant) == result);
+    }
+
+    SECTION("det:R^3x3")
+    {
+      TinyMatrix<3> arg{3,  2,  4,   //
+                        -1, 4,  1,   //
+                        -4, -1, 5};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("det:R^3x3");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const double result = det(arg);
+      REQUIRE(std::get<double>(result_variant) == result);
+    }
+
+    SECTION("trace:R^1x1")
+    {
+      TinyMatrix<1> arg{3};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("trace:R^1x1");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const double result = trace(arg);
+      REQUIRE(std::get<double>(result_variant) == result);
+    }
+
+    SECTION("trace:R^2x2")
+    {
+      TinyMatrix<2> arg{3, 2, 1, -2};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("trace:R^2x2");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const double result = trace(arg);
+      REQUIRE(std::get<double>(result_variant) == result);
+    }
+
+    SECTION("trace:R^3x3")
+    {
+      TinyMatrix<3> arg{3,  2,  4,   //
+                        -1, 4,  1,   //
+                        -4, -1, 5};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("trace:R^3x3");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const double result = trace(arg);
+      REQUIRE(std::get<double>(result_variant) == result);
+    }
+  }
+
+  SECTION("R^dxd -> R^dxd")
+  {
+    SECTION("inverse:R^1x1")
+    {
+      TinyMatrix<1> arg{3};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("inverse:R^1x1");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const TinyMatrix<1> result = inverse(arg);
+      REQUIRE(std::get<TinyMatrix<1>>(result_variant) == result);
+    }
+
+    SECTION("inverse:R^2x2")
+    {
+      TinyMatrix<2> arg{3, 2, 1, -2};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("inverse:R^2x2");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const TinyMatrix<2> result = inverse(arg);
+      REQUIRE(std::get<TinyMatrix<2>>(result_variant) == result);
+    }
+
+    SECTION("inverse:R^3x3")
+    {
+      TinyMatrix<3> arg{3,  2,  4,   //
+                        -1, 4,  1,   //
+                        -4, -1, 5};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("inverse:R^3x3");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const TinyMatrix<3> result = inverse(arg);
+      REQUIRE(std::get<TinyMatrix<3>>(result_variant) == result);
+    }
+
+    SECTION("transpose:R^1x1")
+    {
+      TinyMatrix<1> arg{3};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("transpose:R^1x1");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const TinyMatrix<1> result = transpose(arg);
+      REQUIRE(std::get<TinyMatrix<1>>(result_variant) == result);
+    }
+
+    SECTION("transpose:R^2x2")
+    {
+      TinyMatrix<2> arg{3, 2, 1, -2};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("transpose:R^2x2");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const TinyMatrix<2> result = transpose(arg);
+      REQUIRE(std::get<TinyMatrix<2>>(result_variant) == result);
+    }
+
+    SECTION("transpose:R^3x3")
+    {
+      TinyMatrix<3> arg{3,  2,  4,   //
+                        -1, 4,  1,   //
+                        -4, -1, 5};
+
+      DataVariant arg_variant = arg;
+
+      auto i_function = name_builtin_function.find("transpose:R^3x3");
+      REQUIRE(i_function != name_builtin_function.end());
+
+      IBuiltinFunctionEmbedder& function_embedder = *i_function->second;
+      DataVariant result_variant                  = function_embedder.apply({arg_variant});
+
+      const TinyMatrix<3> result = transpose(arg);
+      REQUIRE(std::get<TinyMatrix<3>>(result_variant) == result);
+    }
+  }
 }
diff --git a/tests/test_MedianDualConnectivityBuilder.cpp b/tests/test_MedianDualConnectivityBuilder.cpp
index a95e6ad22f8a9b2605dec5c28e7c7704d40aed6f..6d091bd037e4ac57635757b98671eff9fe6eb643 100644
--- a/tests/test_MedianDualConnectivityBuilder.cpp
+++ b/tests/test_MedianDualConnectivityBuilder.cpp
@@ -5,6 +5,7 @@
 #include <mesh/DualConnectivityManager.hpp>
 
 #include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
 
@@ -50,6 +51,8 @@ TEST_CASE("MedianDualConnectivityBuilder", "[mesh]")
       DualConnectivityManager::instance().getMedianDualConnectivity(primal_connectivity);
     const ConnectivityType& dual_connectivity = *p_median_dual_connectivity;
 
+    REQUIRE(checkConnectivityOrdering(dual_connectivity));
+
     REQUIRE(dual_connectivity.numberOfNodes() == 192);
     REQUIRE(dual_connectivity.numberOfFaces() == 244);
     REQUIRE(dual_connectivity.numberOfCells() == 53);
diff --git a/tests/test_MeshEdgeBoundary.cpp b/tests/test_MeshEdgeBoundary.cpp
index 32c1d1c6e33c2c109b5c18f67172e3f15136f0f9..07711f27dc7aad4c4da9a18dafb2e8574e5e1cc6 100644
--- a/tests/test_MeshEdgeBoundary.cpp
+++ b/tests/test_MeshEdgeBoundary.cpp
@@ -72,7 +72,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor named_boundary_descriptor(name);
           const auto& edge_boundary = getMeshEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -105,7 +105,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor named_boundary_descriptor(name);
           const auto& edge_boundary = getMeshEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -145,7 +145,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& edge_boundary = getMeshEdgeBoundary(mesh, numbered_boundary_descriptor);
 
@@ -177,7 +177,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "YMIN", "XMAX", "YMIN"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& edge_boundary = getMeshEdgeBoundary(mesh, numbered_boundary_descriptor);
 
@@ -217,7 +217,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& edge_boundary = getMeshEdgeBoundary(mesh, numbered_boundary_descriptor);
 
@@ -249,7 +249,7 @@ TEST_CASE("MeshEdgeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& edge_boundary = getMeshEdgeBoundary(mesh, numbered_boundary_descriptor);
 
diff --git a/tests/test_MeshFaceBoundary.cpp b/tests/test_MeshFaceBoundary.cpp
index 472427aaf0c7f9c85f566dd610020e91759aad84..aa4142d6b5fc9ca64c3f64f5cec9ed3a2b1fc993 100644
--- a/tests/test_MeshFaceBoundary.cpp
+++ b/tests/test_MeshFaceBoundary.cpp
@@ -72,7 +72,7 @@ TEST_CASE("MeshFaceBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor named_boundary_descriptor(name);
           const auto& face_boundary = getMeshFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -105,7 +105,7 @@ TEST_CASE("MeshFaceBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor named_boundary_descriptor(name);
           const auto& face_boundary = getMeshFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -145,7 +145,7 @@ TEST_CASE("MeshFaceBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& face_boundary = getMeshFaceBoundary(mesh, numbered_boundary_descriptor);
 
@@ -177,7 +177,7 @@ TEST_CASE("MeshFaceBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "YMIN", "XMAX", "YMIN"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& face_boundary = getMeshFaceBoundary(mesh, numbered_boundary_descriptor);
 
@@ -217,7 +217,7 @@ TEST_CASE("MeshFaceBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& face_boundary = getMeshFaceBoundary(mesh, numbered_boundary_descriptor);
 
@@ -249,7 +249,7 @@ TEST_CASE("MeshFaceBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& face_boundary = getMeshFaceBoundary(mesh, numbered_boundary_descriptor);
 
diff --git a/tests/test_MeshFlatEdgeBoundary.cpp b/tests/test_MeshFlatEdgeBoundary.cpp
index 94700598238232b1e2a5e98692f57291f568dc39..16d843ade242d9105530fb04cfdbf4094085955e 100644
--- a/tests/test_MeshFlatEdgeBoundary.cpp
+++ b/tests/test_MeshFlatEdgeBoundary.cpp
@@ -95,7 +95,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -158,7 +158,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -250,7 +250,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -346,7 +346,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -494,7 +494,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -669,7 +669,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -817,7 +817,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -898,7 +898,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1005,7 +1005,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1100,7 +1100,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1203,7 +1203,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1317,7 +1317,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1428,7 +1428,7 @@ TEST_CASE("MeshFlatEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshFlatEdgeBoundary(mesh, named_boundary_descriptor);
 
diff --git a/tests/test_MeshFlatFaceBoundary.cpp b/tests/test_MeshFlatFaceBoundary.cpp
index e01d79d1dc456f6f193d42db86c4f1f5ce08b75f..08e1779ce777bb4c6c3600bfd744c063f3359df5 100644
--- a/tests/test_MeshFlatFaceBoundary.cpp
+++ b/tests/test_MeshFlatFaceBoundary.cpp
@@ -95,7 +95,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -158,7 +158,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -239,7 +239,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -313,7 +313,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -406,7 +406,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -493,7 +493,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -597,7 +597,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -678,7 +678,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -785,7 +785,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -880,7 +880,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -983,7 +983,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -1097,7 +1097,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -1208,7 +1208,7 @@ TEST_CASE("MeshFlatFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshFlatFaceBoundary(mesh, named_boundary_descriptor);
 
diff --git a/tests/test_MeshFlatNodeBoundary.cpp b/tests/test_MeshFlatNodeBoundary.cpp
index 29b0110569a14a6612a8b50cc76bc892c476d1ad..9b9169f73c4306f9591ba0a9c0f5c1c622f7ef69 100644
--- a/tests/test_MeshFlatNodeBoundary.cpp
+++ b/tests/test_MeshFlatNodeBoundary.cpp
@@ -95,7 +95,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -158,7 +158,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -250,7 +250,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -346,7 +346,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -494,7 +494,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -669,7 +669,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -817,7 +817,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -898,7 +898,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1005,7 +1005,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1100,7 +1100,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1203,7 +1203,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1317,7 +1317,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1428,7 +1428,7 @@ TEST_CASE("MeshFlatNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "ZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshFlatNodeBoundary(mesh, named_boundary_descriptor);
 
diff --git a/tests/test_MeshLineEdgeBoundary.cpp b/tests/test_MeshLineEdgeBoundary.cpp
index b4b21d0dc7e692216f80d0119f97d423518eb884..31d9a578b956279ce52fdea6d165de1f13250a6d 100644
--- a/tests/test_MeshLineEdgeBoundary.cpp
+++ b/tests/test_MeshLineEdgeBoundary.cpp
@@ -114,7 +114,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -210,7 +210,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -487,7 +487,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
                                                   "XMINZMIN", "XMINZMAX", "XMAXZMAX", "XMINZMAX",
                                                   "YMINZMIN", "YMINZMAX", "YMAXZMAX", "YMAXZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -617,7 +617,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -698,7 +698,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -804,7 +804,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
                                                   "XMINZMIN", "XMINZMAX", "XMAXZMAX", "XMINZMAX",
                                                   "YMINZMIN", "YMINZMAX", "YMAXZMAX", "YMAXZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -892,7 +892,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
                                                   "XMINZMIN", "XMINZMAX", "XMAXZMAX", "XMINZMAX",
                                                   "YMINZMIN", "YMINZMAX", "YMAXZMAX", "YMAXZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -989,7 +989,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1105,7 +1105,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
           const std::set<std::string> name_set = {"XMINYMIN", "XMINYMAX", "XMAXYMIN", "XMAXYMAX", "XMINZMIN",
                                                   "XMINZMAX", "XMAXZMAX", "XMINZMAX", "YMINZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
@@ -1218,7 +1218,7 @@ TEST_CASE("MeshLineEdgeBoundary", "[mesh]")
           const std::set<std::string> name_set = {"XMINYMIN", "XMINYMAX", "XMAXYMIN", "XMAXYMAX", "XMINZMIN",
                                                   "XMINZMAX", "XMAXZMAX", "XMINZMAX", "YMINZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& edge_boundary = getMeshLineEdgeBoundary(mesh, named_boundary_descriptor);
 
diff --git a/tests/test_MeshLineFaceBoundary.cpp b/tests/test_MeshLineFaceBoundary.cpp
index f5cb9eb18714f668d63a0a4c2fd40a04b7cbeee9..86150aa40cb5fb82fd8718b56551dc7b52017c1d 100644
--- a/tests/test_MeshLineFaceBoundary.cpp
+++ b/tests/test_MeshLineFaceBoundary.cpp
@@ -114,7 +114,7 @@ TEST_CASE("MeshLineFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshLineFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -210,7 +210,7 @@ TEST_CASE("MeshLineFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshLineFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -321,7 +321,7 @@ TEST_CASE("MeshLineFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshLineFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -402,7 +402,7 @@ TEST_CASE("MeshLineFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshLineFaceBoundary(mesh, named_boundary_descriptor);
 
@@ -501,7 +501,7 @@ TEST_CASE("MeshLineFaceBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& face_boundary = getMeshLineFaceBoundary(mesh, named_boundary_descriptor);
 
diff --git a/tests/test_MeshLineNodeBoundary.cpp b/tests/test_MeshLineNodeBoundary.cpp
index 36c151e2d4ddf14e21ad5defed6774a06cf6394e..f82f69b142df991b8c757ba2707be0bc045610d3 100644
--- a/tests/test_MeshLineNodeBoundary.cpp
+++ b/tests/test_MeshLineNodeBoundary.cpp
@@ -114,7 +114,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -210,7 +210,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -487,7 +487,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
                                                   "XMINZMIN", "XMINZMAX", "XMAXZMAX", "XMINZMAX",
                                                   "YMINZMIN", "YMINZMAX", "YMAXZMAX", "YMAXZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -617,7 +617,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -698,7 +698,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN", "YMAX"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -804,7 +804,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
                                                   "XMINZMIN", "XMINZMAX", "XMAXZMAX", "XMINZMAX",
                                                   "YMINZMIN", "YMINZMAX", "YMAXZMAX", "YMAXZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -892,7 +892,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
                                                   "XMINZMIN", "XMINZMAX", "XMAXZMAX", "XMINZMAX",
                                                   "YMINZMIN", "YMINZMAX", "YMAXZMAX", "YMAXZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -989,7 +989,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
         {
           const std::set<std::string> name_set = {"XMIN", "XMAX", "YMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1105,7 +1105,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
           const std::set<std::string> name_set = {"XMINYMIN", "XMINYMAX", "XMAXYMIN", "XMAXYMAX", "XMINZMIN",
                                                   "XMINZMAX", "XMAXZMAX", "XMINZMAX", "YMINZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -1218,7 +1218,7 @@ TEST_CASE("MeshLineNodeBoundary", "[mesh]")
           const std::set<std::string> name_set = {"XMINYMIN", "XMINYMAX", "XMAXYMIN", "XMAXYMAX", "XMINZMIN",
                                                   "XMINZMAX", "XMAXZMAX", "XMINZMAX", "YMINZMIN"};
 
-          for (auto name : name_set) {
+          for (const auto& name : name_set) {
             NamedBoundaryDescriptor named_boundary_descriptor(name);
             const auto& node_boundary = getMeshLineNodeBoundary(mesh, named_boundary_descriptor);
 
diff --git a/tests/test_MeshNodeBoundary.cpp b/tests/test_MeshNodeBoundary.cpp
index 2639e6370f9681024f0dab87367468a1df2a5002..c09276cee007b7ac073dae1aae80760c6e887d3c 100644
--- a/tests/test_MeshNodeBoundary.cpp
+++ b/tests/test_MeshNodeBoundary.cpp
@@ -72,7 +72,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor named_boundary_descriptor(name);
           const auto& node_boundary = getMeshNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -105,7 +105,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
       {
         const std::set<std::string> name_set = {"XMIN", "XMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor named_boundary_descriptor(name);
           const auto& node_boundary = getMeshNodeBoundary(mesh, named_boundary_descriptor);
 
@@ -146,7 +146,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
         const std::set<std::string> name_set = {"XMIN",     "XMAX",     "YMIN",     "YMAX",
                                                 "XMINYMIN", "XMINYMAX", "XMAXYMIN", "XMAXYMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& node_boundary = getMeshNodeBoundary(mesh, numbered_boundary_descriptor);
 
@@ -179,7 +179,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
         const std::set<std::string> name_set = {"XMIN",     "YMIN",     "XMAX",     "YMIN",
                                                 "XMINYMIN", "XMINYMAX", "XMAXYMIN", "XMAXYMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& node_boundary = getMeshNodeBoundary(mesh, numbered_boundary_descriptor);
 
@@ -227,7 +227,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
                                                 "XMINYMINZMIN", "XMINYMINZMAX", "XMINYMAXZMIN", "XMINYMAXZMAX",
                                                 "XMAXYMINZMIN", "XMAXYMINZMAX", "XMAXYMAXZMIN", "XMAXYMAXZMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& node_boundary = getMeshNodeBoundary(mesh, numbered_boundary_descriptor);
 
@@ -267,7 +267,7 @@ TEST_CASE("MeshNodeBoundary", "[mesh]")
                                                 "XMINYMINZMIN", "XMINYMINZMAX", "XMINYMAXZMIN", "XMINYMAXZMAX",
                                                 "XMAXYMINZMIN", "XMAXYMINZMAX", "XMAXYMAXZMIN", "XMAXYMAXZMAX"};
 
-        for (auto name : name_set) {
+        for (const auto& name : name_set) {
           NamedBoundaryDescriptor numbered_boundary_descriptor(name);
           const auto& node_boundary = getMeshNodeBoundary(mesh, numbered_boundary_descriptor);
 
diff --git a/tests/test_SubItemArrayPerItem.cpp b/tests/test_SubItemArrayPerItem.cpp
index aa8930269fcd03756cf69495ef4f77bc44869e62..ec4e06e0c31da2906333bd8a37ea390e7ee8d9c3 100644
--- a/tests/test_SubItemArrayPerItem.cpp
+++ b/tests/test_SubItemArrayPerItem.cpp
@@ -66,7 +66,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -189,7 +189,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -355,7 +355,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -552,7 +552,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -625,7 +625,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -697,7 +697,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -769,7 +769,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -880,7 +880,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -991,7 +991,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -1087,7 +1087,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_SubItemArrayPerItemUtils.cpp b/tests/test_SubItemArrayPerItemUtils.cpp
index d3f29a3814fb88cc1fe75c06214dd463ac406525..e534ec34600ef526336249a4b7d1cfc794721827 100644
--- a/tests/test_SubItemArrayPerItemUtils.cpp
+++ b/tests/test_SubItemArrayPerItemUtils.cpp
@@ -18,7 +18,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -90,7 +90,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -122,7 +122,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -154,7 +154,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -189,7 +189,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -221,7 +221,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -253,7 +253,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -289,7 +289,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -319,7 +319,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_2d = named_mesh.mesh();
@@ -375,7 +375,7 @@ TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_SubItemValuePerItem.cpp b/tests/test_SubItemValuePerItem.cpp
index 65741d70089785bb58b34230569afcd33a961c62..349de725023c5156bb05f75265d61d589cc48799 100644
--- a/tests/test_SubItemValuePerItem.cpp
+++ b/tests/test_SubItemValuePerItem.cpp
@@ -76,7 +76,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -212,7 +212,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -388,7 +388,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -593,7 +593,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -709,7 +709,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -895,7 +895,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -1116,7 +1116,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -1161,7 +1161,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -1205,7 +1205,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -1251,7 +1251,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_3d = named_mesh.mesh();
@@ -1350,7 +1350,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -1439,7 +1439,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_SubItemValuePerItemUtils.cpp b/tests/test_SubItemValuePerItemUtils.cpp
index ccda4722d7f5ac6c56bacb0d94c2ed0689166eec..aca9a14498e447b1cd6ffd21411f45e847b34ac5 100644
--- a/tests/test_SubItemValuePerItemUtils.cpp
+++ b/tests/test_SubItemValuePerItemUtils.cpp
@@ -18,7 +18,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
   {
     std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-    for (auto named_mesh : mesh_list) {
+    for (const auto& named_mesh : mesh_list) {
       SECTION(named_mesh.name())
       {
         auto mesh_2d = named_mesh.mesh();
@@ -85,7 +85,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -115,7 +115,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -145,7 +145,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -178,7 +178,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -208,7 +208,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_2d = named_mesh.mesh();
@@ -238,7 +238,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_3d = named_mesh.mesh();
@@ -273,7 +273,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name())
         {
           auto mesh_1d = named_mesh.mesh();
@@ -302,7 +302,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_2d = named_mesh.mesh();
@@ -356,7 +356,7 @@ TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
     {
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
-      for (auto named_mesh : mesh_list) {
+      for (const auto& named_mesh : mesh_list) {
         SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_3d = named_mesh.mesh();
diff --git a/tests/test_TinyMatrix.cpp b/tests/test_TinyMatrix.cpp
index 2f4c09d4ad9ce1dbb5e3d856d8737d27344459f2..f6f09cf55dd2ad1ffef55c9275b989c203598f7e 100644
--- a/tests/test_TinyMatrix.cpp
+++ b/tests/test_TinyMatrix.cpp
@@ -194,6 +194,17 @@ TEST_CASE("TinyMatrix", "[algebra]")
     REQUIRE(det(TinyMatrix<4>(1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 0, 0, 2, 2)) == 0);
   }
 
+  SECTION("checking for trace calculations")
+  {
+    REQUIRE(trace(TinyMatrix<1, 1, int>(6)) == 6);
+    REQUIRE(trace(TinyMatrix<2, 2, int>(5, 1, -3, 6)) == 5 + 6);
+    REQUIRE(trace(TinyMatrix<3, 3, int>(1, 1, 1, 1, 2, 1, 2, 1, 3)) == 1 + 2 + 3);
+    REQUIRE(trace(TinyMatrix<3, 3, int>(6, 5, 3, 8, 34, 6, 35, 6, 7)) == 6 + 34 + 7);
+    REQUIRE(trace(TinyMatrix<4>(1, 2.3, 7, -6.2, 3, 4, 9, 1, 4.1, 5, 2, -3, 2, 27, 3, 17.5)) ==
+            Catch::Approx(1 + 4 + 2 + 17.5).epsilon(1E-14));
+    REQUIRE(trace(TinyMatrix<4>(1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 2, 0, 0, 2, 2)) == 1 + 0 + 1 + 2);
+  }
+
   SECTION("checking for inverse calculations")
   {
     {