From fe9878b58dc8792216c0580084cdb93552db9875 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Fri, 26 Nov 2021 12:56:32 +0100
Subject: [PATCH] Add DiscreteFunctionIntegrator and
 DiscreteFunctionVectorIntegrator

It is now possible to integrate (user) functions within a pugs script
by specifying the mesh and the quadrature formula kind.

The result if a P0 (or P0Vector) discrete function which contains
integrals computed in each cell of the mesh
---
 .../GaussLegendreQuadratureDescriptor.hpp     |  50 ++
 .../GaussLobattoQuadratureDescriptor.hpp      |  50 ++
 src/analysis/GaussQuadratureDescriptor.hpp    |  50 ++
 src/analysis/IQuadratureDescriptor.hpp        |  25 +
 src/analysis/QuadratureManager.hpp            | 111 ++++
 src/analysis/QuadratureType.hpp               |   6 +-
 src/language/modules/SchemeModule.cpp         |  64 ++
 src/language/modules/SchemeModule.hpp         |   5 +
 src/language/utils/IntegrateCellArray.hpp     |  69 ++
 src/language/utils/IntegrateCellValue.hpp     |  52 ++
 src/language/utils/IntegrateOnCells.hpp       | 606 ++++++++++++++++++
 src/scheme/CMakeLists.txt                     |   2 +
 src/scheme/DiscreteFunctionIntegrator.cpp     | 132 ++++
 src/scheme/DiscreteFunctionIntegrator.hpp     |  39 ++
 .../DiscreteFunctionVectorIntegrator.cpp      |  70 ++
 .../DiscreteFunctionVectorIntegrator.hpp      |  47 ++
 16 files changed, 1373 insertions(+), 5 deletions(-)
 create mode 100644 src/analysis/GaussLegendreQuadratureDescriptor.hpp
 create mode 100644 src/analysis/GaussLobattoQuadratureDescriptor.hpp
 create mode 100644 src/analysis/GaussQuadratureDescriptor.hpp
 create mode 100644 src/analysis/IQuadratureDescriptor.hpp
 create mode 100644 src/language/utils/IntegrateCellArray.hpp
 create mode 100644 src/language/utils/IntegrateCellValue.hpp
 create mode 100644 src/language/utils/IntegrateOnCells.hpp
 create mode 100644 src/scheme/DiscreteFunctionIntegrator.cpp
 create mode 100644 src/scheme/DiscreteFunctionIntegrator.hpp
 create mode 100644 src/scheme/DiscreteFunctionVectorIntegrator.cpp
 create mode 100644 src/scheme/DiscreteFunctionVectorIntegrator.hpp

