Repository: KratosMultiphysics/Kratos (C++)

Issue:
[GeoMechanicsApplication] Add support to give list of modelpart names to "ApplyK0ProcedureProcess" process
### Background
Currently, some processes, including the `ApplyK0ProcedureProcess`, require users to define one process instance per modelpart using the 'model_part_name' parameter. This means that when the same process needs to be applied to multiple model parts, the process must be repeated as many times as the number of model parts. This results in unnecessarily repetitive processes.

It was proposed to add a functionality for some processes that accepts a list of model part names. This allows the user to define a single `ApplyK0ProcedureProcess`multiple model parts simultaneously.

Also the integration tests need to be adjusted. 

### Acceptance Criteria

**Given** a geotechnical analysis that executes the model part process
**When** for such an process`"model_part_name"` is defined, but `"model_part_name_list"` is not
**Then** the process is applied to the given model part.

**Given** a geotechnical analysis that executes the model part process
**When** for such a process `"model_part_name_list"` is defined and its value is a non-empty list, and `"model_part_name"` is not defined
**Then** the process is applied to the given model parts.

**Given** a geotechnical analysis that executes the model part process
**When** for such an process `"model_part_name_list"` is defined and its value is an _empty_ list, and `"model_part_name"` is not defined
**Then** an error is raised saying that the list must contain at least one model part name.

**Given** a geotechnical analysis that executes the model part process
**When** for such a process `"model_part_name"` _and_ `"model_part_name_list"` are defined
**Then** an error is raised saying that these input parameters are mutually exclusive.




