From 0ca170b8f3ea2372f03e4ff2a1bfad3e946ccc65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= <joerg.buchwald@ufz.de>
Date: Tue, 15 Jun 2021 18:24:41 +0200
Subject: [PATCH] add ThermoRichardsFlow process

---
 Applications/ApplicationsLib/ProjectData.cpp  |   15 +-
 ProcessLib/ThermoRichardsFlow/CMakeLists.txt  |    8 +
 .../CreateThermoRichardsFlowProcess.cpp       |  170 +++
 .../CreateThermoRichardsFlowProcess.h         |   54 +
 .../ThermoRichardsFlow/IntegrationPointData.h |   52 +
 .../LocalAssemblerInterface.h                 |   58 +
 .../ThermoRichardsFlowFEM-impl.h              | 1352 +++++++++++++++++
 .../ThermoRichardsFlowFEM.h                   |  172 +++
 .../ThermoRichardsFlowProcess.cpp             |  305 ++++
 .../ThermoRichardsFlowProcess.h               |  156 ++
 .../ThermoRichardsFlowProcessData.h           |   53 +
 scripts/cmake/ProcessesSetup.cmake            |    1 +
 12 files changed, 2395 insertions(+), 1 deletion(-)
 create mode 100644 ProcessLib/ThermoRichardsFlow/CMakeLists.txt
 create mode 100644 ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.cpp
 create mode 100644 ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.h
 create mode 100644 ProcessLib/ThermoRichardsFlow/IntegrationPointData.h
 create mode 100644 ProcessLib/ThermoRichardsFlow/LocalAssemblerInterface.h
 create mode 100644 ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM-impl.h
 create mode 100644 ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM.h
 create mode 100644 ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.cpp
 create mode 100644 ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.h
 create mode 100644 ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcessData.h

diff --git a/Applications/ApplicationsLib/ProjectData.cpp b/Applications/ApplicationsLib/ProjectData.cpp
index f5c400293ad..c0747fc788f 100644
--- a/Applications/ApplicationsLib/ProjectData.cpp
+++ b/Applications/ApplicationsLib/ProjectData.cpp
@@ -118,6 +118,9 @@
 #ifdef OGS_BUILD_PROCESS_THERMOMECHANICS
 #include "ProcessLib/ThermoMechanics/CreateThermoMechanicsProcess.h"
 #endif
+#ifdef OGS_BUILD_PROCESS_THERMORICHARDSFLOW
+#include "ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.h"
+#endif
 #ifdef OGS_BUILD_PROCESS_TWOPHASEFLOWWITHPP
 #include "ProcessLib/TwoPhaseFlowWithPP/CreateTwoPhaseFlowWithPPProcess.h"
 #endif
@@ -1047,7 +1050,17 @@ void ProjectData::parseProcesses(
         }
         else
 #endif
