From 2bd86efa9ea27ce461a3b333b0dc8045c522c134 Mon Sep 17 00:00:00 2001
From: Christoph Lehmann <christoph.lehmann@ufz.de>
Date: Mon, 30 May 2016 18:12:16 +0200
Subject: [PATCH] [PL] added TES process

---
 Applications/ApplicationsLib/ProjectData.cpp  |   8 +
 ProcessLib/CMakeLists.txt                     |   3 +
 ProcessLib/TES/.gitignore                     |   1 +
 ProcessLib/TES/CMakeLists.txt                 |  11 +
 ProcessLib/TES/TESAssemblyParams.h            |  84 ++++
 ProcessLib/TES/TESLocalAssembler-impl.h       | 224 +++++++++
 ProcessLib/TES/TESLocalAssembler.h            | 111 +++++
 ProcessLib/TES/TESLocalAssemblerData.cpp      |  31 ++
 ProcessLib/TES/TESLocalAssemblerData.h        |  59 +++
 ProcessLib/TES/TESLocalAssemblerInner-fwd.h   |  33 ++
 ...LocalAssemblerInner-impl-incl-dynamic.h.in |   6 +
 ...ESLocalAssemblerInner-impl-incl-fixed.h.in |   7 +
 ProcessLib/TES/TESLocalAssemblerInner-impl.h  | 372 +++++++++++++++
 ProcessLib/TES/TESLocalAssemblerInner.cpp     |  35 ++
 ProcessLib/TES/TESLocalAssemblerInner.h       |  98 ++++
 ProcessLib/TES/TESOGS5MaterialModels.cpp      |  35 ++
 ProcessLib/TES/TESOGS5MaterialModels.h        | 411 ++++++++++++++++
 ProcessLib/TES/TESProcess.cpp                 | 451 ++++++++++++++++++
 ProcessLib/TES/TESProcess.h                   | 149 ++++++
 ProcessLib/TES/TESReactionAdaptor.cpp         | 373 +++++++++++++++
 ProcessLib/TES/TESReactionAdaptor.h           | 133 ++++++
 21 files changed, 2635 insertions(+)
 create mode 100644 ProcessLib/TES/.gitignore
 create mode 100644 ProcessLib/TES/CMakeLists.txt
 create mode 100644 ProcessLib/TES/TESAssemblyParams.h
 create mode 100644 ProcessLib/TES/TESLocalAssembler-impl.h
 create mode 100644 ProcessLib/TES/TESLocalAssembler.h
 create mode 100644 ProcessLib/TES/TESLocalAssemblerData.cpp
 create mode 100644 ProcessLib/TES/TESLocalAssemblerData.h
 create mode 100644 ProcessLib/TES/TESLocalAssemblerInner-fwd.h
 create mode 100644 ProcessLib/TES/TESLocalAssemblerInner-impl-incl-dynamic.h.in
 create mode 100644 ProcessLib/TES/TESLocalAssemblerInner-impl-incl-fixed.h.in
 create mode 100644 ProcessLib/TES/TESLocalAssemblerInner-impl.h
 create mode 100644 ProcessLib/TES/TESLocalAssemblerInner.cpp
 create mode 100644 ProcessLib/TES/TESLocalAssemblerInner.h
 create mode 100644 ProcessLib/TES/TESOGS5MaterialModels.cpp
 create mode 100644 ProcessLib/TES/TESOGS5MaterialModels.h
 create mode 100644 ProcessLib/TES/TESProcess.cpp
 create mode 100644 ProcessLib/TES/TESProcess.h
 create mode 100644 ProcessLib/TES/TESReactionAdaptor.cpp
 create mode 100644 ProcessLib/TES/TESReactionAdaptor.h

diff --git a/Applications/ApplicationsLib/ProjectData.cpp b/Applications/ApplicationsLib/ProjectData.cpp
index 9d8586a0013..83dca05b08f 100644
--- a/Applications/ApplicationsLib/ProjectData.cpp
+++ b/Applications/ApplicationsLib/ProjectData.cpp
@@ -35,6 +35,7 @@
 #include "UncoupledProcessesTimeLoop.h"
 
 #include "ProcessLib/GroundwaterFlow/GroundwaterFlowProcess-fwd.h"
+#include "ProcessLib/TES/TESProcess.h"
 
 
 namespace detail
@@ -178,6 +179,13 @@ void ProjectData::buildProcesses()
                     *_mesh_vec[0], *nl_slv, std::move(time_disc),
                     _process_variables, _parameters, pc));
         }
