diff --git a/doc/userdoc.org b/doc/userdoc.org
index 12ce427db3bdd792588d6e32a12c9b0af980c02e..a91d0d5b850289bb953ab1d172d03bbbaea3e919 100644
--- a/doc/userdoc.org
+++ b/doc/userdoc.org
@@ -2236,20 +2236,18 @@ follows the similar rules.
 Let us give an example
 #+NAME: functions-lifetime
 #+BEGIN_SRC pugs :exports both :results output
-  let f: R -> R, x -> 2.3*x+2;
   {
-    let f: R -> R, x -> 1.25*x+3;
-    cout << "block 1:  f(2) = " << f(2) << "\n";
     {
       let f: R -> R, x -> 3.25*x-0.3;
-      cout << "block 2:  f(2) = " << f(2) << "\n";
+      cout << "block 1.1:  f(2) = " << f(2) << "\n";
     }
+    let f: R -> R, x -> 1.25*x+3;
+    cout << "block 1:  f(2) = " << f(2) << "\n";
   }
   for (let i:N, i = 1; i<=2; ++i) {
     let f: R -> R, x -> i*x+2;
     cout << "for(i=" << i << "): f(2) = " << f(2) << "\n";
   }
-  cout << "global:   f(2) = " << f(2) << "\n";
 #+END_SRC
 Running this example produces
 #+results: functions-lifetime
@@ -2627,6 +2625,41 @@ The information produced concerns
 - the number of edges and their references,
 - and the number of nodes and their references.
 
+
+***** ~item_type~
+
+This type is used to designate kinds of items (cell, face, edge or node).
+
+***** ~item_value~
+
+The type ~item_value~ is an abstract type use to designate arrays of
+values defined on the entities of a ~mesh~. Actually, these values are
+not associated to the mesh itself but on the *connectivity*. The values
+on the entities can be of type ~B~, ~N~, ~Z~, ~R~, ~R^1~, ~R^2~, ~R^3~, ~R^1x1~, ~R^2x2~
+and ~R^3x3~. Entities themselves can be cells, faces, edges or nodes.
+
+These variables are used to pass data from one function to another.
+
+#+BEGIN_warning
+By now, no mathematical operation is defined on ~item_value~ variables.
+#+END_warning
+
+Moreover, ~item_value~ variables can be post processed. Observe that
+edges or faces values cannot be post processed since neither ~VTK~ nor
+~Gnuplot~ can handle these data.
+
+***** ~sub_item_value~
+
+This abstract type is handle values defined on the sub items of the
+items of a ~mesh~. Similarly these values are attached to a *connectivity*
+and not to a mesh. Values can bed of type The values on the entities
+can be of type ~B~, ~N~, ~Z~, ~R~, ~R^1~, ~R^2~ or ~R^3~. An example of
+~sub_item_value~ is the $\mathbf{C}_{jr}$ vectors which are defined at each
+node of each cell.
+
+These variables are used to pass data from one function to
+another. They cannot be post processed.
+
 **** ~mesh~ provided functions
 
 ***** Boundary descriptor functions
@@ -2897,6 +2930,35 @@ The median dual mesh construction is not yet implemented in 3d and not
 available in parallel
 #+END_warning
 
+***** Item types
+The following functions are used to refer to ~item_type~
+- ~cell: void -> item_type~
+- ~face: void -> item_type~
+- ~edge: void -> item_type~
+- ~node: void -> item_type~
+
+***** ~interpolate: mesh*item_type*function -> item_value~
+
+This function is used to create an ~item_value~ by interpolating a user
+function to a given ~item_type~ using a mesh. The interpolation takes
+place at the center of the items.
+
+#+BEGIN_SRC pugs :exports both :results none
+  import mesh;
+
+  let m:mesh, m = cartesianMesh([0,0], [1,1], (10,10));
+  let f:R^2 -> R, x -> 2*x[0]-x[1];
+
+  let node_values:item_value, node_values = interpolate(m, node(), f);
+  let face_values:item_value, face_values = interpolate(m, face(), f);
+  let cell_values:item_value, cell_values = interpolate(m, cell(), f);
+#+END_SRC
+In this example, we set three arrays defined at all nodes, all the
+ faces and all the cells of the underlying connectivity of the mesh ~m~.
+- ~node_values~ interpolates ~f~ at the nodes of ~m~,
+- ~face_values~ interpolates ~f~ at the centers of the faces of ~m~,
+- ~cell_values~ interpolates ~f~ at the centers of the cells of ~m~.
+
 ***** ~transform: mesh*function -> mesh~
 
 This function allows to compute a new mesh as the transformation of a
@@ -3426,6 +3488,8 @@ vector function.
 Here we defined a $\vec{\mathbb{P}}_0(\mathbb{R})$ discrete function of
 dimension 3.
 
+
+
 ****** ~integrate: mesh*quadrature*function -> Vh~ <<integrate-classic>>
 
 This function integrates the given user function in each cell of a
@@ -4293,6 +4357,96 @@ of ~fh~.
 #+ATTR_HTML: :width 300px;
 #+RESULTS: writer-example2-2-img
 
+***** ~name_output: item_value*string -> output~
+
+This function give a name to an ~item_value~.
+
+#+BEGIN_SRC pugs :exports both :results none
+  import mesh;
+  import writer;
+
+  let m:mesh, m = cartesianMesh([0], [1], 100);
+
+  let x2:R^1 -> R, x -> x[0]*x[0];
+  let cell_values:item_value, cell_values = interpolate(m, node(), x2);
+
+  write(gnuplot_writer("writer-item-value-example1"), m, name_output(cell_values, "x^2"));
+#+END_SRC
+
+#+NAME: writer-item-value-example1-img
+#+BEGIN_SRC gnuplot :exports results :file (substitute-in-file-name "${PUGS_SOURCE_DIR}/doc/writer-item-value-example1.png")
+  reset
+  set grid
+  set border
+  unset key
+  set xtics
+  set ytics
+  set square
+  set terminal png truecolor enhanced size 640,640
+  plot '<(sed "" $PUGS_SOURCE_DIR/doc/writer-item-value-example1.gnu)' lw 2 w l
+#+END_SRC
+
+#+CAPTION: Simple illustration of the output.
+#+NAME: fig:writer-item-value-example1
+#+ATTR_LATEX: :width 0.38\textwidth
+#+ATTR_HTML: :width 300px;
+#+RESULTS: writer-item-value-example1-img
+
+
+#+BEGIN_warning
+One observes that we supplied a mesh to the writer function. The
+reason for that is that ~item_value~ refers a connectivity. It requires
+a ~mesh~ with the same connectivity to be written.
+
+However, is a discrete function (of type ~Vh~) build on the same
+connectivity is provided, there is no need to specify the ~mesh~.
+#+END_warning
+
+Let us illustrate it by an important second example.
+
+#+BEGIN_SRC pugs :exports both :results none
+  import mesh;
+  import scheme;
+  import writer;
+  import math;
+
+  let m:mesh, m = cartesianMesh([0], [1], 100);
+
+  let f:R^1 -> R, x -> 2*x[0]*x[0];
+  let g:R^1 -> R, x -> pow(sin(5*x[0]*x[0]),2);
+
+  let fh:Vh, fh = interpolate(m, P0(), f);
+  let cell_value:item_value, cell_value = interpolate(m, cell(), g);
+
+  let output_list:(output),
+      output_list = (name_output(fh, "f"),
+                     name_output(cell_value, "g"));
+
+  write(gnuplot_1d_writer("writer-item-value-example2"), output_list);
+#+END_SRC
+
+Running this code produces the gnuplot file displayed in Figure
+[[fig:writer-item-value-example2]].
+
+#+NAME: writer-item-value-example2-img
+#+BEGIN_SRC gnuplot :exports results :file (substitute-in-file-name "${PUGS_SOURCE_DIR}/doc/writer-item-value-example2.png")
+  reset
+  set grid
+  set border
+  set key
+  set xtics
+  set ytics
+  set square
+  set terminal png truecolor enhanced size 640,640
+  plot '<(sed "" $PUGS_SOURCE_DIR/doc/writer-item-value-example2.gnu)' u 1:2 lw 2 t "f" w l, '<(sed "" $PUGS_SOURCE_DIR/doc/writer-item-value-example2.gnu)' u 1:3 lw 2 t "g" w l
+#+END_SRC
+
+#+CAPTION: The ~item_value~ is plotted on the same mesh as ~fh~.
+#+NAME: fig:writer-item-value-example2
+#+ATTR_LATEX: :width 0.38\textwidth
+#+ATTR_HTML: :width 300px;
+#+RESULTS: writer-item-value-example2-img
+
 ***** ~gnuplot~ writers <<gnuplot-writers>>
 
 There are two ~gnuplot~ writers. One is dedicated to output of dimension
@@ -4652,6 +4806,14 @@ Trying to use variables defined on different meshes
 gives the runtime error
 #+results: cannot-use-diffrent-meshes-in-writer
 
+****** ~write: writer*mesh*(output) -> void~
+
+This variation permits to specify a ~mesh~. Its main utility is to allow
+to write data in the case where ~output~ list contains only ~item_value~
+data which are not related to a mesh but to a *connectivity*. One checks
+that the connectivity of the ~output~ data are the same as the
+connectivity of the ~mesh~.
+
 ****** ~write: writer*(output)*R -> void~
 
 This ~write~ function is used to save time series data. The real
@@ -4680,6 +4842,13 @@ Trying to use a non time series ~writer~
 gives the runtime error
 #+results: cannot-use-non-time-series-writer
 
+****** ~write: writer*mesh*(output)*R -> void~
+
+This is a variation of the previous function where a mesh is
+specified. The function checks the validity of the ~output~ list:
+functions ~Vh~ must defined on the given ~mesh~ and ~item_value~ data mush
+share the same connectivity with the ~mesh~.
+
 ****** ~force_write: writer*(output)*R -> void~
 
 One probably noticed that using the ~write~ function with a time series
@@ -4744,6 +4913,11 @@ times:
 #+RESULTS: times-in-gp-1d-series-force
 The last post processing time is now 1.
 
+****** ~force_write: writer*mesh*(output)*R -> void~
+
+This variation permits to specify a ~mesh~. Compatibility of output data
+with the ~mesh~ is checked.
+
 ****** ~write_mesh: writer*mesh -> void~ <<write-mesh>>.
 
 This function just saves a ~mesh~ using a ~writer~. The ~gnuplot_1d_writer~
diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 4fc75f4f2b61058b339c748f822720107c92dd1a..9c5cf3aec1e11d16f652357db53f68db54f6ed79 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -5,6 +5,7 @@
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/FunctionTable.hpp>
+#include <language/utils/ItemValueVariantFunctionInterpoler.hpp>
 #include <language/utils/OStream.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <language/utils/SymbolTable.hpp>
@@ -15,6 +16,7 @@
 #include <mesh/GmshReader.hpp>
 #include <mesh/IBoundaryDescriptor.hpp>
 #include <mesh/IZoneDescriptor.hpp>
+#include <mesh/ItemValueVariant.hpp>
 #include <mesh/Mesh.hpp>
 #include <mesh/MeshRelaxer.hpp>
 #include <mesh/MeshTransformer.hpp>
@@ -22,6 +24,7 @@
 #include <mesh/NamedZoneDescriptor.hpp>
 #include <mesh/NumberedBoundaryDescriptor.hpp>
 #include <mesh/NumberedZoneDescriptor.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
 #include <utils/Exceptions.hpp>
 
 #include <Kokkos_Core.hpp>
@@ -32,6 +35,43 @@ MeshModule::MeshModule()
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>>);
 
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const ItemType>>);
+
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const ItemValueVariant>>);
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const SubItemValuePerItemVariant>>);
+
+  this->_addBuiltinFunction("cell", std::function(
+
+                                      []() -> std::shared_ptr<const ItemType> {
+                                        return std::make_shared<ItemType>(ItemType::cell);
+                                      }
+
+                                      ));
+
+  this->_addBuiltinFunction("face", std::function(
+
+                                      []() -> std::shared_ptr<const ItemType> {
+                                        return std::make_shared<ItemType>(ItemType::face);
+                                      }
+
+                                      ));
+
+  this->_addBuiltinFunction("edge", std::function(
+
+                                      []() -> std::shared_ptr<const ItemType> {
+                                        return std::make_shared<ItemType>(ItemType::edge);
+                                      }
+
+                                      ));
+
+  this->_addBuiltinFunction("node", std::function(
+
+                                      []() -> std::shared_ptr<const ItemType> {
+                                        return std::make_shared<ItemType>(ItemType::node);
+                                      }
+
+                                      ));
+
   this->_addBuiltinFunction("readGmsh", std::function(
 
                                           [](const std::string& file_name) -> std::shared_ptr<const IMesh> {
@@ -74,6 +114,16 @@ MeshModule::MeshModule()
 
                                              ));
 
+  this->_addBuiltinFunction("interpolate",
+                            std::function(
+
+                              [](std::shared_ptr<const IMesh> mesh, std::shared_ptr<const ItemType> item_type,
+                                 const FunctionSymbolId& function_id) -> std::shared_ptr<const ItemValueVariant> {
+                                return ItemValueVariantFunctionInterpoler{mesh, *item_type, function_id}.interpolate();
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("transform", std::function(
 
                                            [](std::shared_ptr<const IMesh> p_mesh,
diff --git a/src/language/modules/MeshModule.hpp b/src/language/modules/MeshModule.hpp
index 9047412586f24200789ebea289e9b8c4f0fbd27d..bf9f0b714b327a1216f7ec3e54fc98a3ec65d70f 100644
--- a/src/language/modules/MeshModule.hpp
+++ b/src/language/modules/MeshModule.hpp
@@ -20,6 +20,20 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("zone");
 
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemType>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_type");
+
+class ItemValueVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemValueVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_value");
+
+class SubItemValuePerItemVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemValuePerItemVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_value");
+
 class MeshModule : public BuiltinModule
 {
  public:
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index 01dcc66eb23da733c9b5d5922d8bdb55156d73ca..e3c6f1b96bf5ae660f7e3648ab70858edd4c20a3 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -288,6 +288,24 @@ 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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                                   bc_descriptor_list)
+                                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                                return AcousticSolverHandler{getCommonMesh({rho, c, u, p})}
+                                                  .solver()
+                                                  .compute_fluxes(AcousticSolverHandler::SolverType::Glace, rho, c, u,
+                                                                  p, bc_descriptor_list);
+                                              }
+
+                                              ));
+
   this->_addBuiltinFunction("glace_solver",
                             std::function(
 
@@ -302,14 +320,29 @@ SchemeModule::SchemeModule()
                                 -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
                                               std::shared_ptr<const IDiscreteFunction>,
                                               std::shared_ptr<const IDiscreteFunction>> {
-                                return AcousticSolverHandler{AcousticSolverHandler::SolverType::Glace,
-                                                             rho,
-                                                             c,
-                                                             u,
-                                                             p,
-                                                             bc_descriptor_list}
+                                return AcousticSolverHandler{getCommonMesh({rho, u, E, c, p})}
                                   .solver()
-                                  .apply(dt, rho, u, E);
+                                  .apply(AcousticSolverHandler::SolverType::Glace, dt, rho, u, E, c, p,
+                                         bc_descriptor_list);
+                              }
+
+                              ));
+
+  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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return AcousticSolverHandler{getCommonMesh({rho, c, u, p})}
+                                  .solver()
+                                  .compute_fluxes(AcousticSolverHandler::SolverType::Eucclhyd, rho, c, u, p,
+                                                  bc_descriptor_list);
                               }
 
                               ));
@@ -328,14 +361,29 @@ SchemeModule::SchemeModule()
                                 -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
                                               std::shared_ptr<const IDiscreteFunction>,
                                               std::shared_ptr<const IDiscreteFunction>> {
-                                return AcousticSolverHandler{AcousticSolverHandler::SolverType::Eucclhyd,
-                                                             rho,
-                                                             c,
-                                                             u,
-                                                             p,
-                                                             bc_descriptor_list}
+                                return AcousticSolverHandler{getCommonMesh({rho, u, E, c, p})}
+                                  .solver()
+                                  .apply(AcousticSolverHandler::SolverType::Eucclhyd, dt, rho, u, E, c, p,
+                                         bc_descriptor_list);
+                              }
+
+                              ));
+
+  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 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>> {
+                                return AcousticSolverHandler{getCommonMesh({rho, u, E})}   //
                                   .solver()
-                                  .apply(dt, rho, u, E);
+                                  .apply_fluxes(dt, rho, u, E, ur, Fjr);
                               }
 
                               ));