-
+#ifdef OGS_BUILD_PROCESS_THERMORICHARDSFLOW
+            if (type == "THERMO_RICHARDS_FLOW")
+        {
+            process =
+                ProcessLib::ThermoRichardsFlow::createThermoRichardsFlowProcess(
+                    name, *_mesh_vec[0], std::move(jacobian_assembler),
+                    _process_variables, _parameters, integration_order,
+                    process_config, _media);
+        }
+        else
+#endif
 #ifdef OGS_BUILD_PROCESS_THERMORICHARDSMECHANICS
             if (type == "THERMO_RICHARDS_MECHANICS")
         {
diff --git a/ProcessLib/ThermoRichardsFlow/CMakeLists.txt b/ProcessLib/ThermoRichardsFlow/CMakeLists.txt
new file mode 100644
index 00000000000..aeb8302d1dc
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/CMakeLists.txt
@@ -0,0 +1,8 @@
+get_source_files(SOURCES)
+
+ogs_add_library(ThermoRichardsFlow ${SOURCES})
+target_link_libraries(ThermoRichardsFlow PUBLIC ProcessLib PRIVATE ParameterLib)
+
+if(OGS_BUILD_TESTING)
+    include(Tests.cmake)
+endif()
diff --git a/ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.cpp b/ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.cpp
new file mode 100644
index 00000000000..2ac2c523e5a
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.cpp
@@ -0,0 +1,170 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, 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 "CreateThermoRichardsFlowProcess.h"
+
+#include <cassert>
+
+#include "CreateSimplifiedElasticityModel.h"
+#include "LocalAssemblerInterface.h"
+#include "MaterialLib/MPL/CreateMaterialSpatialDistributionMap.h"
+#include "MaterialLib/MPL/MaterialSpatialDistributionMap.h"
+#include "MaterialLib/MPL/Medium.h"
+#include "MaterialLib/SolidModels/CreateConstitutiveRelation.h"
+#include "MaterialLib/SolidModels/MechanicsBase.h"
+#include "ParameterLib/Utils.h"
+#include "ProcessLib/Output/CreateSecondaryVariables.h"
+#include "ProcessLib/Utils/ProcessUtils.h"
+#include "SimplifiedElasticityModel.h"
+#include "ThermoRichardsFlowProcess.h"
+#include "ThermoRichardsFlowProcessData.h"
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+void checkMPLProperties(
+    std::map<int, std::shared_ptr<MaterialPropertyLib::Medium>> const& media)
+{
+    std::array const required_medium_properties = {
+        MaterialPropertyLib::permeability,
+        MaterialPropertyLib::porosity, MaterialPropertyLib::biot_coefficient,
+        MaterialPropertyLib::relative_permeability,
+        MaterialPropertyLib::saturation};
+    std::array const required_liquid_properties = {
+        MaterialPropertyLib::viscosity,
+        MaterialPropertyLib::density,
+    };
+    std::array const required_solid_properties = {
+        MaterialPropertyLib::density};
+
+    // Thermal properties are not checked because they can be phase property or
+    // meduim property (will be enabled later).
+    for (auto const& m : media)
+    {
+        checkRequiredProperties(*m.second, required_medium_properties);
+        checkRequiredProperties(m.second->phase("AqueousLiquid"),
+                                required_liquid_properties);
+        checkRequiredProperties(m.second->phase("Solid"),
+                                required_solid_properties);
+    }
+}
+
+void checkProcessVariableComponents(ProcessVariable const& variable)
+{
+    if (variable.getNumberOfGlobalComponents() != 1)
+    {
+        OGS_FATAL(
+            "Number of components of the process variable '{:s}' is different "
+            "from one: got {:d}.",
+            variable.getName(),
+            variable.getNumberOfGlobalComponents());
+    }
+}
+
+std::unique_ptr<Process> createThermoRichardsFlowProcess(
+    std::string name,
+    MeshLib::Mesh& mesh,
+    std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
+    std::vector<ProcessVariable> const& variables,
+    std::vector<std::unique_ptr<ParameterLib::ParameterBase>> const& parameters,
+    unsigned const integration_order,
+    BaseLib::ConfigTree const& config,
+    std::map<int, std::shared_ptr<MaterialPropertyLib::Medium>> const& media)
+{
+    //! \ogs_file_param{prj__processes__process__type}
+    config.checkConfigParameter("type", "THERMO_RICHARDS_FLOW");
+    DBUG("Create ThermoRichardsFlowProcess.");
+
+    auto const staggered_scheme =
+        //! \ogs_file_param{prj__processes__process__THERMO_RICHARDS_FLOW__coupling_scheme}
+        config.getConfigParameterOptional<std::string>("coupling_scheme");
+    const bool use_monolithic_scheme =
+        !(staggered_scheme && (*staggered_scheme == "staggered"));
+
+    // Process variable.
+
+    //! \ogs_file_param{prj__processes__process__THERMO_RICHARDS_FLOW__process_variables}
+    auto const pv_config = config.getConfigSubtree("process_variables");
+
+    ProcessVariable* variable_T;
+    ProcessVariable* variable_p;
+    std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>
+        process_variables;
+    if (use_monolithic_scheme)  // monolithic scheme.
+    {
+        auto per_process_variables = findProcessVariables(
+            variables, pv_config,
+            {//! \ogs_file_param_special{prj__processes__process__THERMO_RICHARDS_FLOW__process_variables__temperature}
+             "temperature",
+             //! \ogs_file_param_special{prj__processes__process__THERMO_RICHARDS_FLOW__process_variables__pressure}
+             "pressure"});
+        variable_T = &per_process_variables[0].get();
+        variable_p = &per_process_variables[1].get();
+        process_variables.push_back(std::move(per_process_variables));
+    }
+    else  // staggered scheme.
+    {
+        OGS_FATAL(
+            "So far, only the monolithic scheme is implemented for "
+            "THERMO_RICHARDS_FLOW");
+    }
+
+    checkProcessVariableComponents(*variable_T);
+    checkProcessVariableComponents(*variable_p);
+
+    // Specific body force parameter.
+    Eigen::VectorXd specific_body_force;
+    {
+        std::vector<double> const b =
+            //! \ogs_file_param{prj__processes__process__THERMO_RICHARDS_FLOW__specific_body_force}
+            config.getConfigParameter<std::vector<double>>(
+                "specific_body_force");
+        if (b.size() != mesh.getDimension())
+        {
+            OGS_FATAL(
+                "specific body force (gravity vector) has {:d} components, "
+                "but mesh dimension is {:d}",
+                b.size(), mesh.getDimension());
+        }
+        specific_body_force.resize(b.size());
+        std::copy_n(b.data(), b.size(), specific_body_force.data());
+    }
+
+    auto media_map =
+        MaterialPropertyLib::createMaterialSpatialDistributionMap(media, mesh);
+    DBUG(
+        "Check the media properties of ThermoRichardsFlow process "
+        "...");
+    checkMPLProperties(media);
+    DBUG("Media properties verified.");
+
+    //! \ogs_file_param{prj__processes__process__THERMO_RICHARDS_FLOW__mass_lumping}
+    bool const mass_lumping = config.getConfigParameter<bool>("mass_lumping", false);
+
+    std::unique_ptr<SimplifiedElasticityModel> simplified_elasticity =
+        createElasticityModel(config);
+
+    ThermoRichardsFlowProcessData process_data{
+        std::move(media_map), std::move(specific_body_force),
+        mass_lumping, std::move(simplified_elasticity)};
+
+    SecondaryVariableCollection secondary_variables;
+
+    ProcessLib::createSecondaryVariables(config, secondary_variables);
+
+    return std::make_unique<ThermoRichardsFlowProcess>(
+        std::move(name), mesh, std::move(jacobian_assembler), parameters,
+        integration_order, std::move(process_variables),
+        std::move(process_data), std::move(secondary_variables),
+        use_monolithic_scheme);
+}
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.h b/ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.h
new file mode 100644
index 00000000000..2abf3aabdc0
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/CreateThermoRichardsFlowProcess.h
@@ -0,0 +1,54 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace BaseLib
+{
+class ConfigTree;
+}
+namespace MeshLib
+{
+class Mesh;
+}
+namespace MaterialPropertyLib
+{
+class Medium;
+}
+namespace ParameterLib
+{
+struct ParameterBase;
+}  // namespace ParameterLib
+namespace ProcessLib
+{
+class AbstractJacobianAssembler;
+class Process;
+class ProcessVariable;
+}  // namespace ProcessLib
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+std::unique_ptr<Process> createThermoRichardsFlowProcess(
+    std::string name,
+    MeshLib::Mesh& mesh,
+    std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
+    std::vector<ProcessVariable> const& variables,
+    std::vector<std::unique_ptr<ParameterLib::ParameterBase>> const& parameters,
+    unsigned const integration_order,
+    BaseLib::ConfigTree const& config,
+    std::map<int, std::shared_ptr<MaterialPropertyLib::Medium>> const& media);
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/IntegrationPointData.h b/ProcessLib/ThermoRichardsFlow/IntegrationPointData.h
new file mode 100644
index 00000000000..55eeaf3e8e4
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/IntegrationPointData.h
@@ -0,0 +1,52 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <memory>
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+template <typename ShapeMatricesType>
+struct IntegrationPointData final
+{
+    typename ShapeMatricesType::NodalRowVectorType N;
+    typename ShapeMatricesType::GlobalDimNodalMatrixType dNdx;
+
+    typename ShapeMatricesType::GlobalDimVectorType v_darcy;
+
+    double saturation = std::numeric_limits<double>::quiet_NaN();
+    double saturation_prev = std::numeric_limits<double>::quiet_NaN();
+    double porosity = std::numeric_limits<double>::quiet_NaN();
+    double porosity_prev = std::numeric_limits<double>::quiet_NaN();
+    double transport_porosity = std::numeric_limits<double>::quiet_NaN();
+    double transport_porosity_prev = std::numeric_limits<double>::quiet_NaN();
+    double dry_density_solid = std::numeric_limits<double>::quiet_NaN();
+    double dry_density_pellet_saturated =
+        std::numeric_limits<double>::quiet_NaN();
+    double dry_density_pellet_unsaturated =
+        std::numeric_limits<double>::quiet_NaN();
+
+    double integration_weight;
+
+    void pushBackState()
+    {
+        saturation_prev = saturation;
+        porosity_prev = porosity;
+        transport_porosity_prev = transport_porosity;
+    }
+
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+};
+
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/LocalAssemblerInterface.h b/ProcessLib/ThermoRichardsFlow/LocalAssemblerInterface.h
new file mode 100644
index 00000000000..d7579227601
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/LocalAssemblerInterface.h
@@ -0,0 +1,58 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "NumLib/Extrapolation/ExtrapolatableElement.h"
+#include "ProcessLib/LocalAssemblerInterface.h"
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+struct LocalAssemblerInterface : public ProcessLib::LocalAssemblerInterface,
+                                 public NumLib::ExtrapolatableElement
+{
+    virtual std::size_t setIPDataInitialConditions(
+        std::string const& name, double const* values,
+        int const integration_order) = 0;
+
+    virtual std::vector<double> const& getIntPtDarcyVelocity(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const = 0;
+
+    virtual std::vector<double> getSaturation() const = 0;
+    virtual std::vector<double> const& getIntPtSaturation(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const = 0;
+
+    virtual std::vector<double> getPorosity() const = 0;
+    virtual std::vector<double> const& getIntPtPorosity(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const = 0;
+
+    virtual std::vector<double> const& getIntPtDryDensitySolid(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const = 0;
+
+    // TODO move to NumLib::ExtrapolatableElement
+    virtual unsigned getNumberOfIntegrationPoints() const = 0;
+};
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM-impl.h b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM-impl.h
new file mode 100644
index 00000000000..255c0b12afe
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM-impl.h
@@ -0,0 +1,1352 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#pragma once
+
+#include <cassert>
+
+#include "HydrostaticElasticityModel.h"
+#include "MaterialLib/MPL/Medium.h"
+#include "MaterialLib/MPL/Utils/FormEffectiveThermalConductivity.h"
+#include "MaterialLib/MPL/Utils/FormEigenTensor.h"
+#include "MaterialLib/MPL/Utils/FormEigenVector.h"
+#include "MaterialLib/MPL/Utils/GetLiquidThermalExpansivity.h"
+#include "MaterialLib/PhysicalConstant.h"
+#include "MaterialLib/SolidModels/SelectSolidConstitutiveRelation.h"
+#include "MaterialLib/MPL/MaterialSpatialDistributionMap.h"
+#include "NumLib/Function/Interpolation.h"
+#include "ProcessLib/Utils/SetOrGetIntegrationPointData.h"
+#include "RigidElasticityModel.h"
+#include "UniaxialElasticityModel.h"
+#include "UserDefinedElasticityModel.h"
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod, GlobalDim>::
+    ThermoRichardsFlowLocalAssembler(
+        MeshLib::Element const& e,
+        std::size_t const /*local_matrix_size*/,
+        bool const is_axially_symmetric,
+        unsigned const integration_order,
+        ThermoRichardsFlowProcessData& process_data)
+    : _process_data(process_data),
+      _integration_method(integration_order),
+      _element(e),
+      _is_axially_symmetric(is_axially_symmetric)
+{
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    _ip_data.reserve(n_integration_points);
+
+    auto const shape_matrices =
+        NumLib::initShapeMatrices<ShapeFunction, ShapeMatricesType, GlobalDim>(
+            e, is_axially_symmetric, _integration_method);
+
+    auto const& medium = *_process_data.media_map->getMedium(_element.getID());
+
+    ParameterLib::SpatialPosition x_position;
+    x_position.setElementID(_element.getID());
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        auto const& sm = shape_matrices[ip];
+        x_position.setIntegrationPoint(ip);
+        _ip_data.emplace_back();
+        auto& ip_data = _ip_data[ip];
+        _ip_data[ip].integration_weight =
+            _integration_method.getWeightedPoint(ip).getWeight() *
+            sm.integralMeasure * sm.detJ;
+
+        ip_data.N = sm.N;
+        ip_data.dNdx = sm.dNdx;
+
+        // Initial porosity. Could be read from integration point data or mesh.
+        ip_data.porosity =
+            medium[MPL::porosity]
+                .template initialValue<double>(
+                    x_position,
+                    std::numeric_limits<
+                        double>::quiet_NaN() /* t independent */);
+    }
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::size_t ThermoRichardsFlowLocalAssembler<
+    ShapeFunction, IntegrationMethod,
+    GlobalDim>::setIPDataInitialConditions(std::string const& name,
+                                           double const* values,
+                                           int const integration_order)
+{
+    if (integration_order !=
+        static_cast<int>(_integration_method.getIntegrationOrder()))
+    {
+        OGS_FATAL(
+            "Setting integration point initial conditions; The integration "
+            "order of the local assembler for element {:d} is different "
+            "from the integration order in the initial condition.",
+            _element.getID());
+    }
+
+    if (name == "saturation_ip")
+    {
+        return ProcessLib::setIntegrationPointScalarData(values, _ip_data,
+                                                         &IpData::saturation);
+    }
+    if (name == "porosity_ip")
+    {
+        return ProcessLib::setIntegrationPointScalarData(values, _ip_data,
+                                                         &IpData::porosity);
+    }
+    return 0;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+void ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod,
+                                      GlobalDim>::
+    setInitialConditionsConcrete(std::vector<double> const& local_x,
+                                 double const t,
+                                 bool const /*use_monolithic_scheme*/,
+                                 int const /*process_id*/)
+{
+    assert(local_x.size() == temperature_size + pressure_size);
+
+    auto p_L = Eigen::Map<
+        typename ShapeMatricesType::template VectorType<pressure_size> const>(
+        local_x.data() + pressure_index, pressure_size);
+
+    auto const& medium = *_process_data.media_map->getMedium(_element.getID());
+    MPL::VariableArray variables;
+
+    ParameterLib::SpatialPosition x_position;
+    x_position.setElementID(_element.getID());
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        x_position.setIntegrationPoint(ip);
+
+        auto const& N = _ip_data[ip].N;
+
+        double p_cap_ip;
+        NumLib::shapeFunctionInterpolate(-p_L, N, p_cap_ip);
+
+        variables[static_cast<int>(MPL::Variable::capillary_pressure)] =
+            p_cap_ip;
+        variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip;
+
+        // Note: temperature dependent saturation model is not considered so
+        // far.
+        _ip_data[ip].saturation_prev =
+            medium[MPL::PropertyType::saturation]
+                .template value<double>(
+                    variables, x_position, t,
+                    std::numeric_limits<double>::quiet_NaN());
+    }
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+void ThermoRichardsFlowLocalAssembler<
+    ShapeFunction, IntegrationMethod,
+    GlobalDim>::assembleWithJacobian(double const t, double const dt,
+                                     std::vector<double> const& local_x,
+                                     std::vector<double> const& local_xdot,
+                                     const double /*dxdot_dx*/,
+                                     const double /*dx_dx*/,
+                                     std::vector<double>& /*local_M_data*/,
+                                     std::vector<double>& /*local_K_data*/,
+                                     std::vector<double>& local_rhs_data,
+                                     std::vector<double>& local_Jac_data)
+{
+    auto const local_matrix_dim = pressure_size + temperature_size;
+    assert(local_x.size() == local_matrix_dim);
+
+    auto const T = Eigen::Map<typename ShapeMatricesType::template VectorType<
+        temperature_size> const>(local_x.data() + temperature_index,
+                                 temperature_size);
+    auto const p_L = Eigen::Map<
+        typename ShapeMatricesType::template VectorType<pressure_size> const>(
+        local_x.data() + pressure_index, pressure_size);
+
+    auto const T_dot =
+        Eigen::Map<typename ShapeMatricesType::template VectorType<
+            temperature_size> const>(local_xdot.data() + temperature_index,
+                                     temperature_size);
+    auto const p_L_dot = Eigen::Map<
+        typename ShapeMatricesType::template VectorType<pressure_size> const>(
+        local_xdot.data() + pressure_index, pressure_size);
+
+    auto local_Jac = MathLib::createZeroedMatrix<
+        typename ShapeMatricesType::template MatrixType<local_matrix_dim,
+                                                        local_matrix_dim>>(
+        local_Jac_data, local_matrix_dim, local_matrix_dim);
+
+    auto local_rhs = MathLib::createZeroedVector<
+        typename ShapeMatricesType::template VectorType<local_matrix_dim>>(
+        local_rhs_data, local_matrix_dim);
+
+    typename ShapeMatricesType::NodalMatrixType M_TT =
+        ShapeMatricesType::NodalMatrixType::Zero(temperature_size,
+                                                 temperature_size);
+    typename ShapeMatricesType::NodalMatrixType M_Tp =
+        ShapeMatricesType::NodalMatrixType::Zero(temperature_size,
+                                                 pressure_size);
+    typename ShapeMatricesType::NodalMatrixType K_TT =
+        ShapeMatricesType::NodalMatrixType::Zero(temperature_size,
+                                                 temperature_size);
+    typename ShapeMatricesType::NodalMatrixType K_Tp =
+        ShapeMatricesType::NodalMatrixType::Zero(temperature_size,
+                                                 pressure_size);
+    typename ShapeMatricesType::NodalMatrixType M_pT =
+        ShapeMatricesType::NodalMatrixType::Zero(pressure_size,
+                                                 temperature_size);
+    typename ShapeMatricesType::NodalMatrixType laplace_p =
+        ShapeMatricesType::NodalMatrixType::Zero(pressure_size, pressure_size);
+
+    typename ShapeMatricesType::NodalMatrixType storage_p_a_p =
+        ShapeMatricesType::NodalMatrixType::Zero(pressure_size, pressure_size);
+
+    typename ShapeMatricesType::NodalMatrixType storage_p_a_S_Jpp =
+        ShapeMatricesType::NodalMatrixType::Zero(pressure_size, pressure_size);
+
+    typename ShapeMatricesType::NodalMatrixType storage_p_a_S =
+        ShapeMatricesType::NodalMatrixType::Zero(pressure_size, pressure_size);
+
+    auto const& medium = *_process_data.media_map->getMedium(_element.getID());
+    auto const& liquid_phase = medium.phase("AqueousLiquid");
+    auto const& solid_phase = medium.phase("Solid");
+    MPL::Phase const* gas_phase = medium.hasPhase("Gas") ?
+        &medium.phase("Gas") : nullptr;
+    MPL::VariableArray variables;
+    MPL::VariableArray variables_prev;
+
+    ParameterLib::SpatialPosition x_position;
+    x_position.setElementID(_element.getID());
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        x_position.setIntegrationPoint(ip);
+        auto const& w = _ip_data[ip].integration_weight;
+
+        auto const& N = _ip_data[ip].N;
+        auto const& dNdx = _ip_data[ip].dNdx;
+
+        double T_ip;
+        NumLib::shapeFunctionInterpolate(T, N, T_ip);
+        double T_dot_ip;
+        NumLib::shapeFunctionInterpolate(T_dot, N, T_dot_ip);
+
+        double p_cap_ip;
+        NumLib::shapeFunctionInterpolate(-p_L, N, p_cap_ip);
+
+        double p_cap_dot_ip;
+        NumLib::shapeFunctionInterpolate(-p_L_dot, N, p_cap_dot_ip);
+
+        variables[static_cast<int>(MPL::Variable::capillary_pressure)] =
+            p_cap_ip;
+        variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip;
+        variables[static_cast<int>(MPL::Variable::temperature)] = T_ip;
+
+        auto& S_L = _ip_data[ip].saturation;
+        auto const S_L_prev = _ip_data[ip].saturation_prev;
+        auto const alpha =
+            medium[MPL::PropertyType::biot_coefficient]
+                .template value<double>(variables, x_position, t, dt);
+
+        auto& solid_elasticity = *_process_data.simplified_elasticity;
+        // TODO (buchwaldj)
+        // is bulk_modulus good name for bulk modulus of solid skeleton?
+        auto const beta_S =
+            solid_elasticity.bulkCompressibilityFromYoungsModulus(
+                solid_phase, variables, x_position, t, dt);
+        auto const beta_SR = (1 - alpha) * beta_S;
+        variables[static_cast<int>(MPL::Variable::grain_compressibility)] =
+            beta_SR;
+
+        auto const rho_LR =
+            liquid_phase[MPL::PropertyType::density]
+                .template value<double>(variables, x_position, t, dt);
+        auto const& b = _process_data.specific_body_force;
+
+        double const drho_LR_dp =
+            liquid_phase[MPL::PropertyType::density]
+                .template dValue<double>(variables,
+                                         MPL::Variable::phase_pressure,
+                                         x_position, t, dt);
+        auto const beta_LR = drho_LR_dp / rho_LR;
+
+        S_L = medium[MPL::PropertyType::saturation]
+                  .template value<double>(variables, x_position, t, dt);
+        variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L;
+        variables_prev[static_cast<int>(MPL::Variable::liquid_saturation)] =
+            S_L_prev;
+
+        // tangent derivative for Jacobian
+        double const dS_L_dp_cap = medium[MPL::PropertyType::saturation]
+                .template dValue<double>(variables,
+                                         MPL::Variable::capillary_pressure,
+                                         x_position, t, dt);
+        // secant derivative from time discretization for storage
+        // use tangent, if secant is not available
+        double const DeltaS_L_Deltap_cap =
+            (p_cap_dot_ip == 0) ? dS_L_dp_cap
+                                : (S_L - S_L_prev) / (dt * p_cap_dot_ip);
+
+        auto chi_S_L = S_L;
+        auto chi_S_L_prev = S_L_prev;
+        auto dchi_dS_L = 1.0;
+        if (medium.hasProperty(MPL::PropertyType::bishops_effective_stress))
+        {
+            auto const chi = [&medium, x_position, t, dt](double const S_L) {
+                MPL::VariableArray variables;
+                variables[static_cast<int>(MPL::Variable::liquid_saturation)] =
+                    S_L;
+                return medium[MPL::PropertyType::bishops_effective_stress]
+                    .template value<double>(variables, x_position, t, dt);
+            };
+            chi_S_L = chi(S_L);
+            chi_S_L_prev = chi(S_L_prev);
+
+            dchi_dS_L = medium[MPL::PropertyType::bishops_effective_stress]
+                .template dValue<double>(variables,
+                                         MPL::Variable::liquid_saturation,
+                                         x_position, t, dt);
+        }
+        // TODO (buchwaldj)
+        // should solid_grain_pressure or effective_pore_pressure remain?
+        // double const p_FR = -chi_S_L * p_cap_ip;
+        // variables[static_cast<int>(MPL::Variable::solid_grain_pressure)] =
+        // p_FR;
+
+        variables[static_cast<int>(MPL::Variable::effective_pore_pressure)] =
+            -chi_S_L * p_cap_ip;
+        variables_prev[static_cast<int>(
+            MPL::Variable::effective_pore_pressure)] =
+            -chi_S_L_prev * (p_cap_ip - p_cap_dot_ip * dt);
+
+        auto& phi = _ip_data[ip].porosity;
+        {  // Porosity update
+
+            variables_prev[static_cast<int>(MPL::Variable::porosity)] =
+                _ip_data[ip].porosity_prev;
+            phi = medium[MPL::PropertyType::porosity]
+                      .template value<double>(variables, variables_prev,
+                                              x_position, t, dt);
+            variables[static_cast<int>(MPL::Variable::porosity)] = phi;
+        }
+
+        if (alpha < phi)
+        {
+            OGS_FATAL(
+                "ThermoRichardsFlow: Biot-coefficient {} is smaller than "
+                "porosity {} in element/integration point {}/{}.",
+                alpha, phi, _element.getID(), ip);
+        }
+
+        double const k_rel =
+            medium[MPL::PropertyType::relative_permeability]
+                .template value<double>(variables, x_position, t, dt);
+        auto const mu =
+            liquid_phase[MPL::PropertyType::viscosity]
+                .template value<double>(variables, x_position, t, dt);
+
+        auto const K_intrinsic = MPL::formEigenTensor<GlobalDim>(
+            medium[MPL::PropertyType::permeability]
+                .value(variables, x_position, t, dt));
+
+        GlobalDimMatrixType const Ki_over_mu = K_intrinsic / mu;
+        GlobalDimMatrixType const rho_Ki_over_mu = rho_LR * Ki_over_mu;
+
+        // Consider anisotropic thermal expansion.
+        // Read in 3x3 tensor. 2D case also requires expansion coeff. for z-
+        // component.
+        Eigen::Matrix<double, 3,
+                      3> const solid_linear_thermal_expansion_coefficient =
+            MaterialPropertyLib::formEigenTensor<3>(
+                solid_phase[
+                        MaterialPropertyLib::PropertyType::thermal_expansivity]
+                    .value(variables, x_position, t, dt));
+
+        auto const rho_SR =
+            solid_phase[MPL::PropertyType::density]
+                .template value<double>(variables, x_position, t, dt);
+
+        //
+        // pressure equation, pressure part.
+        //
+        laplace_p.noalias() +=
+            dNdx.transpose() * k_rel * rho_Ki_over_mu * dNdx * w;
+
+        const double alphaB_minus_phi = alpha - phi;
+        double const a0 = alphaB_minus_phi * beta_SR;
+        double const specific_storage_a_p =
+            S_L * (phi * beta_LR + S_L * a0 +
+                   chi_S_L * alpha * alpha *
+                       solid_elasticity.storageContribution(
+                           solid_phase, variables, x_position, t, dt));
+        double const specific_storage_a_S = phi - p_cap_ip * S_L * a0;
+
+        double const dspecific_storage_a_p_dp_cap =
+            dS_L_dp_cap * (phi * beta_LR + 2 * S_L * a0 +
+                           alpha * alpha *
+                               solid_elasticity.storageContribution(
+                                   solid_phase, variables, x_position, t, dt) *
+                               (chi_S_L + dchi_dS_L * S_L));
+        double const dspecific_storage_a_S_dp_cap =
+            -a0 * (S_L + p_cap_ip * dS_L_dp_cap);
+
+        storage_p_a_p.noalias() +=
+            N.transpose() * rho_LR * specific_storage_a_p * N * w;
+
+        storage_p_a_S.noalias() -= N.transpose() * rho_LR *
+                                       specific_storage_a_S * DeltaS_L_Deltap_cap *
+                                       N * w;
+
+        local_Jac
+            .template block<pressure_size, pressure_size>(pressure_index,
+                                                          pressure_index)
+            .noalias() += N.transpose() * p_cap_dot_ip * rho_LR *
+                          dspecific_storage_a_p_dp_cap * N * w;
+
+        storage_p_a_S_Jpp.noalias() -=
+            N.transpose() * rho_LR *
+            ((S_L - S_L_prev) * dspecific_storage_a_S_dp_cap +
+             specific_storage_a_S * dS_L_dp_cap) /
+            dt * N * w;
+
+        double const dk_rel_dS_L =
+            medium[MPL::PropertyType::relative_permeability]
+                .template dValue<double>(variables,
+                                         MPL::Variable::liquid_saturation,
+                                         x_position, t, dt);
+        GlobalDimVectorType const grad_p_cap = -dNdx * p_L;
+        local_Jac
+            .template block<pressure_size, pressure_size>(pressure_index,
+                                                          pressure_index)
+            .noalias() += dNdx.transpose() * rho_Ki_over_mu * grad_p_cap *
+                          dk_rel_dS_L * dS_L_dp_cap * N * w;
+
+        local_Jac
+            .template block<pressure_size, pressure_size>(pressure_index,
+                                                          pressure_index)
+            .noalias() += dNdx.transpose() * rho_LR * rho_Ki_over_mu * b *
+                          dk_rel_dS_L * dS_L_dp_cap * N * w;
+
+        local_rhs.template segment<pressure_size>(pressure_index).noalias() +=
+            dNdx.transpose() * rho_LR * k_rel * rho_Ki_over_mu * b * w;
+
+        //
+        // pressure equation, temperature part.
+        //
+        double const fluid_volumetric_thermal_expansion_coefficient =
+            MPL::getLiquidThermalExpansivity(liquid_phase, variables, rho_LR,
+                                             x_position, t, dt);
+        const double eff_thermal_expansion =
+            S_L * (alphaB_minus_phi *
+                       solid_linear_thermal_expansion_coefficient.trace() +
+                   phi * fluid_volumetric_thermal_expansion_coefficient +
+                   alpha * solid_elasticity.thermalExpansivityContribution(
+                               solid_linear_thermal_expansion_coefficient,
+                               solid_phase, variables, x_position, t, dt));
+        M_pT.noalias() -=
+            N.transpose() * rho_LR * eff_thermal_expansion * N * w;
+
+        //
+        // temperature equation.
+        //
+        {
+            auto const specific_heat_capacity_fluid =
+                liquid_phase[MaterialPropertyLib::specific_heat_capacity]
+                    .template value<double>(variables, x_position, t, dt);
+
+            auto const specific_heat_capacity_solid =
+                solid_phase
+                    [MaterialPropertyLib::PropertyType::
+                                  specific_heat_capacity]
+                    .template value<double>(variables, x_position, t, dt);
+
+            M_TT.noalias() +=
+                w *
+                (rho_SR * specific_heat_capacity_solid * (1 - phi) +
+                 (S_L * rho_LR * specific_heat_capacity_fluid) * phi) *
+                N.transpose() * N;
+
+            auto const thermal_conductivity =
+                MaterialPropertyLib::formEigenTensor<GlobalDim>(
+                            medium[MaterialPropertyLib::PropertyType::
+                                               thermal_conductivity]
+                                .value(variables, x_position, t, dt));
+
+            GlobalDimVectorType const velocity_L =
+                GlobalDimVectorType(-Ki_over_mu * (dNdx * p_L - rho_LR * b));
+
+            K_TT.noalias() +=
+                (dNdx.transpose() * thermal_conductivity * dNdx +
+                 N.transpose() * velocity_L.transpose() * dNdx * rho_LR *
+                     specific_heat_capacity_fluid) *
+                w;
+
+            //
+            // temperature equation, pressure part
+            //
+            K_Tp.noalias() -= rho_LR * specific_heat_capacity_fluid *
+                              N.transpose() * (dNdx * T).transpose() *
+                              Ki_over_mu * dNdx * w;
+        }
+        if (liquid_phase.hasProperty(MPL::PropertyType::vapour_diffusion) &&
+            S_L < 1.0)
+        {
+            variables[static_cast<int>(MPL::Variable::density)] = rho_LR;
+
+            double const rho_wv =
+                liquid_phase[MaterialPropertyLib::vapour_density]
+                    .template value<double>(variables, x_position, t, dt);
+
+            double const drho_wv_dT =
+                liquid_phase[MaterialPropertyLib::vapour_density]
+                    .template dValue<double>(variables,
+                                             MPL::Variable::temperature,
+                                             x_position, t, dt);
+            double const drho_wv_dp =
+                liquid_phase[MaterialPropertyLib::vapour_density]
+                    .template dValue<double>(variables,
+                                             MPL::Variable::phase_pressure,
+                                             x_position, t, dt);
+            auto const f_Tv =
+                liquid_phase[
+                    MPL::PropertyType::thermal_diffusion_enhancement_factor]
+                    .template value<double>(variables, x_position, t, dt);
+
+            variables[static_cast<int>(MPL::Variable::porosity)] = phi;
+            double const D_v =
+                liquid_phase[MPL::PropertyType::vapour_diffusion]
+                    .template value<double>(variables, x_position, t, dt);
+
+            double const f_Tv_D_Tv = f_Tv * D_v * drho_wv_dT;
+            double const D_pv = D_v * drho_wv_dp;
+
+            if (gas_phase &&
+                gas_phase->hasProperty(MPL::PropertyType::heat_capacity))
+            {
+                GlobalDimVectorType const grad_T = dNdx * T;
+                // Vapour velocity
+                GlobalDimVectorType const q_v =
+                    -(f_Tv_D_Tv * grad_T - D_pv * grad_p_cap) / rho_LR;
+                double const specific_heat_capacity_vapour =
+                    gas_phase->property(MaterialPropertyLib::PropertyType::
+                                       specific_heat_capacity)
+                        .template value<double>(variables, x_position, t, dt);
+
+                M_TT.noalias() +=
+                    w *
+                    (rho_wv * specific_heat_capacity_vapour * (1 - S_L) * phi) *
+                    N.transpose() * N;
+
+                K_TT.noalias() += N.transpose() * q_v.transpose() * dNdx *
+                                  rho_wv * specific_heat_capacity_vapour * w;
+            }
+
+            double const storage_coefficient_by_water_vapor =
+                phi * (rho_wv * dS_L_dp_cap + (1 - S_L) * drho_wv_dp);
+
+            storage_p_a_p.noalias() +=
+                N.transpose() * storage_coefficient_by_water_vapor * N * w;
+
+            double const vapor_expansion_factor = phi * (1 - S_L) * drho_wv_dT;
+            M_pT.noalias() += N.transpose() * vapor_expansion_factor * N * w;
+
+            local_Jac
+                .template block<pressure_size, temperature_size>(
+                    pressure_index, temperature_index)
+                .noalias() += dNdx.transpose() * f_Tv_D_Tv * dNdx * w;
+
+            local_rhs.template segment<pressure_size>(pressure_index)
+                .noalias() -= f_Tv_D_Tv * dNdx.transpose() * (dNdx * T) * w;
+
+            laplace_p.noalias() += dNdx.transpose() * D_pv * dNdx * w;
+
+            //
+            // Latent heat term
+            //
+            if (liquid_phase.hasProperty(MPL::PropertyType::latent_heat))
+            {
+                double const factor = phi * (1 - S_L) / rho_LR;
+                // The volumetric latent heat of vaporization of liquid water
+                double const L0 =
+                    liquid_phase[MPL::PropertyType::latent_heat]
+                        .template value<double>(variables, x_position, t, dt) *
+                    rho_LR;
+
+                double const drho_LR_dT =
+                    liquid_phase[MPL::PropertyType::density]
+                        .template dValue<double>(variables,
+                                                 MPL::Variable::temperature,
+                                                 x_position, t, dt);
+
+                double const rho_wv_over_rho_L = rho_wv / rho_LR;
+                M_TT.noalias() +=
+                    factor * L0 *
+                    (drho_wv_dT - rho_wv_over_rho_L * drho_LR_dT) *
+                    N.transpose() * N * w;
+
+                M_Tp.noalias() +=
+                    (factor * L0 *
+                         (drho_wv_dp - rho_wv_over_rho_L * drho_LR_dp) +
+                     L0 * phi * rho_wv_over_rho_L * dS_L_dp_cap) *
+                    N.transpose() * N * w;
+
+                // temperature equation, temperature part
+                K_TT.noalias() +=
+                    L0 * f_Tv_D_Tv * dNdx.transpose() * dNdx * w / rho_LR;
+                // temperature equation, pressure part
+                K_Tp.noalias() +=
+                    L0 * D_pv * dNdx.transpose() * dNdx * w / rho_LR;
+            }
+        }
+    }
+
+    if (_process_data.apply_mass_lumping)
+    {
+        storage_p_a_p = storage_p_a_p.colwise().sum().eval().asDiagonal();
+        storage_p_a_S = storage_p_a_S.colwise().sum().eval().asDiagonal();
+        storage_p_a_S_Jpp =
+            storage_p_a_S_Jpp.colwise().sum().eval().asDiagonal();
+    }
+
+    //
+    // -- Jacobian
+    //
+    // temperature equation.
+    local_Jac
+        .template block<temperature_size, temperature_size>(temperature_index,
+                                                            temperature_index)
+        .noalias() += M_TT / dt + K_TT;
+    // temperature equation, pressure part
+    local_Jac
+        .template block<temperature_size, pressure_size>(temperature_index,
+                                                         pressure_index)
+        .noalias() += K_Tp;
+
+    // pressure equation, pressure part.
+    local_Jac
+        .template block<pressure_size, pressure_size>(pressure_index,
+                                                      pressure_index)
+        .noalias() += laplace_p + storage_p_a_p / dt + storage_p_a_S_Jpp;
+
+    // pressure equation, temperature part (contributed by thermal expansion).
+    local_Jac
+        .template block<pressure_size, temperature_size>(pressure_index,
+                                                         temperature_index)
+        .noalias() += M_pT / dt;
+
+    //
+    // -- Residual
+    //
+    // temperature equation
+    local_rhs.template segment<temperature_size>(temperature_index).noalias() -=
+        M_TT * T_dot + K_TT * T;
+
+    // pressure equation
+    local_rhs.template segment<pressure_size>(pressure_index).noalias() -=
+        laplace_p * p_L + (storage_p_a_p + storage_p_a_S) * p_L_dot +
+        M_pT * T_dot;
+    if (liquid_phase.hasProperty(MPL::PropertyType::vapour_diffusion) &&
+        liquid_phase.hasProperty(MPL::PropertyType::latent_heat))
+    {
+        // Jacobian: temperature equation, pressure part
+        local_Jac
+            .template block<temperature_size, pressure_size>(temperature_index,
+                                                             pressure_index)
+            .noalias() += M_Tp / dt;
+        // RHS: temperature part
+        local_rhs.template segment<temperature_size>(temperature_index)
+            .noalias() -= M_Tp * p_L_dot;
+    }
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+void ThermoRichardsFlowLocalAssembler<
+    ShapeFunction, IntegrationMethod,
+    GlobalDim>::assemble(double const t, double const dt,
+                                     std::vector<double> const& local_x,
+                                     std::vector<double> const& local_xdot,
+                                     std::vector<double>& local_M_data,
+                                     std::vector<double>& local_K_data,
+                                     std::vector<double>& local_rhs_data)
+{
+    auto const local_matrix_dim = pressure_size + temperature_size;
+    assert(local_x.size() == local_matrix_dim);
+
+    auto const T = Eigen::Map<typename ShapeMatricesType::template VectorType<
+        temperature_size> const>(local_x.data() + temperature_index,
+                                 temperature_size);
+    auto const p_L = Eigen::Map<
+        typename ShapeMatricesType::template VectorType<pressure_size> const>(
+        local_x.data() + pressure_index, pressure_size);
+
+    auto const T_dot =
+        Eigen::Map<typename ShapeMatricesType::template VectorType<
+            temperature_size> const>(local_xdot.data() + temperature_index,
+                                     temperature_size);
+    auto const p_L_dot = Eigen::Map<
+        typename ShapeMatricesType::template VectorType<pressure_size> const>(
+        local_xdot.data() + pressure_index, pressure_size);
+
+    auto local_K = MathLib::createZeroedMatrix<
+        typename ShapeMatricesType::template MatrixType<
+            local_matrix_dim, local_matrix_dim>>(
+        local_K_data, local_matrix_dim, local_matrix_dim);
+
+    auto local_M = MathLib::createZeroedMatrix<
+        typename ShapeMatricesType::template MatrixType<
+            local_matrix_dim, local_matrix_dim>>(
+        local_M_data, local_matrix_dim, local_matrix_dim);
+
+    auto local_rhs = MathLib::createZeroedVector<
+        typename ShapeMatricesType::template VectorType<local_matrix_dim>>(
+        local_rhs_data, local_matrix_dim);
+
+    auto const& medium = *_process_data.media_map->getMedium(_element.getID());
+    auto const& liquid_phase = medium.phase("AqueousLiquid");
+    auto const& solid_phase = medium.phase("Solid");
+    MPL::Phase const* gas_phase = medium.hasPhase("Gas") ?
+        &medium.phase("Gas") : nullptr;
+    MPL::VariableArray variables;
+    MPL::VariableArray variables_prev;
+
+    ParameterLib::SpatialPosition x_position;
+    x_position.setElementID(_element.getID());
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        x_position.setIntegrationPoint(ip);
+        auto const& w = _ip_data[ip].integration_weight;
+
+        auto const& N = _ip_data[ip].N;
+        auto const& dNdx = _ip_data[ip].dNdx;
+
+        double T_ip;
+        NumLib::shapeFunctionInterpolate(T, N, T_ip);
+        double T_dot_ip;
+        NumLib::shapeFunctionInterpolate(T_dot, N, T_dot_ip);
+
+        double p_cap_ip;
+        NumLib::shapeFunctionInterpolate(-p_L, N, p_cap_ip);
+
+        double p_cap_dot_ip;
+        NumLib::shapeFunctionInterpolate(-p_L_dot, N, p_cap_dot_ip);
+
+        variables[static_cast<int>(MPL::Variable::capillary_pressure)] =
+            p_cap_ip;
+        variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip;
+        variables[static_cast<int>(MPL::Variable::temperature)] = T_ip;
+
+        auto& S_L = _ip_data[ip].saturation;
+        auto const S_L_prev = _ip_data[ip].saturation_prev;
+        auto const alpha =
+            medium[MPL::PropertyType::biot_coefficient]
+                .template value<double>(variables, x_position, t, dt);
+
+        auto& solid_elasticity = *_process_data.simplified_elasticity;
+        // TODO (buchwaldj)
+        // is bulk_modulus good name for bulk modulus of solid skeleton?
+        auto const beta_S =
+            solid_elasticity.bulkCompressibilityFromYoungsModulus(
+                solid_phase, variables, x_position, t, dt);
+        auto const beta_SR = (1 - alpha) * beta_S;
+        variables[static_cast<int>(MPL::Variable::grain_compressibility)] =
+            beta_SR;
+
+        auto const rho_LR =
+            liquid_phase[MPL::PropertyType::density]
+                .template value<double>(variables, x_position, t, dt);
+        auto const& b = _process_data.specific_body_force;
+
+        double const drho_LR_dp =
+            liquid_phase[MPL::PropertyType::density]
+                .template dValue<double>(variables,
+                                         MPL::Variable::phase_pressure,
+                                         x_position, t, dt);
+        auto const beta_LR = drho_LR_dp / rho_LR;
+
+        S_L = medium[MPL::PropertyType::saturation]
+                  .template value<double>(variables, x_position, t, dt);
+        variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L;
+        variables_prev[static_cast<int>(MPL::Variable::liquid_saturation)] =
+            S_L_prev;
+
+        // tangent derivative for Jacobian
+        double const dS_L_dp_cap = medium[MPL::PropertyType::saturation]
+                .template dValue<double>(variables,
+                                         MPL::Variable::capillary_pressure,
+                                         x_position, t, dt);
+        // secant derivative from time discretization for storage
+        // use tangent, if secant is not available
+        double const DeltaS_L_Deltap_cap =
+            (p_cap_dot_ip == 0) ? dS_L_dp_cap
+                                : (S_L - S_L_prev) / (dt * p_cap_dot_ip);
+
+        auto chi_S_L = S_L;
+        auto chi_S_L_prev = S_L_prev;
+        if (medium.hasProperty(MPL::PropertyType::bishops_effective_stress))
+        {
+            auto const chi = [&medium, x_position, t, dt](double const S_L) {
+                MPL::VariableArray variables;
+                variables[static_cast<int>(MPL::Variable::liquid_saturation)] =
+                    S_L;
+                return medium[MPL::PropertyType::bishops_effective_stress]
+                    .template value<double>(variables, x_position, t, dt);
+            };
+            chi_S_L = chi(S_L);
+            chi_S_L_prev = chi(S_L_prev);
+
+        }
+        // TODO (buchwaldj)
+        // should solid_grain_pressure or effective_pore_pressure remain?
+        // double const p_FR = -chi_S_L * p_cap_ip;
+        // variables[static_cast<int>(MPL::Variable::solid_grain_pressure)] =
+        // p_FR;
+
+        variables[static_cast<int>(MPL::Variable::effective_pore_pressure)] =
+            -chi_S_L * p_cap_ip;
+        variables_prev[static_cast<int>(
+            MPL::Variable::effective_pore_pressure)] =
+            -chi_S_L_prev * (p_cap_ip - p_cap_dot_ip * dt);
+
+        auto& phi = _ip_data[ip].porosity;
+        {  // Porosity update
+
+            variables_prev[static_cast<int>(MPL::Variable::porosity)] =
+                _ip_data[ip].porosity_prev;
+            phi = medium[MPL::PropertyType::porosity]
+                      .template value<double>(variables, variables_prev,
+                                              x_position, t, dt);
+            variables[static_cast<int>(MPL::Variable::porosity)] = phi;
+        }
+
+        if (alpha < phi)
+        {
+            OGS_FATAL(
+                "ThermoRichardsFlow: Biot-coefficient {} is smaller than "
+                "porosity {} in element/integration point {}/{}.",
+                alpha, phi, _element.getID(), ip);
+        }
+
+        double const k_rel =
+            medium[MPL::PropertyType::relative_permeability]
+                .template value<double>(variables, x_position, t, dt);
+        auto const mu =
+            liquid_phase[MPL::PropertyType::viscosity]
+                .template value<double>(variables, x_position, t, dt);
+
+        auto const K_intrinsic = MPL::formEigenTensor<GlobalDim>(
+            medium[MPL::PropertyType::permeability]
+                .value(variables, x_position, t, dt));
+
+        GlobalDimMatrixType const Ki_over_mu = K_intrinsic / mu;
+        GlobalDimMatrixType const rho_Ki_over_mu = rho_LR * Ki_over_mu;
+
+        // Consider anisotropic thermal expansion.
+        // Read in 3x3 tensor. 2D case also requires expansion coeff. for z-
+        // component.
+        Eigen::Matrix<double, 3,
+                      3> const solid_linear_thermal_expansion_coefficient =
+            MaterialPropertyLib::formEigenTensor<3>(
+                solid_phase[
+                        MaterialPropertyLib::PropertyType::thermal_expansivity]
+                    .value(variables, x_position, t, dt));
+
+        auto const rho_SR =
+            solid_phase[MPL::PropertyType::density]
+                .template value<double>(variables, x_position, t, dt);
+
+        //
+        // pressure equation, pressure part.
+        //
+        local_K.template block<pressure_size, pressure_size>(pressure_index,
+                                                       pressure_index)
+            .noalias() += dNdx.transpose() * k_rel * rho_Ki_over_mu * dNdx * w;
+
+        const double alphaB_minus_phi = alpha - phi;
+        double const a0 = alphaB_minus_phi * beta_SR;
+        double const specific_storage_a_p =
+            S_L * (phi * beta_LR + S_L * a0 +
+                   chi_S_L * alpha * alpha *
+                       solid_elasticity.storageContribution(
+                           solid_phase, variables, x_position, t, dt));
+        double const specific_storage_a_S = phi - p_cap_ip * S_L * a0;
+
+
+        local_M.template block<pressure_size, pressure_size>(pressure_index,
+                                                       pressure_index)
+            .noalias() += N.transpose() * rho_LR * (specific_storage_a_p -
+                    specific_storage_a_S * DeltaS_L_Deltap_cap) * N * w;
+
+
+        local_rhs.template segment<pressure_size>(pressure_index).noalias() +=
+            dNdx.transpose() * rho_LR * k_rel * rho_Ki_over_mu * b * w;
+
+        //
+        // pressure equation, temperature part.
+        //
+        double const fluid_volumetric_thermal_expansion_coefficient =
+            MPL::getLiquidThermalExpansivity(liquid_phase, variables, rho_LR,
+                                             x_position, t, dt);
+        const double eff_thermal_expansion =
+            S_L * (alphaB_minus_phi *
+                       solid_linear_thermal_expansion_coefficient.trace() +
+                   phi * fluid_volumetric_thermal_expansion_coefficient +
+                   alpha * solid_elasticity.thermalExpansivityContribution(
+                               solid_linear_thermal_expansion_coefficient,
+                               solid_phase, variables, x_position, t, dt));
+
+        local_M.template block<pressure_size, temperature_size>(pressure_index,
+                                                       temperature_index)
+            .noalias() -=
+            N.transpose() * rho_LR * eff_thermal_expansion * N * w;
+
+        //
+        // temperature equation.
+        //
+        {
+            auto const specific_heat_capacity_fluid =
+                liquid_phase[MaterialPropertyLib::specific_heat_capacity]
+                    .template value<double>(variables, x_position, t, dt);
+
+            auto const specific_heat_capacity_solid =
+                solid_phase[MaterialPropertyLib::PropertyType::
+                                  specific_heat_capacity]
+                    .template value<double>(variables, x_position, t, dt);
+
+            local_M.template block<temperature_size, temperature_size>(temperature_index,
+                                                       temperature_index)
+            .noalias() +=
+                w *
+                (rho_SR * specific_heat_capacity_solid * (1 - phi) +
+                 (S_L * rho_LR * specific_heat_capacity_fluid) * phi) *
+                N.transpose() * N;
+
+            auto const thermal_conductivity =
+                        MaterialPropertyLib::formEigenTensor<GlobalDim>(
+                            medium[MaterialPropertyLib::PropertyType::
+                                               thermal_conductivity]
+                                .value(variables, x_position, t, dt));
+
+            GlobalDimVectorType const velocity_L =
+                GlobalDimVectorType(-Ki_over_mu * (dNdx * p_L - rho_LR * b));
+
+            local_K.template block<temperature_size, temperature_size>(temperature_index,
+                                                       temperature_index)
+            .noalias() +=
+                (dNdx.transpose() * thermal_conductivity * dNdx +
+                 N.transpose() * velocity_L.transpose() * dNdx * rho_LR *
+                     specific_heat_capacity_fluid) *
+                w;
+
+            //
+            // temperature equation, pressure part
+            //
+            local_K.template block<temperature_size, pressure_size>(temperature_index,
+                                                       pressure_index)
+                .noalias() -=
+                rho_LR * specific_heat_capacity_fluid *
+                              N.transpose() * (dNdx * T).transpose() *
+                              Ki_over_mu * dNdx * w;
+        }
+        if (liquid_phase.hasProperty(MPL::PropertyType::vapour_diffusion) &&
+            S_L < 1.0)
+        {
+            variables[static_cast<int>(MPL::Variable::density)] = rho_LR;
+
+            double const rho_wv =
+                liquid_phase[MaterialPropertyLib::vapour_density]
+                    .template value<double>(variables, x_position, t, dt);
+
+            double const drho_wv_dT =
+                liquid_phase[MaterialPropertyLib::vapour_density]
+                    .template dValue<double>(variables,
+                                             MPL::Variable::temperature,
+                                             x_position, t, dt);
+            double const drho_wv_dp =
+                liquid_phase[MaterialPropertyLib::vapour_density]
+                    .template dValue<double>(variables,
+                                             MPL::Variable::phase_pressure,
+                                             x_position, t, dt);
+            auto const f_Tv =
+                liquid_phase[
+                        MPL::PropertyType::thermal_diffusion_enhancement_factor]
+                    .template value<double>(variables, x_position, t, dt);
+
+            variables[static_cast<int>(MPL::Variable::porosity)] = phi;
+            double const D_v =
+                liquid_phase[MPL::PropertyType::vapour_diffusion]
+                    .template value<double>(variables, x_position, t, dt);
+
+            double const f_Tv_D_Tv = f_Tv * D_v * drho_wv_dT;
+            double const D_pv = D_v * drho_wv_dp;
+
+            if (gas_phase &&
+                gas_phase->hasProperty(MPL::PropertyType::heat_capacity))
+            {
+                GlobalDimVectorType const grad_T = dNdx * T;
+                GlobalDimVectorType const grad_p_cap = -dNdx * p_L;
+                // Vapour velocity
+                GlobalDimVectorType const q_v =
+                    -(f_Tv_D_Tv * grad_T - D_pv * grad_p_cap) / rho_LR;
+                double const specific_heat_capacity_vapour =
+                    gas_phase->property(
+                        MaterialPropertyLib::PropertyType::
+                                       specific_heat_capacity)
+                        .template value<double>(variables, x_position, t, dt);
+
+                local_M.template block<temperature_size, temperature_size>(temperature_index,
+                                                       temperature_index)
+                .noalias() +=
+                    w *
+                    (rho_wv * specific_heat_capacity_vapour * (1 - S_L) * phi) *
+                    N.transpose() * N;
+
+                local_K.template block<temperature_size, temperature_size>(temperature_index,
+                                                       temperature_index)
+                .noalias() += N.transpose() * q_v.transpose() * dNdx *
+                                  rho_wv * specific_heat_capacity_vapour * w;
+            }
+
+            double const storage_coefficient_by_water_vapor =
+                phi * (rho_wv * dS_L_dp_cap + (1 - S_L) * drho_wv_dp);
+            local_M.template block<pressure_size, pressure_size>(pressure_index,
+                                                       pressure_index)
+            .noalias() +=
+                N.transpose() * storage_coefficient_by_water_vapor * N * w;
+
+            double const vapor_expansion_factor = phi * (1 - S_L) * drho_wv_dT;
+            local_M.template block<pressure_size, temperature_size>(pressure_index,
+                                                       temperature_index)
+            .noalias() += N.transpose() * vapor_expansion_factor * N * w;
+
+            local_rhs.template segment<pressure_size>(pressure_index)
+                .noalias() -= f_Tv_D_Tv * dNdx.transpose() * (dNdx * T) * w;
+
+            local_K.template block<pressure_size, pressure_size>(pressure_index,
+                                                       pressure_index)
+            .noalias() += dNdx.transpose() * D_pv * dNdx * w;
+
+            //
+            // Latent heat term
+            //
+            if (liquid_phase.hasProperty(MPL::PropertyType::latent_heat))
+            {
+                double const factor = phi * (1 - S_L) / rho_LR;
+                // The volumetric latent heat of vaporization of liquid water
+                double const L0 =
+                    liquid_phase[MPL::PropertyType::latent_heat]
+                        .template value<double>(variables, x_position, t, dt) *
+                    rho_LR;
+
+                double const drho_LR_dT =
+                    liquid_phase[MPL::PropertyType::density]
+                        .template dValue<double>(variables,
+                                                 MPL::Variable::temperature,
+                                                 x_position, t, dt);
+
+                double const rho_wv_over_rho_L = rho_wv / rho_LR;
+                local_M.template block<temperature_size, temperature_size>(temperature_index,
+                                                       temperature_index)
+                .noalias() +=
+                    factor * L0 *
+                    (drho_wv_dT - rho_wv_over_rho_L * drho_LR_dT) *
+                    N.transpose() * N * w;
+
+                local_M.template block<temperature_size, pressure_size>(temperature_index,
+                                                       pressure_index)
+                .noalias() +=
+                    (factor * L0 *
+                         (drho_wv_dp - rho_wv_over_rho_L * drho_LR_dp) +
+                     L0 * phi * rho_wv_over_rho_L * dS_L_dp_cap) *
+                    N.transpose() * N * w;
+
+                // temperature equation, temperature part
+                local_K.template block<temperature_size, temperature_size>(temperature_index,
+                                                       temperature_index)
+                .noalias() +=
+                    L0 * f_Tv_D_Tv * dNdx.transpose() * dNdx * w / rho_LR;
+                // temperature equation, pressure part
+                local_K.template block<temperature_size, pressure_size>(temperature_index,
+                                                       pressure_index)
+                .noalias() +=
+                    L0 * D_pv * dNdx.transpose() * dNdx * w / rho_LR;
+            }
+        }
+    }
+
+    if (_process_data.apply_mass_lumping)
+    {
+        auto Mpp = local_M.template block<pressure_size, pressure_size>(
+            pressure_index, pressure_index);
+        Mpp = Mpp.colwise().sum().eval().asDiagonal();
+    }
+
+
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::vector<double> const&
+ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod, GlobalDim>::
+    getIntPtDarcyVelocity(
+        const double /*t*/,
+        std::vector<GlobalVector*> const& /*x*/,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/,
+        std::vector<double>& cache) const
+{
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    cache.clear();
+    auto cache_matrix = MathLib::createZeroedMatrix<
+        Eigen::Matrix<double, GlobalDim, Eigen::Dynamic, Eigen::RowMajor>>(
+        cache, GlobalDim, n_integration_points);
+
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        cache_matrix.col(ip).noalias() = _ip_data[ip].v_darcy;
+    }
+
+    return cache;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::vector<double> ThermoRichardsFlowLocalAssembler<
+    ShapeFunction, IntegrationMethod, GlobalDim>::getSaturation() const
+{
+    std::vector<double> result;
+    getIntPtSaturation(0, {}, {}, result);
+    return result;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::vector<double> const&
+ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod, GlobalDim>::
+    getIntPtSaturation(
+        const double /*t*/,
+        std::vector<GlobalVector*> const& /*x*/,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/,
+        std::vector<double>& cache) const
+{
+    return ProcessLib::getIntegrationPointScalarData(
+        _ip_data, &IpData::saturation, cache);
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::vector<double> ThermoRichardsFlowLocalAssembler<
+    ShapeFunction, IntegrationMethod, GlobalDim>::getPorosity() const
+{
+    std::vector<double> result;
+    getIntPtPorosity(0, {}, {}, result);
+    return result;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::vector<double> const&
+ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod, GlobalDim>::
+    getIntPtPorosity(
+        const double /*t*/,
+        std::vector<GlobalVector*> const& /*x*/,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/,
+        std::vector<double>& cache) const
+{
+    return ProcessLib::getIntegrationPointScalarData(_ip_data,
+                                                     &IpData::porosity, cache);
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+std::vector<double> const&
+ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod, GlobalDim>::
+    getIntPtDryDensitySolid(
+        const double /*t*/,
+        std::vector<GlobalVector*> const& /*x*/,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/,
+        std::vector<double>& cache) const
+{
+    return ProcessLib::getIntegrationPointScalarData(
+        _ip_data, &IpData::dry_density_solid, cache);
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+void ThermoRichardsFlowLocalAssembler<ShapeFunction, IntegrationMethod,
+                                      GlobalDim>::
+    computeSecondaryVariableConcrete(double const t, double const dt,
+                                     Eigen::VectorXd const& local_x,
+                                     Eigen::VectorXd const& local_x_dot)
+{
+    auto const T =
+        local_x.template segment<temperature_size>(temperature_index);
+
+    auto const T_dot =
+        local_x_dot.template segment<temperature_size>(temperature_index);
+
+    auto const p_L = local_x.template segment<pressure_size>(pressure_index);
+
+    auto p_L_dot = local_x_dot.template segment<pressure_size>(pressure_index);
+
+    auto const& medium = *_process_data.media_map->getMedium(_element.getID());
+    auto const& liquid_phase = medium.phase("AqueousLiquid");
+    auto const& solid_phase = medium.phase("Solid");
+    MPL::VariableArray variables;
+    MPL::VariableArray variables_prev;
+
+    ParameterLib::SpatialPosition x_position;
+    x_position.setElementID(_element.getID());
+
+    unsigned const n_integration_points =
+        _integration_method.getNumberOfPoints();
+
+    double saturation_avg = 0;
+    double porosity_avg = 0;
+
+    for (unsigned ip = 0; ip < n_integration_points; ip++)
+    {
+        x_position.setIntegrationPoint(ip);
+        auto const& N = _ip_data[ip].N;
+
+        double T_ip;
+        NumLib::shapeFunctionInterpolate(T, N, T_ip);
+        double T_dot_ip;
+        NumLib::shapeFunctionInterpolate(T_dot, N, T_dot_ip);
+
+        double p_cap_ip;
+        NumLib::shapeFunctionInterpolate(-p_L, N, p_cap_ip);
+
+        double p_cap_dot_ip;
+        NumLib::shapeFunctionInterpolate(-p_L_dot, N, p_cap_dot_ip);
+
+        variables[static_cast<int>(MPL::Variable::capillary_pressure)] =
+            p_cap_ip;
+        variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip;
+
+        variables[static_cast<int>(MPL::Variable::temperature)] = T_ip;
+
+        auto& S_L = _ip_data[ip].saturation;
+        auto const S_L_prev = _ip_data[ip].saturation_prev;
+        S_L = medium[MPL::PropertyType::saturation]
+                  .template value<double>(variables, x_position, t, dt);
+        variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L;
+        variables_prev[static_cast<int>(MPL::Variable::liquid_saturation)] =
+            S_L_prev;
+
+        auto chi_S_L = S_L;
+        auto chi_S_L_prev = S_L_prev;
+        if (medium.hasProperty(MPL::PropertyType::bishops_effective_stress))
+        {
+            auto const chi = [&medium, x_position, t, dt](double const S_L) {
+                MPL::VariableArray variables;
+                variables[static_cast<int>(MPL::Variable::liquid_saturation)] =
+                    S_L;
+                return medium[MPL::PropertyType::bishops_effective_stress]
+                    .template value<double>(variables, x_position, t, dt);
+            };
+            chi_S_L = chi(S_L);
+            chi_S_L_prev = chi(S_L_prev);
+        }
+        variables[static_cast<int>(MPL::Variable::effective_pore_pressure)] =
+            -chi_S_L * p_cap_ip;
+        variables_prev[static_cast<int>(
+            MPL::Variable::effective_pore_pressure)] =
+            -chi_S_L_prev * (p_cap_ip - p_cap_dot_ip * dt);
+
+        auto const alpha =
+            medium[MPL::PropertyType::biot_coefficient]
+                .template value<double>(variables, x_position, t, dt);
+
+        auto& solid_elasticity = *_process_data.simplified_elasticity;
+        auto const beta_S =
+            solid_elasticity.bulkCompressibilityFromYoungsModulus(
+                solid_phase, variables, x_position, t, dt);
+        auto const beta_SR = (1 - alpha) * beta_S;
+        variables[static_cast<int>(MPL::Variable::grain_compressibility)] =
+            beta_SR;
+
+        auto& phi = _ip_data[ip].porosity;
+        {  // Porosity update
+            variables_prev[static_cast<int>(MPL::Variable::porosity)] =
+                _ip_data[ip].porosity_prev;
+            phi = medium[MPL::PropertyType::porosity]
+                      .template value<double>(variables, variables_prev,
+                                              x_position, t, dt);
+            variables[static_cast<int>(MPL::Variable::porosity)] = phi;
+        }
+
+        auto const mu =
+            liquid_phase[MPL::PropertyType::viscosity]
+                .template value<double>(variables, x_position, t, dt);
+        auto const rho_LR =
+            liquid_phase[MPL::PropertyType::density]
+                .template value<double>(variables, x_position, t, dt);
+
+        auto const K_intrinsic = MPL::formEigenTensor<GlobalDim>(
+            medium[MPL::PropertyType::permeability]
+                .value(variables, x_position, t, dt));
+
+        double const k_rel =
+            medium[MPL::PropertyType::relative_permeability]
+                .template value<double>(variables, x_position, t, dt);
+
+        GlobalDimMatrixType const K_over_mu = k_rel * K_intrinsic / mu;
+
+        auto const rho_SR =
+            solid_phase[MPL::PropertyType::density]
+                .template value<double>(variables, x_position, t, dt);
+        _ip_data[ip].dry_density_solid = (1 - phi) * rho_SR;
+
+        auto const& b = _process_data.specific_body_force;
+
+        // Compute the velocity
+        auto const& dNdx = _ip_data[ip].dNdx;
+        _ip_data[ip].v_darcy.noalias() =
+            -K_over_mu * dNdx * p_L + rho_LR * K_over_mu * b;
+
+        saturation_avg += S_L;
+        porosity_avg += phi;
+    }
+    saturation_avg /= n_integration_points;
+    porosity_avg /= n_integration_points;
+
+    (*_process_data.element_saturation)[_element.getID()] = saturation_avg;
+    (*_process_data.element_porosity)[_element.getID()] = porosity_avg;
+}
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+unsigned ThermoRichardsFlowLocalAssembler<
+    ShapeFunction, IntegrationMethod, GlobalDim>::getNumberOfIntegrationPoints()
+    const
+{
+    return _integration_method.getNumberOfPoints();
+}
+
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM.h b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM.h
new file mode 100644
index 00000000000..9330d99849b
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowFEM.h
@@ -0,0 +1,172 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "IntegrationPointData.h"
+#include "LocalAssemblerInterface.h"
+#include "MaterialLib/SolidModels/LinearElasticIsotropic.h"
+#include "NumLib/DOF/DOFTableUtil.h"
+#include "NumLib/Fem/InitShapeMatrices.h"
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "ParameterLib/Parameter.h"
+#include "ProcessLib/Deformation/BMatrixPolicy.h"
+#include "ProcessLib/Deformation/LinearBMatrix.h"
+#include "ProcessLib/LocalAssemblerTraits.h"
+#include "ThermoRichardsFlowProcessData.h"
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+namespace MPL = MaterialPropertyLib;
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+class ThermoRichardsFlowLocalAssembler : public LocalAssemblerInterface
+{
+public:
+    // Note: temperature variable uses the same shape functions as that are used
+    // by pressure variable.
+    using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, GlobalDim>;
+
+    using GlobalDimMatrixType = typename ShapeMatricesType::GlobalDimMatrixType;
+    using GlobalDimVectorType = typename ShapeMatricesType::GlobalDimVectorType;
+
+    using IpData = IntegrationPointData<ShapeMatricesType>;
+
+    ThermoRichardsFlowLocalAssembler(ThermoRichardsFlowLocalAssembler const&) =
+        delete;
+    ThermoRichardsFlowLocalAssembler(ThermoRichardsFlowLocalAssembler&&) =
+        delete;
+
+    ThermoRichardsFlowLocalAssembler(
+        MeshLib::Element const& e,
+        std::size_t const /*local_matrix_size*/,
+        bool const is_axially_symmetric,
+        unsigned const integration_order,
+        ThermoRichardsFlowProcessData& process_data);
+
+    /// \return the number of read integration points.
+    std::size_t setIPDataInitialConditions(
+        std::string const& name,
+        double const* values,
+        int const integration_order) override;
+
+    void setInitialConditionsConcrete(std::vector<double> const& local_x,
+                                      double const t,
+                                      bool const /*use_monolithic_scheme*/,
+                                      int const /*process_id*/) override;
+
+    void assembleWithJacobian(double const t, double const dt,
+                              std::vector<double> const& local_x,
+                              std::vector<double> const& local_xdot,
+                              const double /*dxdot_dx*/, const double /*dx_dx*/,
+                              std::vector<double>& /*local_M_data*/,
+                              std::vector<double>& /*local_K_data*/,
+                              std::vector<double>& local_rhs_data,
+                              std::vector<double>& local_Jac_data) override;
+
+    void assemble(double const t, double const dt,
+                              std::vector<double> const& local_x,
+                              std::vector<double> const& local_xdot,
+                              std::vector<double>& local_M_data,
+                              std::vector<double>& local_K_data,
+                              std::vector<double>& local_rhs_data) override;
+
+    void initializeConcrete() override
+    {
+        unsigned const n_integration_points =
+            _integration_method.getNumberOfPoints();
+
+        for (unsigned ip = 0; ip < n_integration_points; ip++)
+        {
+            auto& ip_data = _ip_data[ip];
+            ip_data.pushBackState();
+        }
+    }
+
+    void postTimestepConcrete(Eigen::VectorXd const& /*local_x*/,
+                              double const /*t*/,
+                              double const /*dt*/) override
+    {
+        unsigned const n_integration_points =
+            _integration_method.getNumberOfPoints();
+
+        for (unsigned ip = 0; ip < n_integration_points; ip++)
+        {
+            _ip_data[ip].pushBackState();
+        }
+    }
+
+    void computeSecondaryVariableConcrete(
+        double const t, double const dt, Eigen::VectorXd const& local_x,
+        Eigen::VectorXd const& local_x_dot) override;
+
+    Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        const unsigned integration_point) const override
+    {
+        auto const& N = _ip_data[integration_point].N;
+
+        // assumes N is stored contiguously in memory
+        return Eigen::Map<const Eigen::RowVectorXd>(N.data(), N.size());
+    }
+
+    std::vector<double> const& getIntPtDarcyVelocity(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const override;
+
+    std::vector<double> getSaturation() const override;
+    std::vector<double> const& getIntPtSaturation(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const override;
+
+    std::vector<double> getPorosity() const override;
+    std::vector<double> const& getIntPtPorosity(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const override;
+
+    std::vector<double> const& getIntPtDryDensitySolid(
+        const double t,
+        std::vector<GlobalVector*> const& x,
+        std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
+        std::vector<double>& cache) const override;
+
+private:
+    unsigned getNumberOfIntegrationPoints() const override;
+
+private:
+    ThermoRichardsFlowProcessData& _process_data;
+
+    std::vector<IpData, Eigen::aligned_allocator<IpData>> _ip_data;
+
+    IntegrationMethod _integration_method;
+    MeshLib::Element const& _element;
+    bool const _is_axially_symmetric;
+
+    static const int temperature_index = 0;
+    static const int temperature_size = ShapeFunction::NPOINTS;
+    static const int pressure_index = temperature_size;
+    static const int pressure_size = ShapeFunction::NPOINTS;
+};
+
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
+
+#include "ThermoRichardsFlowFEM-impl.h"
diff --git a/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.cpp b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.cpp
new file mode 100644
index 00000000000..7aca7870df0
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.cpp
@@ -0,0 +1,305 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, 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 "ThermoRichardsFlowProcess.h"
+
+#include <cassert>
+
+#include "BaseLib/Error.h"
+#include "MeshLib/Elements/Utils.h"
+#include "NumLib/DOF/ComputeSparsityPattern.h"
+#include "ProcessLib/Process.h"
+#include "ProcessLib/Utils/CreateLocalAssemblers.h"
+#include "ThermoRichardsFlowFEM.h"
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+ThermoRichardsFlowProcess::ThermoRichardsFlowProcess(
+    std::string name,
+    MeshLib::Mesh& mesh,
+    std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&& jacobian_assembler,
+    std::vector<std::unique_ptr<ParameterLib::ParameterBase>> const& parameters,
+    unsigned const integration_order,
+    std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>&&
+        process_variables,
+    ThermoRichardsFlowProcessData&& process_data,
+    SecondaryVariableCollection&& secondary_variables,
+    bool const use_monolithic_scheme)
+    : Process(std::move(name), mesh, std::move(jacobian_assembler), parameters,
+              integration_order, std::move(process_variables),
+              std::move(secondary_variables), use_monolithic_scheme),
+      _process_data(std::move(process_data))
+{
+    _heat_flux = MeshLib::getOrCreateMeshProperty<double>(
+        mesh, "HeatFlux", MeshLib::MeshItemType::Node, 1);
+
+    _hydraulic_flow = MeshLib::getOrCreateMeshProperty<double>(
+        mesh, "HydraulicFlow", MeshLib::MeshItemType::Node, 1);
+
+    // TODO (naumov) remove ip suffix. Probably needs modification of the mesh
+    // properties, s.t. there is no "overlapping" with cell/point data.
+    // See getOrCreateMeshProperty.
+    _integration_point_writer.emplace_back(
+        std::make_unique<IntegrationPointWriter>(
+            "saturation_ip", 1 /*n components*/, integration_order, [this]() {
+                // Result containing integration point data for each local
+                // assembler.
+                std::vector<std::vector<double>> result;
+                result.resize(_local_assemblers.size());
+
+                for (std::size_t i = 0; i < _local_assemblers.size(); ++i)
+                {
+                    auto const& local_asm = *_local_assemblers[i];
+                    result[i] = local_asm.getSaturation();
+                }
+
+                return result;
+            }));
+
+    _integration_point_writer.emplace_back(
+        std::make_unique<IntegrationPointWriter>(
+            "porosity_ip", 1 /*n components*/, integration_order, [this]() {
+                // Result containing integration point data for each local
+                // assembler.
+                std::vector<std::vector<double>> result;
+                result.resize(_local_assemblers.size());
+
+                for (std::size_t i = 0; i < _local_assemblers.size(); ++i)
+                {
+                    auto const& local_asm = *_local_assemblers[i];
+                    result[i] = local_asm.getPorosity();
+                }
+
+                return result;
+            }));
+}
+
+void ThermoRichardsFlowProcess::initializeConcreteProcess(
+    NumLib::LocalToGlobalIndexMap const& dof_table,
+    MeshLib::Mesh const& mesh,
+    unsigned const integration_order)
+{
+    using nlohmann::json;
+
+    const int process_id = 0;
+    const int variable_id = 0;
+    ProcessLib::createLocalAssemblers<ThermoRichardsFlowLocalAssembler>(
+        mesh.getDimension(), mesh.getElements(), dof_table,
+        getProcessVariables(process_id)[variable_id]
+            .get()
+            .getShapeFunctionOrder(),
+        _local_assemblers, mesh.isAxiallySymmetric(), integration_order,
+        _process_data);
+
+    auto add_secondary_variable = [&](std::string const& name,
+                                      int const num_components,
+                                      auto get_ip_values_function) {
+        _secondary_variables.addSecondaryVariable(
+            name,
+            makeExtrapolator(num_components, getExtrapolator(),
+                             _local_assemblers,
+                             std::move(get_ip_values_function)));
+    };
+
+    add_secondary_variable("velocity", mesh.getDimension(),
+                           &LocalAssemblerIF::getIntPtDarcyVelocity);
+
+    add_secondary_variable("saturation", 1,
+                           &LocalAssemblerIF::getIntPtSaturation);
+
+    add_secondary_variable("porosity", 1, &LocalAssemblerIF::getIntPtPorosity);
+
+    add_secondary_variable("dry_density_solid", 1,
+                           &LocalAssemblerIF::getIntPtDryDensitySolid);
+
+    _process_data.element_saturation = MeshLib::getOrCreateMeshProperty<double>(
+        const_cast<MeshLib::Mesh&>(mesh), "saturation_avg",
+        MeshLib::MeshItemType::Cell, 1);
+
+    _process_data.element_porosity = MeshLib::getOrCreateMeshProperty<double>(
+        const_cast<MeshLib::Mesh&>(mesh), "porosity_avg",
+        MeshLib::MeshItemType::Cell, 1);
+
+    // Set initial conditions for integration point data.
+    for (auto const& ip_writer : _integration_point_writer)
+    {
+        // Find the mesh property with integration point writer's name.
+        auto const& name = ip_writer->name();
+        if (!mesh.getProperties().existsPropertyVector<double>(name))
+        {
+            continue;
+        }
+        auto const& mesh_property =
+            *mesh.getProperties().template getPropertyVector<double>(name);
+
+        // The mesh property must be defined on integration points.
+        if (mesh_property.getMeshItemType() !=
+            MeshLib::MeshItemType::IntegrationPoint)
+        {
+            continue;
+        }
+
+        auto const ip_meta_data = getIntegrationPointMetaData(mesh, name);
+
+        // Check the number of components.
+        if (ip_meta_data.n_components !=
+            mesh_property.getNumberOfGlobalComponents())
+        {
+            OGS_FATAL(
+                "Different number of components in meta data ({:d}) than in "
+                "the integration point field data for '{:s}': {:d}.",
+                ip_meta_data.n_components, name,
+                mesh_property.getNumberOfGlobalComponents());
+        }
+
+        // Now we have a properly named vtk's field data array and the
+        // corresponding meta data.
+        std::size_t position = 0;
+        for (auto& local_asm : _local_assemblers)
+        {
+            std::size_t const integration_points_read =
+                local_asm->setIPDataInitialConditions(
+                    name, &mesh_property[position],
+                    ip_meta_data.integration_order);
+            if (integration_points_read == 0)
+            {
+                OGS_FATAL(
+                    "No integration points read in the integration point "
+                    "initial conditions set function.");
+            }
+            position += integration_points_read * ip_meta_data.n_components;
+        }
+    }
+
+    // Initialize local assemblers after all variables have been set.
+    GlobalExecutor::executeMemberOnDereferenced(&LocalAssemblerIF::initialize,
+                                                _local_assemblers,
+                                                *_local_to_global_index_map);
+}
+
+void ThermoRichardsFlowProcess::setInitialConditionsConcreteProcess(
+    std::vector<GlobalVector*>& x, double const t, int const process_id)
+{
+    if (process_id != 0)
+    {
+        return;
+    }
+    DBUG("SetInitialConditions ThermoRichardsFlowProcess.");
+
+    GlobalExecutor::executeMemberOnDereferenced(
+        &LocalAssemblerIF::setInitialConditions, _local_assemblers,
+        *_local_to_global_index_map, *x[process_id], t, _use_monolithic_scheme,
+        process_id);
+}
+
+void ThermoRichardsFlowProcess::assembleConcreteProcess(
+    const double t, double const dt,
+    std::vector<GlobalVector*> const& x,
+    std::vector<GlobalVector*> const& xdot, int const process_id,
+    GlobalMatrix& M, GlobalMatrix& K, GlobalVector& b)
+{
+    DBUG("Assemble the equations for ThermoRichardsFlowProcess.");
+
+    std::vector<std::reference_wrapper<NumLib::LocalToGlobalIndexMap>>
+        dof_table = {std::ref(*_local_to_global_index_map)};
+    ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
+
+    // Call global assembler for each local assembly item.
+    GlobalExecutor::executeSelectedMemberDereferenced(
+        _global_assembler, &VectorMatrixAssembler::assemble, _local_assemblers,
+        pv.getActiveElementIDs(), dof_table, t, dt, x, xdot, process_id, M, K,
+        b);
+}
+
+void ThermoRichardsFlowProcess::assembleWithJacobianConcreteProcess(
+    const double t, double const dt, std::vector<GlobalVector*> const& x,
+    std::vector<GlobalVector*> const& xdot, const double dxdot_dx,
+    const double dx_dx, int const process_id, GlobalMatrix& M, GlobalMatrix& K,
+    GlobalVector& b, GlobalMatrix& Jac)
+{
+    std::vector<std::reference_wrapper<NumLib::LocalToGlobalIndexMap>>
+        dof_tables;
+
+    DBUG(
+        "Assemble the Jacobian of ThermoRichardsFlow for the monolithic "
+        "scheme.");
+    dof_tables.emplace_back(*_local_to_global_index_map);
+
+    ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
+
+    GlobalExecutor::executeSelectedMemberDereferenced(
+        _global_assembler, &VectorMatrixAssembler::assembleWithJacobian,
+        _local_assemblers, pv.getActiveElementIDs(), dof_tables, t, dt, x, xdot,
+        dxdot_dx, dx_dx, process_id, M, K, b, Jac);
+
+    auto copyRhs = [&](int const variable_id, auto& output_vector) {
+        transformVariableFromGlobalVector(b, variable_id, dof_tables[0],
+                                          output_vector, std::negate<double>());
+    };
+
+    copyRhs(0, *_heat_flux);
+    copyRhs(1, *_hydraulic_flow);
+}
+
+void ThermoRichardsFlowProcess::postTimestepConcreteProcess(
+    std::vector<GlobalVector*> const& x, double const t, double const dt,
+    const int process_id)
+{
+    if (process_id != 0)
+    {
+        return;
+    }
+
+    DBUG("PostTimestep ThermoRichardsFlowProcess.");
+
+    auto const dof_tables = getDOFTables(x.size());
+
+    ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
+    GlobalExecutor::executeSelectedMemberOnDereferenced(
+        &LocalAssemblerIF::postTimestep, _local_assemblers,
+        pv.getActiveElementIDs(), dof_tables, x, t, dt);
+}
+
+void ThermoRichardsFlowProcess::computeSecondaryVariableConcrete(
+    const double t, const double dt, std::vector<GlobalVector*> const& x,
+    GlobalVector const& x_dot, int const process_id)
+{
+    if (process_id != 0)
+    {
+        return;
+    }
+    DBUG(
+        "Compute the secondary variables for "
+        "ThermoRichardsFlowProcess.");
+    auto const dof_tables = getDOFTables(x.size());
+    ProcessLib::ProcessVariable const& pv = getProcessVariables(process_id)[0];
+
+    GlobalExecutor::executeSelectedMemberOnDereferenced(
+        &LocalAssemblerIF::computeSecondaryVariable, _local_assemblers,
+        pv.getActiveElementIDs(), dof_tables, t, dt, x, x_dot, process_id);
+}
+
+
+std::vector<NumLib::LocalToGlobalIndexMap const*>
+ThermoRichardsFlowProcess::getDOFTables(
+    int const number_of_processes) const
+{
+    std::vector<NumLib::LocalToGlobalIndexMap const*> dof_tables;
+    dof_tables.reserve(number_of_processes);
+    std::generate_n(std::back_inserter(dof_tables), number_of_processes,
+           [&]() { return _local_to_global_index_map.get(); });
+    return dof_tables;
+}
+
+
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.h b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.h
new file mode 100644
index 00000000000..ed5b3a48809
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcess.h
@@ -0,0 +1,156 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include "LocalAssemblerInterface.h"
+#include "ProcessLib/Process.h"
+#include "ThermoRichardsFlowProcessData.h"
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+/**
+ * \brief Global assembler for the monolithic scheme of the non-isothermal
+ * Richards flow.
+ *
+ * <b>Governing equations without vapor diffusion</b>
+ *
+ * The energy balance equation is given by
+ * \f[
+ *  (\rho c_p)^{eff}\dot T -
+ *  \nabla (\mathbf{k}_T^{eff} \nabla T)+\rho^l c_p^l \nabla T \cdot
+ * \mathbf{v}^l
+ * = Q_T
+ * \f]
+ *  with\f$T\f$ the temperature, \f$(\rho c_p)^{eff}\f$ the  effective
+ * volumetric heat
+ * capacity, \f$\mathbf{k}_T^{eff} \f$
+ *  the effective thermal conductivity, \f$\rho^l\f$ the density of liquid,
+ * \f$c_p^l\f$ the specific heat  capacity of liquid, \f$\mathbf{v}^l\f$ the
+ * liquid velocity, and \f$Q_T\f$ the point heat source. The  effective
+ * volumetric heat can be considered as a composite of the contributions of
+ * solid phase and the liquid phase as
+ * \f[
+ * (\rho c_p)^{eff} = (1-\phi) \rho^s c_p^s + S^l \phi \rho^l c_p^l
+ * \f]
+ * with \f$\phi\f$ the porosity, \f$S^l\f$  the liquid saturation, \f$\rho^s \f$
+ * the solid density, and \f$c_p^s\f$ the specific heat capacity of solid.
+ * Similarly, the effective thermal conductivity is given by
+ * \f[
+ * \mathbf{k}_T^{eff} = (1-\phi) \mathbf{k}_T^s + S^l \phi k_T^l \mathbf I
+ * \f]
+ * where \f$\mathbf{k}_T^s\f$ is the thermal conductivity tensor of solid, \f$
+ *  k_T^l\f$ is the thermal conductivity of liquid, and \f$\mathbf I\f$ is the
+ * identity tensor.
+ *
+ * The mass balance equation is given by
+ * \f{eqnarray*}{
+ * \left(S^l\beta - \phi\frac{\partial S}{\partial p_c}\right) \rho^l\dot p
+ * - S \left( \frac{\partial \rho^l}{\partial T}
+ * +\rho^l(\alpha_B -S)
+ * \alpha_T^s
+ * \right)\dot T\\
+ *  +\nabla (\rho^l \mathbf{v}^l) + S \alpha_B \rho^l \nabla \cdot \dot {\mathbf
+ * u}= Q_H
+ * \f}
+ * where \f$p\f$ is the pore pressure,  \f$p_c\f$ is the
+ * capillary pressure, which is \f$-p\f$ under the single phase assumption,
+ *  \f$\beta\f$ is a composite coefficient by the liquid compressibility and
+ * solid compressibility, \f$\alpha_B\f$ is the Biot's constant,
+ * \f$\alpha_T^s\f$ is the linear thermal  expansivity of solid, \f$Q_H\f$
+ * is the point source or sink term,  \f$H(S-1)\f$ is the Heaviside function, and
+ * \f$ \mathbf u\f$ is the displacement. While this process does not contain a fully
+ * mechanical coupling, simplfied expressions can be given to approximate the latter
+ * term under certain stress conditions.
+ * The liquid velocity \f$\mathbf{v}^l\f$ is
+ * described by the Darcy's law as
+ * \f[
+ * \mathbf{v}^l=-\frac{{\mathbf k} k_{ref}}{\mu} (\nabla p - \rho^l \mathbf g)
+ * \f]
+ * with \f${\mathbf k}\f$ the intrinsic permeability, \f$k_{ref}\f$ the relative
+ * permeability, \f$\mathbf g\f$ the gravitational force.
+ */
+class ThermoRichardsFlowProcess final : public Process
+{
+public:
+    ThermoRichardsFlowProcess(
+        std::string name,
+        MeshLib::Mesh& mesh,
+        std::unique_ptr<ProcessLib::AbstractJacobianAssembler>&&
+            jacobian_assembler,
+        std::vector<std::unique_ptr<ParameterLib::ParameterBase>> const&
+            parameters,
+        unsigned const integration_order,
+        std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>&&
+            process_variables,
+        ThermoRichardsFlowProcessData&& process_data,
+        SecondaryVariableCollection&& secondary_variables,
+        bool const use_monolithic_scheme);
+
+    //! \name ODESystem interface
+    //! @{
+
+    bool isLinear() const override { return false; }
+    //! @}
+
+private:
+    using LocalAssemblerIF = LocalAssemblerInterface;
+
+    void initializeConcreteProcess(
+        NumLib::LocalToGlobalIndexMap const& dof_table,
+        MeshLib::Mesh const& mesh,
+        unsigned const integration_order) override;
+
+    void setInitialConditionsConcreteProcess(std::vector<GlobalVector*>& x,
+                                             double const t,
+                                             int const /*process_id*/) override;
+
+    void assembleConcreteProcess(const double t, double const dt,
+                                 std::vector<GlobalVector*> const& x,
+                                 std::vector<GlobalVector*> const& xdot,
+                                 int const process_id, GlobalMatrix& M,
+                                 GlobalMatrix& K, GlobalVector& b) override;
+
+    void assembleWithJacobianConcreteProcess(
+        const double t, double const dt, std::vector<GlobalVector*> const& x,
+        std::vector<GlobalVector*> const& xdot, const double dxdot_dx,
+        const double dx_dx, int const process_id, GlobalMatrix& M,
+        GlobalMatrix& K, GlobalVector& b, GlobalMatrix& Jac) override;
+
+    void postTimestepConcreteProcess(std::vector<GlobalVector*> const& x,
+                                     double const t, double const dt,
+                                     const int process_id) override;
+
+private:
+    std::vector<MeshLib::Node*> _base_nodes;
+    std::unique_ptr<MeshLib::MeshSubset const> _mesh_subset_base_nodes;
+    ThermoRichardsFlowProcessData _process_data;
+
+    std::vector<std::unique_ptr<LocalAssemblerIF>> _local_assemblers;
+
+    /// Sparsity pattern for the flow equation, and it is initialized only if
+    /// the staggered scheme is used.
+    GlobalSparsityPattern _sparsity_pattern_with_linear_element;
+
+    void computeSecondaryVariableConcrete(double const t, double const dt,
+                                          std::vector<GlobalVector*> const& x,
+                                          GlobalVector const& x_dot,
+                                          int const process_id) override;
+    std::vector<NumLib::LocalToGlobalIndexMap const*> getDOFTables(
+        const int number_of_processes) const;
+
+    MeshLib::PropertyVector<double>* _heat_flux = nullptr;
+    MeshLib::PropertyVector<double>* _hydraulic_flow = nullptr;
+};
+
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcessData.h b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcessData.h
new file mode 100644
index 00000000000..a355697363e
--- /dev/null
+++ b/ProcessLib/ThermoRichardsFlow/ThermoRichardsFlowProcessData.h
@@ -0,0 +1,53 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <Eigen/Dense>
+#include <memory>
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+struct SimplifiedElasticityModel;
+}
+}
+
+namespace MaterialPropertyLib
+{
+class MaterialSpatialDistributionMap;
+}
+
+namespace ProcessLib
+{
+namespace ThermoRichardsFlow
+{
+struct ThermoRichardsFlowProcessData
+{
+    std::unique_ptr<MaterialPropertyLib::MaterialSpatialDistributionMap>
+        media_map = nullptr;
+
+    /// Specific body forces applied to solid and fluid.
+    /// It is usually used to apply gravitational forces.
+    /// A vector of global mesh dimension's length.
+    Eigen::VectorXd const specific_body_force;
+
+    bool const apply_mass_lumping;
+    std::unique_ptr<SimplifiedElasticityModel> simplified_elasticity = nullptr;
+
+    MeshLib::PropertyVector<double>* element_saturation = nullptr;
+    MeshLib::PropertyVector<double>* element_porosity = nullptr;
+
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
+};
+
+}  // namespace ThermoRichardsFlow
+}  // namespace ProcessLib
diff --git a/scripts/cmake/ProcessesSetup.cmake b/scripts/cmake/ProcessesSetup.cmake
index 7ce7c258314..21f9407d562 100644
--- a/scripts/cmake/ProcessesSetup.cmake
+++ b/scripts/cmake/ProcessesSetup.cmake
@@ -23,6 +23,7 @@ set(_processes_list
     ThermoHydroMechanics
     ThermoMechanicalPhaseField
     ThermoMechanics
+    ThermoRichardsFlow
     TwoPhaseFlowWithPP
     TwoPhaseFlowWithPrho
 )
-- 
GitLab