+        else if (type == "TES")
+        {
+            _processes.emplace_back(
+                ProcessLib::TES::createTESProcess<GlobalSetupType>(
+                    *_mesh_vec[0], *nl_slv, std::move(time_disc),
+                    _process_variables, _parameters, pc));
+        }
         else
         {
             ERR("Unknown process type: %s", type.c_str());
diff --git a/ProcessLib/CMakeLists.txt b/ProcessLib/CMakeLists.txt
index e1738ab9840..9c22324d5e3 100644
--- a/ProcessLib/CMakeLists.txt
+++ b/ProcessLib/CMakeLists.txt
@@ -6,6 +6,9 @@ APPEND_SOURCE_FILES(SOURCES)
 add_subdirectory(GroundwaterFlow)
 APPEND_SOURCE_FILES(SOURCES GroundwaterFlow)
 
+add_subdirectory(TES)
+APPEND_SOURCE_FILES(SOURCES TES)
+
 add_library(ProcessLib ${SOURCES})
 
 target_link_libraries(ProcessLib
diff --git a/ProcessLib/TES/.gitignore b/ProcessLib/TES/.gitignore
new file mode 100644
index 00000000000..238847fa1e3
--- /dev/null
+++ b/ProcessLib/TES/.gitignore
@@ -0,0 +1 @@
+TESLocalAssemblerInner-impl-incl.h
diff --git a/ProcessLib/TES/CMakeLists.txt b/ProcessLib/TES/CMakeLists.txt
new file mode 100644
index 00000000000..fac50da00c8
--- /dev/null
+++ b/ProcessLib/TES/CMakeLists.txt
@@ -0,0 +1,11 @@
+if(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES)
+    configure_file(
+        "${CMAKE_CURRENT_SOURCE_DIR}/TESLocalAssemblerInner-impl-incl-dynamic.h.in"
+        "${CMAKE_CURRENT_SOURCE_DIR}/TESLocalAssemblerInner-impl-incl.h" COPYONLY
+        )
+else()
+    configure_file(
+        "${CMAKE_CURRENT_SOURCE_DIR}/TESLocalAssemblerInner-impl-incl-fixed.h.in"
+        "${CMAKE_CURRENT_SOURCE_DIR}/TESLocalAssemblerInner-impl-incl.h" COPYONLY
+        )
+endif()
diff --git a/ProcessLib/TES/TESAssemblyParams.h b/ProcessLib/TES/TESAssemblyParams.h
new file mode 100644
index 00000000000..352bc93a2dc
--- /dev/null
+++ b/ProcessLib/TES/TESAssemblyParams.h
@@ -0,0 +1,84 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESS_LIB_TESPROCESS_NOTPL_H_
+#define PROCESS_LIB_TESPROCESS_NOTPL_H_
+
+#include <Eigen/Eigen>
+#include <Eigen/Sparse>
+
+#include "MaterialsLib/Adsorption/Reaction.h"
+
+#include "ProcessLib/VariableTransformation.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+const unsigned NODAL_DOF = 3;
+const unsigned COMPONENT_ID_PRESSURE = 0;
+const unsigned COMPONENT_ID_TEMPERATURE = 1;
+const unsigned COMPONENT_ID_MASS_FRACTION = 2;
+
+const double M_N2 = 0.028013;
+const double M_H2O = 0.018016;
+
+struct AssemblyParams
+{
+    Trafo trafo_p{1.0};
+    Trafo trafo_T{1.0};
+    Trafo trafo_x{1.0};
+
+    std::unique_ptr<Adsorption::Reaction> react_sys;
+
+    double fluid_specific_heat_source =
+        std::numeric_limits<double>::quiet_NaN();
+    double cpG = std::numeric_limits<
+        double>::quiet_NaN();  // specific isobaric fluid heat capacity
+
+    Eigen::MatrixXd solid_perm_tensor = Eigen::MatrixXd::Constant(
+        3, 3, std::numeric_limits<double>::quiet_NaN());  // TODO get dimensions
+    double solid_specific_heat_source =
+        std::numeric_limits<double>::quiet_NaN();
+    double solid_heat_cond = std::numeric_limits<double>::quiet_NaN();
+    double cpS = std::numeric_limits<
+        double>::quiet_NaN();  // specific isobaric solid heat capacity
+
+    double tortuosity = std::numeric_limits<double>::quiet_NaN();
+    double diffusion_coefficient_component =
+        std::numeric_limits<double>::quiet_NaN();
+
+    double poro = std::numeric_limits<double>::quiet_NaN();
+
+    double rho_SR_dry = std::numeric_limits<double>::quiet_NaN();
+
+    const double M_inert = M_N2;  // N2
+    const double M_react = M_H2O;
+
+    // TODO unify variable names
+    double initial_solid_density = std::numeric_limits<double>::quiet_NaN();
+
+    double delta_t = std::numeric_limits<double>::quiet_NaN();
+    unsigned iteration_in_current_timestep = 0;
+
+    bool output_element_matrices = false;
+
+    unsigned number_of_try_of_iteration = 0;
+    double current_time = std::numeric_limits<double>::quiet_NaN();
+
+    //! Output global matrix/rhs after first iteration.
+    std::size_t timestep = 0;
+    std::size_t total_iteration = 0;
+};
+
+}  // namespace TES
+
+}  // namespace ProcessLib
+
+#endif  // PROCESS_LIB_TESPROCESS_NOTPL_H_
diff --git a/ProcessLib/TES/TESLocalAssembler-impl.h b/ProcessLib/TES/TESLocalAssembler-impl.h
new file mode 100644
index 00000000000..3b9ad99e5c2
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssembler-impl.h
@@ -0,0 +1,224 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#ifndef PROCESS_LIB_TES_FEM_IMPL_H_
+#define PROCESS_LIB_TES_FEM_IMPL_H_
+
+#include "MaterialsLib/Adsorption/Adsorption.h"
+#include "NumLib/Fem/FiniteElement/TemplateIsoparametric.h"
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "NumLib/Function/Interpolation.h"
+#include "ProcessLib/Utils/InitShapeMatrices.h"
+
+#include "TESLocalAssembler.h"
+#include "TESReactionAdaptor.h"
+
+namespace
+{
+enum class MatOutType
+{
+    OGS5,
+    PYTHON
+};
+
+const MatOutType MATRIX_OUTPUT_FORMAT = MatOutType::PYTHON;
+
+// TODO move to some location in the OGS core.
+template <typename Mat>
+void ogs5OutMat(const Mat& mat)
+{
+    for (unsigned r = 0; r < mat.rows(); ++r)
+    {
+        switch (MATRIX_OUTPUT_FORMAT)
+        {
+            case MatOutType::OGS5:
+                if (r != 0)
+                    std::printf("\n");
+                std::printf("|");
+                break;
+            case MatOutType::PYTHON:
+                if (r != 0)
+                    std::printf(",\n");
+                std::printf("[");
+                break;
+        }
+
+        for (unsigned c = 0; c < mat.cols(); ++c)
+        {
+            switch (MATRIX_OUTPUT_FORMAT)
+            {
+                case MatOutType::OGS5:
+                    std::printf(" %.16e", mat(r, c));
+                    break;
+                case MatOutType::PYTHON:
+                    if (c != 0)
+                        std::printf(",");
+                    std::printf(" %23.16g", mat(r, c));
+                    break;
+            }
+        }
+
+        switch (MATRIX_OUTPUT_FORMAT)
+        {
+            case MatOutType::OGS5:
+                std::printf(" | ");
+                break;
+            case MatOutType::PYTHON:
+                std::printf(" ]");
+                break;
+        }
+    }
+    std::printf("\n");
+}
+
+template <typename Vec>
+void ogs5OutVec(const Vec& vec)
+{
+    for (unsigned r = 0; r < vec.size(); ++r)
+    {
+        switch (MATRIX_OUTPUT_FORMAT)
+        {
+            case MatOutType::OGS5:
+                if (r != 0)
+                    std::printf("\n");
+                std::printf("| %.16e | ", vec[r]);
+                break;
+            case MatOutType::PYTHON:
+                if (r != 0)
+                    std::printf(",\n");
+                std::printf("[ %23.16g ]", vec[r]);
+                break;
+        }
+    }
+    std::printf("\n");
+}
+
+}  // anonymous namespace
+
+namespace ProcessLib
+{
+namespace TES
+{
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          typename GlobalMatrix, typename GlobalVector, unsigned GlobalDim>
+TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_, GlobalMatrix, GlobalVector,
+    GlobalDim>::TESLocalAssembler(MeshLib::Element const& e,
+                                  std::size_t const /*local_matrix_size*/,
+                                  unsigned const integration_order,
+                                  AssemblyParams const& asm_params)
+    : _shape_matrices(initShapeMatrices<ShapeFunction, ShapeMatricesType,
+                                        IntegrationMethod_, GlobalDim>(
+          e, integration_order)),
+      _d(asm_params,
+         // TODO narrowing conversion
+         static_cast<const unsigned>(
+             _shape_matrices.front()
+                 .N.rows()) /* number of integration points */,
+         GlobalDim),
+      _local_M(ShapeFunction::NPOINTS * NODAL_DOF,
+               ShapeFunction::NPOINTS * NODAL_DOF),
+      _local_K(ShapeFunction::NPOINTS * NODAL_DOF,
+               ShapeFunction::NPOINTS * NODAL_DOF),
+      _local_b(ShapeFunction::NPOINTS * NODAL_DOF),
+      _integration_order(integration_order)
+{
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          typename GlobalMatrix, typename GlobalVector, unsigned GlobalDim>
+void TESLocalAssembler<ShapeFunction_, IntegrationMethod_, GlobalMatrix,
+                       GlobalVector,
+                       GlobalDim>::assemble(const double /*t*/,
+                                            std::vector<double> const& local_x)
+{
+    _local_M.setZero();
+    _local_K.setZero();
+    _local_b.setZero();
+
+    IntegrationMethod_ integration_method(_integration_order);
+    unsigned const n_integration_points = integration_method.getNPoints();
+
+    _d.preEachAssemble();
+
+    for (std::size_t ip(0); ip < n_integration_points; ip++)
+    {
+        auto const& sm = _shape_matrices[ip];
+        auto const& wp = integration_method.getWeightedPoint(ip);
+        auto const weight = wp.getWeight();
+
+        _d.assembleIntegrationPoint(ip, local_x, sm.N, sm.dNdx, sm.J, sm.detJ,
+                                    weight, _local_M, _local_K, _local_b);
+    }
+
+    if (_d.getAssemblyParameters().output_element_matrices)
+    {
+        std::puts("### Element: ?");
+
+        std::puts("---Velocity of water");
+        for (auto const& vs : _d.getData().velocity)
+        {
+            std::printf("| ");
+            for (auto v : vs)
+            {
+                std::printf("%23.16e ", v);
+            }
+            std::printf("|\n");
+        }
+
+        std::printf("\n---Mass matrix: \n");
+        ogs5OutMat(_local_M);
+        std::printf("\n");
+
+        std::printf("---Laplacian + Advective + Content matrix: \n");
+        ogs5OutMat(_local_K);
+        std::printf("\n");
+
+        std::printf("---RHS: \n");
+        ogs5OutVec(_local_b);
+        std::printf("\n");
+    }
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          typename GlobalMatrix, typename GlobalVector, unsigned GlobalDim>
+void TESLocalAssembler<ShapeFunction_, IntegrationMethod_, GlobalMatrix,
+                       GlobalVector, GlobalDim>::
+    addToGlobal(
+        AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices const& indices,
+        GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b) const
+{
+    M.add(indices, _local_M);
+    K.add(indices, _local_K);
+    b.add(indices.rows, _local_b);
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          typename GlobalMatrix, typename GlobalVector, unsigned GlobalDim>
+std::vector<double> const& TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_, GlobalMatrix, GlobalVector,
+    GlobalDim>::getIntegrationPointValues(TESIntPtVariables const var,
+                                          std::vector<double>& cache) const
+{
+    return _d.getIntegrationPointValues(var, cache);
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          typename GlobalMatrix, typename GlobalVector, unsigned GlobalDim>
+bool TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_, GlobalMatrix, GlobalVector,
+    GlobalDim>::checkBounds(std::vector<double> const& local_x,
+                            std::vector<double> const& local_x_prev_ts)
+{
+    return _d.getReactionAdaptor().checkBounds(local_x, local_x_prev_ts);
+}
+
+}  // namespace TES
+}  // namespace ProcessLib
+
+#endif  // PROCESS_LIB_TES_FEM_IMPL_H_
diff --git a/ProcessLib/TES/TESLocalAssembler.h b/ProcessLib/TES/TESLocalAssembler.h
new file mode 100644
index 00000000000..68da0989069
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssembler.h
@@ -0,0 +1,111 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESS_LIB_TES_FEM_H_
+#define PROCESS_LIB_TES_FEM_H_
+
+#include <memory>
+#include <vector>
+
+#include "TESAssemblyParams.h"
+#include "TESLocalAssemblerInner-fwd.h"
+
+#include "NumLib/Extrapolation/Extrapolator.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+template <typename GlobalMatrix, typename GlobalVector>
+class TESLocalAssemblerInterface
+    : public NumLib::Extrapolatable<GlobalVector, TESIntPtVariables>
+{
+public:
+    virtual ~TESLocalAssemblerInterface() = default;
+
+    virtual void assemble(double const t,
+                          std::vector<double> const& local_x) = 0;
+
+    virtual void addToGlobal(
+        AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices const&,
+        GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b) const = 0;
+
+    virtual bool checkBounds(std::vector<double> const& local_x,
+                             std::vector<double> const& local_x_prev_ts) = 0;
+};
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          typename GlobalMatrix, typename GlobalVector, unsigned GlobalDim>
+class TESLocalAssembler final
+    : public TESLocalAssemblerInterface<GlobalMatrix, GlobalVector>
+{
+public:
+    using ShapeFunction = ShapeFunction_;
+    using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, GlobalDim>;
+    using ShapeMatrices = typename ShapeMatricesType::ShapeMatrices;
+
+    TESLocalAssembler(MeshLib::Element const& e,
+                      std::size_t const local_matrix_size,
+                      unsigned const integration_order,
+                      AssemblyParams const& asm_params);
+
+    void assemble(double const t, std::vector<double> const& local_x) override;
+
+    void addToGlobal(
+        AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices const& indices,
+        GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b) const override;
+
+    Eigen::Map<const Eigen::VectorXd> getShapeMatrix(
+        const unsigned integration_point) const override
+    {
+        auto const& N = _shape_matrices[integration_point].N;
+
+        // assumes N is stored contiguously in memory
+        return Eigen::Map<const Eigen::VectorXd>(N.data(), N.size());
+    }
+
+    bool checkBounds(std::vector<double> const& local_x,
+                     std::vector<double> const& local_x_prev_ts) override;
+
+    std::vector<double> const& getIntegrationPointValues(
+        TESIntPtVariables const var, std::vector<double>& cache) const override;
+
+private:
+    std::vector<ShapeMatrices> _shape_matrices;
+
+    using LAT = LocalAssemblerTraits<ShapeMatricesType, ShapeFunction::NPOINTS,
+                                     NODAL_DOF, GlobalDim>;
+
+    TESLocalAssemblerInner<LAT> _d;
+
+    using NodalMatrixType = typename LAT::LocalMatrix;
+    using NodalVectorType = typename LAT::LocalVector;
+
+    static_assert(
+        std::is_same<NodalMatrixType, typename LAT::LocalMatrix>::value,
+        "local matrix and data traits matrix do not coincide");
+    static_assert(
+        std::is_same<NodalVectorType, typename LAT::LocalVector>::value,
+        "local vector and data traits vector do not coincide");
+
+    // TODO Change VectorMatrixAssembler s.t. these can be omitted.
+    NodalMatrixType _local_M;
+    NodalMatrixType _local_K;
+    NodalVectorType _local_b;
+
+    // TODO Use the value from Process
+    unsigned const _integration_order;
+};
+
+}  // namespace TES
+}  // namespace ProcessLib
+
+#include "TESLocalAssembler-impl.h"
+
+#endif  // PROCESS_LIB_TES_FEM_H_
diff --git a/ProcessLib/TES/TESLocalAssemblerData.cpp b/ProcessLib/TES/TESLocalAssemblerData.cpp
new file mode 100644
index 00000000000..9a1ae0d91fb
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerData.cpp
@@ -0,0 +1,31 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#include "TESLocalAssemblerData.h"
+#include "TESReactionAdaptor.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+TESLocalAssemblerData::TESLocalAssemblerData(AssemblyParams const& ap_,
+                                             const unsigned num_int_pts,
+                                             const unsigned dimension)
+    : ap(ap_),
+      solid_density(num_int_pts, ap_.initial_solid_density),
+      reaction_rate(num_int_pts),
+      velocity(dimension, std::vector<double>(num_int_pts)),
+      reaction_adaptor(TESFEMReactionAdaptor::newInstance(*this)),
+      solid_density_prev_ts(num_int_pts, ap_.initial_solid_density),
+      reaction_rate_prev_ts(num_int_pts)
+{
+}
+
+TESLocalAssemblerData::~TESLocalAssemblerData() = default;
+}
+}  // namespaces
diff --git a/ProcessLib/TES/TESLocalAssemblerData.h b/ProcessLib/TES/TESLocalAssemblerData.h
new file mode 100644
index 00000000000..cd86950c22b
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerData.h
@@ -0,0 +1,59 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESSLIB_TES_TESLOCALASSEMBLERDATA_H
+#define PROCESSLIB_TES_TESLOCALASSEMBLERDATA_H
+
+#include "TESAssemblyParams.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+class TESFEMReactionAdaptor;
+
+struct TESLocalAssemblerData
+{
+    TESLocalAssemblerData(AssemblyParams const& ap_, const unsigned num_int_pts,
+                          const unsigned dimension);
+
+    ~TESLocalAssemblerData();
+
+    AssemblyParams const& ap;
+
+    // integration point quantities
+    std::vector<double> solid_density;
+    std::vector<double> reaction_rate;  // dC/dt * rho_SR_dry
+    std::vector<std::vector<double>>
+        velocity;  // vector of velocities for each integration point
+
+    // integration point values of unknowns -- temporary storage
+    double p = std::numeric_limits<double>::quiet_NaN();  // gas pressure
+    double T = std::numeric_limits<double>::quiet_NaN();  // temperature
+    double vapour_mass_fraction = std::numeric_limits<
+        double>::quiet_NaN();  // fluid mass fraction of the second component
+
+    // temporary storage for some properties
+    // values do not change during the assembly of one integration point
+    double rho_GR = std::numeric_limits<double>::quiet_NaN();
+    double p_V =
+        std::numeric_limits<double>::quiet_NaN();  // vapour partial pressure
+    double qR = std::numeric_limits<
+        double>::quiet_NaN();  // reaction rate, use this in assembly!!!
+
+    std::unique_ptr<TESFEMReactionAdaptor> const reaction_adaptor;
+
+    // variables at previous timestep
+    std::vector<double> solid_density_prev_ts;
+    std::vector<double> reaction_rate_prev_ts;  // could also be calculated from
+                                                // solid_density_prev_ts
+};
+}
+}  // namespaces
+#endif  // PROCESSLIB_TES_TESLOCALASSEMBLERDATA_H
diff --git a/ProcessLib/TES/TESLocalAssemblerInner-fwd.h b/ProcessLib/TES/TESLocalAssemblerInner-fwd.h
new file mode 100644
index 00000000000..b15365a1520
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerInner-fwd.h
@@ -0,0 +1,33 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESS_LIB_TESFEM_DATA_FWD_H_
+#define PROCESS_LIB_TESFEM_DATA_FWD_H_
+
+#include "TESLocalAssemblerInner.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+#ifdef OGS_EIGEN_DYNAMIC_SHAPE_MATRICES
+
+extern template class TESLocalAssemblerInner<
+    LocalAssemblerTraits<ShapeMatrixPolicyType<void, 0>, 0, 0, 0>>;
+
+static_assert(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES_FLAG == 1,
+              "inconsistent use of macros");
+#else
+static_assert(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES_FLAG == 0,
+              "inconsistent use of macros");
+#endif
+}
+}
+
+#endif  // PROCESS_LIB_TESFEM_DATA_FWD_H_
diff --git a/ProcessLib/TES/TESLocalAssemblerInner-impl-incl-dynamic.h.in b/ProcessLib/TES/TESLocalAssemblerInner-impl-incl-dynamic.h.in
new file mode 100644
index 00000000000..a905c565945
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerInner-impl-incl-dynamic.h.in
@@ -0,0 +1,6 @@
+#ifndef PROCESSLIB_TES_FEM_DATA_IMPL_INCL
+#define PROCESSLIB_TES_FEM_DATA_IMPL_INCL
+
+static_assert(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES_FLAG == 1, "inconsistent use of macros");
+
+#endif // PROCESSLIB_TES_FEM_DATA_IMPL_INCL
diff --git a/ProcessLib/TES/TESLocalAssemblerInner-impl-incl-fixed.h.in b/ProcessLib/TES/TESLocalAssemblerInner-impl-incl-fixed.h.in
new file mode 100644
index 00000000000..4da6ccdcae2
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerInner-impl-incl-fixed.h.in
@@ -0,0 +1,7 @@
+#ifndef PROCESSLIB_TES_FEM_DATA_IMPL_INCL
+#define PROCESSLIB_TES_FEM_DATA_IMPL_INCL
+
+static_assert(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES_FLAG == 0, "inconsistent use of macros");
+#include "TESLocalAssemblerInner-impl.h"
+
+#endif // PROCESSLIB_TES_FEM_DATA_IMPL_INCL
diff --git a/ProcessLib/TES/TESLocalAssemblerInner-impl.h b/ProcessLib/TES/TESLocalAssemblerInner-impl.h
new file mode 100644
index 00000000000..f58e1cb4c5d
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerInner-impl.h
@@ -0,0 +1,372 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ * The code of this file is used to decouple the evaluation of matrix elements
+ * from the rest of OGS6,
+ * not all of OGS6 has to be recompiled every time a small change is done.
+ */
+
+#ifndef PROCESS_LIB_TESFEM_DATA_IMPL_H_
+#define PROCESS_LIB_TESFEM_DATA_IMPL_H_
+
+#include <cstdio>
+#include <iostream>
+
+#include <logog/include/logog.hpp>
+
+#include "NumLib/Function/Interpolation.h"
+
+#include "TESLocalAssemblerInner-fwd.h"
+#include "TESOGS5MaterialModels.h"
+#include "TESReactionAdaptor.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+template <typename Traits>
+TESLocalAssemblerInner<Traits>::TESLocalAssemblerInner(
+    const AssemblyParams& ap, const unsigned num_int_pts,
+    const unsigned dimension)
+    : _d{ap, num_int_pts, dimension}
+{
+}
+
+template <typename Traits>
+Eigen::Matrix3d TESLocalAssemblerInner<Traits>::getMassCoeffMatrix(
+    const unsigned int_pt)
+{
+    // TODO: Dalton's law property
+    const double dxn_dxm = Adsorption::AdsorptionReaction::dMolarFraction(
+        _d.vapour_mass_fraction, _d.ap.M_react, _d.ap.M_inert);
+
+    const double M_pp = _d.ap.poro / _d.p * _d.rho_GR;
+    const double M_pT = -_d.ap.poro / _d.T * _d.rho_GR;
+    const double M_px = (_d.ap.M_react - _d.ap.M_inert) * _d.p /
+                        (GAS_CONST * _d.T) * dxn_dxm * _d.ap.poro;
+
+    const double M_Tp = -_d.ap.poro;
+    const double M_TT =
+        _d.ap.poro * _d.rho_GR * _d.ap.cpG  // TODO: vapour heat capacity
+        +
+        (1.0 - _d.ap.poro) * _d.solid_density[int_pt] *
+            _d.ap.cpS;  // TODO: adsorbate heat capacity
+    const double M_Tx = 0.0;
+
+    const double M_xp = 0.0;
+    const double M_xT = 0.0;
+    const double M_xx = _d.ap.poro * _d.rho_GR;
+
+    Eigen::Matrix3d M;
+    M << M_pp, M_pT, M_px, M_Tp, M_TT, M_Tx, M_xp, M_xT, M_xx;
+
+    return M;
+}
+
+template <typename Traits>
+typename Traits::LaplaceMatrix
+TESLocalAssemblerInner<Traits>::getLaplaceCoeffMatrix(const unsigned /*int_pt*/,
+                                                      const unsigned dim)
+{
+    const double eta_GR = fluid_viscosity(_d.p, _d.T, _d.vapour_mass_fraction);
+
+    const double lambda_F =
+        fluid_heat_conductivity(_d.p, _d.T, _d.vapour_mass_fraction);
+    const double lambda_S = _d.ap.solid_heat_cond;
+
+    using Mat = typename Traits::MatrixDimDim;
+
+    typename Traits::LaplaceMatrix L =
+        Traits::LaplaceMatrix::Zero(dim * NODAL_DOF, dim * NODAL_DOF);
+
+    // TODO: k_rel
+    // L_pp
+    Traits::blockDimDim(L, 0, 0, dim, dim) =
+        Traits::blockDimDim(_d.ap.solid_perm_tensor, 0, 0, dim, dim) *
+        _d.rho_GR / eta_GR;
+
+    // TODO: add zeolite part
+    // L_TT
+    Traits::blockDimDim(L, dim, dim, dim, dim) =
+        Mat::Identity(dim, dim) *
+        (_d.ap.poro * lambda_F + (1.0 - _d.ap.poro) * lambda_S);
+
+    // L_xx
+    Traits::blockDimDim(L, 2 * dim, 2 * dim, dim, dim) =
+        Mat::Identity(dim, dim) * (_d.ap.tortuosity * _d.ap.poro * _d.rho_GR *
+                                   _d.ap.diffusion_coefficient_component);
+
+    return L;
+}
+
+template <typename Traits>
+Eigen::Matrix3d TESLocalAssemblerInner<Traits>::getAdvectionCoeffMatrix(
+    const unsigned /*int_pt*/)
+{
+    const double A_pp = 0.0;
+    const double A_pT = 0.0;
+
+    const double A_px = 0.0;
+
+    const double A_Tp = 0.0;
+
+    const double A_TT = _d.rho_GR * _d.ap.cpG;  // porosity?
+    const double A_Tx = 0.0;
+
+    const double A_xp = 0.0;
+    const double A_xT = 0.0;
+    const double A_xx = _d.rho_GR;  // porosity?
+
+    Eigen::Matrix3d A;
+    A << A_pp, A_pT, A_px, A_Tp, A_TT, A_Tx, A_xp, A_xT, A_xx;
+
+    return A;
+}
+
+template <typename Traits>
+Eigen::Matrix3d TESLocalAssemblerInner<Traits>::getContentCoeffMatrix(
+    const unsigned /*int_pt*/)
+{
+    const double C_pp = 0.0;
+    const double C_pT = 0.0;
+
+    const double C_px = 0.0;
+
+    const double C_Tp = 0.0;
+
+    const double C_TT = 0.0;
+    const double C_Tx = 0.0;
+
+    const double C_xp = 0.0;
+    const double C_xT = 0.0;
+    const double C_xx = (_d.ap.poro - 1.0) * _d.qR;
+
+    Eigen::Matrix3d C;
+    C << C_pp, C_pT, C_px, C_Tp, C_TT, C_Tx, C_xp, C_xT, C_xx;
+
+    return C;
+}
+
+template <typename Traits>
+Eigen::Vector3d TESLocalAssemblerInner<Traits>::getRHSCoeffVector(
+    const unsigned int_pt)
+{
+    const double reaction_enthalpy =
+        _d.ap.react_sys->getEnthalpy(_d.p_V, _d.T, _d.ap.M_react);
+
+    const double rhs_p =
+        (_d.ap.poro - 1.0) * _d.qR;  // TODO [CL] body force term
+
+    const double rhs_T =
+        _d.rho_GR * _d.ap.poro * _d.ap.fluid_specific_heat_source +
+        (1.0 - _d.ap.poro) * _d.qR * reaction_enthalpy +
+        _d.solid_density[int_pt] * (1.0 - _d.ap.poro) *
+            _d.ap.solid_specific_heat_source;
+    // TODO [CL] momentum production term
+
+    const double rhs_x =
+        (_d.ap.poro - 1.0) * _d.qR;  // TODO [CL] what if x < 0.0
+
+    Eigen::Vector3d rhs;
+    rhs << rhs_p, rhs_T, rhs_x;
+
+    return rhs;
+}
+
+template <typename Traits>
+void TESLocalAssemblerInner<Traits>::initReaction(const unsigned int_pt)
+{
+    auto const& rate = _d.reaction_adaptor->initReaction(int_pt);
+    _d.qR = rate.reaction_rate;
+    _d.reaction_rate[int_pt] = rate.reaction_rate;
+    _d.solid_density[int_pt] = rate.solid_density;
+}
+
+template <typename Traits>
+void TESLocalAssemblerInner<Traits>::preEachAssembleIntegrationPoint(
+    const unsigned int_pt,
+    const std::vector<double>& localX,
+    typename Traits::ShapeMatrices::ShapeType const& smN,
+    typename Traits::ShapeMatrices::DxShapeType const& /*smDNdx*/,
+    typename Traits::ShapeMatrices::JacobianType const& /*smJ*/,
+    const double /*smDetJ*/)
+{
+#ifndef NDEBUG
+    // fill local data with garbage to aid in debugging
+    _d.p = _d.T = _d.vapour_mass_fraction = _d.p_V = _d.rho_GR = _d.qR =
+        std::numeric_limits<double>::quiet_NaN();
+#endif
+
+    NumLib::shapeFunctionInterpolate(localX, smN, _d.p, _d.T,
+                                     _d.vapour_mass_fraction);
+
+    // pre-compute certain properties
+    _d.p_V = _d.p * Adsorption::AdsorptionReaction::getMolarFraction(
+                        _d.vapour_mass_fraction, _d.ap.M_react, _d.ap.M_inert);
+
+    initReaction(int_pt);
+
+    assert(_d.p > 0.0);
+    assert(_d.T > 0.0);
+    assert(0.0 <= _d.vapour_mass_fraction && _d.vapour_mass_fraction <= 1.0);
+
+    _d.rho_GR = fluid_density(_d.p, _d.T, _d.vapour_mass_fraction);
+}
+
+template <typename Traits>
+std::vector<double> const&
+TESLocalAssemblerInner<Traits>::getIntegrationPointValues(
+    TESIntPtVariables const var, std::vector<double>& cache) const
+{
+    switch (var)
+    {
+        case TESIntPtVariables::REACTION_RATE:
+            return _d.reaction_rate;
+        case TESIntPtVariables::SOLID_DENSITY:
+            return _d.solid_density;
+        case TESIntPtVariables::VELOCITY_X:
+            return _d.velocity[0];
+        case TESIntPtVariables::VELOCITY_Y:
+            assert(_d.velocity.size() >= 2);
+            return _d.velocity[1];
+        case TESIntPtVariables::VELOCITY_Z:
+            assert(_d.velocity.size() >= 3);
+            return _d.velocity[2];
+
+        case TESIntPtVariables::LOADING:
+        {
+            auto& Cs = cache;
+            Cs.clear();
+            Cs.reserve(_d.solid_density.size());
+
+            for (auto rho_SR : _d.solid_density)
+            {
+                Cs.push_back(rho_SR / _d.ap.rho_SR_dry - 1.0);
+            }
+
+            return Cs;
+        }
+
+        // TODO that's an element value, ain't it?
+        case TESIntPtVariables::REACTION_DAMPING_FACTOR:
+        {
+            auto const num_integration_points = _d.solid_density.size();
+            auto& alphas = cache;
+            alphas.clear();
+            alphas.resize(num_integration_points,
+                          _d.reaction_adaptor->getReactionDampingFactor());
+
+            return alphas;
+        }
+    }
+
+    cache.clear();
+    return cache;
+}
+
+template <typename Traits>
+void TESLocalAssemblerInner<Traits>::assembleIntegrationPoint(
+    unsigned integration_point,
+    std::vector<double> const& localX,
+    const typename Traits::ShapeMatrices::ShapeType& smN,
+    const typename Traits::ShapeMatrices::DxShapeType& smDNdx,
+    const typename Traits::ShapeMatrices::JacobianType& smJ,
+    const double smDetJ,
+    const double weight,
+    typename Traits::LocalMatrix& local_M,
+    typename Traits::LocalMatrix& local_K,
+    typename Traits::LocalVector& local_b)
+{
+    preEachAssembleIntegrationPoint(integration_point, localX, smN, smDNdx, smJ,
+                                    smDetJ);
+
+    auto const N = smDNdx.cols();  // number of integration points
+    auto const D = smDNdx.rows();  // global dimension: 1, 2 or 3
+
+    assert(N * NODAL_DOF == local_M.cols());
+
+    auto const laplaceCoeffMat = getLaplaceCoeffMatrix(integration_point, D);
+    assert(laplaceCoeffMat.cols() == D * NODAL_DOF);
+    auto const massCoeffMat = getMassCoeffMatrix(integration_point);
+    auto const advCoeffMat = getAdvectionCoeffMatrix(integration_point);
+    auto const contentCoeffMat = getContentCoeffMatrix(integration_point);
+
+    // calculate velocity
+    assert((unsigned)smDNdx.rows() == _d.velocity.size() &&
+           (unsigned)smDNdx.cols() == _d.velocity[0].size());
+
+    auto const velocity =
+        (Traits::blockDimDim(laplaceCoeffMat, 0, 0, D, D) *
+         (smDNdx * Eigen::Map<const typename Traits::Vector1Comp>(localX.data(),
+                                                                  N)  // grad_p
+          /
+          -_d.rho_GR))
+            .eval();
+    assert(velocity.size() == D);
+
+    for (unsigned d = 0; d < D; ++d)
+    {
+        _d.velocity[d][integration_point] = velocity[d];
+    }
+
+    auto const detJ_w_N = (smDetJ * weight * smN).eval();
+    auto const detJ_w_N_NT = (detJ_w_N * smN.transpose()).eval();
+    assert(detJ_w_N_NT.rows() == N && detJ_w_N_NT.cols() == N);
+
+    auto const detJ_w_N_vT_dNdx =
+        (detJ_w_N * velocity.transpose() * smDNdx).eval();
+    assert(detJ_w_N_vT_dNdx.rows() == N && detJ_w_N_vT_dNdx.cols() == N);
+
+    for (unsigned r = 0; r < NODAL_DOF; ++r)
+    {
+        for (unsigned c = 0; c < NODAL_DOF; ++c)
+        {
+            Traits::blockShpShp(local_K, N * r, N * c, N, N).noalias() +=
+                smDetJ * weight * smDNdx.transpose() *
+                    Traits::blockDimDim(laplaceCoeffMat, D * r, D * c, D, D) *
+                    smDNdx  // end Laplacian part
+                + detJ_w_N_NT * contentCoeffMat(r, c) +
+                detJ_w_N_vT_dNdx * advCoeffMat(r, c);
+            Traits::blockShpShp(local_M, N * r, N * c, N, N).noalias() +=
+                detJ_w_N_NT * massCoeffMat(r, c);
+        }
+    }
+
+    auto const rhsCoeffVector = getRHSCoeffVector(integration_point);
+
+    for (unsigned r = 0; r < NODAL_DOF; ++r)
+    {
+        Traits::blockShp(local_b, N * r, N).noalias() +=
+            rhsCoeffVector(r) * smN * smDetJ * weight;
+    }
+}
+
+template <typename Traits>
+void TESLocalAssemblerInner<Traits>::preEachAssemble()
+{
+    if (_d.ap.iteration_in_current_timestep == 1)
+    {
+        if (_d.ap.number_of_try_of_iteration ==
+            1)  // TODO has to hold if the above holds.
+        {
+            _d.solid_density_prev_ts = _d.solid_density;
+            _d.reaction_rate_prev_ts = _d.reaction_rate;
+
+            _d.reaction_adaptor->preZerothTryAssemble();
+        }
+        else
+        {
+            _d.solid_density = _d.solid_density_prev_ts;
+        }
+    }
+}
+
+}  // namespace TES
+
+}  // namespace ProcessLib
+
+#endif  // PROCESS_LIB_TESFEM_DATA_IMPL_H_
diff --git a/ProcessLib/TES/TESLocalAssemblerInner.cpp b/ProcessLib/TES/TESLocalAssemblerInner.cpp
new file mode 100644
index 00000000000..12893b6d2fa
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerInner.cpp
@@ -0,0 +1,35 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ * The code of this file is used to decouple the evaluation of matrix elements
+ * from the rest of OGS6,
+ * not all of OGS6 has to be recompiled every time a small change is done.
+ */
+
+#include "TESLocalAssemblerInner-fwd.h"
+
+#ifdef OGS_EIGEN_DYNAMIC_SHAPE_MATRICES
+#include "TESLocalAssemblerInner-impl.h"
+#endif
+
+namespace ProcessLib
+{
+namespace TES
+{
+#ifdef OGS_EIGEN_DYNAMIC_SHAPE_MATRICES
+
+template class TESLocalAssemblerInner<
+    LocalAssemblerTraits<ShapeMatrixPolicyType<void, 0>, 0, 0, 0>>;
+
+static_assert(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES_FLAG == 1,
+              "inconsistent use of macros");
+#else
+static_assert(OGS_EIGEN_DYNAMIC_SHAPE_MATRICES_FLAG == 0,
+              "inconsistent use of macros");
+#endif
+}
+}
diff --git a/ProcessLib/TES/TESLocalAssemblerInner.h b/ProcessLib/TES/TESLocalAssemblerInner.h
new file mode 100644
index 00000000000..3e36cb47180
--- /dev/null
+++ b/ProcessLib/TES/TESLocalAssemblerInner.h
@@ -0,0 +1,98 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ * The code of this file is used to decouple the evaluation of matrix elements
+ * from the rest of OGS6,
+ * not all of OGS6 has to be recompiled every time a small change is done.
+ */
+
+#ifndef PROCESS_LIB_TES_FEM_NOTPL_H_
+#define PROCESS_LIB_TES_FEM_NOTPL_H_
+
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "ProcessLib/LocalAssemblerTraits.h"
+#include "ProcessLib/VariableTransformation.h"
+
+#include "TESLocalAssemblerData.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+enum class TESIntPtVariables : unsigned
+{
+    SOLID_DENSITY,
+    REACTION_RATE,
+    VELOCITY_X,
+    VELOCITY_Y,
+    VELOCITY_Z,
+    LOADING,
+    REACTION_DAMPING_FACTOR
+};
+
+template <typename Traits>
+class TESLocalAssemblerInner
+{
+public:
+    explicit TESLocalAssemblerInner(AssemblyParams const& ap,
+                                    const unsigned num_int_pts,
+                                    const unsigned dimension);
+
+    void assembleIntegrationPoint(
+        unsigned integration_point,
+        std::vector<double> const& localX,
+        typename Traits::ShapeMatrices::ShapeType const& smN,
+        typename Traits::ShapeMatrices::DxShapeType const& smDNdx,
+        typename Traits::ShapeMatrices::JacobianType const& smJ,
+        const double smDetJ,
+        const double weight,
+        typename Traits::LocalMatrix& local_M,
+        typename Traits::LocalMatrix& local_K,
+        typename Traits::LocalVector& local_b);
+
+    void preEachAssemble();
+
+    std::vector<double> const& getIntegrationPointValues(
+        TESIntPtVariables const var, std::vector<double>& cache) const;
+
+    // TODO better encapsulation
+    AssemblyParams const& getAssemblyParameters() const { return _d.ap; }
+    TESFEMReactionAdaptor const& getReactionAdaptor() const
+    {
+        return *_d.reaction_adaptor;
+    }
+    TESFEMReactionAdaptor& getReactionAdaptor() { return *_d.reaction_adaptor; }
+    TESLocalAssemblerData const& getData() const { return _d; }
+private:
+    Eigen::Matrix3d getMassCoeffMatrix(const unsigned int_pt);
+    typename Traits::LaplaceMatrix getLaplaceCoeffMatrix(const unsigned int_pt,
+                                                         const unsigned dim);
+    Eigen::Matrix3d getAdvectionCoeffMatrix(const unsigned int_pt);
+    Eigen::Matrix3d getContentCoeffMatrix(const unsigned int_pt);
+    Eigen::Vector3d getRHSCoeffVector(const unsigned int_pt);
+
+    void preEachAssembleIntegrationPoint(
+        const unsigned int_pt,
+        std::vector<double> const& localX,
+        typename Traits::ShapeMatrices::ShapeType const& smN,
+        typename Traits::ShapeMatrices::DxShapeType const& smDNdx,
+        typename Traits::ShapeMatrices::JacobianType const& smJ,
+        const double smDetJ);
+
+    void initReaction(const unsigned int_pt);
+
+    TESLocalAssemblerData _d;
+};
+
+}  // namespace TES
+
+}  // namespace ProcessLib
+
+// tricking cmake dependency checker
+#include "TESLocalAssemblerInner-impl-incl.h"
+
+#endif  // PROCESS_LIB_TES_FEM_NOTPL_H_
diff --git a/ProcessLib/TES/TESOGS5MaterialModels.cpp b/ProcessLib/TES/TESOGS5MaterialModels.cpp
new file mode 100644
index 00000000000..1a234d686b3
--- /dev/null
+++ b/ProcessLib/TES/TESOGS5MaterialModels.cpp
@@ -0,0 +1,35 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#include "TESOGS5MaterialModels.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+const double FluidHeatConductivityN2::A[5] = {0.46649, -0.57015, 0.19164,
+                                              -0.03708, 0.00241};
+
+const double FluidHeatConductivityN2::f[9] = {
+    -0.837079888737e3,   0.37914711487e2,    -0.601737844275,
+    0.350418363823e1,    -0.874955653028e-5, 0.148968607239e-7,
+    -0.256370354277e-11, 0.100773735767e1,   0.335340610e4};
+
+const double FluidHeatConductivityN2::C[4] = {3.3373542, 0.37098251, 0.89913456,
+                                              0.16972505};
+
+const double FluidViscosityN2::A[5] = {0.46649, -0.57015, 0.19164, -0.03708,
+                                       0.00241};
+const double FluidViscosityN2::C[5] = {-20.09997, 3.4376416, -1.4470051,
+                                       -0.027766561, -0.21662362};
+
+const double FluidHeatConductivityH2O::a[4] = {0.0102811, 0.0299621, 0.0156146,
+                                               -0.00422464};
+
+}  // TES
+}  // ProcessLib
diff --git a/ProcessLib/TES/TESOGS5MaterialModels.h b/ProcessLib/TES/TESOGS5MaterialModels.h
new file mode 100644
index 00000000000..c4e366d9826
--- /dev/null
+++ b/ProcessLib/TES/TESOGS5MaterialModels.h
@@ -0,0 +1,411 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#ifndef PROCESSLIB_TES_OGS5MATERIALMODELS
+#define PROCESSLIB_TES_OGS5MATERIALMODELS
+
+#include "TESAssemblyParams.h"
+
+namespace
+{
+const double GAS_CONST = 8.3144621;
+}
+
+namespace ProcessLib
+{
+namespace TES
+{
+inline double fluid_density(const double p, const double T, const double x)
+{
+    // OGS-5 density model 26
+
+    const double M0 = ProcessLib::TES::M_N2;
+    const double M1 = ProcessLib::TES::M_H2O;
+
+    const double xn = M0 * x / (M0 * x + M1 * (1.0 - x));
+
+    return p / (GAS_CONST * T) * (M1 * xn + M0 * (1.0 - xn));
+    ;
+}
+
+template <int i>
+double mypow(const double x)
+{
+    if (i < 0)
+    {
+        return 1.0 / mypow<-i>(x);
+    }
+    else
+    {
+        const double p = mypow<(i >> 1)>(x);
+        return (i & 1) ? p * p * x : p * p;
+    }
+}
+
+template <>
+inline double mypow<0>(const double /*x*/)
+{
+    return 1.0;
+}
+
+struct FluidViscosityN2
+{
+    static double get(double rho, double T)
+    {
+        const double rho_c = 314;   // [kg/m3]
+        const double CVF = 14.058;  // [1e-3 Pa-s]
+
+        const double sigma = 0.36502496e-09;
+        const double k = 1.38062e-23;
+        const double eps = 138.08483e-23;
+        const double c1 = 0.3125;
+        const double c2 = 2.0442e-49;
+
+        const double T_star = T * k / eps;
+        rho = rho / rho_c;
+
+        double Omega = loop1_term<0>(T_star);
+        Omega += loop1_term<1>(T_star);
+        Omega += loop1_term<2>(T_star);
+        Omega += loop1_term<3>(T_star);
+        Omega += loop1_term<4>(T_star);
+
+        Omega = std::exp(Omega);
+
+        // eta in [Pa*s]
+        const double eta_0 = c1 * std::sqrt(c2 * T) / (sigma * sigma * Omega);
+
+        double sum = loop2_term<2>(rho);
+        sum += loop2_term<3>(rho);
+        sum += loop2_term<4>(rho);
+
+        //
+        const double eta_r =
+            CVF * 1e-6 * (C[0] / (rho - C[1]) + C[0] / C[1] + sum);
+
+        return eta_0 + eta_r;  // [Pa*s]
+    }
+
+private:
+    template <unsigned i>
+    static double loop1_term(double T_star)
+    {
+        return A[i] * mypow<i>(log(T_star));
+    }
+
+    template <unsigned i>
+    static double loop2_term(double rho)
+    {
+        return C[i] * mypow<i - 1>(rho);
+    }
+
+    static const double A[5];
+    static const double C[5];
+};
+
+struct FluidViscosityH2O
+{
+    static double get(double rho, double T)
+    {
+        double my, my_0, my_1;
+        double H[4];
+
+        T = T / 647.096;
+        rho = rho / 322.0;
+
+        H[0] = 1.67752;
+        H[1] = 2.20462;
+        H[2] = 0.6366564;
+        H[3] = -0.241605;
+
+        double h[6][7] = {0.0};
+        h[0][0] = 0.520094000;
+        h[1][0] = 0.085089500;
+        h[2][0] = -1.083740000;
+        h[3][0] = -0.289555000;
+        h[0][1] = 0.222531000;
+        h[1][1] = 0.999115000;
+        h[2][1] = 1.887970000;
+        h[3][1] = 1.266130000;
+        h[5][1] = 0.120573000;
+        h[0][2] = -0.281378000;
+        h[1][2] = -0.906851000;
+        h[2][2] = -0.772479000;
+        h[3][2] = -0.489837000;
+        h[4][2] = -0.257040000;
+        h[0][3] = 0.161913000;
+        h[1][3] = 0.257399000;
+        h[0][4] = -0.032537200;
+        h[3][4] = 0.069845200;
+        h[4][5] = 0.008721020;
+        h[3][6] = -0.004356730;
+        h[5][6] = -0.000593264;
+
+        double sum1 = H[0] / mypow<0>(T);
+        sum1 += H[1] / mypow<1>(T);
+        sum1 += H[2] / mypow<2>(T);
+        sum1 += H[3] / mypow<3>(T);
+
+        my_0 = 100 * std::sqrt(T) / sum1;
+
+        double sum2 = inner_loop<0>(rho, T, h);
+        sum2 += inner_loop<1>(rho, T, h);
+        sum2 += inner_loop<2>(rho, T, h);
+        sum2 += inner_loop<3>(rho, T, h);
+        sum2 += inner_loop<4>(rho, T, h);
+        sum2 += inner_loop<5>(rho, T, h);
+
+        my_1 = std::exp(rho * sum2);
+
+        my = (my_0 * my_1) / 1e6;
+        return my;
+    }
+
+private:
+    template <int i>
+    static double inner_loop(const double rho,
+                             const double T,
+                             const double (&h)[6][7])
+    {
+        const double base = rho - 1.0;
+
+        double sum3 = h[i][0] * mypow<0>(base);
+        sum3 += h[i][1] * mypow<1>(base);
+        sum3 += h[i][2] * mypow<2>(base);
+        sum3 += h[i][3] * mypow<3>(base);
+        sum3 += h[i][4] * mypow<4>(base);
+        sum3 += h[i][5] * mypow<5>(base);
+        sum3 += h[i][6] * mypow<6>(base);
+
+        return mypow<i>(1 / T - 1) * sum3;
+    }
+};
+
+inline double fluid_viscosity(const double p, const double T, const double x)
+{
+    // OGS 5 viscosity model 26
+
+    const double M0 = ProcessLib::TES::M_N2;
+    const double M1 = ProcessLib::TES::M_H2O;
+
+    // reactive component
+    const double x0 =
+        M0 * x / (M0 * x + M1 * (1.0 - x));  // mass in mole fraction
+    const double V0 = FluidViscosityH2O::get(M1 * p / (GAS_CONST * T), T);
+    // inert component
+    const double x1 = 1.0 - x0;
+    const double V1 = FluidViscosityN2::get(M0 * p / (GAS_CONST * T), T);
+
+    const double M0_over_M1(M1 / M0);  // reactive over inert
+    const double V0_over_V1(V0 / V1);
+
+    const double phi_12 =
+        mypow<2>(1.0 +
+                 std::sqrt(V0_over_V1) * std::pow(1.0 / M0_over_M1, 0.25)) /
+        std::sqrt(8.0 * (1.0 + M0_over_M1));
+
+    const double phi_21 = phi_12 * M0_over_M1 / V0_over_V1;
+
+    return V0 * x0 / (x0 + x1 * phi_12) + V1 * x1 / (x1 + x0 * phi_21);
+}
+
+struct FluidHeatConductivityN2
+{
+    static double get(double rho, double T)
+    {
+        const double X1 = 0.95185202;
+        const double X2 = 1.0205422;
+
+        const double rho_c = 314;  // [kg/m3]
+        const double M = 28.013;
+        const double k = 1.38062e-23;
+        const double eps = 138.08483e-23;
+        const double N_A = 6.02213E26;
+        const double R = 8.31434;
+        // const double R = GAS_CONST;
+        const double CCF = 4.173;  // mW/m/K
+
+        const double c1 = 0.3125;
+        const double c2 = 2.0442e-49;
+        const double sigma = 0.36502496e-09;
+
+        rho /= rho_c;
+
+        // dilute heat conductivity
+        const double sum1 = loop1_term<0>(T) + loop1_term<1>(T) +
+                            loop1_term<2>(T) + loop1_term<3>(T) +
+                            loop1_term<4>(T) + loop1_term<5>(T) +
+                            loop1_term<6>(T);
+        const double temp(std::exp((f[8] / T)) - 1);
+        const double c_v0 =
+            R *
+            (sum1 + ((f[7] * (f[8] / T) * (f[8] / T) * (std::exp((f[8] / T)))) /
+                         (temp * temp) -
+                     1));
+
+        double cvint;
+        cvint = c_v0 * 1000 / N_A;
+
+        // dilute gas viscosity
+        const double log_T_star = std::log(T * k / eps);
+
+        const double Omega =
+            std::exp(loop2_term<0>(log_T_star) + loop2_term<1>(log_T_star) +
+                     loop2_term<2>(log_T_star) + loop2_term<3>(log_T_star) +
+                     loop2_term<4>(log_T_star));
+
+        // eta in [Pa*s]
+        const double eta_0 =
+            1e6 * (c1 * std::sqrt(c2 * T) / (sigma * sigma * Omega));
+
+        const double F = eta_0 * k * N_A / (M * 1000);
+
+        const double lambda_tr = 2.5 * (1.5 - X1);
+        const double lambda_in = X2 * (cvint / k + X1);
+
+        const double lambda_0 = F * (lambda_tr + lambda_in);
+
+        const double sum2 = loop3_term<0>(rho) + loop3_term<1>(rho) +
+                            loop3_term<2>(rho) + loop3_term<3>(rho);
+        const double lambda_r = sum2 * CCF;
+
+        return (lambda_0 + lambda_r) / 1000;  // lambda in [W/m/K]
+    }
+
+private:
+    template <int i>
+    static double loop1_term(const double T)
+    {
+        return f[i] * mypow<i - 3>(T);
+    }
+
+    template <int i>
+    static double loop2_term(const double log_T_star)
+    {
+        return A[i] * mypow<i>(log_T_star);
+    }
+
+    template <int i>
+    static double loop3_term(const double rho)
+    {
+        return C[i] * mypow<i + 1>(rho);
+    }
+
+    const static double A[5];
+    const static double f[9];
+    const static double C[4];
+};
+
+struct FluidHeatConductivityH2O
+{
+    static double get(double rho, double T)
+    {
+        double S, Q;
+        double b[3], B[2], d[4], C[6];
+
+        T /= 647.096;
+        rho /= 317.11;
+
+        b[0] = -0.397070;
+        b[1] = 0.400302;
+        b[2] = 1.060000;
+
+        B[0] = -0.171587;
+        B[1] = 2.392190;
+
+        d[0] = 0.0701309;
+        d[1] = 0.0118520;
+        d[2] = 0.00169937;
+        d[3] = -1.0200;
+
+        C[0] = 0.642857;
+        C[1] = -4.11717;
+        C[2] = -6.17937;
+        C[3] = 0.00308976;
+        C[4] = 0.0822994;
+        C[5] = 10.0932;
+
+        const double sum1 = loop_term<0>(T) + loop_term<1>(T) +
+                            loop_term<2>(T) + loop_term<3>(T);
+
+        const double lambda_0 = std::sqrt(T) * sum1;
+        const double lambda_1 =
+            b[0] + b[1] * rho +
+            b[2] * std::exp(B[0] * (rho + B[1]) * (rho + B[1]));
+
+        const double dT = fabs(T - 1) + C[3];
+        const double dT_pow_3_5 = std::pow(dT, 3. / 5.);
+        Q = 2 + (C[4] / dT_pow_3_5);
+
+        if (T >= 1)
+            S = 1 / dT;
+        else
+            S = C[5] / dT_pow_3_5;
+
+        const double rho_pow_9_5 = std::pow(rho, 9. / 5.);
+        const double rho_pow_Q = std::pow(rho, Q);
+        const double T_pow_3_2 = T * std::sqrt(T);
+        const double lambda_2 =
+            (d[0] / mypow<10>(T) + d[1]) * rho_pow_9_5 *
+                std::exp(C[0] * (1 - rho * rho_pow_9_5)) +
+            d[2] * S * rho_pow_Q *
+                std::exp((Q / (1. + Q)) * (1 - rho * rho_pow_Q)) +
+            d[3] * std::exp(C[1] * T_pow_3_2 + C[2] / mypow<5>(rho));
+
+        return lambda_0 + lambda_1 + lambda_2;  // lambda in [W/m/K]
+    }
+
+private:
+    template <unsigned i>
+    static double loop_term(const double T)
+    {
+        return a[i] * mypow<i>(T);
+    }
+
+    static const double a[4];
+};
+
+inline double fluid_heat_conductivity(const double p,
+                                      const double T,
+                                      const double x)
+{
+    // OGS 5 fluid heat conductivity model 11
+
+    const double M0 = ProcessLib::TES::M_N2;
+    const double M1 = ProcessLib::TES::M_H2O;
+
+    // TODO [CL] max() is redundant if the fraction is guaranteed to be between
+    // 0 and 1.
+    // reactive component
+    const double x0 = std::max(M0 * x / (M0 * x + M1 * (1.0 - x)),
+                               0.);  // convert mass to mole fraction
+    const double k0 =
+        FluidHeatConductivityH2O::get(M1 * p / (GAS_CONST * T), T);
+    // inert component
+    const double x1 = 1.0 - x0;
+    const double k1 = FluidHeatConductivityN2::get(M0 * p / (GAS_CONST * T), T);
+
+    const double M1_over_M2 = M1 / M0;  // reactive over inert
+    const double V1_over_V2 =
+        FluidViscosityH2O::get(M1 * p / (GAS_CONST * T), T) /
+        FluidViscosityN2::get(M0 * p / (GAS_CONST * T), T);
+    const double L1_over_L2 = V1_over_V2 / M1_over_M2;
+
+    const double M12_pow_mquarter = std::pow(M1_over_M2, -0.25);
+    const double phi_12 = (1.0 + std::sqrt(L1_over_L2) * M12_pow_mquarter) *
+                          (1.0 + std::sqrt(V1_over_V2) * M12_pow_mquarter) /
+                          std::sqrt(8.0 * (1.0 + M1_over_M2));
+    const double phi_21 = phi_12 * M1_over_M2 / V1_over_V2;
+
+    return k0 * x0 / (x0 + x1 * phi_12) + k1 * x1 / (x1 + x0 * phi_21);
+}
+
+}  // TES
+}  // ProcessLib
+
+#endif  // PROCESSLIB_TES_OGS5MATERIALMODELS
diff --git a/ProcessLib/TES/TESProcess.cpp b/ProcessLib/TES/TESProcess.cpp
new file mode 100644
index 00000000000..930bc4d4c92
--- /dev/null
+++ b/ProcessLib/TES/TESProcess.cpp
@@ -0,0 +1,451 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include "TESProcess.h"
+
+// TODO Copied from VectorMatrixAssembler. Could be provided by the DOF table.
+inline AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices
+getRowColumnIndices_(std::size_t const id,
+                     AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+                     std::vector<GlobalIndexType>& indices)
+{
+    assert(dof_table.size() > id);
+    indices.clear();
+
+    // Local matrices and vectors will always be ordered by component,
+    // no matter what the order of the global matrix is.
+    for (unsigned c = 0; c < dof_table.getNumComponents(); ++c)
+    {
+        auto const& idcs = dof_table(id, c).rows;
+        indices.reserve(indices.size() + idcs.size());
+        indices.insert(indices.end(), idcs.begin(), idcs.end());
+    }
+
+    return AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices(indices,
+                                                                 indices);
+}
+
+template <typename GlobalVector>
+void getVectorValues(
+    GlobalVector const& x,
+    AssemblerLib::LocalToGlobalIndexMap::RowColumnIndices const& r_c_indices,
+    std::vector<double>& local_x)
+{
+    auto const& indices = r_c_indices.rows;
+    local_x.clear();
+    local_x.reserve(indices.size());
+
+    for (auto i : indices)
+    {
+        local_x.emplace_back(x.get(i));
+    }
+}
+
+// TODO that essentially duplicates code which is also present in ProcessOutput.
+template <typename GlobalVector>
+double getNodalValue(GlobalVector const& x, MeshLib::Mesh const& mesh,
+                     AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+                     std::size_t const node_id,
+                     std::size_t const global_component_id)
+{
+    MeshLib::Location const l{mesh.getID(), MeshLib::MeshItemType::Node,
+                              node_id};
+
+    auto const index = dof_table.getLocalIndex(
+        l, global_component_id, x.getRangeBegin(), x.getRangeEnd());
+
+    return x.get(index);
+}
+
+namespace ProcessLib
+{
+namespace TES
+{
+template <typename GlobalSetup>
+TESProcess<GlobalSetup>::TESProcess(
+    MeshLib::Mesh& mesh,
+    typename Process<GlobalSetup>::NonlinearSolver& nonlinear_solver,
+    std::unique_ptr<typename Process<GlobalSetup>::TimeDiscretization>&&
+        time_discretization,
+    std::vector<std::reference_wrapper<ProcessVariable>>&& process_variables,
+    SecondaryVariableCollection<GlobalVector>&& secondary_variables,
+    ProcessOutput<GlobalVector>&& process_output,
+    const BaseLib::ConfigTree& config)
+    : Process<GlobalSetup>(
+          mesh, nonlinear_solver, std::move(time_discretization),
+          std::move(process_variables), std::move(secondary_variables),
+          std::move(process_output))
+{
+    DBUG("Create TESProcess.");
+
+    // physical parameters for local assembly
+    {
+        std::vector<std::pair<std::string, double*>> params{
+            {"fluid_specific_heat_source",
+             &_assembly_params.fluid_specific_heat_source},
+            {"fluid_specific_isobaric_heat_capacity", &_assembly_params.cpG},
+            {"solid_specific_heat_source",
+             &_assembly_params.solid_specific_heat_source},
+            {"solid_heat_conductivity", &_assembly_params.solid_heat_cond},
+            {"solid_specific_isobaric_heat_capacity", &_assembly_params.cpS},
+            {"tortuosity", &_assembly_params.tortuosity},
+            {"diffusion_coefficient",
+             &_assembly_params.diffusion_coefficient_component},
+            {"porosity", &_assembly_params.poro},
+            {"solid_density_dry", &_assembly_params.rho_SR_dry},
+            {"solid_density_initial", &_assembly_params.initial_solid_density}};
+
+        for (auto const& p : params)
+        {
+            if (auto const par = config.getConfParamOptional<double>(p.first))
+            {
+                DBUG("setting parameter `%s' to value `%g'", p.first.c_str(),
+                     *par);
+                *p.second = *par;
+            }
+        }
+    }
+
+    // characteristic values of primary variables
+    {
+        std::vector<std::pair<std::string, Trafo*>> const params{
+            {"characteristic_pressure", &_assembly_params.trafo_p},
+            {"characteristic_temperature", &_assembly_params.trafo_T},
+            {"characteristic_vapour_mass_fraction", &_assembly_params.trafo_x}};
+
+        for (auto const& p : params)
+        {
+            if (auto const par = config.getConfParamOptional<double>(p.first))
+            {
+                INFO("setting parameter `%s' to value `%g'", p.first.c_str(),
+                     *par);
+                *p.second = Trafo{*par};
+            }
+        }
+    }
+
+    // permeability
+    if (auto par =
+            config.getConfParamOptional<double>("solid_hydraulic_permeability"))
+    {
+        DBUG(
+            "setting parameter `solid_hydraulic_permeability' to isotropic "
+            "value `%g'",
+            *par);
+        const auto dim = mesh.getDimension();
+        _assembly_params.solid_perm_tensor =
+            Eigen::MatrixXd::Identity(dim, dim) * (*par);
+    }
+
+    // reactive system
+    _assembly_params.react_sys = Adsorption::AdsorptionReaction::newInstance(
+        config.getConfSubtree("reactive_system"));
+
+    // debug output
+    if (auto const param =
+            config.getConfParamOptional<bool>("output_element_matrices"))
+    {
+        DBUG("output_element_matrices: %s", (*param) ? "true" : "false");
+
+        _assembly_params.output_element_matrices = *param;
+    }
+
+    // TODO somewhere else
+    /*
+    if (auto const param =
+    config.getConfParamOptional<bool>("output_global_matrix"))
+    {
+        DBUG("output_global_matrix: %s", (*param) ? "true" : "false");
+
+        this->_process_output.output_global_matrix = *param;
+    }
+    */
+}
+
+template <typename GlobalSetup>
+void TESProcess<GlobalSetup>::initializeConcreteProcess(
+    AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+    MeshLib::Mesh const& mesh, unsigned const integration_order)
+{
+    DBUG("Create global assembler.");
+    _global_assembler.reset(new GlobalAssembler(dof_table));
+
+    ProcessLib::createLocalAssemblers<GlobalSetup, TESLocalAssembler>(
+        mesh.getDimension(), mesh.getElements(), dof_table, integration_order,
+        _local_assemblers, _assembly_params);
+
+    // TODO move the two data members somewhere else.
+    // for extrapolation of secondary variables
+    std::vector<std::unique_ptr<MeshLib::MeshSubsets>>
+        all_mesh_subsets_single_component;
+    all_mesh_subsets_single_component.emplace_back(
+        new MeshLib::MeshSubsets(this->_mesh_subset_all_nodes.get()));
+    _local_to_global_index_map_single_component.reset(
+        new AssemblerLib::LocalToGlobalIndexMap(
+            std::move(all_mesh_subsets_single_component),
+            // by location order is needed for output
+            AssemblerLib::ComponentOrder::BY_LOCATION));
+
+    _extrapolator.reset(
+        new ExtrapolatorImplementation(MathLib::MatrixSpecifications(
+            0u, 0u, nullptr, _local_to_global_index_map_single_component.get(),
+            &mesh)));
+
+    // secondary variables
+    auto add2nd = [&](std::string const& var_name, unsigned const n_components,
+                      SecondaryVariableFunctions<GlobalVector>&& fcts) {
+        this->_secondary_variables.addSecondaryVariable(var_name, n_components,
+                                                        std::move(fcts));
+    };
+    auto makeEx =
+        [&](TESIntPtVariables var) -> SecondaryVariableFunctions<GlobalVector> {
+        return ProcessLib::makeExtrapolator(var, *_extrapolator,
+                                            _local_assemblers);
+    };
+
+    add2nd("solid_density", 1, makeEx(TESIntPtVariables::SOLID_DENSITY));
+    add2nd("reaction_rate", 1, makeEx(TESIntPtVariables::REACTION_RATE));
+    add2nd("velocity_x", 1, makeEx(TESIntPtVariables::VELOCITY_X));
+    if (mesh.getDimension() >= 2)
+        add2nd("velocity_y", 1, makeEx(TESIntPtVariables::VELOCITY_Y));
+    if (mesh.getDimension() >= 3)
+        add2nd("velocity_z", 1, makeEx(TESIntPtVariables::VELOCITY_Z));
+
+    add2nd("loading", 1, makeEx(TESIntPtVariables::LOADING));
+    add2nd("reaction_damping_factor", 1,
+           makeEx(TESIntPtVariables::REACTION_DAMPING_FACTOR));
+
+    namespace PH = std::placeholders;
+    using Self = TESProcess<GlobalSetup>;
+
+    add2nd("vapour_partial_pressure", 1,
+           {std::bind(&Self::computeVapourPartialPressure, this, PH::_1, PH::_2,
+                      PH::_3),
+            nullptr});
+    add2nd("relative_humidity", 1, {std::bind(&Self::computeRelativeHumidity,
+                                              this, PH::_1, PH::_2, PH::_3),
+                                    nullptr});
+    add2nd("equilibrium_loading", 1,
+           {std::bind(&Self::computeEquilibriumLoading, this, PH::_1, PH::_2,
+                      PH::_3),
+            nullptr});
+}
+
+template <typename GlobalSetup>
+void TESProcess<GlobalSetup>::assembleConcreteProcess(const double t,
+                                                      GlobalVector const& x,
+                                                      GlobalMatrix& M,
+                                                      GlobalMatrix& K,
+                                                      GlobalVector& b)
+{
+    DBUG("Assemble TESProcess.");
+
+    // Call global assembler for each local assembly item.
+    GlobalSetup::executeMemberDereferenced(*_global_assembler,
+                                           &GlobalAssembler::assemble,
+                                           _local_assemblers, t, x, M, K, b);
+}
+
+template <typename GlobalSetup>
+void TESProcess<GlobalSetup>::preTimestep(GlobalVector const& x, const double t,
+                                          const double delta_t)
+{
+    DBUG("new timestep");
+
+    _assembly_params.delta_t = delta_t;
+    _assembly_params.current_time = t;
+    ++_assembly_params.timestep;  // TODO remove that
+
+    _x_previous_timestep =
+        MathLib::MatrixVectorTraits<GlobalVector>::newInstance(x);
+}
+
+template <typename GlobalSetup>
+void TESProcess<GlobalSetup>::preIteration(const unsigned iter,
+                                           GlobalVector const& /*x*/)
+{
+    _assembly_params.iteration_in_current_timestep = iter;
+    ++_assembly_params.total_iteration;
+    ++_assembly_params.number_of_try_of_iteration;
+}
+
+template <typename GlobalSetup>
+NumLib::IterationResult TESProcess<GlobalSetup>::postIteration(
+    GlobalVector const& x)
+{
+    if (this->_process_output.output_iteration_results)
+    {
+        DBUG("output results of iteration %li",
+             _assembly_params.total_iteration);
+        std::string fn =
+            "tes_iter_" + std::to_string(_assembly_params.total_iteration) +
+            +"_ts_" + std::to_string(_assembly_params.timestep) + "_" +
+            std::to_string(_assembly_params.iteration_in_current_timestep) +
+            "_" + std::to_string(_assembly_params.number_of_try_of_iteration) +
+            ".vtu";
+
+        this->output(fn, 0, x);
+    }
+
+    bool check_passed = true;
+
+    if (!Trafo::constrained)
+    {
+        // bounds checking only has to happen if the vapour mass fraction is
+        // non-logarithmic.
+
+        std::vector<GlobalIndexType> indices_cache;
+        std::vector<double> local_x_cache;
+        std::vector<double> local_x_prev_ts_cache;
+
+        auto check_variable_bounds = [&](std::size_t id,
+                                         LocalAssembler& loc_asm) {
+            auto const r_c_indices = getRowColumnIndices_(
+                id, *this->_local_to_global_index_map, indices_cache);
+            getVectorValues(x, r_c_indices, local_x_cache);
+            getVectorValues(*_x_previous_timestep, r_c_indices,
+                            local_x_prev_ts_cache);
+
+            if (!loc_asm.checkBounds(local_x_cache, local_x_prev_ts_cache))
+                check_passed = false;
+        };
+
+        GlobalSetup::executeDereferenced(check_variable_bounds,
+                                         _local_assemblers);
+    }
+
+    if (!check_passed)
+        return NumLib::IterationResult::REPEAT_ITERATION;
+
+    // TODO remove
+    DBUG("ts %lu iteration %lu (in current ts: %lu) try %u accepted",
+         _assembly_params.timestep, _assembly_params.total_iteration,
+         _assembly_params.iteration_in_current_timestep,
+         _assembly_params.number_of_try_of_iteration);
+
+    _assembly_params.number_of_try_of_iteration = 0;
+
+    return NumLib::IterationResult::SUCCESS;
+}
+
+template <typename GlobalSetup>
+typename TESProcess<GlobalSetup>::GlobalVector const&
+TESProcess<GlobalSetup>::computeVapourPartialPressure(
+    typename TESProcess::GlobalVector const& x,
+    AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+    std::unique_ptr<GlobalVector>& result_cache)
+{
+    assert(&dof_table == this->_local_to_global_index_map.get());
+
+    result_cache = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(
+        {0, 0, nullptr, _local_to_global_index_map_single_component.get(),
+         nullptr});
+
+    GlobalIndexType nnodes = this->_mesh.getNNodes();
+
+    for (GlobalIndexType node_id = 0; node_id < nnodes; ++node_id)
+    {
+        auto const p = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                     COMPONENT_ID_PRESSURE);
+        auto const x_mV = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                        COMPONENT_ID_MASS_FRACTION);
+
+        auto const x_nV = Adsorption::AdsorptionReaction::getMolarFraction(
+            x_mV, _assembly_params.M_react, _assembly_params.M_inert);
+
+        // TODO Problems with PETSc? (local vs. global index)
+        result_cache->set(node_id, p * x_nV);
+    }
+
+    return *result_cache;
+}
+
+template <typename GlobalSetup>
+typename TESProcess<GlobalSetup>::GlobalVector const&
+TESProcess<GlobalSetup>::computeRelativeHumidity(
+    typename TESProcess::GlobalVector const& x,
+    AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+    std::unique_ptr<GlobalVector>& result_cache)
+{
+    assert(&dof_table == this->_local_to_global_index_map.get());
+
+    result_cache = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(
+        {0, 0, nullptr, _local_to_global_index_map_single_component.get(),
+         nullptr});
+
+    GlobalIndexType nnodes = this->_mesh.getNNodes();
+
+    for (GlobalIndexType node_id = 0; node_id < nnodes; ++node_id)
+    {
+        auto const p = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                     COMPONENT_ID_PRESSURE);
+        auto const T = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                     COMPONENT_ID_TEMPERATURE);
+        auto const x_mV = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                        COMPONENT_ID_MASS_FRACTION);
+
+        auto const x_nV = Adsorption::AdsorptionReaction::getMolarFraction(
+            x_mV, _assembly_params.M_react, _assembly_params.M_inert);
+
+        auto const p_S =
+            Adsorption::AdsorptionReaction::getEquilibriumVapourPressure(T);
+
+        // TODO Problems with PETSc? (local vs. global index)
+        result_cache->set(node_id, p * x_nV / p_S);
+    }
+
+    return *result_cache;
+}
+
+template <typename GlobalSetup>
+typename TESProcess<GlobalSetup>::GlobalVector const&
+TESProcess<GlobalSetup>::computeEquilibriumLoading(
+    typename TESProcess::GlobalVector const& x,
+    AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+    std::unique_ptr<GlobalVector>& result_cache)
+{
+    assert(&dof_table == this->_local_to_global_index_map.get());
+
+    result_cache = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(
+        {0, 0, nullptr, _local_to_global_index_map_single_component.get(),
+         nullptr});
+
+    GlobalIndexType nnodes = this->_mesh.getNNodes();
+
+    for (GlobalIndexType node_id = 0; node_id < nnodes; ++node_id)
+    {
+        auto const p = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                     COMPONENT_ID_PRESSURE);
+        auto const T = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                     COMPONENT_ID_TEMPERATURE);
+        auto const x_mV = getNodalValue(x, this->_mesh, dof_table, node_id,
+                                        COMPONENT_ID_MASS_FRACTION);
+
+        auto const x_nV = Adsorption::AdsorptionReaction::getMolarFraction(
+            x_mV, _assembly_params.M_react, _assembly_params.M_inert);
+
+        auto const p_V = p * x_nV;
+        auto const C_eq =
+            (p_V <= 0.0) ? 0.0
+                         : _assembly_params.react_sys->getEquilibriumLoading(
+                               p_V, T, _assembly_params.M_react);
+
+        // TODO Problems with PETSc? (local vs. global index)
+        result_cache->set(node_id, C_eq);
+    }
+
+    return *result_cache;
+}
+
+// Explicitly instantiate TESProcess for GlobalSetupType.
+template class TESProcess<GlobalSetupType>;
+
+}  // namespace TES
+
+}  // namespace ProcessLib
diff --git a/ProcessLib/TES/TESProcess.h b/ProcessLib/TES/TESProcess.h
new file mode 100644
index 00000000000..5149b329af5
--- /dev/null
+++ b/ProcessLib/TES/TESProcess.h
@@ -0,0 +1,149 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESS_LIB_TESPROCESS_H_
+#define PROCESS_LIB_TESPROCESS_H_
+
+#include "AssemblerLib/LocalToGlobalIndexMap.h"
+#include "NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h"
+#include "ProcessLib/Process.h"
+
+#include "TESAssemblyParams.h"
+#include "TESLocalAssembler.h"
+
+namespace MeshLib
+{
+class Element;
+class Mesh;
+template <typename PROP_VAL_TYPE>
+class PropertyVector;
+}
+
+namespace ProcessLib
+{
+namespace TES
+{
+template <typename GlobalSetup>
+class TESProcess final : public Process<GlobalSetup>
+{
+    using BP = Process<GlobalSetup>;  //!< "Base Process"
+
+public:
+    using GlobalVector = typename GlobalSetup::VectorType;
+    using GlobalMatrix = typename GlobalSetup::MatrixType;
+
+    TESProcess(
+        MeshLib::Mesh& mesh,
+        typename Process<GlobalSetup>::NonlinearSolver& nonlinear_solver,
+        std::unique_ptr<typename Process<GlobalSetup>::TimeDiscretization>&&
+            time_discretization,
+        std::vector<std::reference_wrapper<ProcessVariable>>&&
+            process_variables,
+        SecondaryVariableCollection<GlobalVector>&& secondary_variables,
+        ProcessOutput<GlobalVector>&& process_output,
+        BaseLib::ConfigTree const& config);
+
+    void preTimestep(GlobalVector const& x, const double t,
+                     const double delta_t) override;
+    void preIteration(const unsigned iter, GlobalVector const& x) override;
+    NumLib::IterationResult postIteration(GlobalVector const& x) override;
+
+    bool isLinear() const override { return false; }
+private:
+    using LocalAssembler =
+        TESLocalAssemblerInterface<GlobalMatrix, GlobalVector>;
+
+    using GlobalAssembler = AssemblerLib::VectorMatrixAssembler<
+        GlobalMatrix, GlobalVector, LocalAssembler,
+        NumLib::ODESystemTag::FirstOrderImplicitQuasilinear>;
+
+    using ExtrapolatorInterface =
+        NumLib::Extrapolator<GlobalVector, TESIntPtVariables, LocalAssembler>;
+    using ExtrapolatorImplementation =
+        NumLib::LocalLinearLeastSquaresExtrapolator<
+            GlobalVector, TESIntPtVariables, LocalAssembler>;
+
+    void initializeConcreteProcess(
+        AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+        MeshLib::Mesh const& mesh, unsigned const integration_order) override;
+
+    void assembleConcreteProcess(const double t, GlobalVector const& x,
+                                 GlobalMatrix& M, GlobalMatrix& K,
+                                 GlobalVector& b) override;
+
+    GlobalVector const& computeVapourPartialPressure(
+        GlobalVector const& x,
+        AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+        std::unique_ptr<GlobalVector>& result_cache);
+
+    GlobalVector const& computeRelativeHumidity(
+        GlobalVector const& x,
+        AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+        std::unique_ptr<GlobalVector>& result_cache);
+
+    GlobalVector const& computeEquilibriumLoading(
+        GlobalVector const& x,
+        AssemblerLib::LocalToGlobalIndexMap const& dof_table,
+        std::unique_ptr<GlobalVector>& result_cache);
+
+    std::unique_ptr<GlobalAssembler> _global_assembler;
+    std::vector<std::unique_ptr<LocalAssembler>> _local_assemblers;
+
+    AssemblyParams _assembly_params;
+
+    std::unique_ptr<AssemblerLib::LocalToGlobalIndexMap>
+        _local_to_global_index_map_single_component;
+
+    std::unique_ptr<ExtrapolatorInterface> _extrapolator;
+
+    // used for checkBounds()
+    std::unique_ptr<GlobalVector> _x_previous_timestep;
+};
+
+template <typename GlobalSetup>
+std::unique_ptr<TESProcess<GlobalSetup>> createTESProcess(
+    MeshLib::Mesh& mesh,
+    typename Process<GlobalSetup>::NonlinearSolver& nonlinear_solver,
+    std::unique_ptr<typename Process<GlobalSetup>::TimeDiscretization>&&
+        time_discretization,
+    std::vector<ProcessVariable> const& variables,
+    std::vector<std::unique_ptr<ParameterBase>> const& /*parameters*/,
+    BaseLib::ConfigTree const& config)
+{
+    config.checkConfParam("type", "TES");
+
+    DBUG("Create TESProcess.");
+
+    auto process_variables = findProcessVariables(
+        variables, config,
+        {"fluid_pressure", "temperature", "vapour_mass_fraction"});
+
+    SecondaryVariableCollection<typename GlobalSetup::VectorType>
+        secondary_variables{
+            config.getConfSubtreeOptional("secondary_variables"),
+            {"solid_density", "reaction_rate", "velocity_x", "velocity_y",
+             "velocity_z", "loading", "reaction_damping_factor",
+             "vapour_partial_pressure", "relative_humidity",
+             "equilibrium_loading"}};
+
+    ProcessOutput<typename GlobalSetup::VectorType> process_output{
+        config.getConfSubtree("output"), process_variables,
+        secondary_variables};
+
+    return std::unique_ptr<TESProcess<GlobalSetup>>{new TESProcess<GlobalSetup>{
+        mesh, nonlinear_solver, std::move(time_discretization),
+        std::move(process_variables), std::move(secondary_variables),
+        std::move(process_output), config}};
+}
+
+}  // namespace TES
+
+}  // namespace ProcessLib
+
+#endif  // PROCESS_LIB_TESPROCESS_H_
diff --git a/ProcessLib/TES/TESReactionAdaptor.cpp b/ProcessLib/TES/TESReactionAdaptor.cpp
new file mode 100644
index 00000000000..8259ba24bb3
--- /dev/null
+++ b/ProcessLib/TES/TESReactionAdaptor.cpp
@@ -0,0 +1,373 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include <cassert>
+
+#include <logog/include/logog.hpp>
+
+#include "MathLib/Nonlinear/Root1D.h"
+#include "MathLib/ODE/ODESolverBuilder.h"
+
+#include "MaterialsLib/Adsorption/Adsorption.h"
+#include "MaterialsLib/Adsorption/ReactionInert.h"
+#include "MaterialsLib/Adsorption/ReactionSinusoidal.h"
+
+#include "TESLocalAssemblerInner.h"
+#include "TESReactionAdaptor.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+std::unique_ptr<TESFEMReactionAdaptor> TESFEMReactionAdaptor::newInstance(
+    TESLocalAssemblerData const& data)
+{
+    auto const* ads = data.ap.react_sys.get();
+    if (dynamic_cast<Adsorption::AdsorptionReaction const*>(ads) != nullptr)
+    {
+        return std::unique_ptr<TESFEMReactionAdaptor>(
+            new TESFEMReactionAdaptorAdsorption(data));
+    }
+    else if (dynamic_cast<Adsorption::ReactionInert const*>(ads) != nullptr)
+    {
+        return std::unique_ptr<TESFEMReactionAdaptor>(
+            new TESFEMReactionAdaptorInert(data));
+    }
+    else if (dynamic_cast<Adsorption::ReactionSinusoidal const*>(ads) !=
+             nullptr)
+    {
+        return std::unique_ptr<TESFEMReactionAdaptor>(
+            new TESFEMReactionAdaptorSinusoidal(data));
+    }
+    else if (dynamic_cast<Adsorption::ReactionCaOH2 const*>(ads) != nullptr)
+    {
+        return std::unique_ptr<TESFEMReactionAdaptor>(
+            new TESFEMReactionAdaptorCaOH2(data));
+    }
+
+    ERR("No suitable TESFEMReactionAdaptor found. Aborting.");
+    std::abort();
+    return std::unique_ptr<TESFEMReactionAdaptor>(nullptr);
+}
+
+TESFEMReactionAdaptorAdsorption::TESFEMReactionAdaptorAdsorption(
+    TESLocalAssemblerData const& data)
+    // caution fragile: this relies in this constructor b eing called __after__
+    // data.solid_density has been properly set up!
+    : _bounds_violation(data.solid_density.size(), false),
+      _d(data)
+{
+    assert(dynamic_cast<Adsorption::AdsorptionReaction const*>(
+               data.ap.react_sys.get()) != nullptr &&
+           "Reactive system has wrong type.");
+    assert(_bounds_violation.size() != 0);
+}
+
+ReactionRate
+TESFEMReactionAdaptorAdsorption::initReaction_slowDownUndershootStrategy(
+    const unsigned int_pt)
+{
+    assert(_d.ap.number_of_try_of_iteration <= 20);
+
+    const double loading = Adsorption::AdsorptionReaction::getLoading(
+        _d.solid_density_prev_ts[int_pt], _d.ap.rho_SR_dry);
+
+    double react_rate_R =
+        _d.ap.react_sys->getReactionRate(_d.p_V, _d.T, _d.ap.M_react, loading) *
+        _d.ap.rho_SR_dry;
+
+    // set reaction rate based on current damping factor
+    react_rate_R = (_reaction_damping_factor > 1e-3)
+                       ? _reaction_damping_factor * react_rate_R
+                       : 0.0;
+
+    if (_d.p_V <
+            0.01 * Adsorption::AdsorptionReaction::getEquilibriumVapourPressure(
+                       _d.T) &&
+        react_rate_R > 0.0)
+    {
+        react_rate_R = 0.0;
+    }
+    else if (_d.p_V < 100.0 ||
+             _d.p_V < 0.05 * Adsorption::AdsorptionReaction::
+                                 getEquilibriumVapourPressure(_d.T))
+    {
+        // use equilibrium reaction for dry regime
+
+        // in the case of zeroth try in zeroth iteration: _p_V and loading are
+        // the values
+        // at the end of the previous timestep
+
+        const double pV_eq = estimateAdsorptionEquilibrium(_d.p_V, loading);
+        // TODO [CL]: it would be more correct to subtract pV from the previous
+        // timestep here
+        const double delta_pV = pV_eq - _d.p_V;
+        const double delta_rhoV = delta_pV * _d.ap.M_react /
+                                  Adsorption::GAS_CONST / _d.T * _d.ap.poro;
+        const double delta_rhoSR = delta_rhoV / (_d.ap.poro - 1.0);
+        double react_rate_R2 = delta_rhoSR / _d.ap.delta_t;
+
+        if (_bounds_violation[int_pt])
+        {
+            react_rate_R2 *= 0.5;
+        }
+
+        // 0th try: make sure reaction is not slower than allowed by local
+        // estimation
+        // nth try: make sure reaction is not made faster by local estimation
+        if ((_d.ap.number_of_try_of_iteration == 1 &&
+             std::abs(react_rate_R2) > std::abs(react_rate_R)) ||
+            (_d.ap.number_of_try_of_iteration > 1 &&
+             std::abs(react_rate_R2) < std::abs(react_rate_R)))
+        {
+            react_rate_R = react_rate_R2;
+        }
+    }
+
+    // smooth out readjustment of reaction rate
+    if (_d.ap.iteration_in_current_timestep > 4)
+    {
+        if (_d.ap.iteration_in_current_timestep <= 9)
+        {
+            // update reaction rate for for five iterations
+            const auto N = _d.ap.iteration_in_current_timestep - 4;
+
+            // take average s.t. does not oscillate so much
+            react_rate_R = 1.0 / (1.0 + N) *
+                           (N * _d.reaction_rate[int_pt] + 1.0 * react_rate_R);
+        }
+        else
+        {
+            // afterwards no update anymore
+            react_rate_R = _d.reaction_rate[int_pt];
+        }
+    }
+
+    if (_d.ap.number_of_try_of_iteration > 1)
+    {
+        // assert that within tries reaction does not get faster
+        // (e.g. due to switch equilibrium reaction <--> kinetic reaction)
+
+        // factor of 0.9*N: in fact, even slow down reaction over tries
+        const double r = std::pow(0.9, _d.ap.number_of_try_of_iteration - 1) *
+                         _d.reaction_rate[int_pt];
+        if (std::abs(react_rate_R) > std::abs(r))
+        {
+            react_rate_R = r;
+        }
+    }
+
+    return {react_rate_R,
+            _d.solid_density_prev_ts[int_pt] + react_rate_R * _d.ap.delta_t};
+}
+
+double TESFEMReactionAdaptorAdsorption::estimateAdsorptionEquilibrium(
+    const double p_V0, const double C0) const
+{
+    auto f = [this, p_V0, C0](double pV) -> double {
+        // pV0 := _p_V
+        const double C_eq =
+            _d.ap.react_sys->getEquilibriumLoading(pV, _d.T, _d.ap.M_react);
+        return (pV - p_V0) * _d.ap.M_react / Adsorption::GAS_CONST / _d.T *
+                   _d.ap.poro +
+               (1.0 - _d.ap.poro) * (C_eq - C0) * _d.ap.rho_SR_dry;
+    };
+
+    // range where to search for roots of f
+    const double C_eq0 =
+        _d.ap.react_sys->getEquilibriumLoading(p_V0, _d.T, _d.ap.M_react);
+    const double limit =
+        (C_eq0 > C0)
+            ? 1e-8
+            : Adsorption::AdsorptionReaction::getEquilibriumVapourPressure(
+                  _d.T);
+
+    // search for roots
+    auto rf = MathLib::Nonlinear::makeRegulaFalsi<MathLib::Nonlinear::Pegasus>(
+        f, p_V0, limit);
+    rf.step(3);
+
+    // set vapour pressure
+    return rf.getResult();
+}
+
+bool TESFEMReactionAdaptorAdsorption::checkBounds(
+    std::vector<double> const& local_x,
+    std::vector<double> const& local_x_prev_ts)
+{
+    double alpha = 1.0;
+
+    const double min_xmV = 1e-6;
+    const std::size_t nnodes = local_x.size() / NODAL_DOF;
+    const std::size_t xmV_offset = COMPONENT_ID_MASS_FRACTION * nnodes;
+
+    for (std::size_t i = 0; i < nnodes; ++i)
+    {
+        auto const xnew = local_x[xmV_offset + i];
+        auto const xold = local_x_prev_ts[xmV_offset + i];
+
+        if (xnew < min_xmV)
+        {
+            const auto a = xold / (xold - xnew);
+            alpha = std::min(alpha, a);
+            _bounds_violation[i] = true;
+        }
+        else if (xnew > 1.0)
+        {
+            const auto a = xold / (xnew - xold);
+            alpha = std::min(alpha, a);
+            _bounds_violation[i] = true;
+        }
+        else
+        {
+            _bounds_violation[i] = false;
+        }
+    }
+
+    assert(alpha > 0.0);
+
+    if (alpha != 1.0)
+    {
+        if (alpha > 0.5)
+            alpha = 0.5;
+        if (alpha < 0.05)
+            alpha = 0.05;
+
+        if (_d.ap.number_of_try_of_iteration <= 3)
+        {
+            _reaction_damping_factor *= sqrt(alpha);
+        }
+        else
+        {
+            _reaction_damping_factor *= alpha;
+        }
+    }
+
+    return alpha == 1.0;
+}
+
+void TESFEMReactionAdaptorAdsorption::preZerothTryAssemble()
+{
+    if (_reaction_damping_factor < 1e-3)
+        _reaction_damping_factor = 1e-3;
+
+    _reaction_damping_factor = std::min(std::sqrt(_reaction_damping_factor),
+                                        10.0 * _reaction_damping_factor);
+}
+
+TESFEMReactionAdaptorInert::TESFEMReactionAdaptorInert(
+    TESLocalAssemblerData const& data)
+    : _d(data)
+{
+}
+
+ReactionRate TESFEMReactionAdaptorInert::initReaction(const unsigned int_pt)
+{
+    return {0.0, _d.solid_density_prev_ts[int_pt]};
+    // _d._qR = 0.0;
+    // _d._reaction_rate[int_pt] = 0.0;
+}
+
+TESFEMReactionAdaptorSinusoidal::TESFEMReactionAdaptorSinusoidal(
+    TESLocalAssemblerData const& data)
+    : _d(data)
+{
+    assert(dynamic_cast<Adsorption::ReactionSinusoidal const*>(
+               data.ap.react_sys.get()) != nullptr &&
+           "Reactive system has wrong type.");
+}
+
+ReactionRate TESFEMReactionAdaptorSinusoidal::initReaction(
+    const unsigned /*int_pt*/)
+{
+    const double t = _d.ap.current_time;
+
+    // Cf. OGS5
+    const double rhoSR0 = 1.0;
+    const double rhoTil = 0.1;
+    const double omega = 2.0 * 3.1416;
+    const double poro = _d.ap.poro;
+
+    return {rhoTil * omega * cos(omega * t) / (1.0 - poro),
+            rhoSR0 + rhoTil * std::sin(omega * t) / (1.0 - poro)};
+}
+
+TESFEMReactionAdaptorCaOH2::TESFEMReactionAdaptorCaOH2(
+    TESLocalAssemblerData const& data)
+    : _d(data),
+      _react(dynamic_cast<Adsorption::ReactionCaOH2&>(*data.ap.react_sys.get()))
+{
+    _ode_solver = MathLib::ODE::createODESolver<1>(_react.getOdeSolverConfig());
+    // TODO invalidate config
+
+    _ode_solver->setTolerance(1e-10, 1e-10);
+
+    auto f = [this](const double /*t*/,
+                    MathLib::ODE::MappedConstVector<1> const y,
+                    MathLib::ODE::MappedVector<1>
+                        ydot) -> bool {
+        ydot[0] = _react.getReactionRate(y[0]);
+        return true;
+    };
+
+    _ode_solver->setFunction(f, nullptr);
+}
+
+ReactionRate TESFEMReactionAdaptorCaOH2::initReaction(const unsigned int int_pt)
+{
+    // TODO if the first holds, the second also has to hold
+    if (_d.ap.iteration_in_current_timestep > 1 ||
+        _d.ap.number_of_try_of_iteration > 1)
+    {
+        return {_d.reaction_rate[int_pt], _d.solid_density[int_pt]};
+    }
+
+    // TODO: double check!
+    // const double xv_NR  = SolidProp->non_reactive_solid_volume_fraction;
+    // const double rho_NR = SolidProp->non_reactive_solid_density;
+    const double xv_NR = 0.0;
+    const double rho_NR = 0.0;
+
+    const double t0 = 0.0;
+    const double y0 =
+        (_d.solid_density_prev_ts[int_pt] - xv_NR * rho_NR) / (1.0 - xv_NR);
+
+    const double t_end = _d.ap.delta_t;
+
+    _react.updateParam(_d.T, _d.p, _d.vapour_mass_fraction,
+                       _d.solid_density_prev_ts[int_pt]);
+
+    _ode_solver->setIC(t0, {y0});
+    _ode_solver->preSolve();
+    _ode_solver->solve(t_end);
+
+    const double time_reached = _ode_solver->getTime();
+    (void)time_reached;
+    assert(std::abs(t_end - time_reached) <
+           std::numeric_limits<double>::epsilon());
+
+    auto const& y_new = _ode_solver->getSolution();
+    auto const& y_dot_new = _ode_solver->getYDot(t_end, y_new);
+
+    double rho_react;
+
+    // cut off when limits are reached
+    if (y_new[0] < _react.rho_low)
+        rho_react = _react.rho_low;
+    else if (y_new[0] > _react.rho_up)
+        rho_react = _react.rho_up;
+    else
+        rho_react = y_new[0];
+
+    return {y_dot_new[0] * (1.0 - xv_NR),
+            (1.0 - xv_NR) * rho_react + xv_NR * rho_NR};
+}
+
+}  // namespace TES
+}  // namespace ProcessLib
diff --git a/ProcessLib/TES/TESReactionAdaptor.h b/ProcessLib/TES/TESReactionAdaptor.h
new file mode 100644
index 00000000000..a1742dc78bb
--- /dev/null
+++ b/ProcessLib/TES/TESReactionAdaptor.h
@@ -0,0 +1,133 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESSLIB_TES_TESREACTIONADAPTOR_H
+#define PROCESSLIB_TES_TESREACTIONADAPTOR_H
+
+#include <memory>
+#include <vector>
+
+#include "MaterialsLib/Adsorption/ReactionCaOH2.h"
+#include "MathLib/ODE/ODESolver.h"
+
+namespace ProcessLib
+{
+namespace TES
+{
+struct TESLocalAssemblerData;
+
+struct ReactionRate
+{
+    const double reaction_rate;
+    const double solid_density;
+};
+
+class TESFEMReactionAdaptor
+{
+public:
+    virtual bool checkBounds(std::vector<double> const& /*local_x*/,
+                             std::vector<double> const& /*local_x_prev_ts*/)
+    {
+        return true;  // by default accept everything
+    }
+
+    virtual ReactionRate initReaction(const unsigned int_pt) = 0;
+
+    virtual void preZerothTryAssemble() {}
+    // TODO: remove
+    virtual double getReactionDampingFactor() const { return -1.0; }
+    virtual ~TESFEMReactionAdaptor() = default;
+
+    static std::unique_ptr<TESFEMReactionAdaptor> newInstance(
+        TESLocalAssemblerData const& data);
+};
+
+class TESFEMReactionAdaptorAdsorption final : public TESFEMReactionAdaptor
+{
+public:
+    explicit TESFEMReactionAdaptorAdsorption(TESLocalAssemblerData const& data);
+
+    bool checkBounds(std::vector<double> const& local_x,
+                     std::vector<double> const& local_x_prev_ts) override;
+
+    ReactionRate initReaction(const unsigned int_pt) override
+    {
+        return initReaction_slowDownUndershootStrategy(int_pt);
+    }
+
+    void preZerothTryAssemble() override;
+
+    // TODO: get rid of
+    double getReactionDampingFactor() const override
+    {
+        return _reaction_damping_factor;
+    }
+
+private:
+    ReactionRate initReaction_slowDownUndershootStrategy(const unsigned int_pt);
+
+    /// returns estimated equilibrium vapour pressure
+    /// based on a local (i.e. no diffusion/advection) balance
+    /// of adsorbate loading and vapour partial pressure
+    double estimateAdsorptionEquilibrium(const double p_V0,
+                                         const double C0) const;
+
+    double _reaction_damping_factor = 1.0;
+    std::vector<bool> _bounds_violation;
+
+    TESLocalAssemblerData const& _d;
+};
+
+class TESFEMReactionAdaptorInert final : public TESFEMReactionAdaptor
+{
+public:
+    explicit TESFEMReactionAdaptorInert(TESLocalAssemblerData const&);
+
+    ReactionRate initReaction(const unsigned int_pt) override;
+
+private:
+    TESLocalAssemblerData const& _d;
+};
+
+class TESFEMReactionAdaptorSinusoidal final : public TESFEMReactionAdaptor
+{
+public:
+    explicit TESFEMReactionAdaptorSinusoidal(TESLocalAssemblerData const& data);
+
+    ReactionRate initReaction(const unsigned) override;
+
+private:
+    TESLocalAssemblerData const& _d;
+};
+
+class TESFEMReactionAdaptorCaOH2 final : public TESFEMReactionAdaptor
+{
+public:
+    explicit TESFEMReactionAdaptorCaOH2(TESLocalAssemblerData const& data);
+
+    ReactionRate initReaction(const unsigned) override;
+
+private:
+    using Data = TESLocalAssemblerData;
+    using React = Adsorption::ReactionCaOH2;
+    Data const& _d;
+    React& _react;
+
+    std::unique_ptr<MathLib::ODE::ODESolver<1>> _ode_solver;
+
+    static bool odeRhs(const double /*t*/,
+                       MathLib::ODE::MappedConstVector<1> const y,
+                       MathLib::ODE::MappedVector<1>
+                           ydot,
+                       React& reaction);
+};
+
+}  // namespace TES
+}  // namespace ProcessLib
+#endif  // PROCESSLIB_TES_TESREACTIONADAPTOR_H
-- 
GitLab