From 4639a1733bf267574e29b8c38a6523475119cdbe Mon Sep 17 00:00:00 2001
From: Dmitri Naumov <dmitri.naumov@ufz.de>
Date: Mon, 22 Jan 2024 11:39:36 +0100
Subject: [PATCH] [PL/CT] Optional temperature variable.

---
 .../ComponentTransportFEM.h                   | 86 ++++++++++++++-----
 .../ComponentTransportProcess.cpp             | 42 ++++++---
 .../ComponentTransportProcessData.h           |  6 +-
 .../CreateComponentTransportProcess.cpp       | 47 ++++++++--
 4 files changed, 137 insertions(+), 44 deletions(-)

diff --git a/ProcessLib/ComponentTransport/ComponentTransportFEM.h b/ProcessLib/ComponentTransport/ComponentTransportFEM.h
index 1982fa9d57a..215cbfc9748 100644
--- a/ProcessLib/ComponentTransport/ComponentTransportFEM.h
+++ b/ProcessLib/ComponentTransport/ComponentTransportFEM.h
@@ -210,7 +210,8 @@ class LocalAssemblerData : public ComponentTransportLocalAssemblerInterface
     // When monolithic scheme is adopted, nodal pressure and nodal concentration
     // are accessed by vector index.
     static const int pressure_index = 0;
-    static const int first_concentration_index = ShapeFunction::NPOINTS;
+    const int temperature_index = -1;
+    const int first_concentration_index = -1;
 
     static const int pressure_size = ShapeFunction::NPOINTS;
     static const int temperature_size = ShapeFunction::NPOINTS;
@@ -247,7 +248,12 @@ public:
         ComponentTransportProcessData const& process_data,
         std::vector<std::reference_wrapper<ProcessVariable>> const&
             transport_process_variables)
-        : _element(element),
+        : temperature_index(process_data.isothermal ? -1
+                                                    : ShapeFunction::NPOINTS),
+          first_concentration_index(process_data.isothermal
+                                        ? ShapeFunction::NPOINTS
+                                        : 2 * ShapeFunction::NPOINTS),
+          _element(element),
           _process_data(process_data),
           _integration_method(integration_method),
           _transport_process_variables(transport_process_variables)
@@ -811,6 +817,25 @@ public:
         auto const local_C_prev =
             local_x_prev.segment<concentration_size>(first_concentration_index);
 