diff --git a/src/analysis/GaussLegendreQuadratureDescriptor.hpp b/src/analysis/GaussLegendreQuadratureDescriptor.hpp
new file mode 100644
index 000000000..4cccc87e0
--- /dev/null
+++ b/src/analysis/GaussLegendreQuadratureDescriptor.hpp
@@ -0,0 +1,50 @@
+#ifndef GAUSS_LEGENDRE_QUADRATURE_DESCRIPTOR_HPP
+#define GAUSS_LEGENDRE_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+
+#include <sstream>
+
+class GaussLegendreQuadratureDescriptor final : public IQuadratureDescriptor
+{
+ private:
+  size_t m_degree;
+
+ public:
+  bool
+  isTensorial() const
+  {
+    return true;
+  }
+
+  QuadratureType
+  type() const
+  {
+    return QuadratureType::GaussLegendre;
+  }
+
+  size_t
+  degree() const
+  {
+    return m_degree;
+  }
+
+  std::string
+  name() const
+  {
+    std::ostringstream os;
+    os << "GaussLegendre(" << m_degree << ")";
+    return os.str();
+  }
+
+  GaussLegendreQuadratureDescriptor(size_t degree) : m_degree{degree} {}
+  GaussLegendreQuadratureDescriptor() noexcept = delete;
+
+  GaussLegendreQuadratureDescriptor(const GaussLegendreQuadratureDescriptor&) = default;
+
+  GaussLegendreQuadratureDescriptor(GaussLegendreQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~GaussLegendreQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // GAUSS_LEGENDRE_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/GaussLobattoQuadratureDescriptor.hpp b/src/analysis/GaussLobattoQuadratureDescriptor.hpp
new file mode 100644
index 000000000..5b939437b
--- /dev/null
+++ b/src/analysis/GaussLobattoQuadratureDescriptor.hpp
@@ -0,0 +1,50 @@
+#ifndef GAUSS_LOBATTO_QUADRATURE_DESCRIPTOR_HPP
+#define GAUSS_LOBATTO_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+
+#include <sstream>
+
+class GaussLobattoQuadratureDescriptor final : public IQuadratureDescriptor
+{
+ private:
+  size_t m_degree;
+
+ public:
+  bool
+  isTensorial() const
+  {
+    return true;
+  }
+
+  QuadratureType
+  type() const
+  {
+    return QuadratureType::GaussLobatto;
+  }
+
+  size_t
+  degree() const
+  {
+    return m_degree;
+  }
+
+  std::string
+  name() const
+  {
+    std::ostringstream os;
+    os << "GaussLobatto(" << m_degree << ")";
+    return os.str();
+  }
+
+  GaussLobattoQuadratureDescriptor(size_t degree) : m_degree{degree} {}
+  GaussLobattoQuadratureDescriptor() noexcept = delete;
+
+  GaussLobattoQuadratureDescriptor(const GaussLobattoQuadratureDescriptor&) = default;
+
+  GaussLobattoQuadratureDescriptor(GaussLobattoQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~GaussLobattoQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // GAUSS_LOBATTO_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/GaussQuadratureDescriptor.hpp b/src/analysis/GaussQuadratureDescriptor.hpp
new file mode 100644
index 000000000..13282f65d
--- /dev/null
+++ b/src/analysis/GaussQuadratureDescriptor.hpp
@@ -0,0 +1,50 @@
+#ifndef GAUSS_QUADRATURE_DESCRIPTOR_HPP
+#define GAUSS_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+
+#include <sstream>
+
+class GaussQuadratureDescriptor final : public IQuadratureDescriptor
+{
+ private:
+  size_t m_degree;
+
+ public:
+  bool
+  isTensorial() const
+  {
+    return false;
+  }
+
+  QuadratureType
+  type() const
+  {
+    return QuadratureType::Gauss;
+  }
+
+  size_t
+  degree() const
+  {
+    return m_degree;
+  }
+
+  std::string
+  name() const
+  {
+    std::ostringstream os;
+    os << "Gauss(" << m_degree << ")";
+    return os.str();
+  }
+
+  GaussQuadratureDescriptor(size_t degree) : m_degree{degree} {}
+  GaussQuadratureDescriptor() noexcept = delete;
+
+  GaussQuadratureDescriptor(const GaussQuadratureDescriptor&) = default;
+
+  GaussQuadratureDescriptor(GaussQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~GaussQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // GAUSS_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/IQuadratureDescriptor.hpp b/src/analysis/IQuadratureDescriptor.hpp
new file mode 100644
index 000000000..5d7e3685a
--- /dev/null
+++ b/src/analysis/IQuadratureDescriptor.hpp
@@ -0,0 +1,25 @@
+#ifndef I_QUADRATURE_DESCRIPTOR_HPP
+#define I_QUADRATURE_DESCRIPTOR_HPP
+
+#include <analysis/QuadratureType.hpp>
+
+#include <string>
+
+class IQuadratureDescriptor
+{
+ public:
+  virtual QuadratureType type() const = 0;
+  virtual bool isTensorial() const    = 0;
+  virtual size_t degree() const       = 0;
+  virtual std::string name() const    = 0;
+
+  IQuadratureDescriptor() noexcept = default;
+
+  IQuadratureDescriptor(const IQuadratureDescriptor&) = default;
+
+  IQuadratureDescriptor(IQuadratureDescriptor&&) noexcept = default;
+
+  virtual ~IQuadratureDescriptor() noexcept = default;
+};
+
+#endif   // I_QUADRATURE_DESCRIPTOR_HPP
diff --git a/src/analysis/QuadratureManager.hpp b/src/analysis/QuadratureManager.hpp
index 875a2a3fd..d205c457f 100644
--- a/src/analysis/QuadratureManager.hpp
+++ b/src/analysis/QuadratureManager.hpp
@@ -1,8 +1,10 @@
 #ifndef QUADRATURE_MANAGER_HPP
 #define QUADRATURE_MANAGER_HPP
 
+#include <analysis/IQuadratureDescriptor.hpp>
 #include <analysis/QuadratureFormula.hpp>
 #include <utils/Array.hpp>
+#include <utils/Exceptions.hpp>
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsMacros.hpp>
 
@@ -60,6 +62,115 @@ class QuadratureManager
     return m_line_gauss_lobatto_formula_list.size() * 2 - 1;
   }
 
+  const QuadratureFormula<1>&
+  getLineFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss:
+    case QuadratureType::GaussLegendre: {
+      return m_line_gauss_legendre_formula_list[quadrature_descriptor.degree() / 2];
+      break;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_line_gauss_lobatto_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  const QuadratureFormula<2>&
+  getTriangleFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_triangle_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on triangles");
+    }
+    }
+  }
+
+  const QuadratureFormula<2>&
+  getSquareFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_square_gauss_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    case QuadratureType::GaussLegendre: {
+      return m_square_gauss_legendre_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_square_gauss_lobatto_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getTetrahedronFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_tetrahedron_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on tetrahedron");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getPrismFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_prism_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on prism");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getPyramidFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_pyramid_gauss_formula_list[quadrature_descriptor.degree() - 1];
+    }
+    default: {
+      throw UnexpectedError(quadrature_descriptor.name() + " is not defined on pyramid");
+    }
+    }
+  }
+
+  const QuadratureFormula<3>&
+  getCubeFormula(const IQuadratureDescriptor& quadrature_descriptor) const
+  {
+    switch (quadrature_descriptor.type()) {
+    case QuadratureType::Gauss: {
+      return m_cube_gauss_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    case QuadratureType::GaussLegendre: {
+      return m_cube_gauss_legendre_formula_list[quadrature_descriptor.degree() / 2];
+      break;
+    }
+    case QuadratureType::GaussLobatto: {
+      return m_cube_gauss_lobatto_formula_list[quadrature_descriptor.degree() / 2];
+    }
+    default: {
+      throw UnexpectedError("invalid quadrature type");
+    }
+    }
+  }
+
   const QuadratureFormula<1>&
   getLineGaussLegendreFormula(const size_t degree) const
   {
diff --git a/src/analysis/QuadratureType.hpp b/src/analysis/QuadratureType.hpp
index dca48806d..795961591 100644
--- a/src/analysis/QuadratureType.hpp
+++ b/src/analysis/QuadratureType.hpp
@@ -3,13 +3,9 @@
 
 enum class QuadratureType
 {
-  QT__begin = 0,
-  //
-  Gauss         = QT__begin,
+  Gauss         = 0,
   GaussLegendre = 1,
   GaussLobatto  = 2,
-  //
-  QT__end
 };
 
 #endif   // QUADRATURE_TYPE_HPP
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index d989bff45..fcebda3de 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -1,5 +1,8 @@
 #include <language/modules/SchemeModule.hpp>
 
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
 #include <language/modules/BinaryOperatorRegisterForVh.hpp>
 #include <language/modules/MathFunctionRegisterForVh.hpp>
 #include <language/modules/UnaryOperatorRegisterForVh.hpp>
@@ -17,9 +20,11 @@
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
 #include <scheme/DiscreteFunctionDescriptorP0.hpp>
 #include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
+#include <scheme/DiscreteFunctionIntegrator.hpp>
 #include <scheme/DiscreteFunctionInterpoler.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
 #include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVectorIntegrator.hpp>
 #include <scheme/DiscreteFunctionVectorInterpoler.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
@@ -38,6 +43,7 @@ SchemeModule::SchemeModule()
 {
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunction>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>>);
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>>);
 
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>>);
@@ -59,6 +65,64 @@ SchemeModule::SchemeModule()
 
                               ));
 