Fix (patch):
diff --git a/applications/GeoMechanicsApplication/custom_processes/README.md b/applications/GeoMechanicsApplication/custom_processes/README.md
index 4e70ee68cfe7..aed0a54db779 100644
--- a/applications/GeoMechanicsApplication/custom_processes/README.md
+++ b/applications/GeoMechanicsApplication/custom_processes/README.md
@@ -128,6 +128,23 @@ The process is defined as follows in "ProjectParameters.json" (also found in som
   ]
 }
 ```
+Next to specifying a single model part, it is also possible to provide a list:
+```json
+{
+  "auxiliary_process_list": [
+    {
+      "python_module": "apply_k0_procedure_process",
+      "kratos_module": "KratosMultiphysics.GeoMechanicsApplication",
+      "process_name": "ApplyK0ProcedureProcess",
+      "Parameters": {
+        "model_part_name_list": ["PorousDomain.Clay", "PorousDomain.Sand"],
+        "use_standard_procedure": true
+      }
+    }
+  ]
+}
+```
+
 The "apply_k0_procedure_process" needs the following material parameter input to be added in the "MaterialParameters.json".
 ```json
 {
diff --git a/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.cpp b/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.cpp
index 0a30c41ee192..90ccb7971039 100644
--- a/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.cpp
+++ b/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.cpp
@@ -12,17 +12,17 @@
 
 #include "apply_k0_procedure_process.h"
 
-#include <cmath>
+#include <algorithm>
+#include <iterator>
 #include <ostream>
 
-#include "containers/flags.h"
+#include "containers/model.h"
 #include "custom_constitutive/linear_elastic_law.h"
 #include "custom_utilities/constitutive_law_utilities.h"
 #include "geo_aliases.h"
 #include "geo_mechanics_application_variables.h"
 #include "includes/element.h"
 #include "includes/model_part.h"
-#include "utilities/math_utils.h"
 
 namespace
 {
@@ -43,37 +43,65 @@ void SetConsiderDiagonalEntriesOnlyAndNoShear(ModelPart::ElementsContainerType&
 namespace Kratos
 {
 
-ApplyK0ProcedureProcess::ApplyK0ProcedureProcess(ModelPart& model_part, Parameters K0Settings)
-    : Process(Flags()), mrModelPart(model_part), mSettings(std::move(K0Settings))
+ApplyK0ProcedureProcess::ApplyK0ProcedureProcess(Model& rModel, Parameters K0Settings)
+    : mSettings(std::move(K0Settings))
 {
+    KRATOS_ERROR_IF_NOT(mSettings.Has("model_part_name") || mSettings.Has("model_part_name_list"))
+        << "Please specify 'model_part_name' or 'model_part_name_list' for "
+        << ApplyK0ProcedureProcess::Info();
+
+    KRATOS_ERROR_IF(mSettings.Has("model_part_name") && mSettings.Has("model_part_name_list"))
+        << "The parameters 'model_part_name' and 'model_part_name_list' are mutually exclusive for "
+        << ApplyK0ProcedureProcess::Info();
+
+    const auto model_part_names = mSettings.Has("model_part_name_list")
+                                      ? mSettings["model_part_name_list"].GetStringArray()
+                                      : std::vector{mSettings["model_part_name"].GetString()};
+    KRATOS_ERROR_IF(model_part_names.empty()) << "The parameters 'model_part_name_list' needs "
+                                                 "to contain at least one model part name for "
+                                              << ApplyK0ProcedureProcess::Info();
+    std::ranges::transform(
+        model_part_names, std::back_inserter(mrModelParts),
+        [&rModel](const auto& rName) -> ModelPart& { return rModel.GetModelPart(rName); });
 }
 
 void ApplyK0ProcedureProcess::ExecuteInitialize()
 {
-    if (UseStandardProcedure())
-        SetConsiderDiagonalEntriesOnlyAndNoShear(mrModelPart.Elements(), true);
+    if (UseStandardProcedure()) {
+        for (const auto& r_model_part : mrModelParts) {
+            SetConsiderDiagonalEntriesOnlyAndNoShear(r_model_part.get().Elements(), true);
+        }
+    }
 }
 
 void ApplyK0ProcedureProcess::ExecuteFinalize()
 {
-    if (UseStandardProcedure())
-        SetConsiderDiagonalEntriesOnlyAndNoShear(mrModelPart.Elements(), false);
+    if (UseStandardProcedure()) {
+        for (const auto& r_model_part : mrModelParts) {
+            SetConsiderDiagonalEntriesOnlyAndNoShear(r_model_part.get().Elements(), false);
+        }
+    }
 }
 
 int ApplyK0ProcedureProcess::Check()
 {
-    KRATOS_ERROR_IF(mrModelPart.Elements().empty())
-        << "ApplyK0ProcedureProces has no elements in modelpart " << mrModelPart.Name() << std::endl;
-
-    block_for_each(mrModelPart.Elements(), [](Element& rElement) {
-        const auto& r_properties = rElement.GetProperties();
-        CheckK0MainDirection(r_properties, rElement.Id());
-        CheckSufficientMaterialParameters(r_properties, rElement.Id());
-        CheckOCRorPOP(r_properties, rElement.Id());
-        CheckPoissonUnloadingReloading(r_properties, rElement.Id());
-        CheckPhi(r_properties, rElement.Id());
-        CheckK0(r_properties, rElement.Id());
-    });
+    for (const auto& r_model_part : mrModelParts) {
+        KRATOS_ERROR_IF(r_model_part.get().Elements().empty())
+            << "ApplyK0ProcedureProces has no elements in modelpart " << r_model_part.get().Name()
+            << std::endl;
+    }
+
+    for (const auto& r_model_part : mrModelParts) {
+        block_for_each(r_model_part.get().Elements(), [](Element& rElement) {
+            const auto& r_properties = rElement.GetProperties();
+            CheckK0MainDirection(r_properties, rElement.Id());
+            CheckSufficientMaterialParameters(r_properties, rElement.Id());
+            CheckOCRorPOP(r_properties, rElement.Id());
+            CheckPoissonUnloadingReloading(r_properties, rElement.Id());
+            CheckPhi(r_properties, rElement.Id());
+            CheckK0(r_properties, rElement.Id());
+        });
+    }
 
     return 0;
 }
@@ -177,9 +205,11 @@ void ApplyK0ProcedureProcess::ExecuteFinalizeSolutionStep()
     KRATOS_TRY
 
     // K0 procedure for the model part:
-    block_for_each(mrModelPart.Elements(),
-                   [this](Element& rElement) { CalculateK0Stresses(rElement); });
-
+    for (const auto& r_model_part : mrModelParts) {
+        block_for_each(r_model_part.get().Elements(), [&r_model_part, this](Element& rElement) {
+            CalculateK0Stresses(rElement, r_model_part.get().GetProcessInfo());
+        });
+    }
     KRATOS_CATCH("")
 }
 
@@ -209,7 +239,7 @@ array_1d<double, 3> ApplyK0ProcedureProcess::CreateK0Vector(const Element::Prope
     return k0_vector;
 }
 
-void ApplyK0ProcedureProcess::CalculateK0Stresses(Element& rElement) const
+void ApplyK0ProcedureProcess::CalculateK0Stresses(Element& rElement, const ProcessInfo& rProcessInfo) const
 {
     // Get K0 material parameters of this element ( probably there is something more efficient )
     const Element::PropertiesType& rProp             = rElement.GetProperties();
@@ -234,9 +264,8 @@ void ApplyK0ProcedureProcess::CalculateK0Stresses(Element& rElement) const
         }
     }
     // Get element stress vectors
-    const ProcessInfo& rCurrentProcessInfo = this->mrModelPart.GetProcessInfo();
     std::vector<ConstitutiveLaw::StressVectorType> rStressVectors;
-    rElement.CalculateOnIntegrationPoints(CAUCHY_STRESS_VECTOR, rStressVectors, rCurrentProcessInfo);
+    rElement.CalculateOnIntegrationPoints(CAUCHY_STRESS_VECTOR, rStressVectors, rProcessInfo);
 
     // Loop over integration point stress vectors
     for (auto& rStressVector : rStressVectors) {
@@ -251,7 +280,7 @@ void ApplyK0ProcedureProcess::CalculateK0Stresses(Element& rElement) const
         std::fill(rStressVector.begin() + 3, rStressVector.end(), 0.0);
     }
     // Set element integration point stress tensors
-    rElement.SetValuesOnIntegrationPoints(CAUCHY_STRESS_VECTOR, rStressVectors, rCurrentProcessInfo);
+    rElement.SetValuesOnIntegrationPoints(CAUCHY_STRESS_VECTOR, rStressVectors, rProcessInfo);
 }
 
 } // namespace Kratos