+        NodalVectorType local_T;
+        if (_process_data.isothermal)
+        {
+            if (_process_data.temperature)
+            {
+                local_T = _process_data.temperature->getNodalValuesOnElement(
+                    _element, t);
+            }
+            else
+            {
+                local_T = NodalVectorType::Zero(temperature_size);
+            }
+        }
+        else
+        {
+            local_T =
+                local_x.template segment<temperature_size>(temperature_index);
+        }
+
         auto local_M = MathLib::createZeroedMatrix<LocalBlockMatrixType>(
             local_M_data, pressure_size, pressure_size);
         auto local_K = MathLib::createZeroedMatrix<LocalBlockMatrixType>(
@@ -846,14 +871,13 @@ public:
             auto& porosity = ip_data.porosity;
             auto const& porosity_prev = ip_data.porosity_prev;
 
-            double C_int_pt = 0.0;
-            double p_int_pt = 0.0;
-
-            NumLib::shapeFunctionInterpolate(local_C, N, C_int_pt);
-            NumLib::shapeFunctionInterpolate(local_p, N, p_int_pt);
+            double const C_int_pt = N.dot(local_C);
+            double const p_int_pt = N.dot(local_p);
+            double const T_int_pt = N.dot(local_T);
 
             vars.concentration = C_int_pt;
             vars.liquid_phase_pressure = p_int_pt;
+            vars.temperature = T_int_pt;
 
             //  porosity
             {
@@ -1048,20 +1072,38 @@ public:
         std::vector<double>& local_K_data,
         std::vector<double>& /*local_b_data*/, int const transport_process_id)
     {
+        assert(static_cast<int>(local_x.size()) ==
+               pressure_size +
+                   concentration_size *
+                       static_cast<int>(_transport_process_variables.size()) +
+                   (_process_data.isothermal ? 0 : temperature_size));
+
         auto const local_p =
             local_x.template segment<pressure_size>(pressure_index);
-        auto const local_C = local_x.template segment<concentration_size>(
-            first_concentration_index +
-            (transport_process_id - 1) * concentration_size);
-        auto const local_p_prev =
-            local_x_prev.segment<pressure_size>(pressure_index);
-
         NodalVectorType local_T;
-        if (_process_data.temperature)
+        if (_process_data.isothermal)
+        {
+            if (_process_data.temperature)
+            {
+                local_T = _process_data.temperature->getNodalValuesOnElement(
+                    _element, t);
+            }
+            else
+            {
+                local_T = NodalVectorType::Zero(temperature_size);
+            }
+        }
+        else
         {
             local_T =
-                _process_data.temperature->getNodalValuesOnElement(_element, t);
+                local_x.template segment<temperature_size>(temperature_index);
         }
+        auto const local_C = local_x.template segment<concentration_size>(
+            first_concentration_index +
+            (transport_process_id - (_process_data.isothermal ? 1 : 2)) *
+                concentration_size);
+        auto const local_p_prev =
+            local_x_prev.segment<pressure_size>(pressure_index);
 
         auto local_M = MathLib::createZeroedMatrix<LocalBlockMatrixType>(
             local_M_data, concentration_size, concentration_size);
@@ -1094,9 +1136,8 @@ public:
         auto const& medium =
             *_process_data.media_map.getMedium(_element.getID());
         auto const& phase = medium.phase("AqueousLiquid");
-        // Hydraulic process id is 0 and thus transport process id starts
-        // from 1.
-        auto const component_id = transport_process_id - 1;
+        auto const component_id =
+            transport_process_id - (_process_data.isothermal ? 1 : 2);
         auto const& component = phase.component(
             _transport_process_variables[component_id].get().getName());
 
@@ -1111,14 +1152,13 @@ public:
             auto& porosity = ip_data.porosity;
             auto const& porosity_prev = ip_data.porosity_prev;
 
-            double C_int_pt = 0.0;
-            double p_int_pt = 0.0;
-
-            NumLib::shapeFunctionInterpolate(local_C, N, C_int_pt);
-            NumLib::shapeFunctionInterpolate(local_p, N, p_int_pt);
+            double const C_int_pt = N.dot(local_C);
+            double const p_int_pt = N.dot(local_p);
+            double const T_int_pt = N.dot(local_T);
 
             vars.concentration = C_int_pt;
             vars.liquid_phase_pressure = p_int_pt;
+            vars.temperature = T_int_pt;
 
             if (_process_data.temperature)
             {
diff --git a/ProcessLib/ComponentTransport/ComponentTransportProcess.cpp b/ProcessLib/ComponentTransport/ComponentTransportProcess.cpp
index 5e816c4a883..6a15ad753ba 100644
--- a/ProcessLib/ComponentTransport/ComponentTransportProcess.cpp
+++ b/ProcessLib/ComponentTransport/ComponentTransportProcess.cpp
@@ -11,6 +11,7 @@
 #include "ComponentTransportProcess.h"
 
 #include <cassert>
+#include <range/v3/algorithm/copy.hpp>
 #include <range/v3/view/drop.hpp>
 
 #include "BaseLib/RunTime.h"
@@ -88,27 +89,36 @@ ComponentTransportProcess::ComponentTransportProcess(
             "probably expect.");
     }
 
-    _residua.push_back(MeshLib::getOrCreateMeshProperty<double>(
-        mesh, "LiquidMassFlowRate", MeshLib::MeshItemType::Node, 1));
+    auto residuum_name = [&](auto const& pv) -> std::string
+    {
+        std::string const& pv_name = pv.getName();
+        if (pv_name == "pressure")
+        {
+            return "LiquidMassFlowRate";
+        }
+        if (pv_name == "temperature")
+        {
+            return "HeatFlowRate";
+        }
+        return pv_name + "FlowRate";
+    };
 
     if (_use_monolithic_scheme)
     {
-        const int process_id = 0;
-        for (auto const& pv :
-             _process_variables[process_id] | ranges::views::drop(1))
+        int const process_id = 0;
+        for (auto const& pv : _process_variables[process_id])
         {
             _residua.push_back(MeshLib::getOrCreateMeshProperty<double>(
-                mesh, pv.get().getName() + "FlowRate",
-                MeshLib::MeshItemType::Node, 1));
+                mesh, residuum_name(pv.get()), MeshLib::MeshItemType::Node, 1));
         }
     }
     else
     {
-        for (auto const& pv : _process_variables | ranges::views::drop(1))
+        for (auto const& pv : _process_variables)
         {
             _residua.push_back(MeshLib::getOrCreateMeshProperty<double>(
-                mesh, pv[0].get().getName() + "FlowRate",
-                MeshLib::MeshItemType::Node, 1));
+                mesh, residuum_name(pv[0].get()), MeshLib::MeshItemType::Node,
+                1));
         }
     }
 }