+  this->_addBuiltinFunction("Gauss", std::make_shared<
+                                       BuiltinFunctionEmbedder<std::shared_ptr<const IQuadratureDescriptor>(uint64_t)>>(
+                                       [](uint64_t degree) -> std::shared_ptr<const IQuadratureDescriptor> {
+                                         return std::make_shared<GaussQuadratureDescriptor>(degree);
+                                       }
+
+                                       ));
+
+  this->_addBuiltinFunction("GaussLobatto",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const IQuadratureDescriptor>(uint64_t)>>(
+                              [](uint64_t degree) -> std::shared_ptr<const IQuadratureDescriptor> {
+                                return std::make_shared<GaussLobattoQuadratureDescriptor>(degree);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("GaussLegendre",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const IQuadratureDescriptor>(uint64_t)>>(
+                              [](uint64_t degree) -> std::shared_ptr<const IQuadratureDescriptor> {
+                                return std::make_shared<GaussLegendreQuadratureDescriptor>(degree);
+                              }
+
+                              ));
+
+  this
+    ->_addBuiltinFunction("integrate",
+                          std::make_shared<BuiltinFunctionEmbedder<
+                            std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IMesh>,
+                                                                     std::shared_ptr<const IQuadratureDescriptor>,
+                                                                     std::shared_ptr<const IDiscreteFunctionDescriptor>,
+                                                                     const std::vector<FunctionSymbolId>&)>>(
+                            [](std::shared_ptr<const IMesh> mesh,
+                               std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
+                               std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
+                               const std::vector<FunctionSymbolId>& function_id_list)
+                              -> std::shared_ptr<const IDiscreteFunction> {
+                              return DiscreteFunctionVectorIntegrator{mesh, quadrature_descriptor,
+                                                                      discrete_function_descriptor, function_id_list}
+                                .integrate();
+                            }
+
+                            ));
+
+  this->_addBuiltinFunction("integrate",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              std::shared_ptr<const IDiscreteFunction>(std::shared_ptr<const IMesh>,
+                                                                       std::shared_ptr<const IQuadratureDescriptor>,
+                                                                       const FunctionSymbolId&)>>(
+                              [](std::shared_ptr<const IMesh> mesh,
+                                 std::shared_ptr<const IQuadratureDescriptor> quadrature_descriptor,
+                                 const FunctionSymbolId& function_id) -> std::shared_ptr<const IDiscreteFunction> {
+                                return DiscreteFunctionIntegrator{mesh, quadrature_descriptor, function_id}.integrate();
+                              }
+
+                              ));
+
   this->_addBuiltinFunction(
     "interpolate",
     std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index 758b5f9c5..52fc163ee 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -25,6 +25,11 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh_type");
 
+class IQuadratureDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("quadrature");
+
 class SchemeModule : public BuiltinModule
 {
   friend class MathFunctionRegisterForVh;
diff --git a/src/language/utils/IntegrateCellArray.hpp b/src/language/utils/IntegrateCellArray.hpp
new file mode 100644
index 000000000..41450de3b
--- /dev/null
+++ b/src/language/utils/IntegrateCellArray.hpp
@@ -0,0 +1,69 @@
+#ifndef INTEGRATE_CELL_ARRAY_HPP
+#define INTEGRATE_CELL_ARRAY_HPP
+
+#include <language/utils/IntegrateCellValue.hpp>
+#include <mesh/CellType.hpp>
+#include <mesh/ItemArray.hpp>
+
+template <typename T>
+class IntegrateCellArray;
+template <typename OutputType, typename InputType>
+class IntegrateCellArray<OutputType(InputType)>
+{
+  static constexpr size_t Dimension = OutputType::Dimension;
+
+ public:
+  template <typename MeshType>
+  PUGS_INLINE static CellArray<OutputType>
+  integrate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh)
+  {
+    CellArray<OutputType> cell_array{mesh.connectivity(), function_symbol_id_list.size()};
+
+    for (size_t i_function_symbol = 0; i_function_symbol < function_symbol_id_list.size(); ++i_function_symbol) {
+      const FunctionSymbolId& function_symbol_id = function_symbol_id_list[i_function_symbol];
+      CellValue<OutputType> cell_value =
+        IntegrateCellValue<OutputType(InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh);
+      parallel_for(
+        cell_value.numberOfItems(),
+        PUGS_LAMBDA(CellId cell_id) { cell_array[cell_id][i_function_symbol] = cell_value[cell_id]; });
+    }
+
+    return cell_array;
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Table<OutputType>
+  integrate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<const CellId>& list_of_cells)
+  {
+    Table<OutputType> table{list_of_cells.size(), function_symbol_id_list.size()};
+
+    for (size_t i_function_symbol = 0; i_function_symbol < function_symbol_id_list.size(); ++i_function_symbol) {
+      const FunctionSymbolId& function_symbol_id = function_symbol_id_list[i_function_symbol];
+      Array<OutputType> array =
+        IntegrateCellValue<OutputType(InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh,
+                                                             list_of_cells);
+
+      parallel_for(
+        array.size(), PUGS_LAMBDA(size_t i) { table[i][i_function_symbol] = array[i]; });
+    }
+
+    return table;
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Table<OutputType>
+  integrate(const std::vector<FunctionSymbolId>& function_symbol_id_list,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<CellId>& list_of_cells)
+  {
+    return integrate(function_symbol_id_list, quadrature_descriptor, mesh, Array<const CellId>{list_of_cells});
+  }
+};
+
+#endif   // INTEGRATE_CELL_ARRAY_HPP
diff --git a/src/language/utils/IntegrateCellValue.hpp b/src/language/utils/IntegrateCellValue.hpp
new file mode 100644
index 000000000..c4eafc151
--- /dev/null
+++ b/src/language/utils/IntegrateCellValue.hpp
@@ -0,0 +1,52 @@
+#ifndef INTEGRATE_CELL_VALUE_HPP
+#define INTEGRATE_CELL_VALUE_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/IntegrateOnCells.hpp>
+#include <mesh/ItemType.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/Mesh.hpp>
+
+template <typename T>
+class IntegrateCellValue;
+template <typename OutputType, typename InputType>
+class IntegrateCellValue<OutputType(InputType)>
+{
+ public:
+  template <typename MeshType>
+  PUGS_INLINE static CellValue<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh)
+  {
+    CellValue<OutputType> value(mesh.connectivity());
+    IntegrateOnCells<OutputType(const InputType)>::template integrateTo<MeshType>(function_symbol_id,
+                                                                                  quadrature_descriptor, mesh, value);
+
+    return value;
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Array<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<const CellId>& list_of_cells)
+  {
+    return IntegrateOnCells<OutputType(const InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh,
+                                                                    list_of_cells);
+  }
+
+  template <typename MeshType>
+  PUGS_INLINE static Array<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<CellId>& list_of_cells)
+  {
+    return IntegrateOnCells<OutputType(const InputType)>::integrate(function_symbol_id, quadrature_descriptor, mesh,
+                                                                    Array<const CellId>{list_of_cells});
+  }
+};
+
+#endif   // INTEGRATE_CELL_VALUE_HPP
diff --git a/src/language/utils/IntegrateOnCells.hpp b/src/language/utils/IntegrateOnCells.hpp
new file mode 100644
index 000000000..d4463da96
--- /dev/null
+++ b/src/language/utils/IntegrateOnCells.hpp
@@ -0,0 +1,606 @@
+#ifndef INTEGRATE_ON_CELLS_HPP
+#define INTEGRATE_ON_CELLS_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <geometry/AffineTransformation.hpp>
+#include <geometry/PrismTransformation.hpp>
+#include <geometry/PyramidTransformation.hpp>
+#include <geometry/Q1Transformation.hpp>
+#include <language/utils/PugsFunctionAdapter.hpp>
+#include <mesh/CellType.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+
+class FunctionSymbolId;
+
+template <typename T>
+class IntegrateOnCells;
+template <typename OutputType, typename InputType>
+class IntegrateOnCells<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+{
+ private:
+  using Adapter = PugsFunctionAdapter<OutputType(InputType)>;
+
+  template <size_t Dimension>
+  class AllCells
+  {
+   private:
+    const Connectivity<Dimension>& m_connectivity;
+
+   public:
+    using index_type = CellId;
+
+    PUGS_INLINE
+    CellId
+    cellId(const CellId cell_id) const
+    {
+      return cell_id;
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_connectivity.numberOfCells();
+    }
+
+    PUGS_INLINE
+    AllCells(const Connectivity<Dimension>& connectivity) : m_connectivity{connectivity} {}
+  };
+
+  class CellList
+  {
+   private:
+    const Array<CellId>& m_cell_list;
+
+   public:
+    using index_type = Array<CellId>::index_type;
+
+    PUGS_INLINE
+    CellId
+    cellId(const index_type index) const
+    {
+      return m_cell_list[index];
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const
+    {
+      return m_cell_list.size();
+    }
+
+    PUGS_INLINE
+    CellList(const Array<CellId>& cell_list) : m_cell_list{cell_list} {}
+  };
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT>
+  static PUGS_INLINE void
+  _tensorialIntegrateTo(const FunctionSymbolId& function_symbol_id,
+                        const IQuadratureDescriptor& quadrature_descriptor,
+                        const MeshType& mesh,
+                        const ListTypeT& cell_list,
+                        OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, InputType>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    auto context_list = Adapter::getContextList(expression);
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+    const auto cell_type           = connectivity.cellType();
+    const auto xr                  = mesh.xr();
+
+    auto invalid_cell = std::make_pair(false, CellId{});
+
+    const auto qf = [&] {
+      if constexpr (Dimension == 1) {
+        return QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+      } else if constexpr (Dimension == 2) {
+        return QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+      } else {
+        static_assert(Dimension == 3);
+        return QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+      }
+    }();
+
+    parallel_for(cell_list.size(), [=, &expression, &tokens, &invalid_cell, &qf,
+                                    &cell_list](typename ListTypeT::index_type index) {
+      const int32_t t        = tokens.acquire();
+      auto& execution_policy = context_list[t];
+
+      const CellId cell_id = cell_list.cellId(index);
+
+      const auto cell_to_node = cell_to_node_matrix[cell_id];
+
+      if constexpr (Dimension == 1) {
+        switch (cell_type[cell_id]) {
+        case CellType::Line: {
+          const AffineTransformation<1> T({xr[cell_to_node[0]], xr[cell_to_node[1]]});
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else if constexpr (Dimension == 2) {
+        switch (cell_type[cell_id]) {
+        case CellType::Triangle: {
+          const Q1Transformation<2> T(
+            {xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[2]]});
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Quadrangle: {
+          const Q1Transformation<2> T(
+            {xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]]});
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else {
+        static_assert(Dimension == 3);
+
+        switch (cell_type[cell_id]) {
+        case CellType::Tetrahedron: {
+          const Q1Transformation<3> T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                       xr[cell_to_node[2]],   //
+                                       xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[3]],
+                                       xr[cell_to_node[3]]});
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Pyramid: {
+          if (cell_to_node.size() == 5) {
+            const Q1Transformation<3> T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],   //
+                                         xr[cell_to_node[3]],                                             //
+                                         xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]],
+                                         xr[cell_to_node[4]]});
+
+            for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+              const auto xi = qf.point(i_point);
+              Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+              auto result = expression.execute(execution_policy);
+              value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+            }
+          } else {
+            throw NotImplementedError("integration on pyramid with non-quadrangular base (number of nodes " +
+                                      std::to_string(cell_to_node.size()) + ")");
+          }
+          break;
+        }
+        case CellType::Diamond: {
+          if (cell_to_node.size() == 5) {
+            {   // top tetrahedron
+              const Q1Transformation<3> T0({xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                            xr[cell_to_node[3]],   //
+                                            xr[cell_to_node[4]], xr[cell_to_node[4]], xr[cell_to_node[4]],
+                                            xr[cell_to_node[4]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+            {   // bottom tetrahedron
+              const Q1Transformation<3> T1({xr[cell_to_node[3]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                            xr[cell_to_node[1]],   //
+                                            xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                            xr[cell_to_node[0]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+          } else if (cell_to_node.size() == 6) {
+            {   // top pyramid
+              const Q1Transformation<3> T0({xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                            xr[cell_to_node[4]],   //
+                                            xr[cell_to_node[5]], xr[cell_to_node[5]], xr[cell_to_node[5]],
+                                            xr[cell_to_node[5]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+            {   // bottom pyramid
+              const Q1Transformation<3> T1({xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                            xr[cell_to_node[1]],   //
+                                            xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                            xr[cell_to_node[0]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+          } else {
+            throw NotImplementedError("integration on diamond with non-quadrangular base (" +
+                                      std::to_string(cell_to_node.size()) + ")");
+          }
+          break;
+        }
+        case CellType::Prism: {
+          const Q1Transformation<3> T({xr[cell_to_node[0]], xr[cell_to_node[1]],   //
+                                       xr[cell_to_node[2]], xr[cell_to_node[2]],   //
+                                       xr[cell_to_node[3]], xr[cell_to_node[4]],   //
+                                       xr[cell_to_node[5]], xr[cell_to_node[5]]});
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Hexahedron: {
+          const Q1Transformation<3> T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                       xr[cell_to_node[3]], xr[cell_to_node[4]], xr[cell_to_node[5]],
+                                       xr[cell_to_node[6]], xr[cell_to_node[7]]});
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      }
+
+      tokens.release(t);
+    });
+
+    if (invalid_cell.first) {
+      std::ostringstream os;
+      os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]);
+      throw UnexpectedError(os.str());
+    }
+  }
+
+  template <typename MeshType, typename OutputArrayT, typename ListTypeT>
+  static PUGS_INLINE void
+  _directIntegrateTo(const FunctionSymbolId& function_symbol_id,
+                     const IQuadratureDescriptor& quadrature_descriptor,
+                     const MeshType& mesh,
+                     const ListTypeT& cell_list,
+                     OutputArrayT& value)
+  {
+    Assert(not quadrature_descriptor.isTensorial());
+
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    static_assert(std::is_same_v<TinyVector<Dimension>, InputType>, "invalid input data type");
+    static_assert(std::is_same_v<std::remove_const_t<typename OutputArrayT::data_type>, OutputType>,
+                  "invalid output data type");
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    auto context_list = Adapter::getContextList(expression);
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    if constexpr (std::is_arithmetic_v<OutputType>) {
+      value.fill(0);
+    } else if constexpr (is_tiny_vector_v<OutputType> or is_tiny_matrix_v<OutputType>) {
+      value.fill(zero);
+    } else {
+      static_assert(std::is_same_v<OutputType, double>, "unexpected output type");
+    }
+
+    const auto& connectivity = mesh.connectivity();
+
+    const auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+    const auto cell_type           = connectivity.cellType();
+    const auto xr                  = mesh.xr();
+
+    auto invalid_cell = std::make_pair(false, CellId{});
+
+    parallel_for(cell_list.size(), [=, &expression, &tokens, &invalid_cell, &quadrature_descriptor,
+                                    &cell_list](const typename ListTypeT::index_type& index) {
+      const int32_t t        = tokens.acquire();
+      auto& execution_policy = context_list[t];
+
+      const CellId cell_id = cell_list.cellId(index);
+
+      const auto cell_to_node = cell_to_node_matrix[cell_id];
+
+      if constexpr (Dimension == 1) {
+        switch (cell_type[cell_id]) {
+        case CellType::Line: {
+          const AffineTransformation<1> T({xr[cell_to_node[0]], xr[cell_to_node[1]]});
+          const auto qf = QuadratureManager::instance().getLineFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else if constexpr (Dimension == 2) {
+        switch (cell_type[cell_id]) {
+        case CellType::Triangle: {
+          const AffineTransformation<2> T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]]});
+          const auto qf = QuadratureManager::instance().getTriangleFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Quadrangle: {
+          const Q1Transformation<2> T(
+            {xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]]});
+          const auto qf = QuadratureManager::instance().getSquareFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      } else {
+        static_assert(Dimension == 3);
+
+        switch (cell_type[cell_id]) {
+        case CellType::Tetrahedron: {
+          const AffineTransformation<3> T({xr[cell_to_node[0]], xr[cell_to_node[1]],   //
+                                           xr[cell_to_node[2]], xr[cell_to_node[3]]});
+          const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant() * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Pyramid: {
+          if (cell_to_node.size() == 5) {
+            const PyramidTransformation T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],   //
+                                           xr[cell_to_node[3]], xr[cell_to_node[4]]});
+            const auto qf = QuadratureManager::instance().getPyramidFormula(quadrature_descriptor);
+
+            for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+              const auto xi = qf.point(i_point);
+              Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+              auto result = expression.execute(execution_policy);
+              value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+            }
+          } else {
+            throw NotImplementedError("integration on pyramid with non-quadrangular base");
+          }
+          break;
+        }
+        case CellType::Diamond: {
+          if (cell_to_node.size() == 5) {
+            const auto qf = QuadratureManager::instance().getTetrahedronFormula(quadrature_descriptor);
+            {   // top tetrahedron
+              const AffineTransformation<3> T0({xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],   //
+                                                xr[cell_to_node[4]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant() * convert_result(std::move(result));
+              }
+            }
+            {   // bottom tetrahedron
+              const AffineTransformation<3> T1({xr[cell_to_node[3]], xr[cell_to_node[2]], xr[cell_to_node[1]],   //
+                                                xr[cell_to_node[0]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant() * convert_result(std::move(result));
+              }
+            }
+          } else if (cell_to_node.size() == 6) {
+            const auto qf = QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+            {   // top pyramid
+              const Q1Transformation<3> T0({xr[cell_to_node[1]], xr[cell_to_node[2]], xr[cell_to_node[3]],
+                                            xr[cell_to_node[4]],   //
+                                            xr[cell_to_node[5]], xr[cell_to_node[5]], xr[cell_to_node[5]],
+                                            xr[cell_to_node[5]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T0(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T0.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+            {   // bottom pyramid
+              const Q1Transformation<3> T1({xr[cell_to_node[4]], xr[cell_to_node[3]], xr[cell_to_node[2]],
+                                            xr[cell_to_node[1]],   //
+                                            xr[cell_to_node[0]], xr[cell_to_node[0]], xr[cell_to_node[0]],
+                                            xr[cell_to_node[0]]});
+
+              for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+                const auto xi = qf.point(i_point);
+                Adapter::convertArgs(execution_policy.currentContext(), T1(xi));
+                auto result = expression.execute(execution_policy);
+                value[index] += qf.weight(i_point) * T1.jacobianDeterminant(xi) * convert_result(std::move(result));
+              }
+            }
+          } else {
+            throw NotImplementedError("integration on diamond with non-quadrangular base (" +
+                                      std::to_string(cell_to_node.size()) + ")");
+          }
+          break;
+        }
+        case CellType::Prism: {
+          const PrismTransformation T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                       xr[cell_to_node[3]], xr[cell_to_node[4]], xr[cell_to_node[5]]});
+          const auto qf = QuadratureManager::instance().getPrismFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        case CellType::Hexahedron: {
+          const Q1Transformation<3> T({xr[cell_to_node[0]], xr[cell_to_node[1]], xr[cell_to_node[2]],
+                                       xr[cell_to_node[3]], xr[cell_to_node[4]], xr[cell_to_node[5]],
+                                       xr[cell_to_node[6]], xr[cell_to_node[7]]});
+          const auto qf = QuadratureManager::instance().getCubeFormula(quadrature_descriptor);
+
+          for (size_t i_point = 0; i_point < qf.numberOfPoints(); ++i_point) {
+            const auto xi = qf.point(i_point);
+            Adapter::convertArgs(execution_policy.currentContext(), T(xi));
+            auto result = expression.execute(execution_policy);
+            value[index] += qf.weight(i_point) * T.jacobianDeterminant(xi) * convert_result(std::move(result));
+          }
+          break;
+        }
+        default: {
+          invalid_cell = std::make_pair(true, cell_id);
+          break;
+        }
+        }
+      }
+
+      tokens.release(t);
+    });
+
+    if (invalid_cell.first) {
+      std::ostringstream os;
+      os << "invalid cell type for integration: " << name(cell_type[invalid_cell.second]);
+      throw UnexpectedError(os.str());
+    }
+  }
+
+ public:
+  template <typename MeshType, typename OutputArrayT>
+  static PUGS_INLINE void
+  integrateTo(const FunctionSymbolId& function_symbol_id,
+              const IQuadratureDescriptor& quadrature_descriptor,
+              const MeshType& mesh,
+              OutputArrayT& value)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, OutputArrayT>(function_symbol_id, quadrature_descriptor, mesh,
+                                                    AllCells<Dimension>{mesh.connectivity()}, value);
+    } else {
+      _directIntegrateTo<MeshType, OutputArrayT>(function_symbol_id, quadrature_descriptor, mesh,
+                                                 AllCells<Dimension>{mesh.connectivity()}, value);
+    }
+  }
+
+  template <typename MeshType, typename OutputArrayT>
+  static PUGS_INLINE Array<OutputType>
+  integrate(const FunctionSymbolId& function_symbol_id,
+            const IQuadratureDescriptor& quadrature_descriptor,
+            const MeshType& mesh,
+            const Array<CellId>& cell_list)
+  {
+    constexpr size_t Dimension = MeshType::Dimension;
+
+    Array<OutputType> value(size(cell_list));
+    if (quadrature_descriptor.isTensorial()) {
+      _tensorialIntegrateTo<MeshType, OutputArrayT>(function_symbol_id, quadrature_descriptor, mesh,
+                                                    CellList{cell_list}, value);
+    } else {
+      _directIntegrateTo<MeshType, OutputArrayT>(function_symbol_id, quadrature_descriptor, mesh, CellList{cell_list},
+                                                 value);
+    }
+
+    return value;
+  }
+};
+
+#endif   // INTEGRATE_ON_CELLS_HPP
diff --git a/src/scheme/CMakeLists.txt b/src/scheme/CMakeLists.txt
index 2131f2fec..115d7bc08 100644
--- a/src/scheme/CMakeLists.txt
+++ b/src/scheme/CMakeLists.txt
@@ -3,6 +3,8 @@
 add_library(
   PugsScheme
   AcousticSolver.cpp
+  DiscreteFunctionIntegrator.cpp
   DiscreteFunctionInterpoler.cpp
   DiscreteFunctionUtils.cpp
+  DiscreteFunctionVectorIntegrator.cpp
   DiscreteFunctionVectorInterpoler.cpp)
diff --git a/src/scheme/DiscreteFunctionIntegrator.cpp b/src/scheme/DiscreteFunctionIntegrator.cpp
new file mode 100644
index 000000000..91cf921df
--- /dev/null
+++ b/src/scheme/DiscreteFunctionIntegrator.cpp
@@ -0,0 +1,132 @@
+#include <scheme/DiscreteFunctionIntegrator.hpp>
+
+#include <language/utils/IntegrateCellValue.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <utils/Exceptions.hpp>
+
+template <size_t Dimension, typename DataType, typename ValueType>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionIntegrator::_integrate() const
+{
+  using MeshType       = Mesh<Connectivity<Dimension>>;
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(m_mesh);
+
+  if constexpr (std::is_same_v<DataType, ValueType>) {
+    return std::make_shared<
+      DiscreteFunctionP0<Dimension, ValueType>>(mesh,
+                                                IntegrateCellValue<DataType(TinyVector<Dimension>)>::template integrate<
+                                                  MeshType>(m_function_id, *m_quadrature_descriptor, *mesh));
+  } else {
+    static_assert(std::is_convertible_v<DataType, ValueType>);
+
+    CellValue<DataType> cell_data =
+      IntegrateCellValue<DataType(TinyVector<Dimension>)>::template integrate<MeshType>(m_function_id,
+                                                                                        *m_quadrature_descriptor,
+                                                                                        *mesh);
+
+    CellValue<ValueType> cell_value{mesh->connectivity()};
+
+    parallel_for(
+      mesh->numberOfCells(), PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = cell_data[cell_id]; });
+
+    return std::make_shared<DiscreteFunctionP0<Dimension, ValueType>>(mesh, cell_value);
+  }
+}
+
+template <size_t Dimension>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionIntegrator::_integrate() 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->_integrate<Dimension, bool, double>();
+  }
+  case ASTNodeDataType::unsigned_int_t: {
+    return this->_integrate<Dimension, uint64_t, double>();
+  }
+  case ASTNodeDataType::int_t: {
+    return this->_integrate<Dimension, int64_t, double>();
+  }
+  case ASTNodeDataType::double_t: {
+    return this->_integrate<Dimension, double>();
+  }
+  case ASTNodeDataType::vector_t: {
+    switch (data_type.dimension()) {
+    case 1: {
+      return this->_integrate<Dimension, TinyVector<1>>();
+    }
+    case 2: {
+      return this->_integrate<Dimension, TinyVector<2>>();
+    }
+    case 3: {
+      return this->_integrate<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->_integrate<Dimension, TinyMatrix<1>>();
+    }
+    case 2: {
+      return this->_integrate<Dimension, TinyMatrix<2>>();
+    }
+    case 3: {
+      return this->_integrate<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 integrate value type: " << rang::fgB::red << dataTypeName(data_type) << rang::style::reset;
+
+    throw UnexpectedError(os.str());
+  }
+    // LCOV_EXCL_STOP
+  }
+}
+
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionIntegrator::integrate() const
+{
+  std::shared_ptr<IDiscreteFunction> discrete_function;
+  switch (m_mesh->dimension()) {
+  case 1: {
+    return this->_integrate<1>();
+  }
+  case 2: {
+    return this->_integrate<2>();
+  }
+  case 3: {
+    return this->_integrate<3>();
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
diff --git a/src/scheme/DiscreteFunctionIntegrator.hpp b/src/scheme/DiscreteFunctionIntegrator.hpp
new file mode 100644
index 000000000..1784f891e
--- /dev/null
+++ b/src/scheme/DiscreteFunctionIntegrator.hpp
@@ -0,0 +1,39 @@
+#ifndef DISCRETE_FUNCTION_INTEGRATOR_HPP
+#define DISCRETE_FUNCTION_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+
+#include <memory>
+
+class DiscreteFunctionIntegrator
+{
+ private:
+  std::shared_ptr<const IMesh> m_mesh;
+  std::shared_ptr<const IQuadratureDescriptor> m_quadrature_descriptor;
+  const FunctionSymbolId m_function_id;
+
+  template <size_t Dimension, typename DataType, typename ValueType = DataType>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+  template <size_t Dimension>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+ public:
+  std::shared_ptr<IDiscreteFunction> integrate() const;
+
+  DiscreteFunctionIntegrator(const std::shared_ptr<const IMesh>& mesh,
+                             const std::shared_ptr<const IQuadratureDescriptor>& quadrature_descriptor,
+                             const FunctionSymbolId& function_id)
+    : m_mesh{mesh}, m_quadrature_descriptor{quadrature_descriptor}, m_function_id{function_id}
+  {}
+
+  DiscreteFunctionIntegrator(const DiscreteFunctionIntegrator&) = delete;
+  DiscreteFunctionIntegrator(DiscreteFunctionIntegrator&&)      = delete;
+
+  ~DiscreteFunctionIntegrator() = default;
+};
+
+#endif   // DISCRETE_FUNCTION_INTEGRATOR_HPP
diff --git a/src/scheme/DiscreteFunctionVectorIntegrator.cpp b/src/scheme/DiscreteFunctionVectorIntegrator.cpp
new file mode 100644
index 000000000..6d8937f84
--- /dev/null
+++ b/src/scheme/DiscreteFunctionVectorIntegrator.cpp
@@ -0,0 +1,70 @@
+#include <scheme/DiscreteFunctionVectorIntegrator.hpp>
+
+#include <language/utils/IntegrateCellArray.hpp>
+
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <utils/Exceptions.hpp>
+
+template <size_t Dimension, typename DataType>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVectorIntegrator::_integrate() const
+{
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const Mesh<Connectivity<Dimension>>>(m_mesh);
+
+  return std::make_shared<
+    DiscreteFunctionP0Vector<Dimension, DataType>>(mesh, IntegrateCellArray<DataType(TinyVector<Dimension>)>::
+                                                           template integrate(m_function_id_list,
+                                                                              *m_quadrature_descriptor, *mesh));
+}
+
+template <size_t Dimension>
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVectorIntegrator::_integrate() const
+{
+  for (const auto& function_id : m_function_id_list) {
+    const auto& function_descriptor = 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:
+    case ASTNodeDataType::unsigned_int_t:
+    case ASTNodeDataType::int_t:
+    case ASTNodeDataType::double_t: {
+      break;
+    }
+    default: {
+      std::ostringstream os;
+      os << "vector functions require scalar value type.\n"
+         << "Invalid interpolation value type: " << rang::fgB::red << dataTypeName(data_type) << rang::style::reset;
+      throw NormalError(os.str());
+    }
+    }
+  }
+  return this->_integrate<Dimension, double>();
+}
+
+std::shared_ptr<IDiscreteFunction>
+DiscreteFunctionVectorIntegrator::integrate() const
+{
+  if (m_discrete_function_descriptor->type() != DiscreteFunctionType::P0Vector) {
+    throw NormalError("invalid discrete function type for vector interpolation");
+  }
+
+  switch (m_mesh->dimension()) {
+  case 1: {
+    return this->_integrate<1>();
+  }
+  case 2: {
+    return this->_integrate<2>();
+  }
+  case 3: {
+    return this->_integrate<3>();
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
diff --git a/src/scheme/DiscreteFunctionVectorIntegrator.hpp b/src/scheme/DiscreteFunctionVectorIntegrator.hpp
new file mode 100644
index 000000000..01e086e7e
--- /dev/null
+++ b/src/scheme/DiscreteFunctionVectorIntegrator.hpp
@@ -0,0 +1,47 @@
+#ifndef DISCRETE_FUNCTION_VECTOR_INTEGRATOR_HPP
+#define DISCRETE_FUNCTION_VECTOR_INTEGRATOR_HPP
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+
+#include <memory>
+#include <vector>
+
+class DiscreteFunctionVectorIntegrator
+{
+ private:
+  std::shared_ptr<const IMesh> m_mesh;
+  std::shared_ptr<const IQuadratureDescriptor> m_quadrature_descriptor;
+  std::shared_ptr<const IDiscreteFunctionDescriptor> m_discrete_function_descriptor;
+  const std::vector<FunctionSymbolId> m_function_id_list;
+
+  template <size_t Dimension, typename DataType>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+  template <size_t Dimension>
+  std::shared_ptr<IDiscreteFunction> _integrate() const;
+
+ public:
+  std::shared_ptr<IDiscreteFunction> integrate() const;
+
+  DiscreteFunctionVectorIntegrator(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::shared_ptr<const IQuadratureDescriptor>& quadrature_descriptor,
+    const std::shared_ptr<const IDiscreteFunctionDescriptor>& discrete_function_descriptor,
+    const std::vector<FunctionSymbolId>& function_id_list)
+    : m_mesh{mesh},
+      m_quadrature_descriptor(quadrature_descriptor),
+      m_discrete_function_descriptor{discrete_function_descriptor},
+      m_function_id_list{function_id_list}
+  {}
+
+  DiscreteFunctionVectorIntegrator(const DiscreteFunctionVectorIntegrator&) = delete;
+  DiscreteFunctionVectorIntegrator(DiscreteFunctionVectorIntegrator&&)      = delete;
+
+  ~DiscreteFunctionVectorIntegrator() = default;
+};
+
+#endif   // DISCRETE_FUNCTION_VECTOR_INTEGRATOR_HPP
-- 
GitLab