diff --git a/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.h b/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.h
index 76c5a001657e..f56ed8f3cf2e 100644
--- a/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.h
+++ b/applications/GeoMechanicsApplication/custom_processes/apply_k0_procedure_process.h
@@ -28,7 +28,7 @@ class KRATOS_API(GEO_MECHANICS_APPLICATION) ApplyK0ProcedureProcess : public Pro
 public:
     KRATOS_CLASS_POINTER_DEFINITION(ApplyK0ProcedureProcess);
 
-    ApplyK0ProcedureProcess(ModelPart& model_part, Parameters rK0Settings);
+    ApplyK0ProcedureProcess(Model& rModel, Parameters K0Settings);
     ~ApplyK0ProcedureProcess() override = default;
 
     void ExecuteInitialize() override;
@@ -41,7 +41,7 @@ class KRATOS_API(GEO_MECHANICS_APPLICATION) ApplyK0ProcedureProcess : public Pro
 private:
     [[nodiscard]] bool                       UseStandardProcedure() const;
     [[nodiscard]] static array_1d<double, 3> CreateK0Vector(const Element::PropertiesType& rProp);
-    void                                     CalculateK0Stresses(Element& rElement) const;
+    void        CalculateK0Stresses(Element& rElement, const ProcessInfo& rProcessInfo) const;
     static void CheckK0(const Properties& rProperties, IndexType ElementId);
     static void CheckK0MainDirection(const Properties& rProperties, IndexType ElementId);
     static void CheckOCRorPOP(const Properties& rProperties, IndexType ElementId);
@@ -49,8 +49,8 @@ class KRATOS_API(GEO_MECHANICS_APPLICATION) ApplyK0ProcedureProcess : public Pro
     static void CheckPoissonUnloadingReloading(const Properties& rProperties, IndexType ElementId);
     static void CheckSufficientMaterialParameters(const Properties& rProperties, IndexType ElementId);
 
-    ModelPart&       mrModelPart;
-    const Parameters mSettings;
+    std::vector<std::reference_wrapper<ModelPart>> mrModelParts;
+    const Parameters                               mSettings;
 };
 
 } // namespace Kratos
diff --git a/applications/GeoMechanicsApplication/custom_python/add_custom_processes_to_python.cpp b/applications/GeoMechanicsApplication/custom_python/add_custom_processes_to_python.cpp
index 01168c768752..c6b1011dcb8f 100644
--- a/applications/GeoMechanicsApplication/custom_python/add_custom_processes_to_python.cpp
+++ b/applications/GeoMechanicsApplication/custom_python/add_custom_processes_to_python.cpp
@@ -15,6 +15,7 @@
 
 // Project includes
 #include "custom_python/add_custom_processes_to_python.h"
+#include "containers/model.h"
 #include "includes/kratos_parameters.h"
 #include "includes/model_part.h"
 #include "processes/process.h"
@@ -134,7 +135,7 @@ void AddCustomProcessesToPython(pybind11::module& m)
 
     py::class_<ApplyK0ProcedureProcess, ApplyK0ProcedureProcess::Pointer, Process>(
         m, "ApplyK0ProcedureProcess")