@@ -142,11 +152,13 @@ void ComponentTransportProcess::initializeConcreteProcess(
     }
     else
     {
-        for (auto pv_iter = std::next(_process_variables.begin());
-             pv_iter != _process_variables.end();
-             ++pv_iter)
+        // All process variables but the pressure and optionally the temperature
+        // are transport variables.
+        for (auto const& pv :
+             _process_variables |
+                 ranges::views::drop(_process_data.isothermal ? 1 : 2))
         {
-            transport_process_variables.push_back((*pv_iter)[0]);
+            transport_process_variables.push_back(pv[0]);
         }
     }
 
@@ -232,6 +244,8 @@ void ComponentTransportProcess::assembleConcreteProcess(
                             _global_assembler, _local_assemblers,
                             pv.getActiveElementIDs());
 
+    // TODO (naumov) What about temperature? A test with FCT would reveal any
+    // problems.
     if (process_id == _process_data.hydraulic_process_id)
     {
         return;
diff --git a/ProcessLib/ComponentTransport/ComponentTransportProcessData.h b/ProcessLib/ComponentTransport/ComponentTransportProcessData.h
index 3aa65da281d..2ac5512eed9 100644
--- a/ProcessLib/ComponentTransport/ComponentTransportProcessData.h
+++ b/ProcessLib/ComponentTransport/ComponentTransportProcessData.h
@@ -82,13 +82,17 @@ struct ComponentTransportProcessData
     ParameterLib::Parameter<double> const& aperture_size =
         ParameterLib::ConstantParameter<double>("constant_one", 1.0);
 
+    bool const isothermal;
+
     static const int hydraulic_process_id = 0;
+    // Thermal process is optional, indicated by -1. If present, it is positive.
+    const int thermal_process_id = isothermal ? -1 : 1;
     // TODO (renchao-lu): This variable is used in the calculation of the
     // fluid's density and flux, indicating the transport process id. For now it
     // is assumed that these quantities depend on the first occurring transport
     // process only. The density and flux calculations have to be extended to
     // all processes.
-    static const int first_transport_process_id = 1;
+    const int first_transport_process_id = isothermal ? 1 : 2;
 
     MeshLib::PropertyVector<double>* mesh_prop_velocity = nullptr;
     MeshLib::PropertyVector<double>* mesh_prop_porosity = nullptr;
diff --git a/ProcessLib/ComponentTransport/CreateComponentTransportProcess.cpp b/ProcessLib/ComponentTransport/CreateComponentTransportProcess.cpp
index 2aea148a7bd..bb69b4fe94e 100644
--- a/ProcessLib/ComponentTransport/CreateComponentTransportProcess.cpp
+++ b/ProcessLib/ComponentTransport/CreateComponentTransportProcess.cpp
@@ -107,14 +107,27 @@ std::unique_ptr<Process> createComponentTransportProcess(
     std::vector<std::vector<std::reference_wrapper<ProcessVariable>>>
         process_variables;
 
-    /// Primary process variables as they appear in the global component vector:
-    auto const collected_process_variables = findProcessVariables(
+    /// Primary process variables as they appear in the global component vector.
+    std::vector collected_process_variables = findProcessVariables(
         variables, pv_config,
         {//! \ogs_file_param_special{prj__processes__process__ComponentTransport__process_variables__pressure}
          "pressure",
          //! \ogs_file_param_special{prj__processes__process__ComponentTransport__process_variables__concentration}
          "concentration"});
 
+    /// Temperature is optional.
+    std::vector const temperature_variable = findProcessVariables(
+        variables, pv_config,
+        //! \ogs_file_param_special{prj__processes__process__ComponentTransport__process_variables__temperature}
+        "temperature", true /*temperature is optional*/);
+    bool const isothermal = temperature_variable.empty();
+    if (!isothermal)
+    {
+        assert(temperature_variable.size() == 1);
+        collected_process_variables.insert(
+            ++collected_process_variables.begin(), temperature_variable[0]);
+    }
+
     // Check number of components for each process variable
     auto it = std::find_if(
         collected_process_variables.cbegin(),
@@ -136,6 +149,13 @@ std::unique_ptr<Process> createComponentTransportProcess(
     // depending on what scheme is adopted
     if (use_monolithic_scheme)  // monolithic scheme.
     {
+        if (!isothermal)
+        {
+            OGS_FATAL(
+                "Currently, non-isothermal component transport process can "
+                "only be simulated in staggered scheme.");
+        }
+
         process_variables.push_back(std::move(collected_process_variables));
     }
     else  // staggered scheme.
@@ -183,8 +203,16 @@ std::unique_ptr<Process> createComponentTransportProcess(
             // pressure
             per_process_variable.emplace_back(collected_process_variables[0]);
             process_variables.push_back(std::move(per_process_variable));
+            // temperature
+            if (!isothermal)
+            {
+                per_process_variable.emplace_back(
+                    collected_process_variables[1]);
+                process_variables.push_back(std::move(per_process_variable));
+            }
             // concentration
-            assert(components.size() + 1 == collected_process_variables.size());
+            assert(components.size() + (isothermal ? 1 : 2) ==
+                   collected_process_variables.size());
             std::transform(components.begin(), components.end(),
                            std::back_inserter(process_variables),
                            sort_by_component);
@@ -222,9 +250,14 @@ std::unique_ptr<Process> createComponentTransportProcess(
         config.getConfigParameter<bool>("chemically_induced_porosity_change",
                                         false);
 
-    auto const temperature = ParameterLib::findOptionalTagParameter<double>(
+    auto const temperature_field = ParameterLib::findOptionalTagParameter<
+        double>(
         //! \ogs_file_param_special{prj__processes__process__ComponentTransport__temperature_field}
         config, "temperature_field", parameters, 1, &mesh);
+    if (!isothermal && temperature_field != nullptr)
+    {
+        OGS_FATAL("Temperature field is set for non-isothermal setup.")
+    }
 
     auto media_map =
         MaterialPropertyLib::createMaterialSpatialDistributionMap(media, mesh);
@@ -275,14 +308,16 @@ std::unique_ptr<Process> createComponentTransportProcess(
         std::move(media_map),
         has_gravity,
         non_advective_form,
-        temperature,
+        temperature_field,
         chemically_induced_porosity_change,
         chemical_solver_interface.get(),
         std::move(lookup_table),
         std::move(stabilizer),
         projected_specific_body_force_vectors,
         mesh_space_dimension,
-        *aperture_size_parameter};
+        *aperture_size_parameter,
+        isothermal,
+    };
 
     SecondaryVariableCollection secondary_variables;
 
-- 
GitLab