diff --git a/src/language/modules/WriterModule.cpp b/src/language/modules/WriterModule.cpp
index 89d25001b6f70ceaec5c8465718cbd01da32a928..6523d56a93d4fe0b4f953fb6065cd4a15be7330c 100644
--- a/src/language/modules/WriterModule.cpp
+++ b/src/language/modules/WriterModule.cpp
@@ -4,11 +4,13 @@
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/GmshReader.hpp>
+#include <mesh/ItemValueVariant.hpp>
 #include <mesh/Mesh.hpp>
 #include <output/GnuplotWriter.hpp>
 #include <output/GnuplotWriter1D.hpp>
 #include <output/IWriter.hpp>
 #include <output/NamedDiscreteFunction.hpp>
+#include <output/NamedItemValueVariant.hpp>
 #include <output/VTKWriter.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/IDiscreteFunction.hpp>
@@ -16,7 +18,7 @@
 
 WriterModule::WriterModule()
 {
-  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const NamedDiscreteFunction>>);
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const INamedDiscreteData>>);
 
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IWriter>>);
 
@@ -71,15 +73,24 @@ WriterModule::WriterModule()
 
                               ));
 
-  this->_addBuiltinFunction("name_output",
-                            std::function(
+  this->_addBuiltinFunction("name_output", std::function(
 
-                              [](std::shared_ptr<const IDiscreteFunction> discrete_function,
-                                 const std::string& name) -> std::shared_ptr<const NamedDiscreteFunction> {
-                                return std::make_shared<const NamedDiscreteFunction>(discrete_function, name);
-                              }
+                                             [](std::shared_ptr<const IDiscreteFunction> discrete_function,
+                                                const std::string& name) -> std::shared_ptr<const INamedDiscreteData> {
+                                               return std::make_shared<const NamedDiscreteFunction>(discrete_function,
+                                                                                                    name);
+                                             }
 
-                              ));
+                                             ));
+  this->_addBuiltinFunction("name_output", std::function(
+
+                                             [](std::shared_ptr<const ItemValueVariant> item_value_variant,
+                                                const std::string& name) -> std::shared_ptr<const INamedDiscreteData> {
+                                               return std::make_shared<const NamedItemValueVariant>(item_value_variant,
+                                                                                                    name);
+                                             }
+
+                                             ));
 
   this->_addBuiltinFunction("write_mesh",
                             std::function(
@@ -93,7 +104,7 @@ WriterModule::WriterModule()
   this->_addBuiltinFunction("write", std::function(
 
                                        [](std::shared_ptr<const IWriter> writer,
-                                          const std::vector<std::shared_ptr<const NamedDiscreteFunction>>&
+                                          const std::vector<std::shared_ptr<const INamedDiscreteData>>&
                                             named_discrete_function_list) -> void {
                                          writer->write(named_discrete_function_list);
                                        }
@@ -103,7 +114,7 @@ WriterModule::WriterModule()
   this->_addBuiltinFunction("write", std::function(
 
                                        [](std::shared_ptr<const IWriter> writer,
-                                          const std::vector<std::shared_ptr<const NamedDiscreteFunction>>&
+                                          const std::vector<std::shared_ptr<const INamedDiscreteData>>&
                                             named_discrete_function_list,
                                           const double& time) -> void {
                                          writer->writeIfNeeded(named_discrete_function_list, time);
@@ -114,13 +125,46 @@ WriterModule::WriterModule()
   this->_addBuiltinFunction("force_write", std::function(
 
                                              [](std::shared_ptr<const IWriter> writer,
-                                                const std::vector<std::shared_ptr<const NamedDiscreteFunction>>&
+                                                const std::vector<std::shared_ptr<const INamedDiscreteData>>&
                                                   named_discrete_function_list,
                                                 const double& time) -> void {
                                                writer->writeForced(named_discrete_function_list, time);
                                              }
 
                                              ));
+
+  this->_addBuiltinFunction("write", std::function(
+
+                                       [](std::shared_ptr<const IWriter> writer, std::shared_ptr<const IMesh> mesh,
+                                          const std::vector<std::shared_ptr<const INamedDiscreteData>>&
+                                            named_discrete_function_list) -> void {
+                                         writer->writeOnMesh(mesh, named_discrete_function_list);
+                                       }
+
+                                       ));
+
+  this->_addBuiltinFunction("write", std::function(
+
+                                       [](std::shared_ptr<const IWriter> writer, std::shared_ptr<const IMesh> mesh,
+                                          const std::vector<std::shared_ptr<const INamedDiscreteData>>&
+                                            named_discrete_function_list,
+                                          const double& time) -> void {
+                                         writer->writeOnMeshIfNeeded(mesh, named_discrete_function_list, time);
+                                       }
+
+                                       ));
+
+  this->_addBuiltinFunction("force_write",
+                            std::function(
+
+                              [](std::shared_ptr<const IWriter> writer, std::shared_ptr<const IMesh> mesh,
+                                 const std::vector<std::shared_ptr<const INamedDiscreteData>>&
+                                   named_discrete_function_list,
+                                 const double& time) -> void {
+                                writer->writeOnMeshForced(mesh, named_discrete_function_list, time);
+                              }
+
+                              ));
 }
 
 void
diff --git a/src/language/modules/WriterModule.hpp b/src/language/modules/WriterModule.hpp
index adcd92530298a99f088743e7ae6583bc09faa93c..f61eafb0e4847c248e9def011bf269b3b9a4022e 100644
--- a/src/language/modules/WriterModule.hpp
+++ b/src/language/modules/WriterModule.hpp
@@ -6,13 +6,13 @@
 #include <utils/PugsMacros.hpp>
 
 class OutputNamedItemValueSet;
-class NamedDiscreteFunction;
+class INamedDiscreteData;
 class IDiscreteFunction;
 
 #include <string>
 
 template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const NamedDiscreteFunction>> =
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const INamedDiscreteData>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("output");
 
 class IWriter;
diff --git a/src/language/utils/CMakeLists.txt b/src/language/utils/CMakeLists.txt
index b76655995fd813239304c75c8189a4836c20e6d5..0fe1c798ba77d98b4453ded4d4c3334c0d2199e5 100644
--- a/src/language/utils/CMakeLists.txt
+++ b/src/language/utils/CMakeLists.txt
@@ -29,6 +29,7 @@ add_library(PugsLanguageUtils
   FunctionSymbolId.cpp
   IncDecOperatorRegisterForN.cpp
   IncDecOperatorRegisterForZ.cpp
+  ItemValueVariantFunctionInterpoler.cpp
   OFStream.cpp
   OperatorRepository.cpp
   UnaryOperatorRegisterForB.cpp
diff --git a/src/language/utils/ItemValueVariantFunctionInterpoler.cpp b/src/language/utils/ItemValueVariantFunctionInterpoler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..018dd7a9837997e4faf595898773f7c847f47f20
--- /dev/null
+++ b/src/language/utils/ItemValueVariantFunctionInterpoler.cpp
@@ -0,0 +1,145 @@
+#include <language/utils/ItemValueVariantFunctionInterpoler.hpp>
+
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <utils/Exceptions.hpp>
+
+#include <memory>
+
+template <size_t Dimension, typename DataType, typename ValueType>
+std::shared_ptr<ItemValueVariant>
+ItemValueVariantFunctionInterpoler::_interpolate() const
+{
+  std::shared_ptr p_mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
+  using MeshDataType     = MeshData<Dimension>;
+
+  switch (m_item_type) {
+  case ItemType::cell: {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*p_mesh);
+    return std::make_shared<ItemValueVariant>(
+      InterpolateItemValue<DataType(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(m_function_id,
+                                                                                                  mesh_data.xj()));
+  }
+  case ItemType::face: {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*p_mesh);
+    return std::make_shared<ItemValueVariant>(
+      InterpolateItemValue<DataType(TinyVector<Dimension>)>::template interpolate<ItemType::face>(m_function_id,
+                                                                                                  mesh_data.xl()));
+  }
+  case ItemType::edge: {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*p_mesh);
+    return std::make_shared<ItemValueVariant>(
+      InterpolateItemValue<DataType(TinyVector<Dimension>)>::template interpolate<ItemType::edge>(m_function_id,
+                                                                                                  mesh_data.xe()));
+  }
+  case ItemType::node: {
+    return std::make_shared<ItemValueVariant>(
+      InterpolateItemValue<DataType(TinyVector<Dimension>)>::template interpolate<ItemType::node>(m_function_id,
+                                                                                                  p_mesh->xr()));
+  }
+  default: {
+    throw UnexpectedError("invalid item type");
+  }
+  }
+}
+
+template <size_t Dimension>
+std::shared_ptr<ItemValueVariant>
+ItemValueVariantFunctionInterpoler::_interpolate() const
+{
+  const auto& function_descriptor = m_function_id.descriptor();
+  Assert(function_descriptor.domainMappingNode().children[1]->m_data_type == ASTNodeDataType::typename_t);
+
+  const ASTNodeDataType& data_type = function_descriptor.domainMappingNode().children[1]->m_data_type.contentType();
+
+  switch (data_type) {
+  case ASTNodeDataType::bool_t: {
+    return this->_interpolate<Dimension, bool, double>();
+  }
+  case ASTNodeDataType::unsigned_int_t: {
+    return this->_interpolate<Dimension, uint64_t, double>();
+  }
+  case ASTNodeDataType::int_t: {
+    return this->_interpolate<Dimension, int64_t, double>();
+  }
+  case ASTNodeDataType::double_t: {
+    return this->_interpolate<Dimension, double>();
+  }
+  case ASTNodeDataType::vector_t: {
+    switch (data_type.dimension()) {
+    case 1: {
+      return this->_interpolate<Dimension, TinyVector<1>>();
+    }
+    case 2: {
+      return this->_interpolate<Dimension, TinyVector<2>>();
+    }
+    case 3: {
+      return this->_interpolate<Dimension, TinyVector<3>>();
+    }
+      // LCOV_EXCL_START
+    default: {
+      std::ostringstream os;
+      os << "invalid vector dimension " << rang::fgB::red << data_type.dimension() << rang::style::reset;
+
+      throw UnexpectedError(os.str());
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+  case ASTNodeDataType::matrix_t: {
+    Assert(data_type.numberOfColumns() == data_type.numberOfRows(), "undefined matrix type");
+    switch (data_type.numberOfColumns()) {
+    case 1: {
+      return this->_interpolate<Dimension, TinyMatrix<1>>();
+    }
+    case 2: {
+      return this->_interpolate<Dimension, TinyMatrix<2>>();
+    }
+    case 3: {
+      return this->_interpolate<Dimension, TinyMatrix<3>>();
+    }
+      // LCOV_EXCL_START
+    default: {
+      std::ostringstream os;
+      os << "invalid vector dimension " << rang::fgB::red << data_type.dimension() << rang::style::reset;
+
+      throw UnexpectedError(os.str());
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+    // LCOV_EXCL_START
+  default: {
+    std::ostringstream os;
+    os << "invalid interpolation value type: " << rang::fgB::red << dataTypeName(data_type) << rang::style::reset;
+
+    throw UnexpectedError(os.str());
+  }
+    // LCOV_EXCL_STOP
+  }
+}
+
+std::shared_ptr<ItemValueVariant>
+ItemValueVariantFunctionInterpoler::interpolate() const
+{
+  switch (m_mesh->dimension()) {
+  case 1: {
+    return this->_interpolate<1>();
+  }
+  case 2: {
+    return this->_interpolate<2>();
+  }
+  case 3: {
+    return this->_interpolate<3>();
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
diff --git a/src/language/utils/ItemValueVariantFunctionInterpoler.hpp b/src/language/utils/ItemValueVariantFunctionInterpoler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4e03f72fc41ec74cf628e4ced7d331b6354d6f6a
--- /dev/null
+++ b/src/language/utils/ItemValueVariantFunctionInterpoler.hpp
@@ -0,0 +1,38 @@
+#ifndef ITEM_VALUE_VARIANT_FUNCTION_INTERPOLER_HPP
+#define ITEM_VALUE_VARIANT_FUNCTION_INTERPOLER_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <mesh/IZoneDescriptor.hpp>
+#include <mesh/ItemType.hpp>
+#include <mesh/ItemValueVariant.hpp>
+
+class ItemValueVariantFunctionInterpoler
+{
+ private:
+  std::shared_ptr<const IMesh> m_mesh;
+  const ItemType m_item_type;
+  const FunctionSymbolId m_function_id;
+
+  template <size_t Dimension, typename DataType, typename ValueType = DataType>
+  std::shared_ptr<ItemValueVariant> _interpolate() const;
+
+  template <size_t Dimension>
+  std::shared_ptr<ItemValueVariant> _interpolate() const;
+
+ public:
+  std::shared_ptr<ItemValueVariant> interpolate() const;
+
+  ItemValueVariantFunctionInterpoler(const std::shared_ptr<const IMesh>& mesh,
+                                     const ItemType& item_type,
+                                     const FunctionSymbolId& function_id)
+    : m_mesh{mesh}, m_item_type{item_type}, m_function_id{function_id}
+  {}
+
+  ItemValueVariantFunctionInterpoler(const ItemValueVariantFunctionInterpoler&) = delete;
+  ItemValueVariantFunctionInterpoler(ItemValueVariantFunctionInterpoler&&)      = delete;
+
+  ~ItemValueVariantFunctionInterpoler() = default;
+};
+
+#endif   // ITEM_VALUE_VARIANT_FUNCTION_INTERPOLER_HPP
diff --git a/src/mesh/ItemValue.hpp b/src/mesh/ItemValue.hpp
index 496c33242317e49f19842441e9c0fbec0f16ef70..da6bb88eb5a9d92dda08ee7ed535aae104639d95 100644
--- a/src/mesh/ItemValue.hpp
+++ b/src/mesh/ItemValue.hpp
@@ -43,6 +43,13 @@ class ItemValue
   friend ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityWeakPtr>;
 
  public:
+  // This is not the correct way to look at ItemValue, use with care
+  Array<const DataType>
+  arrayView() const
+  {
+    return m_values;
+  }
+
   [[nodiscard]] friend PUGS_INLINE ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr>
   copy(const ItemValue<DataType, item_type, ConnectivityPtr>& source)
   {
diff --git a/src/mesh/ItemValueVariant.hpp b/src/mesh/ItemValueVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1bfff8f84248f29b0cab089e8e246a8d59c369cc
--- /dev/null
+++ b/src/mesh/ItemValueVariant.hpp
@@ -0,0 +1,113 @@
+#ifndef ITEM_VALUE_VARIANT_HPP
+#define ITEM_VALUE_VARIANT_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <mesh/ItemValue.hpp>
+#include <utils/Exceptions.hpp>
+
+class ItemValueVariant
+{
+ private:
+  using Variant = std::variant<NodeValue<const bool>,
+                               NodeValue<const long int>,
+                               NodeValue<const unsigned long int>,
+                               NodeValue<const double>,
+                               NodeValue<const TinyVector<1, double>>,
+                               NodeValue<const TinyVector<2, double>>,
+                               NodeValue<const TinyVector<3, double>>,
+                               NodeValue<const TinyMatrix<1, 1, double>>,
+                               NodeValue<const TinyMatrix<2, 2, double>>,
+                               NodeValue<const TinyMatrix<3, 3, double>>,
+
+                               EdgeValue<const bool>,
+                               EdgeValue<const long int>,
+                               EdgeValue<const unsigned long int>,
+                               EdgeValue<const double>,
+                               EdgeValue<const TinyVector<1, double>>,
+                               EdgeValue<const TinyVector<2, double>>,
+                               EdgeValue<const TinyVector<3, double>>,
+                               EdgeValue<const TinyMatrix<1, 1, double>>,
+                               EdgeValue<const TinyMatrix<2, 2, double>>,
+                               EdgeValue<const TinyMatrix<3, 3, double>>,
+
+                               FaceValue<const bool>,
+                               FaceValue<const long int>,
+                               FaceValue<const unsigned long int>,
+                               FaceValue<const double>,
+                               FaceValue<const TinyVector<1, double>>,
+                               FaceValue<const TinyVector<2, double>>,
+                               FaceValue<const TinyVector<3, double>>,
+                               FaceValue<const TinyMatrix<1, 1, double>>,
+                               FaceValue<const TinyMatrix<2, 2, double>>,
+                               FaceValue<const TinyMatrix<3, 3, double>>,
+
+                               CellValue<const bool>,
+                               CellValue<const long int>,
+                               CellValue<const unsigned long int>,
+                               CellValue<const double>,
+                               CellValue<const TinyVector<1, double>>,
+                               CellValue<const TinyVector<2, double>>,
+                               CellValue<const TinyVector<3, double>>,
+                               CellValue<const TinyMatrix<1, 1, double>>,
+                               CellValue<const TinyMatrix<2, 2, double>>,
+                               CellValue<const TinyMatrix<3, 3, double>>>;
+
+  Variant m_item_value;
+
+ public:
+  PUGS_INLINE
+  const Variant&
+  itemValue() const
+  {
+    return m_item_value;
+  }
+
+  template <typename ItemValueT>
+  PUGS_INLINE auto
+  get() const
+  {
+    using DataType               = typename ItemValueT::data_type;
+    constexpr ItemType item_type = ItemValueT::item_t;
+
+    if constexpr (std::is_same_v<ItemValueT, ItemValue<DataType, item_type>> or
+                  std::is_same_v<ItemValueT, ItemValue<const DataType, item_type>> or
+                  std::is_same_v<ItemValueT, WeakItemValue<DataType, item_type>> or
+                  std::is_same_v<ItemValueT, WeakItemValue<const DataType, item_type>>) {
+      if (not std::holds_alternative<ItemValue<const DataType, item_type>>(this->m_item_value)) {
+        throw NormalError("invalid ItemValue type");
+      }
+      return std::get<ItemValue<const DataType, item_type>>(this->itemValue());
+    } else {
+      static_assert(std::is_same_v<ItemValueT, ItemValueT>, "invalid template argument");
+    }
+  }
+
+  template <typename DataType, ItemType item_type>
+  ItemValueVariant(const ItemValue<DataType, item_type>& item_value)
+    : m_item_value{ItemValue<const DataType, item_type>{item_value}}
+  {
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, bool> or                         //
+                    std::is_same_v<std::remove_const_t<DataType>, long int> or                   //
+                    std::is_same_v<std::remove_const_t<DataType>, unsigned long int> or          //
+                    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>>,
+                  "ItemValue with this DataType is not allowed in variant");
+  }
+
+  ItemValueVariant& operator=(ItemValueVariant&&) = default;
+  ItemValueVariant& operator=(const ItemValueVariant&) = default;
+
+  ItemValueVariant(const ItemValueVariant&) = default;
+  ItemValueVariant(ItemValueVariant&&)      = default;
+
+  ItemValueVariant()  = delete;
+  ~ItemValueVariant() = default;
+};
+
+#endif   // ITEM_VALUE_VARIANT_HPP
diff --git a/src/mesh/MeshData.hpp b/src/mesh/MeshData.hpp
index ada0f8fbdbcf0b8d2075ebbb8ad05cafa3a63a2e..9b0c89c8a6df4c4aab44b4b2f1aadc0615265fe8 100644
--- a/src/mesh/MeshData.hpp
+++ b/src/mesh/MeshData.hpp
@@ -45,6 +45,7 @@ class MeshData : public IMeshData
   FaceValue<const Rd> m_Nl;
   FaceValue<const Rd> m_nl;
   FaceValue<const double> m_ll;
+  EdgeValue<const Rd> m_xe;
 
   PUGS_INLINE
   void
@@ -136,7 +137,7 @@ class MeshData : public IMeshData
   _computeFaceIsobarycenter()
   {   // Computes vertices isobarycenter
     if constexpr (Dimension == 1) {
-      static_assert(Dimension != 1, "xl does not make sense in 1d");
+      m_xl = FaceValue<const Rd>{m_mesh.connectivity(), m_mesh.xr().arrayView()};
     } else {
       const NodeValue<const Rd>& xr = m_mesh.xr();
 
@@ -155,6 +156,28 @@ class MeshData : public IMeshData
     }
   }
 
+  PUGS_INLINE
+  void
+  _computeEdgeCenter()
+  {   // Computes vertices isobarycenter
+    if constexpr (Dimension < 3) {
+      m_xe = EdgeValue<const Rd>(m_mesh.connectivity(), this->xl().arrayView());
+    } else {
+      const NodeValue<const Rd>& xr = m_mesh.xr();
+
+      const auto& edge_to_node_matrix = m_mesh.connectivity().edgeToNodeMatrix();
+      EdgeValue<Rd> xe(m_mesh.connectivity());
+      parallel_for(
+        m_mesh.numberOfEdges(), PUGS_LAMBDA(EdgeId edge_id) {
+          const auto& edge_nodes = edge_to_node_matrix[edge_id];
+          const NodeId node0     = edge_nodes[0];
+          const NodeId node1     = edge_nodes[1];
+          xe[edge_id]            = 0.5 * (xr[node0] + xr[node1]);
+        });
+      m_xe = xe;
+    }
+  }
+
   PUGS_INLINE
   void
   _computeCellIsoBarycenter()
@@ -614,6 +637,16 @@ class MeshData : public IMeshData
     return m_xl;
   }
 
+  PUGS_INLINE
+  EdgeValue<const Rd>
+  xe()
+  {
+    if (not m_xe.isBuilt()) {
+      this->_computeEdgeCenter();
+    }
+    return m_xe;
+  }
+
   PUGS_INLINE
   CellValue<const Rd>
   cellIsoBarycenter()
diff --git a/src/mesh/SubItemValuePerItemVariant.hpp b/src/mesh/SubItemValuePerItemVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d711f400a869b4d2a9431e9425ab7b5e53e09b5b
--- /dev/null
+++ b/src/mesh/SubItemValuePerItemVariant.hpp
@@ -0,0 +1,163 @@
+#ifndef SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
+#define SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <utils/Exceptions.hpp>
+
+class SubItemValuePerItemVariant
+{
+ private:
+  using Variant = std::variant<NodeValuePerEdge<const bool>,
+                               NodeValuePerEdge<const long int>,
+                               NodeValuePerEdge<const unsigned long int>,
+                               NodeValuePerEdge<const double>,
+                               NodeValuePerEdge<const TinyVector<1, double>>,
+                               NodeValuePerEdge<const TinyVector<2, double>>,
+                               NodeValuePerEdge<const TinyVector<3, double>>,
+
+                               NodeValuePerFace<const bool>,
+                               NodeValuePerFace<const long int>,
+                               NodeValuePerFace<const unsigned long int>,
+                               NodeValuePerFace<const double>,
+                               NodeValuePerFace<const TinyVector<1, double>>,
+                               NodeValuePerFace<const TinyVector<2, double>>,
+                               NodeValuePerFace<const TinyVector<3, double>>,
+
+                               NodeValuePerCell<const bool>,
+                               NodeValuePerCell<const long int>,
+                               NodeValuePerCell<const unsigned long int>,
+                               NodeValuePerCell<const double>,
+                               NodeValuePerCell<const TinyVector<1, double>>,
+                               NodeValuePerCell<const TinyVector<2, double>>,
+                               NodeValuePerCell<const TinyVector<3, double>>,
+
+                               EdgeValuePerNode<const bool>,
+                               EdgeValuePerNode<const long int>,
+                               EdgeValuePerNode<const unsigned long int>,
+                               EdgeValuePerNode<const double>,
+                               EdgeValuePerNode<const TinyVector<1, double>>,
+                               EdgeValuePerNode<const TinyVector<2, double>>,
+                               EdgeValuePerNode<const TinyVector<3, double>>,
+
+                               EdgeValuePerFace<const bool>,
+                               EdgeValuePerFace<const long int>,
+                               EdgeValuePerFace<const unsigned long int>,
+                               EdgeValuePerFace<const double>,
+                               EdgeValuePerFace<const TinyVector<1, double>>,
+                               EdgeValuePerFace<const TinyVector<2, double>>,
+                               EdgeValuePerFace<const TinyVector<3, double>>,
+
+                               EdgeValuePerCell<const bool>,
+                               EdgeValuePerCell<const long int>,
+                               EdgeValuePerCell<const unsigned long int>,
+                               EdgeValuePerCell<const double>,
+                               EdgeValuePerCell<const TinyVector<1, double>>,
+                               EdgeValuePerCell<const TinyVector<2, double>>,
+                               EdgeValuePerCell<const TinyVector<3, double>>,
+
+                               FaceValuePerNode<const bool>,
+                               FaceValuePerNode<const long int>,
+                               FaceValuePerNode<const unsigned long int>,
+                               FaceValuePerNode<const double>,
+                               FaceValuePerNode<const TinyVector<1, double>>,
+                               FaceValuePerNode<const TinyVector<2, double>>,
+                               FaceValuePerNode<const TinyVector<3, double>>,
+
+                               FaceValuePerEdge<const bool>,
+                               FaceValuePerEdge<const long int>,
+                               FaceValuePerEdge<const unsigned long int>,
+                               FaceValuePerEdge<const double>,
+                               FaceValuePerEdge<const TinyVector<1, double>>,
+                               FaceValuePerEdge<const TinyVector<2, double>>,
+                               FaceValuePerEdge<const TinyVector<3, double>>,
+
+                               FaceValuePerCell<const bool>,
+                               FaceValuePerCell<const long int>,
+                               FaceValuePerCell<const unsigned long int>,
+                               FaceValuePerCell<const double>,
+                               FaceValuePerCell<const TinyVector<1, double>>,
+                               FaceValuePerCell<const TinyVector<2, double>>,
+                               FaceValuePerCell<const TinyVector<3, double>>,
+
+                               CellValuePerNode<const bool>,
+                               CellValuePerNode<const long int>,
+                               CellValuePerNode<const unsigned long int>,
+                               CellValuePerNode<const double>,
+                               CellValuePerNode<const TinyVector<1, double>>,
+                               CellValuePerNode<const TinyVector<2, double>>,
+                               CellValuePerNode<const TinyVector<3, double>>,
+
+                               CellValuePerEdge<const bool>,
+                               CellValuePerEdge<const long int>,
+                               CellValuePerEdge<const unsigned long int>,
+                               CellValuePerEdge<const double>,
+                               CellValuePerEdge<const TinyVector<1, double>>,
+                               CellValuePerEdge<const TinyVector<2, double>>,
+                               CellValuePerEdge<const TinyVector<3, double>>,
+
+                               CellValuePerFace<const bool>,
+                               CellValuePerFace<const long int>,
+                               CellValuePerFace<const unsigned long int>,
+                               CellValuePerFace<const double>,
+                               CellValuePerFace<const TinyVector<1, double>>,
+                               CellValuePerFace<const TinyVector<2, double>>,
+                               CellValuePerFace<const TinyVector<3, double>>>;
+
+  Variant m_sub_item_value_per_item;
+
+ public:
+  PUGS_INLINE
+  const Variant&
+  itemValue() const
+  {
+    return m_sub_item_value_per_item;
+  }
+
+  template <typename SubItemValuePerItemT>
+  PUGS_INLINE auto
+  get() const
+  {
+    using DataType        = typename SubItemValuePerItemT::data_type;
+    using ItemOfItemTypeT = typename SubItemValuePerItemT::ItemOfItemType;
+
+    if constexpr (std::is_same_v<SubItemValuePerItemT, SubItemValuePerItem<DataType, ItemOfItemTypeT>> or
+                  std::is_same_v<SubItemValuePerItemT, SubItemValuePerItem<const DataType, ItemOfItemTypeT>> or
+                  std::is_same_v<SubItemValuePerItemT, WeakSubItemValuePerItem<DataType, ItemOfItemTypeT>> or
+                  std::is_same_v<SubItemValuePerItemT, WeakSubItemValuePerItem<const DataType, ItemOfItemTypeT>>) {
+      if (not std::holds_alternative<SubItemValuePerItem<const DataType, ItemOfItemTypeT>>(
+            this->m_sub_item_value_per_item)) {
+        throw NormalError("invalid SubItemValuePerItem type");
+      }
+      return std::get<SubItemValuePerItem<const DataType, ItemOfItemTypeT>>(this->m_sub_item_value_per_item);
+    } else {
+      static_assert(std::is_same_v<SubItemValuePerItemT, SubItemValuePerItemT>, "invalid template argument");
+    }
+  }
+
+  template <typename DataType, typename ItemOfItemTypeT>
+  SubItemValuePerItemVariant(const SubItemValuePerItem<DataType, ItemOfItemTypeT>& sub_item_value_per_item)
+    : m_sub_item_value_per_item{SubItemValuePerItem<const DataType, ItemOfItemTypeT>{sub_item_value_per_item}}
+  {
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, bool> or                      //
+                    std::is_same_v<std::remove_const_t<DataType>, long int> or                //
+                    std::is_same_v<std::remove_const_t<DataType>, unsigned long int> or       //
+                    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>>,
+                  "SubItemValuePerItem with this DataType is not allowed in variant");
+  }
+
+  SubItemValuePerItemVariant& operator=(SubItemValuePerItemVariant&&) = default;
+  SubItemValuePerItemVariant& operator=(const SubItemValuePerItemVariant&) = default;
+
+  SubItemValuePerItemVariant(const SubItemValuePerItemVariant&) = default;
+  SubItemValuePerItemVariant(SubItemValuePerItemVariant&&)      = default;
+
+  SubItemValuePerItemVariant()  = delete;
+  ~SubItemValuePerItemVariant() = default;
+};
+
+#endif   // SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
diff --git a/src/output/GnuplotWriter.cpp b/src/output/GnuplotWriter.cpp
index ae0ba69aff214a7d0f4a9957113bdac52939315d..ef976be76e21b8d28ecc307a58a77161ffa2804c 100644
--- a/src/output/GnuplotWriter.cpp
+++ b/src/output/GnuplotWriter.cpp
@@ -255,6 +255,34 @@ GnuplotWriter::_write(const std::shared_ptr<const MeshType>& mesh,
     this->_writePreamble<MeshType::Dimension>(output_named_item_data_set, fout);
   }
 
+  for (const auto& [name, item_data_variant] : output_named_item_data_set) {
+    std::visit(
+      [&, name = name](auto&& item_data) {
+        using ItemDataType = std::decay_t<decltype(item_data)>;
+        if (ItemDataType::item_t == ItemType::face) {
+          std::ostringstream error_msg;
+          error_msg << "gnuplot_writer does not support face data, cannot save variable \"" << rang::fgB::yellow << name
+                    << rang::fg::reset << '"';
+          throw NormalError(error_msg.str());
+        }
+      },
+      item_data_variant);
+  }
+
+  for (const auto& [name, item_data_variant] : output_named_item_data_set) {
+    std::visit(
+      [&, name = name](auto&& item_data) {
+        using ItemDataType = std::decay_t<decltype(item_data)>;
+        if (ItemDataType::item_t == ItemType::edge) {
+          std::ostringstream error_msg;
+          error_msg << "gnuplot_writer does not support edge data, cannot save variable \"" << rang::fgB::yellow << name
+                    << rang::fg::reset << '"';
+          throw NormalError(error_msg.str());
+        }
+      },
+      item_data_variant);
+  }
+
   for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
     if (i_rank == parallel::rank()) {
       std::ofstream fout(_getFilename(), std::ios_base::app);
@@ -268,13 +296,11 @@ GnuplotWriter::_write(const std::shared_ptr<const MeshType>& mesh,
 }
 
 void
-GnuplotWriter::_writeAtTime(
-  const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
-  double time) const
+GnuplotWriter::_writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                            const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                            double time) const
 {
-  std::shared_ptr mesh = this->_getMesh(named_discrete_function_list);
-
-  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_function_list);
+  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_data_list);
 
   switch (mesh->dimension()) {
   case 1: {
@@ -312,12 +338,10 @@ GnuplotWriter::_writeMesh(const std::shared_ptr<const IMesh>& p_mesh) const
 }
 
 void
-GnuplotWriter::_write(
-  const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const
+GnuplotWriter::_write(const std::shared_ptr<const IMesh>& mesh,
+                      const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
 {
-  std::shared_ptr mesh = this->_getMesh(named_discrete_function_list);
-
-  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_function_list);
+  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_data_list);
 
   switch (mesh->dimension()) {
   case 1: {
diff --git a/src/output/GnuplotWriter.hpp b/src/output/GnuplotWriter.hpp
index 112b460625c9df4a436d55863daa989986611243..7d4c0c8a430df6dec0eb21a8246b848dcc41cc39 100644
--- a/src/output/GnuplotWriter.hpp
+++ b/src/output/GnuplotWriter.hpp
@@ -50,11 +50,12 @@ class GnuplotWriter final : public WriterBase
               const OutputNamedItemDataSet& output_named_item_data_set,
               std::optional<double> time) const;
 
-  void _writeAtTime(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+  void _writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                     double time) const final;
 
-  void _write(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const final;
+  void _write(const std::shared_ptr<const IMesh>& mesh,
+              const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;
 
   void _writeMesh(const std::shared_ptr<const IMesh>& mesh) const final;
 
diff --git a/src/output/GnuplotWriter1D.cpp b/src/output/GnuplotWriter1D.cpp
index 73e5a4e64a0e85eda1a5571503d28c8b221f2cdc..e98ebdd17780b385783eff189538c0ae90f6dcc6 100644
--- a/src/output/GnuplotWriter1D.cpp
+++ b/src/output/GnuplotWriter1D.cpp
@@ -50,6 +50,20 @@ GnuplotWriter1D::_is_cell_data(const ItemDataType&) const
   return ItemDataType::item_t == ItemType::cell;
 }
 
+template <typename ItemDataType>
+bool
+GnuplotWriter1D::_is_face_data(const ItemDataType&) const
+{
+  return ItemDataType::item_t == ItemType::face;
+}
+
+template <typename ItemDataType>
+bool
+GnuplotWriter1D::_is_edge_data(const ItemDataType&) const
+{
+  return ItemDataType::item_t == ItemType::edge;
+}
+
 template <typename ItemDataType>
 bool
 GnuplotWriter1D::_is_node_data(const ItemDataType&) const
@@ -269,6 +283,32 @@ GnuplotWriter1D::_write(const std::shared_ptr<const MeshType>& mesh,
     has_cell_data |= std::visit([&](auto&& item_data) { return this->_is_cell_data(item_data); }, item_data_variant);
   }
 
+  for (const auto& [name, item_data_variant] : output_named_item_data_set) {
+    std::visit(
+      [&, name = name](auto&& item_data) {
+        if (this->_is_face_data(item_data)) {
+          std::ostringstream error_msg;
+          error_msg << "gnuplot_1d_writer does not support face data, cannot save variable \"" << rang::fgB::yellow
+                    << name << rang::fg::reset << '"';
+          throw NormalError(error_msg.str());
+        }
+      },
+      item_data_variant);
+  }
+
+  for (const auto& [name, item_data_variant] : output_named_item_data_set) {
+    std::visit(
+      [&, name = name](auto&& item_data) {
+        if (this->_is_edge_data(item_data)) {
+          std::ostringstream error_msg;
+          error_msg << "gnuplot_1d_writer does not support edge data, cannot save variable \"" << rang::fgB::yellow
+                    << name << rang::fg::reset << '"';
+          throw NormalError(error_msg.str());
+        }
+      },
+      item_data_variant);
+  }
+
   bool has_node_data = false;
   for (const auto& [name, item_data_variant] : output_named_item_data_set) {
     has_node_data |= std::visit([&](auto&& item_data) { return this->_is_node_data(item_data); }, item_data_variant);
@@ -311,13 +351,11 @@ GnuplotWriter1D::_writeMesh(const std::shared_ptr<const IMesh>&) const
 }
 
 void
-GnuplotWriter1D::_writeAtTime(
-  const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
-  double time) const
+GnuplotWriter1D::_writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                              const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                              double time) const
 {
-  std::shared_ptr mesh = this->_getMesh(named_discrete_function_list);
-
-  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_function_list);
+  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_data_list);
 
   switch (mesh->dimension()) {
   case 1: {
@@ -338,12 +376,10 @@ GnuplotWriter1D::_writeAtTime(
 }
 
 void
-GnuplotWriter1D::_write(
-  const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const
+GnuplotWriter1D::_write(const std::shared_ptr<const IMesh>& mesh,
+                        const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
 {
-  std::shared_ptr mesh = this->_getMesh(named_discrete_function_list);
-
-  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_function_list);
+  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_data_list);
 
   switch (mesh->dimension()) {
   case 1: {
diff --git a/src/output/GnuplotWriter1D.hpp b/src/output/GnuplotWriter1D.hpp
index fad94c13687e3a8ff5c1989401bb39f9bf92f505..93f81404659e8ef9aa869ad59b9a78885381a70a 100644
--- a/src/output/GnuplotWriter1D.hpp
+++ b/src/output/GnuplotWriter1D.hpp
@@ -22,6 +22,12 @@ class GnuplotWriter1D final : public WriterBase
   template <typename ItemDataType>
   bool _is_cell_data(const ItemDataType&) const;
 
+  template <typename ItemDataType>
+  bool _is_face_data(const ItemDataType&) const;
+
+  template <typename ItemDataType>
+  bool _is_edge_data(const ItemDataType&) const;
+
   template <typename ItemDataType>
   bool _is_node_data(const ItemDataType&) const;
 
@@ -43,11 +49,12 @@ class GnuplotWriter1D final : public WriterBase
               const OutputNamedItemDataSet& output_named_item_value_set,
               std::optional<double> time) const;
 
-  void _writeAtTime(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+  void _writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                     double time) const final;
 
-  void _write(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const final;
+  void _write(const std::shared_ptr<const IMesh>& mesh,
+              const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;
 
   void _writeMesh(const std::shared_ptr<const IMesh>& mesh) const final;
 
diff --git a/src/output/INamedDiscreteData.hpp b/src/output/INamedDiscreteData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5d22e82c2e9d599cb695970ec9ba7049efdf08bf
--- /dev/null
+++ b/src/output/INamedDiscreteData.hpp
@@ -0,0 +1,27 @@
+#ifndef I_NAMED_DISCRETE_DATA_HPP
+#define I_NAMED_DISCRETE_DATA_HPP
+
+#include <string>
+
+class INamedDiscreteData
+{
+ public:
+  enum class Type
+  {
+    item_value,
+    discrete_function
+  };
+
+  virtual Type type() const = 0;
+
+  virtual const std::string& name() const = 0;
+
+  INamedDiscreteData(const INamedDiscreteData&) = default;
+  INamedDiscreteData(INamedDiscreteData&&)      = default;
+
+  INamedDiscreteData() = default;
+
+  virtual ~INamedDiscreteData() = default;
+};
+
+#endif   // I_NAMED_DISCRETE_DATA_HPP
diff --git a/src/output/IWriter.hpp b/src/output/IWriter.hpp
index dab629604c0b51f2be9233436af42fe7dee7134b..a277bdea3c7d33051ddf228b3ce95b052655b454 100644
--- a/src/output/IWriter.hpp
+++ b/src/output/IWriter.hpp
@@ -1,7 +1,7 @@
 #ifndef I_WRITER_HPP
 #define I_WRITER_HPP
 
-#include <output/NamedDiscreteFunction.hpp>
+#include <output/INamedDiscreteData.hpp>
 
 #include <memory>
 #include <vector>
@@ -13,17 +13,27 @@ class IWriter
  public:
   virtual void writeMesh(const std::shared_ptr<const IMesh>& mesh) const = 0;
 
-  virtual void write(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const = 0;
+  virtual void write(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const = 0;
 
-  virtual void writeIfNeeded(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
-    double time) const = 0;
+  virtual void writeIfNeeded(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                             double time) const = 0;
+
+  virtual void writeForced(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                           double time) const = 0;
 
-  virtual void writeForced(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+  virtual void writeOnMesh(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const = 0;
+
+  virtual void writeOnMeshIfNeeded(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
     double time) const = 0;
 
+  virtual void writeOnMeshForced(const std::shared_ptr<const IMesh>& mesh,
+                                 const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                                 double time) const = 0;
+
   IWriter()          = default;
   virtual ~IWriter() = default;
 };
diff --git a/src/output/NamedDiscreteFunction.hpp b/src/output/NamedDiscreteFunction.hpp
index 1b0850040a1fd944c5e3d3ba361f7d764bece3dd..d56d44369bb2e5492c078a35a0cf0a01e5524d42 100644
--- a/src/output/NamedDiscreteFunction.hpp
+++ b/src/output/NamedDiscreteFunction.hpp
@@ -1,20 +1,28 @@
 #ifndef NAMED_DISCRETE_FUNCTION_HPP
 #define NAMED_DISCRETE_FUNCTION_HPP
 
+#include <output/INamedDiscreteData.hpp>
+
 #include <memory>
 #include <string>
 
 class IDiscreteFunction;
 
-class NamedDiscreteFunction
+class NamedDiscreteFunction final : public INamedDiscreteData
 {
  private:
   std::shared_ptr<const IDiscreteFunction> m_discrete_function;
   std::string m_name;
 
  public:
+  Type
+  type() const final
+  {
+    return INamedDiscreteData::Type::discrete_function;
+  }
+
   const std::string&
-  name() const
+  name() const final
   {
     return m_name;
   }
diff --git a/src/output/NamedItemValueVariant.hpp b/src/output/NamedItemValueVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0415582b728fb614019e5452f73fc7d22e371001
--- /dev/null
+++ b/src/output/NamedItemValueVariant.hpp
@@ -0,0 +1,47 @@
+#ifndef NAMED_ITEM_VALUE_VARIANT_HPP
+#define NAMED_ITEM_VALUE_VARIANT_HPP
+
+#include <output/INamedDiscreteData.hpp>
+
+#include <memory>
+#include <string>
+
+class ItemValueVariant;
+
+class NamedItemValueVariant final : public INamedDiscreteData
+{
+ private:
+  std::shared_ptr<const ItemValueVariant> m_item_value_variant;
+  std::string m_name;
+
+ public:
+  Type
+  type() const final
+  {
+    return INamedDiscreteData::Type::item_value;
+  }
+
+  const std::string&
+  name() const final
+  {
+    return m_name;
+  }
+
+  const std::shared_ptr<const ItemValueVariant>
+  itemValueVariant() const
+  {
+    return m_item_value_variant;
+  }
+
+  NamedItemValueVariant(const std::shared_ptr<const ItemValueVariant>& item_value_variant, const std::string& name)
+    : m_item_value_variant{item_value_variant}, m_name{name}
+  {}
+
+  NamedItemValueVariant(const NamedItemValueVariant&) = default;
+  NamedItemValueVariant(NamedItemValueVariant&&)      = default;
+
+  NamedItemValueVariant()  = default;
+  ~NamedItemValueVariant() = default;
+};
+
+#endif   // NAMED_ITEM_VALUE_VARIANT_HPP
diff --git a/src/output/OutputNamedItemValueSet.hpp b/src/output/OutputNamedItemValueSet.hpp
index 80c3d205200a7604840f48282b4b8cff8c137dc8..aa2c5e2ed67140d47c7d4ddf1f57caecc2f28360 100644
--- a/src/output/OutputNamedItemValueSet.hpp
+++ b/src/output/OutputNamedItemValueSet.hpp
@@ -72,6 +72,30 @@ class OutputNamedItemDataSet
                                        NodeValue<const TinyMatrix<2, 2, double>>,
                                        NodeValue<const TinyMatrix<3, 3, double>>,
 
+                                       EdgeValue<const bool>,
+                                       EdgeValue<const int>,
+                                       EdgeValue<const long int>,
+                                       EdgeValue<const unsigned long int>,
+                                       EdgeValue<const double>,
+                                       EdgeValue<const TinyVector<1, double>>,
+                                       EdgeValue<const TinyVector<2, double>>,
+                                       EdgeValue<const TinyVector<3, double>>,
+                                       EdgeValue<const TinyMatrix<1, 1, double>>,
+                                       EdgeValue<const TinyMatrix<2, 2, double>>,
+                                       EdgeValue<const TinyMatrix<3, 3, double>>,
+
+                                       FaceValue<const bool>,
+                                       FaceValue<const int>,
+                                       FaceValue<const long int>,
+                                       FaceValue<const unsigned long int>,
+                                       FaceValue<const double>,
+                                       FaceValue<const TinyVector<1, double>>,
+                                       FaceValue<const TinyVector<2, double>>,
+                                       FaceValue<const TinyVector<3, double>>,
+                                       FaceValue<const TinyMatrix<1, 1, double>>,
+                                       FaceValue<const TinyMatrix<2, 2, double>>,
+                                       FaceValue<const TinyMatrix<3, 3, double>>,
+
                                        CellValue<const bool>,
                                        CellValue<const int>,
                                        CellValue<const long int>,
@@ -90,6 +114,18 @@ class OutputNamedItemDataSet
                                        NodeArray<const unsigned long int>,
                                        NodeArray<const double>,
 
+                                       EdgeArray<const bool>,
+                                       EdgeArray<const int>,
+                                       EdgeArray<const long int>,
+                                       EdgeArray<const unsigned long int>,
+                                       EdgeArray<const double>,
+
+                                       FaceArray<const bool>,
+                                       FaceArray<const int>,
+                                       FaceArray<const long int>,
+                                       FaceArray<const unsigned long int>,
+                                       FaceArray<const double>,
+
                                        CellArray<const bool>,
                                        CellArray<const int>,
                                        CellArray<const long int>,
diff --git a/src/output/VTKWriter.cpp b/src/output/VTKWriter.cpp
index 277c0d5ccd3a7c80c622a4a8f54a20daf9ff13d1..342a384e7f0ea4989b53fd9fd39ede1d0973171c 100644
--- a/src/output/VTKWriter.cpp
+++ b/src/output/VTKWriter.cpp
@@ -389,6 +389,26 @@ VTKWriter::_write(const std::shared_ptr<const MeshType>& mesh,
     }
     fout << "</PCells>\n";
 
+    for (const auto& [name, item_value_variant] : output_named_item_data_set) {
+      std::visit(
+        [&, name = name](auto&& item_value) {
+          using ItemValueType = std::decay_t<decltype(item_value)>;
+          if constexpr (ItemValueType::item_t == ItemType::edge) {
+            std::ostringstream error_msg;
+            error_msg << "VTK format does not support edge data, cannot save variable \"" << rang::fgB::yellow << name
+                      << rang::fg::reset << '"';
+            throw NormalError(error_msg.str());
+          }
+          if constexpr (ItemValueType::item_t == ItemType::face) {
+            std::ostringstream error_msg;
+            error_msg << "VTK format does not support face data, cannot save variable \"" << rang::fgB::yellow << name
+                      << rang::fg::reset << '"';
+            throw NormalError(error_msg.str());
+          }
+        },
+        item_value_variant);
+    }
+
     fout << "<PPointData>\n";
     for (const auto& [name, item_value_variant] : output_named_item_data_set) {
       std::visit([&, name = name](auto&& item_value) { return this->_write_node_pvtu(fout, name, item_value); },
@@ -662,12 +682,11 @@ VTKWriter::_writeMesh(const std::shared_ptr<const IMesh>& mesh) const
 }
 
 void
-VTKWriter::_writeAtTime(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+VTKWriter::_writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                        const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                         double time) const
 {
-  std::shared_ptr mesh = this->_getMesh(named_discrete_function_list);
-
-  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_function_list);
+  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_data_list);
 
   switch (mesh->dimension()) {
   case 1: {
@@ -689,11 +708,10 @@ VTKWriter::_writeAtTime(const std::vector<std::shared_ptr<const NamedDiscreteFun
 }
 
 void
-VTKWriter::_write(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const
+VTKWriter::_write(const std::shared_ptr<const IMesh>& mesh,
+                  const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
 {
-  std::shared_ptr mesh = this->_getMesh(named_discrete_function_list);
-
-  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_function_list);
+  OutputNamedItemDataSet output_named_item_data_set = this->_getOutputNamedItemDataSet(named_discrete_data_list);
 
   switch (mesh->dimension()) {
   case 1: {
diff --git a/src/output/VTKWriter.hpp b/src/output/VTKWriter.hpp
index c0adbd6c4e86cff510924018f80b5fb27229001a..1b6fc35f6ee02dc7a441f9dc956165af32c33276 100644
--- a/src/output/VTKWriter.hpp
+++ b/src/output/VTKWriter.hpp
@@ -100,11 +100,12 @@ class VTKWriter final : public WriterBase
               const OutputNamedItemDataSet& output_named_item_data_set,
               std::optional<double> time) const;
 
-  void _writeAtTime(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+  void _writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                     double time) const final;
 
-  void _write(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const final;
+  void _write(const std::shared_ptr<const IMesh>& mesh,
+              const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;
 
   void _writeMesh(const std::shared_ptr<const IMesh>& mesh) const final;
 
diff --git a/src/output/WriterBase.cpp b/src/output/WriterBase.cpp
index 2fe733d540e89c7f7e1bdea6e801e221e6a96eb8..0ce785cd9e6bd3f3985466d6f2f90e38fc49b802 100644
--- a/src/output/WriterBase.cpp
+++ b/src/output/WriterBase.cpp
@@ -1,6 +1,9 @@
 #include <output/WriterBase.hpp>
 
 #include <mesh/IMesh.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <output/NamedDiscreteFunction.hpp>
+#include <output/NamedItemValueVariant.hpp>
 #include <output/OutputNamedItemValueSet.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionP0Vector.hpp>
@@ -10,9 +13,9 @@
 
 template <typename DiscreteFunctionType>
 void
-WriterBase::_register(const std::string& name,
-                      const DiscreteFunctionType& discrete_function,
-                      OutputNamedItemDataSet& named_item_data_set)
+WriterBase::_registerDiscreteFunction(const std::string& name,
+                                      const DiscreteFunctionType& discrete_function,
+                                      OutputNamedItemDataSet& named_item_data_set)
 {
   if constexpr (DiscreteFunctionType::handled_data_type == IDiscreteFunction::HandledItemDataType::value) {
     named_item_data_set.add(NamedItemData{name, discrete_function.cellValues()});
@@ -23,30 +26,30 @@ WriterBase::_register(const std::string& name,
 
 template <size_t Dimension, template <size_t DimensionT, typename DataTypeT> typename DiscreteFunctionType>
 void
-WriterBase::_register(const std::string& name,
-                      const IDiscreteFunction& i_discrete_function,
-                      OutputNamedItemDataSet& named_item_data_set)
+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: {
-    _register(name, dynamic_cast<const DiscreteFunctionType<Dimension, bool>&>(i_discrete_function),
-              named_item_data_set);
+    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, bool>&>(i_discrete_function),
+                              named_item_data_set);
     break;
   }
   case ASTNodeDataType::unsigned_int_t: {
-    _register(name, dynamic_cast<const DiscreteFunctionType<Dimension, uint64_t>&>(i_discrete_function),
-              named_item_data_set);
+    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, uint64_t>&>(i_discrete_function),
+                              named_item_data_set);
     break;
   }
   case ASTNodeDataType::int_t: {
-    _register(name, dynamic_cast<const DiscreteFunctionType<Dimension, int64_t>&>(i_discrete_function),
-              named_item_data_set);
+    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, int64_t>&>(i_discrete_function),
+                              named_item_data_set);
     break;
   }
   case ASTNodeDataType::double_t: {
-    _register(name, dynamic_cast<const DiscreteFunctionType<Dimension, double>&>(i_discrete_function),
-              named_item_data_set);
+    _registerDiscreteFunction(name, dynamic_cast<const DiscreteFunctionType<Dimension, double>&>(i_discrete_function),
+                              named_item_data_set);
     break;
   }
   case ASTNodeDataType::vector_t: {
@@ -56,21 +59,24 @@ WriterBase::_register(const std::string& name,
     } else {
       switch (data_type.dimension()) {
       case 1: {
-        _register(name,
-                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<1, double>>&>(i_discrete_function),
-                  named_item_data_set);
+        _registerDiscreteFunction(name,
+                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<1, double>>&>(
+                                    i_discrete_function),
+                                  named_item_data_set);
         break;
       }
       case 2: {
-        _register(name,
-                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<2, double>>&>(i_discrete_function),
-                  named_item_data_set);
+        _registerDiscreteFunction(name,
+                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<2, double>>&>(
+                                    i_discrete_function),
+                                  named_item_data_set);
         break;
       }
       case 3: {
-        _register(name,
-                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<3, double>>&>(i_discrete_function),
-                  named_item_data_set);
+        _registerDiscreteFunction(name,
+                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyVector<3, double>>&>(
+                                    i_discrete_function),
+                                  named_item_data_set);
         break;
       }
       default: {
@@ -88,21 +94,24 @@ WriterBase::_register(const std::string& name,
       Assert(data_type.numberOfRows() == data_type.numberOfColumns(), "invalid matrix dimensions");
       switch (data_type.numberOfRows()) {
       case 1: {
-        _register(name,
-                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<1, 1, double>>&>(i_discrete_function),
-                  named_item_data_set);
+        _registerDiscreteFunction(name,
+                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<1, 1, double>>&>(
+                                    i_discrete_function),
+                                  named_item_data_set);
         break;
       }
       case 2: {
-        _register(name,
-                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<2, 2, double>>&>(i_discrete_function),
-                  named_item_data_set);
+        _registerDiscreteFunction(name,
+                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<2, 2, double>>&>(
+                                    i_discrete_function),
+                                  named_item_data_set);
         break;
       }
       case 3: {
-        _register(name,
-                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<3, 3, double>>&>(i_discrete_function),
-                  named_item_data_set);
+        _registerDiscreteFunction(name,
+                                  dynamic_cast<const DiscreteFunctionType<Dimension, TinyMatrix<3, 3, double>>&>(
+                                    i_discrete_function),
+                                  named_item_data_set);
         break;
       }
       default: {
@@ -120,21 +129,22 @@ WriterBase::_register(const std::string& name,
 
 template <template <size_t Dimension, typename DataType> typename DiscreteFunctionType>
 void
-WriterBase::_register(const NamedDiscreteFunction& named_discrete_function, OutputNamedItemDataSet& named_item_data_set)
+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: {
-    _register<1, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
+    _registerDiscreteFunction<1, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
     break;
   }
   case 2: {
-    _register<2, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
+    _registerDiscreteFunction<2, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
     break;
   }
   case 3: {
-    _register<3, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
+    _registerDiscreteFunction<3, DiscreteFunctionType>(name, i_discrete_function, named_item_data_set);
     break;
   }
   default: {
@@ -143,54 +153,207 @@ WriterBase::_register(const NamedDiscreteFunction& named_discrete_function, Outp
   }
 }
 
+void
+WriterBase::_checkConnectivity(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
+{
+  Assert(named_discrete_data_list.size() > 0);
+
+  std::shared_ptr<const IConnectivity> connectivity = [&]() -> std::shared_ptr<const IConnectivity> {
+    switch (mesh->dimension()) {
+    case 1: {
+      return dynamic_cast<const Mesh<Connectivity<1>>&>(*mesh).shared_connectivity();
+    }
+    case 2: {
+      return dynamic_cast<const Mesh<Connectivity<2>>&>(*mesh).shared_connectivity();
+    }
+    case 3: {
+      return dynamic_cast<const Mesh<Connectivity<3>>&>(*mesh).shared_connectivity();
+    }
+    default: {
+      throw UnexpectedError("invalid dimension");
+    }
+    }
+  }();
+
+  for (size_t i = 0; i < named_discrete_data_list.size(); ++i) {
+    const auto& named_discrete_data = named_discrete_data_list[i];
+
+    if (named_discrete_data->type() == INamedDiscreteData::Type::item_value) {
+      const NamedItemValueVariant& named_item_value_variant =
+        dynamic_cast<const NamedItemValueVariant&>(*named_discrete_data);
+
+      std::visit(
+        [&](auto&& item_value) {
+          if (item_value.connectivity_ptr() != connectivity) {
+            std::ostringstream error_msg;
+            error_msg << "The variable " << rang::fgB::yellow << named_item_value_variant.name() << rang::fg::reset
+                      << " is not defined on the provided same connectivity as the mesh\n";
+            throw NormalError(error_msg.str());
+          }
+        },
+        named_item_value_variant.itemValueVariant()->itemValue());
+    }
+  }
+}
+
+void
+WriterBase::_checkMesh(const std::shared_ptr<const IMesh>& mesh,
+                       const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
+{
+  Assert(named_discrete_data_list.size() > 0);
+
+  for (size_t i = 0; i < named_discrete_data_list.size(); ++i) {
+    const auto& named_discrete_data = named_discrete_data_list[i];
+
+    if (named_discrete_data->type() == INamedDiscreteData::Type::discrete_function) {
+      const NamedDiscreteFunction& named_discrete_function =
+        dynamic_cast<const NamedDiscreteFunction&>(*named_discrete_data);
+
+      if (mesh != named_discrete_function.discreteFunction()->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";
+        throw NormalError(error_msg.str());
+      }
+    }
+  }
+}
+
 std::shared_ptr<const IMesh>
-WriterBase::_getMesh(
-  const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const
+WriterBase::_getMesh(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
 {
-  Assert(named_discrete_function_list.size() > 0);
+  Assert(named_discrete_data_list.size() > 0);
+
+  std::map<std::shared_ptr<const IMesh>, std::string> mesh_set;
+  std::map<std::shared_ptr<const IConnectivity>, std::string> connectivity_set;
+
+  for (size_t i = 0; i < named_discrete_data_list.size(); ++i) {
+    const auto& named_discrete_data = named_discrete_data_list[i];
+
+    switch (named_discrete_data->type()) {
+    case INamedDiscreteData::Type::discrete_function: {
+      const NamedDiscreteFunction& named_discrete_function =
+        dynamic_cast<const NamedDiscreteFunction&>(*named_discrete_data);
 
-  std::shared_ptr mesh = named_discrete_function_list[0]->discreteFunction()->mesh();
-  for (size_t i = 1; i < named_discrete_function_list.size(); ++i) {
-    if (mesh != named_discrete_function_list[i]->discreteFunction()->mesh()) {
+      std::shared_ptr mesh = named_discrete_function.discreteFunction()->mesh();
+      mesh_set[mesh]       = named_discrete_function.name();
+
+      switch (mesh->dimension()) {
+      case 1: {
+        connectivity_set[dynamic_cast<const Mesh<Connectivity<1>>&>(*mesh).shared_connectivity()] =
+          named_discrete_function.name();
+        break;
+      }
+      case 2: {
+        connectivity_set[dynamic_cast<const Mesh<Connectivity<2>>&>(*mesh).shared_connectivity()] =
+          named_discrete_function.name();
+        break;
+      }
+      case 3: {
+        connectivity_set[dynamic_cast<const Mesh<Connectivity<3>>&>(*mesh).shared_connectivity()] =
+          named_discrete_function.name();
+        break;
+      }
+      default: {
+        throw UnexpectedError("invalid dimension");
+      }
+      }
+      break;
+    }
+    case INamedDiscreteData::Type::item_value: {
+      const NamedItemValueVariant& named_item_value_variant =
+        dynamic_cast<const NamedItemValueVariant&>(*named_discrete_data);
+
+      std::visit([&](
+                   auto&&
+                     item_value) { connectivity_set[item_value.connectivity_ptr()] = named_item_value_variant.name(); },
+                 named_item_value_variant.itemValueVariant()->itemValue());
+    }
+    }
+  }
+
+  if (mesh_set.size() != 1) {
+    if (mesh_set.size() == 0) {
+      throw NormalError("cannot find any mesh associated to output quantities");
+    } else {
       std::ostringstream error_msg;
-      error_msg << "discrete functions must be defined on the same mesh!\n"
-                << rang::fgB::yellow << "note:" << rang::style::reset << " cannot write " << rang::fgB::blue
-                << named_discrete_function_list[0]->name() << rang::style::reset << " and " << rang::fgB::blue
-                << named_discrete_function_list[i]->name() << rang::style::reset << " in the same file.";
+      error_msg << "cannot save data using different " << rang::fgB::red << "meshes" << rang::fg::reset
+                << " in the same file!\n";
+      error_msg << rang::fgB::yellow << "note:" << rang::fg::reset
+                << "the following variables are defined on different meshes:";
+      for (const auto& [mesh, name] : mesh_set) {
+        error_msg << "\n- " << name;
+      }
       throw NormalError(error_msg.str());
     }
   }
 
-  return mesh;
+  if (connectivity_set.size() > 1) {
+    std::ostringstream error_msg;
+    error_msg << "cannot save data using different " << rang::fgB::red << "connectivities" << rang::fg::reset
+              << " in the same file!\n";
+    error_msg << rang::fgB::yellow << "note:" << rang::fg::reset
+              << "the following variables are defined on different connectivities:";
+    for (const auto& [connectivity, name] : connectivity_set) {
+      error_msg << "\n- " << name;
+    }
+    throw NormalError(error_msg.str());
+  }
+
+  return mesh_set.begin()->first;
 }
 
 OutputNamedItemDataSet
 WriterBase::_getOutputNamedItemDataSet(
-  const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const
+  const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
 {
   OutputNamedItemDataSet named_item_data_set;
 
-  for (auto& named_discrete_function : named_discrete_function_list) {
-    const IDiscreteFunction& i_discrete_function = *named_discrete_function->discreteFunction();
+  for (auto& named_discrete_data : named_discrete_data_list) {
+    switch (named_discrete_data->type()) {
+    case INamedDiscreteData::Type::discrete_function: {
+      const NamedDiscreteFunction& named_discrete_function =
+        dynamic_cast<const NamedDiscreteFunction&>(*named_discrete_data);
 
-    switch (i_discrete_function.descriptor().type()) {
-    case DiscreteFunctionType::P0: {
-      WriterBase::_register<DiscreteFunctionP0>(*named_discrete_function, named_item_data_set);
+      const IDiscreteFunction& i_discrete_function = *named_discrete_function.discreteFunction();
+
+      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());
+      }
+      }
       break;
     }
-    case DiscreteFunctionType::P0Vector: {
-      WriterBase::_register<DiscreteFunctionP0Vector>(*named_discrete_function, named_item_data_set);
+    case INamedDiscreteData::Type::item_value: {
+      const NamedItemValueVariant& named_item_value_variant =
+        dynamic_cast<const NamedItemValueVariant&>(*named_discrete_data);
+
+      const std::string& name = named_item_value_variant.name();
+
+      const ItemValueVariant& item_value_variant = *named_item_value_variant.itemValueVariant();
+
+      std::visit([&](auto&& item_value) { named_item_data_set.add(NamedItemData{name, item_value}); },
+                 item_value_variant.itemValue());
       break;
     }
     default: {
-      std::ostringstream error_msg;
-      error_msg << "the type of discrete function of " << rang::fgB::blue << named_discrete_function->name()
-                << rang::style::reset << " is not supported";
-      throw NormalError(error_msg.str());
+      throw UnexpectedError("invalid discrete data type");
     }
     }
   }
-
   return named_item_data_set;
 }
 
@@ -201,7 +364,18 @@ WriterBase::WriterBase(const std::string& base_filename, const double& time_peri
 WriterBase::WriterBase(const std::string& base_filename) : m_base_filename{base_filename} {}
 
 void
-WriterBase::writeIfNeeded(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+WriterBase::write(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
+{
+  if (m_period_manager.has_value()) {
+    throw NormalError("this writer requires time value");
+  } else {
+    std::shared_ptr<const IMesh> mesh = _getMesh(named_discrete_data_list);
+    this->_write(mesh, named_discrete_data_list);
+  }
+}
+
+void
+WriterBase::writeIfNeeded(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                           double time) const
 {
   if (m_period_manager.has_value()) {
@@ -210,7 +384,8 @@ WriterBase::writeIfNeeded(const std::vector<std::shared_ptr<const NamedDiscreteF
       return;   // output already performed
 
     if (time >= m_period_manager->nextTime()) {
-      this->_writeAtTime(named_discrete_function_list, time);
+      std::shared_ptr<const IMesh> mesh = _getMesh(named_discrete_data_list);
+      this->_writeAtTime(mesh, named_discrete_data_list, time);
       m_period_manager->setSaveTime(time);
     }
   } else {
@@ -219,14 +394,14 @@ WriterBase::writeIfNeeded(const std::vector<std::shared_ptr<const NamedDiscreteF
 }
 
 void
-WriterBase::writeForced(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+WriterBase::writeForced(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                         double time) const
 {
   if (m_period_manager.has_value()) {
     if (time == m_period_manager->getLastTime())
       return;   // output already performed
-
-    this->_writeAtTime(named_discrete_function_list, time);
+    std::shared_ptr<const IMesh> mesh = _getMesh(named_discrete_data_list);
+    this->_writeAtTime(mesh, named_discrete_data_list, time);
     m_period_manager->setSaveTime(time);
   } else {
     throw NormalError("this writer does not allow time value");
@@ -234,12 +409,49 @@ WriterBase::writeForced(const std::vector<std::shared_ptr<const NamedDiscreteFun
 }
 
 void
-WriterBase::write(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const
+WriterBase::writeOnMesh(const std::shared_ptr<const IMesh>& mesh,
+                        const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
 {
   if (m_period_manager.has_value()) {
     throw NormalError("this writer requires time value");
   } else {
-    this->_write(named_discrete_function_list);
+    _checkMesh(mesh, named_discrete_data_list);
+    _checkConnectivity(mesh, named_discrete_data_list);
+    this->_write(mesh, named_discrete_data_list);
+  }
+}
+
+void
+WriterBase::writeOnMeshIfNeeded(const std::shared_ptr<const IMesh>& mesh,
+                                const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                                double time) const
+{
+  if (m_period_manager.has_value()) {
+    if (time == m_period_manager->getLastTime())
+      return;   // output already performed
+    _checkMesh(mesh, named_discrete_data_list);
+    _checkConnectivity(mesh, named_discrete_data_list);
+    this->_writeAtTime(mesh, named_discrete_data_list, time);
+    m_period_manager->setSaveTime(time);
+  } else {
+    throw NormalError("this writer does not allow time value");
+  }
+}
+
+void
+WriterBase::writeOnMeshForced(const std::shared_ptr<const IMesh>& mesh,
+                              const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                              double time) const
+{
+  if (m_period_manager.has_value()) {
+    if (time == m_period_manager->getLastTime())
+      return;   // output already performed
+    _checkMesh(mesh, named_discrete_data_list);
+    _checkConnectivity(mesh, named_discrete_data_list);
+    this->_writeAtTime(mesh, named_discrete_data_list, time);
+    m_period_manager->setSaveTime(time);
+  } else {
+    throw NormalError("this writer does not allow time value");
   }
 }
 
diff --git a/src/output/WriterBase.hpp b/src/output/WriterBase.hpp
index 2f29120b596f481bdd437cb42e7fd2fc665cf911..6f6a8e143b5b181bed10d76223f809851cda4229 100644
--- a/src/output/WriterBase.hpp
+++ b/src/output/WriterBase.hpp
@@ -10,6 +10,8 @@
 
 class IMesh;
 class OutputNamedItemDataSet;
+class IDiscreteFunction;
+class NamedDiscreteFunction;
 
 class WriterBase : public IWriter
 {
@@ -86,39 +88,55 @@ class WriterBase : public IWriter
 
  private:
   template <typename DiscreteFunctionType>
-  static void _register(const std::string& name, const DiscreteFunctionType&, OutputNamedItemDataSet&);
+  static void _registerDiscreteFunction(const std::string& name, const DiscreteFunctionType&, OutputNamedItemDataSet&);
 
   template <size_t Dimension, template <size_t DimensionT, typename DataTypeT> typename DiscreteFunctionType>
-  static void _register(const std::string& name, const IDiscreteFunction&, OutputNamedItemDataSet&);
+  static void _registerDiscreteFunction(const std::string& name, const IDiscreteFunction&, OutputNamedItemDataSet&);
 
   template <template <size_t DimensionT, typename DataTypeT> typename DiscreteFunctionType>
-  static void _register(const NamedDiscreteFunction&, OutputNamedItemDataSet&);
+  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;
+
+  void _checkMesh(const std::shared_ptr<const IMesh>& mesh,
+                  const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;
+
   std::shared_ptr<const IMesh> _getMesh(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const;
+    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;
 
   OutputNamedItemDataSet _getOutputNamedItemDataSet(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const;
+    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;
 
-  virtual void _writeAtTime(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
-    double time) const = 0;
+  virtual void _writeAtTime(const std::shared_ptr<const IMesh>& mesh,
+                            const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                            double time) const = 0;
 
-  virtual void _write(
-    const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const = 0;
+  virtual void _write(const std::shared_ptr<const IMesh>& mesh,
+                      const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const = 0;
 
   virtual void _writeMesh(const std::shared_ptr<const IMesh>& mesh) const = 0;
 
  public:
-  void write(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list) const final;
+  void write(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;
 
-  void writeIfNeeded(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+  void writeIfNeeded(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                      double time) const final;
 
-  void writeForced(const std::vector<std::shared_ptr<const NamedDiscreteFunction>>& named_discrete_function_list,
+  void writeForced(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                    double time) const final;
 
+  void writeOnMesh(const std::shared_ptr<const IMesh>& mesh,
+                   const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;
+
+  void writeOnMeshIfNeeded(const std::shared_ptr<const IMesh>& mesh,
+                           const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                           double time) const final;
+  void writeOnMeshForced(const std::shared_ptr<const IMesh>& mesh,
+                         const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
+                         double time) const final;
+
   void writeMesh(const std::shared_ptr<const IMesh>& mesh) const final;
 
   WriterBase() = delete;
diff --git a/src/scheme/AcousticSolver.cpp b/src/scheme/AcousticSolver.cpp
index 1ef88c67af2968ba891874da558e206a3dd86964..589b1d380d6ba1fbac10a61290552fe85ab1d193 100644
--- a/src/scheme/AcousticSolver.cpp
+++ b/src/scheme/AcousticSolver.cpp
@@ -2,9 +2,11 @@
 
 #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>
@@ -87,14 +89,8 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
 
   using BoundaryConditionList = std::vector<BoundaryCondition>;
 
-  const SolverType m_solver_type;
-  BoundaryConditionList m_boundary_condition_list;
-
-  NodeValue<const Rd> m_ur;
-  NodeValuePerCell<const Rd> m_Fjr;
-
   NodeValuePerCell<const Rdxd>
-  _computeGlaceAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoc)
+  _computeGlaceAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoc) const
   {
     MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
 
@@ -116,7 +112,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
   }
 
   NodeValuePerCell<const Rdxd>
-  _computeEucclhydAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoc)
+  _computeEucclhydAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoc) const
   {
     MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
 
@@ -164,12 +160,12 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
   }
 
   NodeValuePerCell<const Rdxd>
-  _computeAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoc)
+  _computeAjr(const SolverType& solver_type, const MeshType& mesh, const DiscreteScalarFunction& rhoc) const
   {
     if constexpr (Dimension == 1) {
       return _computeGlaceAjr(mesh, rhoc);
     } else {
-      switch (m_solver_type) {
+      switch (solver_type) {
       case SolverType::Glace: {
         return _computeGlaceAjr(mesh, rhoc);
       }
@@ -184,7 +180,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
   }
 
   NodeValue<Rdxd>
-  _computeAr(const MeshType& mesh, const NodeValuePerCell<const Rdxd>& Ajr)
+  _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();
@@ -212,7 +208,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
   _computeBr(const Mesh<Connectivity<Dimension>>& mesh,
              const NodeValuePerCell<const Rdxd>& Ajr,
              const DiscreteVectorFunction& u,
-             const DiscreteScalarFunction& p)
+             const DiscreteScalarFunction& p) const
   {
     MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
 
@@ -243,7 +239,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
 
   BoundaryConditionList
   _getBCList(const MeshType& mesh,
-             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
   {
     BoundaryConditionList bc_list;
 
@@ -327,21 +323,27 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
     return bc_list;
   }
 
-  void _applyPressureBC(const MeshType& mesh, NodeValue<Rd>& br);
-  void _applySymmetryBC(NodeValue<Rdxd>& Ar, NodeValue<Rd>& br);
-  void _applyVelocityBC(NodeValue<Rdxd>& Ar, NodeValue<Rd>& br);
-  void _applyExternalVelocityBC(const DiscreteScalarFunction& p, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br);
+  void _applyPressureBC(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 _applyExternalVelocityBC(const BoundaryConditionList& bc_list,
+                                const DiscreteScalarFunction& p,
+                                NodeValue<Rdxd>& Ar,
+                                NodeValue<Rd>& br) const;
 
   void
-  _applyBoundaryConditions(const MeshType& mesh, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br)
+  _applyBoundaryConditions(const BoundaryConditionList& bc_list,
+                           const MeshType& mesh,
+                           NodeValue<Rdxd>& Ar,
+                           NodeValue<Rd>& br) const
   {
-    this->_applyPressureBC(mesh, br);
-    this->_applySymmetryBC(Ar, br);
-    this->_applyVelocityBC(Ar, br);
+    this->_applyPressureBC(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)
+  _computeUr(const MeshType& mesh, const NodeValue<Rdxd>& Ar, const NodeValue<Rd>& br) const
   {
     NodeValue<Rd> u{mesh.connectivity()};
     parallel_for(
@@ -355,7 +357,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
               const NodeValuePerCell<const Rdxd>& Ajr,
               const NodeValue<const Rd>& ur,
               const DiscreteVectorFunction& u,
-              const DiscreteScalarFunction& p)
+              const DiscreteScalarFunction& p) const
   {
     MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
 
@@ -376,46 +378,71 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
     return F;
   }
 
-  AcousticSolver(SolverType solver_type,
-                 const MeshType& mesh,
-                 const DiscreteScalarFunction& rho,
-                 const DiscreteScalarFunction& c,
-                 const DiscreteVectorFunction& u,
-                 const DiscreteScalarFunction& p,
-                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
-    : m_solver_type{solver_type}, m_boundary_condition_list{this->_getBCList(mesh, bc_descriptor_list)}
+ 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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
   {
-    NodeValuePerCell<const Rdxd> Ajr = this->_computeAjr(mesh, rho * c);
+    std::shared_ptr i_mesh = getCommonMesh({i_rho, i_c, i_u, i_p});
+    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)) {
+      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);
+
+    NodeValuePerCell<const Rdxd> Ajr = this->_computeAjr(solver_type, mesh, rho * c);
 
     NodeValue<Rdxd> Ar = this->_computeAr(mesh, Ajr);
     NodeValue<Rd> br   = this->_computeBr(mesh, Ajr, u, p);
 
-    this->_applyBoundaryConditions(mesh, Ar, br);
-    this->_applyExternalVelocityBC(p, Ar, br);
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_applyBoundaryConditions(bc_list, mesh, Ar, br);
+    this->_applyExternalVelocityBC(bc_list, p, Ar, br);
 
     synchronize(Ar);
     synchronize(br);
 
-    m_ur  = this->_computeUr(mesh, Ar, br);
-    m_Fjr = this->_computeFjr(mesh, Ajr, m_ur, u, p);
+    NodeValue<const Rd> ur         = this->_computeUr(mesh, Ar, br);
+    NodeValuePerCell<const Rd> Fjr = this->_computeFjr(mesh, Ajr, ur, u, p);
+
+    return std::make_tuple(std::make_shared<const ItemValueVariant>(ur),
+                           std::make_shared<const SubItemValuePerItemVariant>(Fjr));
   }
 
- public:
   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>>>
-  apply(const double& dt,
-        const MeshType& mesh,
-        const DiscreteFunctionP0<Dimension, double>& rho,
-        const DiscreteFunctionP0<Dimension, Rd>& u,
-        const DiscreteFunctionP0<Dimension, double>& E) const
+  apply_fluxes(const double& dt,
+               const MeshType& mesh,
+               const DiscreteFunctionP0<Dimension, double>& rho,
+               const DiscreteFunctionP0<Dimension, Rd>& u,
+               const DiscreteFunctionP0<Dimension, double>& E,
+               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 * m_ur[r]; });
+      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);
 
@@ -433,8 +460,8 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
         double energy_fluxes = 0;
         for (size_t R = 0; R < cell_nodes.size(); ++R) {
           const NodeId r = cell_nodes[R];
-          momentum_fluxes += m_Fjr(j, R);
-          energy_fluxes += dot(m_Fjr(j, R), m_ur[r]);
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
         }
         const double dt_over_Mj = dt / (rho[j] * Vj[j]);
         new_u[j] -= dt_over_Mj * momentum_fluxes;
@@ -455,10 +482,12 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
              std::shared_ptr<const IDiscreteFunction>,
              std::shared_ptr<const IDiscreteFunction>,
              std::shared_ptr<const IDiscreteFunction>>
-  apply(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
+  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 ItemValueVariant>& ur,
+               const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr) const
   {
     std::shared_ptr i_mesh = getCommonMesh({rho, u, E});
     if (not i_mesh) {
@@ -469,26 +498,31 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
       throw NormalError("acoustic solver expects P0 functions");
     }
 
-    return this->apply(dt, dynamic_cast<const MeshType&>(*i_mesh), dynamic_cast<const DiscreteScalarFunction&>(*rho),
-                       dynamic_cast<const DiscreteVectorFunction&>(*u),
-                       dynamic_cast<const DiscreteScalarFunction&>(*E));
-  }
-
-  AcousticSolver(SolverType solver_type,
-                 const std::shared_ptr<const IMesh>& mesh,
-                 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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
-    : AcousticSolver{solver_type,
-                     dynamic_cast<const MeshType&>(*mesh),
-                     dynamic_cast<const DiscreteScalarFunction&>(*rho),
-                     dynamic_cast<const DiscreteScalarFunction&>(*c),
-                     dynamic_cast<const DiscreteVectorFunction&>(*u),
-                     dynamic_cast<const DiscreteScalarFunction&>(*p),
-                     bc_descriptor_list}
-  {}
+    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>>(),                      //
+                              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>>
+  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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    auto [ur, Fjr] = compute_fluxes(solver_type, rho, c, u, p, bc_descriptor_list);
+    return apply_fluxes(dt, rho, u, E, ur, Fjr);
+  }
 
   AcousticSolver()                 = default;
   AcousticSolver(AcousticSolver&&) = default;
@@ -497,9 +531,11 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
 
 template <size_t Dimension>
 void
-AcousticSolverHandler::AcousticSolver<Dimension>::_applyPressureBC(const MeshType& mesh, NodeValue<Rd>& br)
+AcousticSolverHandler::AcousticSolver<Dimension>::_applyPressureBC(const BoundaryConditionList& bc_list,
+                                                                   const MeshType& mesh,
+                                                                   NodeValue<Rd>& br) const
 {
-  for (const auto& boundary_condition : m_boundary_condition_list) {
+  for (const auto& boundary_condition : bc_list) {
     std::visit(
       [&](auto&& bc) {
         using T = std::decay_t<decltype(bc)>;
@@ -560,9 +596,11 @@ AcousticSolverHandler::AcousticSolver<Dimension>::_applyPressureBC(const MeshTyp
 
 template <size_t Dimension>
 void
-AcousticSolverHandler::AcousticSolver<Dimension>::_applySymmetryBC(NodeValue<Rdxd>& Ar, NodeValue<Rd>& br)
+AcousticSolverHandler::AcousticSolver<Dimension>::_applySymmetryBC(const BoundaryConditionList& bc_list,
+                                                                   NodeValue<Rdxd>& Ar,
+                                                                   NodeValue<Rd>& br) const
 {
-  for (const auto& boundary_condition : m_boundary_condition_list) {
+  for (const auto& boundary_condition : bc_list) {
     std::visit(
       [&](auto&& bc) {
         using T = std::decay_t<decltype(bc)>;
@@ -589,9 +627,11 @@ AcousticSolverHandler::AcousticSolver<Dimension>::_applySymmetryBC(NodeValue<Rdx
 
 template <size_t Dimension>
 void
-AcousticSolverHandler::AcousticSolver<Dimension>::_applyVelocityBC(NodeValue<Rdxd>& Ar, NodeValue<Rd>& br)
+AcousticSolverHandler::AcousticSolver<Dimension>::_applyVelocityBC(const BoundaryConditionList& bc_list,
+                                                                   NodeValue<Rdxd>& Ar,
+                                                                   NodeValue<Rd>& br) const
 {
-  for (const auto& boundary_condition : m_boundary_condition_list) {
+  for (const auto& boundary_condition : bc_list) {
     std::visit(
       [&](auto&& bc) {
         using T = std::decay_t<decltype(bc)>;
@@ -624,11 +664,12 @@ AcousticSolverHandler::AcousticSolver<Dimension>::_applyVelocityBC(NodeValue<Rdx
 
 template <size_t Dimension>
 void
-AcousticSolverHandler::AcousticSolver<Dimension>::_applyExternalVelocityBC(const DiscreteScalarFunction& p,
+AcousticSolverHandler::AcousticSolver<Dimension>::_applyExternalVelocityBC(const BoundaryConditionList& bc_list,
+                                                                           const DiscreteScalarFunction& p,
                                                                            NodeValue<Rdxd>& Ar,
-                                                                           NodeValue<Rd>& br)
+                                                                           NodeValue<Rd>& br) const
 {
-  for (const auto& boundary_condition : m_boundary_condition_list) {
+  for (const auto& boundary_condition : bc_list) {
     std::visit(
       [&](auto&& bc) {
         using T = std::decay_t<decltype(bc)>;
@@ -840,34 +881,23 @@ class AcousticSolverHandler::AcousticSolver<Dimension>::SymmetryBoundaryConditio
   ~SymmetryBoundaryCondition() = default;
 };
 
-AcousticSolverHandler::AcousticSolverHandler(
-  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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+AcousticSolverHandler::AcousticSolverHandler(const std::shared_ptr<const IMesh>& i_mesh)
 {
-  std::shared_ptr i_mesh = getCommonMesh({rho, c, u, p});
   if (not i_mesh) {
     throw NormalError("discrete functions are not defined on the same mesh");
   }
 
-  if (not checkDiscretizationType({rho, c, u, p}, DiscreteFunctionType::P0)) {
-    throw NormalError("acoustic solver expects P0 functions");
-  }
-
   switch (i_mesh->dimension()) {
   case 1: {
-    m_acoustic_solver = std::make_unique<AcousticSolver<1>>(solver_type, i_mesh, rho, c, u, p, bc_descriptor_list);
+    m_acoustic_solver = std::make_unique<AcousticSolver<1>>();
     break;
   }
   case 2: {
-    m_acoustic_solver = std::make_unique<AcousticSolver<2>>(solver_type, i_mesh, rho, c, u, p, bc_descriptor_list);
+    m_acoustic_solver = std::make_unique<AcousticSolver<2>>();
     break;
   }
   case 3: {
-    m_acoustic_solver = std::make_unique<AcousticSolver<3>>(solver_type, i_mesh, rho, c, u, p, bc_descriptor_list);
+    m_acoustic_solver = std::make_unique<AcousticSolver<3>>();
     break;
   }
   default: {
diff --git a/src/scheme/AcousticSolver.hpp b/src/scheme/AcousticSolver.hpp
index 2ed97075717f8984afb22799648b4b26fbc9327b..489b8e795d843f9677daf562214bd0266f803baa 100644
--- a/src/scheme/AcousticSolver.hpp
+++ b/src/scheme/AcousticSolver.hpp
@@ -8,6 +8,8 @@
 class IDiscreteFunction;
 class IBoundaryConditionDescriptor;
 class IMesh;
+class ItemValueVariant;
+class SubItemValuePerItemVariant;
 
 double acoustic_dt(const std::shared_ptr<const IDiscreteFunction>& c);
 
@@ -23,14 +25,39 @@ class AcousticSolverHandler
  private:
   struct IAcousticSolver
   {
+    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 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::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>>
+    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 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>>
-    apply(const double& dt,
+    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 = 0;
+          const std::shared_ptr<const IDiscreteFunction>& E,
+          const std::shared_ptr<const IDiscreteFunction>& c,
+          const std::shared_ptr<const IDiscreteFunction>& p,
+          const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
 
     IAcousticSolver()                  = default;
     IAcousticSolver(IAcousticSolver&&) = default;
@@ -51,12 +78,7 @@ class AcousticSolverHandler
     return *m_acoustic_solver;
   }
 
-  AcousticSolverHandler(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::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
+  AcousticSolverHandler(const std::shared_ptr<const IMesh>& mesh);
 };
 
 #endif   // ACOUSTIC_SOLVER_HPP
diff --git a/src/scheme/DiscreteFunctionInterpoler.cpp b/src/scheme/DiscreteFunctionInterpoler.cpp
index daf7646ee266c7c874c17b6b7403388fc33c3ce6..e19073f1e93bd70d649f66f7ca0bd2f49ef8a102 100644
--- a/src/scheme/DiscreteFunctionInterpoler.cpp
+++ b/src/scheme/DiscreteFunctionInterpoler.cpp
@@ -185,7 +185,6 @@ DiscreteFunctionInterpoler::_interpolate() const
 std::shared_ptr<IDiscreteFunction>
 DiscreteFunctionInterpoler::interpolate() const
 {
-  std::shared_ptr<IDiscreteFunction> discrete_function;
   switch (m_mesh->dimension()) {
   case 1: {
     return this->_interpolate<1>();
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 196938400cab3735d0633b1d1ba41efc00358f4b..3f174398186c2b5fd0bcc56aef017f40936f9704 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -175,6 +175,8 @@ add_executable (mpi_unit_tests
   test_ItemArrayUtils.cpp
   test_ItemValue.cpp
   test_ItemValueUtils.cpp
+  test_ItemValueVariant.cpp
+  test_ItemValueVariantFunctionInterpoler.cpp
   test_MeshEdgeBoundary.cpp
   test_MeshFaceBoundary.cpp
   test_MeshFlatEdgeBoundary.cpp
@@ -189,6 +191,7 @@ add_executable (mpi_unit_tests
   test_Partitioner.cpp
   test_RandomEngine.cpp
   test_SubItemValuePerItem.cpp
+  test_SubItemValuePerItemVariant.cpp
   test_SubItemValuePerItemUtils.cpp
   test_SubItemArrayPerItem.cpp
   test_SubItemArrayPerItemUtils.cpp
diff --git a/tests/test_ItemValueVariant.cpp b/tests/test_ItemValueVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d8fca46dac3d6ccd0c3e88a5d6fa5fc9c065fc4
--- /dev/null
+++ b/tests/test_ItemValueVariant.cpp
@@ -0,0 +1,101 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ItemValueVariant", "[mesh]")
+{
+  std::shared_ptr mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+  const Connectivity<2>& connectivity = *mesh->shared_connectivity();
+
+  using R1 = TinyVector<1>;
+  using R2 = TinyVector<2>;
+  using R3 = TinyVector<3>;
+
+  using R1x1 = TinyMatrix<1>;
+  using R2x2 = TinyMatrix<2>;
+  using R3x3 = TinyMatrix<3>;
+
+  SECTION("NodeValue<double>")
+  {
+    NodeValue<double> node_value{connectivity};
+    ItemValueVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<NodeValue<const double>>());
+    REQUIRE_THROWS_WITH(v.get<NodeValue<int64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<uint64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R2>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R1x1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R2x2>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R3x3>>(), "error: invalid ItemValue type");
+
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const double>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const double>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const double>>(), "error: invalid ItemValue type");
+  }
+
+  SECTION("EdgeValue<R3>")
+  {
+    EdgeValue<R3> node_value{connectivity};
+    ItemValueVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<int64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<uint64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<double>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const R1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const R2>>(), "error: invalid ItemValue type");
+    REQUIRE_NOTHROW(v.get<EdgeValue<const R3>>());
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const R1x1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const R2x2>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const R3x3>>(), "error: invalid ItemValue type");
+
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const R3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R3>>(), "error: invalid ItemValue type");
+  }
+
+  SECTION("FaceValue<R3x3>")
+  {
+    FaceValue<R3x3> node_value{connectivity};
+    ItemValueVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<FaceValue<int64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<uint64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<double>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const R1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const R2>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const R3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const R1x1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const R2x2>>(), "error: invalid ItemValue type");
+    REQUIRE_NOTHROW(v.get<FaceValue<const R3x3>>());
+
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const R3x3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const R3x3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R3x3>>(), "error: invalid ItemValue type");
+  }
+
+  SECTION("CellValue<int64_t>")
+  {
+    CellValue<int64_t> node_value{connectivity};
+    ItemValueVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<CellValue<const int64_t>>());
+    REQUIRE_THROWS_WITH(v.get<CellValue<const uint64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const double>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R2>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R3>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R1x1>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R2x2>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<CellValue<const R3x3>>(), "error: invalid ItemValue type");
+
+    REQUIRE_THROWS_WITH(v.get<NodeValue<const int64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValue<const int64_t>>(), "error: invalid ItemValue type");
+    REQUIRE_THROWS_WITH(v.get<FaceValue<const int64_t>>(), "error: invalid ItemValue type");
+  }
+}
diff --git a/tests/test_ItemValueVariantFunctionInterpoler.cpp b/tests/test_ItemValueVariantFunctionInterpoler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8689b1086a816b10552b4b8c6f6ae78bcced7670
--- /dev/null
+++ b/tests/test_ItemValueVariantFunctionInterpoler.cpp
@@ -0,0 +1,547 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionEvaluationExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <language/utils/SymbolTable.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+
+#include <language/utils/ItemValueVariantFunctionInterpoler.hpp>
+
+#include <pegtl/string_input.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ItemValueVariantFunctionInterpoler", "[scheme]")
+{
+  auto same_item_value = [](auto f, auto g) -> bool {
+    using ItemIdType = typename decltype(f)::index_type;
+    for (ItemIdType item_id = 0; item_id < f.numberOfItems(); ++item_id) {
+      if (f[item_id] != g[item_id]) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  SECTION("1D")
+  {
+    constexpr size_t Dimension = 1;
+
+    std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_1d = named_mesh.mesh();
+
+        auto xj = MeshDataManager::instance().getMeshData(*mesh_1d).xj();
+        auto xr = mesh_1d->xr();
+
+        std::string_view data = R"(
+import math;
+let B_scalar_non_linear_1d: R^1 -> B, x -> (exp(2 * x[0]) + 3 > 4);
+let N_scalar_non_linear_1d: R^1 -> N, x -> floor(3 * x[0] * x[0] + 2);
+let Z_scalar_non_linear_1d: R^1 -> Z, x -> floor(exp(2 * x[0]) - 1);
+let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
+let R1_non_linear_1d: R^1 -> R^1, x -> 2 * exp(x[0]);
+let R2_non_linear_1d: R^1 -> R^2, x -> [2 * exp(x[0]), -3*x[0]];
+let R3_non_linear_1d: R^1 -> R^3, x -> [2 * exp(x[0]) + 3, x[0] - 2, 3];
+let R1x1_non_linear_1d: R^1 -> R^1x1, x -> (2 * exp(x[0]) * sin(x[0]) + 3);
+let R2x2_non_linear_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0])], [3, x[0] * x[0]]];
+let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 * x[0]), 3], [x[0] * x[0], -4*x[0], 2*x[0]+1], [3, -6*x[0], exp(x[0])]];
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
+
+        SECTION("B_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<bool> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::exp(2 * x[0]) + 3 > 4;
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_data_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_data_variant->get<CellValue<bool>>()));
+        }
+
+        SECTION("N_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("N_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          NodeValue<uint64_t> node_value{mesh_1d->connectivity()};
+          parallel_for(
+            node_value.numberOfItems(), PUGS_LAMBDA(const NodeId node_id) {
+              const TinyVector<Dimension>& x = xr[node_id];
+              node_value[node_id]            = std::floor(3 * x[0] * x[0] + 2);
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::node, function_symbol_id);
+          std::shared_ptr item_data_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(node_value, item_data_variant->get<NodeValue<uint64_t>>()));
+        }
+
+        SECTION("Z_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("Z_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<int64_t> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = std::floor(std::exp(2 * x[0]) - 1);
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<int64_t>>()));
+        }
+
+        SECTION("R_scalar_non_linear_1d")
+        {
+          auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<double> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = 2 * std::exp(x[0]) + 3;
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<double>>()));
+        }
+
+        SECTION("R1_non_linear_1d")
+        {
+          using DataType = TinyVector<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0])};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
+        }
+
+        SECTION("R2_non_linear_1d")
+        {
+          using DataType = TinyVector<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]), -3 * x[0]};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
+        }
+
+        SECTION("R3_non_linear_1d")
+        {
+          using DataType = TinyVector<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) + 3, x[0] - 2, 3};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
+        }
+
+        SECTION("R1x1_non_linear_1d")
+        {
+          using DataType = TinyMatrix<1>;
+
+          auto [i_symbol, found] = symbol_table->find("R1x1_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * std::exp(x[0]) * std::sin(x[0]) + 3};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
+        }
+
+        SECTION("R2x2_non_linear_1d")
+        {
+          using DataType = TinyMatrix<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id] =
+                DataType{2 * std::exp(x[0]) * std::sin(x[0]) + 3, std::sin(x[0] - 2 * x[0]), 3, x[0] * x[0]};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
+        }
+
+        SECTION("R3x3_non_linear_1d")
+        {
+          using DataType = TinyMatrix<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_1d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          CellValue<DataType> cell_value{mesh_1d->connectivity()};
+          parallel_for(
+            cell_value.numberOfItems(), PUGS_LAMBDA(const CellId cell_id) {
+              const TinyVector<Dimension>& x = xj[cell_id];
+              cell_value[cell_id]            = DataType{2 * exp(x[0]) * std::sin(x[0]) + 3,
+                                             std::sin(x[0] - 2 * x[0]),
+                                             3,
+                                             x[0] * x[0],
+                                             -4 * x[0],
+                                             2 * x[0] + 1,
+                                             3,
+                                             -6 * x[0],
+                                             std::exp(x[0])};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_1d, ItemType::cell, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
+        }
+      }
+    }
+  }
+
+  SECTION("2D")
+  {
+    constexpr size_t Dimension = 2;
+
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        auto xl = MeshDataManager::instance().getMeshData(*mesh_2d).xl();
+
+        std::string_view data = R"(
+import math;
+let B_scalar_non_linear_2d: R^2 -> B, x -> (exp(2 * x[0])< 2*x[1]);
+let R2_non_linear_2d: R^2 -> R^2, x -> [2 * exp(x[0]), -3*x[1]];
+let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(x[1] - 2 * x[0]), 3], [x[1] * x[0], -4*x[1], 2*x[0]+1], [3, -6*x[0], exp(x[1])]];
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
+
+        SECTION("B_scalar_non_linear_2d")
+        {
+          using DataType = bool;
+
+          auto [i_symbol, found] = symbol_table->find("B_scalar_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          FaceValue<DataType> face_value{mesh_2d->connectivity()};
+          parallel_for(
+            face_value.numberOfItems(), PUGS_LAMBDA(const FaceId face_id) {
+              const TinyVector<Dimension>& x = xl[face_id];
+              face_value[face_id]            = std::exp(2 * x[0]) < 2 * x[1];
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_2d, ItemType::face, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(face_value, item_value_variant->get<FaceValue<DataType>>()));
+        }
+
+        SECTION("R2_non_linear_2d")
+        {
+          using DataType = TinyVector<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          FaceValue<DataType> face_value{mesh_2d->connectivity()};
+          parallel_for(
+            face_value.numberOfItems(), PUGS_LAMBDA(const FaceId face_id) {
+              const TinyVector<Dimension>& x = xl[face_id];
+              face_value[face_id]            = DataType{2 * std::exp(x[0]), -3 * x[1]};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_2d, ItemType::face, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(face_value, item_value_variant->get<FaceValue<DataType>>()));
+        }
+
+        SECTION("R3x3_non_linear_2d")
+        {
+          using DataType = TinyMatrix<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3x3_non_linear_2d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          FaceValue<DataType> face_value{mesh_2d->connectivity()};
+          parallel_for(
+            face_value.numberOfItems(), PUGS_LAMBDA(const FaceId face_id) {
+              const TinyVector<Dimension>& x = xl[face_id];
+
+              face_value[face_id] = DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3,
+                                             std::sin(x[1] - 2 * x[0]),
+                                             3,
+                                             x[1] * x[0],
+                                             -4 * x[1],
+                                             2 * x[0] + 1,
+                                             3,
+                                             -6 * x[0],
+                                             std::exp(x[1])};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_2d, ItemType::face, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(face_value, item_value_variant->get<FaceValue<DataType>>()));
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    constexpr size_t Dimension = 3;
+
+    std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_3d = named_mesh.mesh();
+
+        auto xe = MeshDataManager::instance().getMeshData(*mesh_3d).xe();
+
+        std::string_view data = R"(
+import math;
+let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]+x[2]) + 3 * x[1];
+let R3_non_linear_3d: R^3 -> R^3, x -> [2 * exp(x[0]) + 3, x[1] - 2, 3 * x[2]];
+let R2x2_non_linear_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(x[2] - 2 * x[0])], [3, x[1] * x[0] - x[2]]];
+)";
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+        ASTNodeDataTypeBuilder{*ast};
+
+        ASTNodeTypeCleaner<language::var_declaration>{*ast};
+        ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+        ASTNodeExpressionBuilder{*ast};
+
+        std::shared_ptr<SymbolTable> symbol_table = ast->m_symbol_table;
+
+        TAO_PEGTL_NAMESPACE::position position{TAO_PEGTL_NAMESPACE::internal::iterator{"fixture"}, "fixture"};
+        position.byte = data.size();   // ensure that variables are declared at this point
+
+        SECTION("R_scalar_non_linear_3d")
+        {
+          auto [i_symbol, found] = symbol_table->find("R_scalar_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          EdgeValue<double> edge_value{mesh_3d->connectivity()};
+          parallel_for(
+            edge_value.numberOfItems(), PUGS_LAMBDA(const EdgeId edge_id) {
+              const TinyVector<Dimension>& x = xe[edge_id];
+              edge_value[edge_id]            = 2 * std::exp(x[0] + x[2]) + 3 * x[1];
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_3d, ItemType::edge, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(edge_value, item_value_variant->get<EdgeValue<double>>()));
+        }
+
+        SECTION("R3_non_linear_3d")
+        {
+          using DataType = TinyVector<3>;
+
+          auto [i_symbol, found] = symbol_table->find("R3_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          EdgeValue<DataType> edge_value{mesh_3d->connectivity()};
+          parallel_for(
+            edge_value.numberOfItems(), PUGS_LAMBDA(const EdgeId edge_id) {
+              const TinyVector<Dimension>& x = xe[edge_id];
+              edge_value[edge_id]            = DataType{2 * std::exp(x[0]) + 3, x[1] - 2, 3 * x[2]};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_3d, ItemType::edge, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(edge_value, item_value_variant->get<EdgeValue<DataType>>()));
+        }
+
+        SECTION("R2x2_non_linear_3d")
+        {
+          using DataType = TinyMatrix<2>;
+
+          auto [i_symbol, found] = symbol_table->find("R2x2_non_linear_3d", position);
+          REQUIRE(found);
+          REQUIRE(i_symbol->attributes().dataType() == ASTNodeDataType::function_t);
+
+          FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
+
+          EdgeValue<DataType> edge_value{mesh_3d->connectivity()};
+          parallel_for(
+            edge_value.numberOfItems(), PUGS_LAMBDA(const EdgeId edge_id) {
+              const TinyVector<Dimension>& x = xe[edge_id];
+              edge_value[edge_id] =
+                DataType{2 * std::exp(x[0]) * std::sin(x[1]) + 3, std::sin(x[2] - 2 * x[0]), 3, x[1] * x[0] - x[2]};
+            });
+
+          ItemValueVariantFunctionInterpoler interpoler(mesh_3d, ItemType::edge, function_symbol_id);
+          std::shared_ptr item_value_variant = interpoler.interpolate();
+
+          REQUIRE(same_item_value(edge_value, item_value_variant->get<EdgeValue<DataType>>()));
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_SubItemValuePerItemVariant.cpp b/tests/test_SubItemValuePerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d37e9ab403421c7f706583537b4ca724f25f4774
--- /dev/null
+++ b/tests/test_SubItemValuePerItemVariant.cpp
@@ -0,0 +1,165 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <utils/Messenger.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubItemValuePerItemVariant", "[mesh]")
+{
+  std::shared_ptr mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+  const Connectivity<3>& connectivity = *mesh->shared_connectivity();
+
+  using R1 = TinyVector<1>;
+  using R2 = TinyVector<2>;
+  using R3 = TinyVector<3>;
+
+  SECTION("NodeValuePerCell<double>")
+  {
+    NodeValuePerCell<double> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<NodeValuePerCell<double>>());
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("NodeValuePerFace<R1>")
+  {
+    NodeValuePerFace<R1> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<const double>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<const int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<const uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_NOTHROW(v.get<NodeValuePerFace<const R1>>());
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<const R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<const R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("NodeValuePerEdge<int64_t>")
+  {
+    NodeValuePerEdge<int64_t> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<double>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_NOTHROW(v.get<NodeValuePerEdge<int64_t>>());
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("EdgeValuePerCell<R2>")
+  {
+    EdgeValuePerCell<R2> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerCell<double>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerCell<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerCell<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerCell<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_NOTHROW(v.get<EdgeValuePerCell<R2>>());
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerCell<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("EdgeValuePerFace<R1>")
+  {
+    EdgeValuePerFace<R1> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerFace<const double>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerFace<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerFace<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_NOTHROW(v.get<EdgeValuePerFace<R1>>());
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerFace<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerFace<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("EdgeValuePerNode<double>")
+  {
+    EdgeValuePerNode<double> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<EdgeValuePerNode<double>>());
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerNode<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerNode<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerNode<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerNode<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<EdgeValuePerNode<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("FaceValuePerCell<uint64_t>")
+  {
+    FaceValuePerCell<uint64_t> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerCell<double>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerCell<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_NOTHROW(v.get<FaceValuePerCell<uint64_t>>());
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerCell<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerCell<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerCell<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("FaceValuePerEdge<double>")
+  {
+    FaceValuePerEdge<double> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<FaceValuePerEdge<double>>());
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerEdge<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerEdge<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerEdge<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerEdge<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerEdge<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("FaceValuePerNode<double>")
+  {
+    FaceValuePerNode<double> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<FaceValuePerNode<double>>());
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerNode<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerNode<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerNode<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerNode<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<FaceValuePerNode<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("NodeValuePerCell<double>")
+  {
+    NodeValuePerCell<double> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<NodeValuePerCell<double>>());
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerCell<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+
+  SECTION("NodeValuePerFace<R3>")
+  {
+    NodeValuePerFace<R3> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<double>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerFace<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_NOTHROW(v.get<NodeValuePerFace<R3>>());
+  }
+
+  SECTION("NodeValuePerEdge<double>")
+  {
+    NodeValuePerEdge<double> node_value{connectivity};
+    SubItemValuePerItemVariant v(node_value);
+    REQUIRE_NOTHROW(v.get<NodeValuePerEdge<double>>());
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<int64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<uint64_t>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<R1>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<R2>>(), "error: invalid SubItemValuePerItem type");
+    REQUIRE_THROWS_WITH(v.get<NodeValuePerEdge<R3>>(), "error: invalid SubItemValuePerItem type");
+  }
+}