-        .def(py::init<ModelPart&, Parameters&>());
+        .def(py::init<Model&, Parameters>());
 
     py::class_<FindNeighbourElementsOfConditionsProcess, FindNeighbourElementsOfConditionsProcess::Pointer, Process>(
         m, "FindNeighbourElementsOfConditionsProcess")
diff --git a/applications/GeoMechanicsApplication/custom_utilities/transport_equation_utilities.hpp b/applications/GeoMechanicsApplication/custom_utilities/transport_equation_utilities.hpp
index c79cdfe51e89..57880cff716e 100644
--- a/applications/GeoMechanicsApplication/custom_utilities/transport_equation_utilities.hpp
+++ b/applications/GeoMechanicsApplication/custom_utilities/transport_equation_utilities.hpp
@@ -129,8 +129,8 @@ class KRATOS_API(GEO_MECHANICS_APPLICATION) GeoTransportEquationUtilities
                                                                                       double BishopCoefficient,
                                                                                       double IntegrationCoefficient)
     {
-        return PORE_PRESSURE_SIGN_FACTOR * BiotCoefficient * BishopCoefficient * IntegrationCoefficient *
-                       outer_prod(Vector(prod(trans(rB), rVoigtVector)), rNp);
+        return PORE_PRESSURE_SIGN_FACTOR * BiotCoefficient * BishopCoefficient *
+               IntegrationCoefficient * outer_prod(Vector(prod(trans(rB), rVoigtVector)), rNp);
     }
 
     template <unsigned int TNumNodes>
diff --git a/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.cpp b/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.cpp
index d3849d71528f..61687778bc4e 100644
--- a/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.cpp
+++ b/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.cpp
@@ -156,7 +156,7 @@ void KratosGeoSettlement::InitializeProcessFactory()
                                 MakeCreatorFor<ApplyVectorConstraintTableProcess>());
     mProcessFactory->AddCreator("SetParameterFieldProcess", MakeCreatorFor<SetParameterFieldProcess>());
     mProcessFactory->AddCreator("ApplyExcavationProcess", MakeCreatorFor<ApplyExcavationProcess>());
-    mProcessFactory->AddCreator("ApplyK0ProcedureProcess", MakeCreatorFor<ApplyK0ProcedureProcess>());
+    mProcessFactory->AddCreator("ApplyK0ProcedureProcess", MakeCreatorWithModelFor<ApplyK0ProcedureProcess>());
     mProcessFactory->AddCreator("GeoExtrapolateIntegrationPointValuesToNodesProcess",
                                 MakeCreatorFor<GeoExtrapolateIntegrationPointValuesToNodesProcess>());
     mProcessFactory->AddCreator("FixWaterPressuresAbovePhreaticLineProcess",
diff --git a/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.h b/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.h
index 6d0ae3c2794e..f23e73a7d4d3 100644
--- a/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.h
+++ b/applications/GeoMechanicsApplication/custom_workflows/dgeosettlement.h
@@ -92,6 +92,14 @@ class KRATOS_API(GEO_MECHANICS_APPLICATION) KratosGeoSettlement
         };
     }
 
+    template <typename ProcessType>
+    std::function<ProcessFactory::ProductType(const Parameters&)> MakeCreatorWithModelFor()
+    {
+        return [&model = mModel](const Parameters& rProcessSettings) {
+            return std::make_unique<ProcessType>(model, rProcessSettings);
+        };
+    }
+
     Kernel                                                mKernel;
     Model                                                 mModel;
     std::string                                           mModelPartName;
diff --git a/applications/GeoMechanicsApplication/python_scripts/apply_k0_procedure_process.py b/applications/GeoMechanicsApplication/python_scripts/apply_k0_procedure_process.py
index 966859c50514..0681e997663d 100644
--- a/applications/GeoMechanicsApplication/python_scripts/apply_k0_procedure_process.py
+++ b/applications/GeoMechanicsApplication/python_scripts/apply_k0_procedure_process.py
@@ -6,5 +6,4 @@ def Factory(settings, model):
     if not isinstance(settings, Core.Parameters):
         raise TypeError("expected input shall be a Parameters object, encapsulating a json string")
 
-    model_part = model[settings["Parameters"]["model_part_name"].GetString()]
-    return Geo.ApplyK0ProcedureProcess(model_part, settings["Parameters"])
+    return Geo.ApplyK0ProcedureProcess(model, settings["Parameters"])