Skip to content
Snippets Groups Projects
Forked from ogs / ogs
792 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ProjectData.cpp 56.26 KiB
/**
 * \file
 * \author Karsten Rink
 * \date   2010-08-25
 * \brief  Implementation of the project data class.
 *
 * \copyright
 * Copyright (c) 2012-2024, 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 "ProjectData.h"

#include <pybind11/eval.h>

#include <algorithm>
#include <boost/algorithm/string/predicate.hpp>
#include <cctype>
#include <range/v3/action/sort.hpp>
#include <range/v3/action/unique.hpp>
#include <range/v3/algorithm/contains.hpp>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/adjacent_remove_if.hpp>
#include <set>

#include "BaseLib/Algorithm.h"
#include "BaseLib/ConfigTree.h"
#include "BaseLib/FileTools.h"
#include "BaseLib/Logging.h"
#include "BaseLib/StringTools.h"
#include "GeoLib/GEOObjects.h"
#include "GeoLib/IO/AsciiRasterInterface.h"
#include "GeoLib/IO/NetCDFRasterReader.h"
#include "GeoLib/Raster.h"
#include "InfoLib/CMakeInfo.h"
#include "MaterialLib/MPL/CreateMedium.h"
#include "MathLib/Curve/CreatePiecewiseLinearCurve.h"
#if defined(USE_LIS)
#include "MathLib/LinAlg/EigenLis/LinearSolverOptionsParser.h"
#elif defined(USE_PETSC)
#include "MathLib/LinAlg/PETSc/LinearSolverOptionsParser.h"
#else
#include "MathLib/LinAlg/Eigen/LinearSolverOptionsParser.h"
#endif
#include "MeshGeoToolsLib/ConstructMeshesFromGeometries.h"
#include "MeshGeoToolsLib/CreateSearchLength.h"
#include "MeshGeoToolsLib/SearchLength.h"
#include "MeshLib/Mesh.h"
#include "MeshLib/Utils/SetMeshSpaceDimension.h"
#include "MeshToolsLib/ZeroMeshFieldDataByMaterialIDs.h"
#include "NumLib/ODESolver/ConvergenceCriterion.h"
#include "ProcessLib/CreateJacobianAssembler.h"
#include "ProcessLib/DeactivatedSubdomain.h"

// FileIO
#include "GeoLib/IO/XmlIO/Boost/BoostXmlGmlInterface.h"
#include "MeshLib/IO/readMeshFromFile.h"
#include "ParameterLib/ConstantParameter.h"
#include "ParameterLib/CreateCoordinateSystem.h"
#include "ParameterLib/Utils.h"
#include "ProcessLib/CreateTimeLoop.h"
#include "ProcessLib/TimeLoop.h"

#ifdef OGS_EMBED_PYTHON_INTERPRETER
#include "Applications/CLI/ogs_embedded_python.h"
#endif
#ifdef OGS_BUILD_PROCESS_COMPONENTTRANSPORT
#include "ChemistryLib/CreateChemicalSolverInterface.h"
#include "ProcessLib/ComponentTransport/CreateComponentTransportProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_STEADYSTATEDIFFUSION
#include "ProcessLib/SteadyStateDiffusion/CreateSteadyStateDiffusion.h"
#endif
#ifdef OGS_BUILD_PROCESS_HT
#include "ProcessLib/HT/CreateHTProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_HEATCONDUCTION
#include "ProcessLib/HeatConduction/CreateHeatConductionProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_HEATTRANSPORTBHE
#include "ProcessLib/HeatTransportBHE/CreateHeatTransportBHEProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_WELLBORESIMULATOR
#include "ProcessLib/WellboreSimulator/CreateWellboreSimulatorProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_HYDROMECHANICS
#include "ProcessLib/HydroMechanics/CreateHydroMechanicsProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_LARGEDEFORMATION
#include "ProcessLib/LargeDeformation/CreateLargeDeformationProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_LIE
#include "ProcessLib/LIE/HydroMechanics/CreateHydroMechanicsProcess.h"
#include "ProcessLib/LIE/SmallDeformation/CreateSmallDeformationProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_LIQUIDFLOW
#include "ProcessLib/LiquidFlow/CreateLiquidFlowProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_STOKESFLOW
#include "ProcessLib/StokesFlow/CreateStokesFlowProcess.h"
#endif

#ifdef OGS_BUILD_PROCESS_THERMORICHARDSMECHANICS
#include "ProcessLib/ThermoRichardsMechanics/CreateThermoRichardsMechanicsProcess.h"
#endif

#ifdef OGS_BUILD_PROCESS_PHASEFIELD
#include "ProcessLib/PhaseField/CreatePhaseFieldProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_RICHARDSCOMPONENTTRANSPORT
#include "ProcessLib/RichardsComponentTransport/CreateRichardsComponentTransportProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_RICHARDSFLOW
#include "ProcessLib/RichardsFlow/CreateRichardsFlowProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_RICHARDSMECHANICS
#include "ProcessLib/RichardsMechanics/CreateRichardsMechanicsProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_SMALLDEFORMATION
#include "ProcessLib/SmallDeformation/CreateSmallDeformationProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_SMALLDEFORMATIONNONLOCAL
#include "ProcessLib/SmallDeformationNonlocal/CreateSmallDeformationNonlocalProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_TES
#include "ProcessLib/TES/CreateTESProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_TH2M
#include "ProcessLib/TH2M/CreateTH2MProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_THERMALTWOPHASEFLOWWITHPP
#include "ProcessLib/ThermalTwoPhaseFlowWithPP/CreateThermalTwoPhaseFlowWithPPProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_THERMOHYDROMECHANICS
#include "ProcessLib/ThermoHydroMechanics/CreateThermoHydroMechanicsProcess.h"
#endif
#ifdef OGS_BUILD_PROCESS_THERMOMECHANICALPHASEFIELD
#include "ProcessLib/ThermoMechanicalPhaseField/CreateThermoMechanicalPhaseFieldProcess.h"
#endif
#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
#ifdef OGS_BUILD_PROCESS_TWOPHASEFLOWWITHPRHO
#include "ProcessLib/TwoPhaseFlowWithPrho/CreateTwoPhaseFlowWithPrhoProcess.h"
#endif

namespace
{
void readGeometry(std::string const& fname, GeoLib::GEOObjects& geo_objects,
                  std::string const& dir_first, std::string const& dir_second)
{
    DBUG("Reading geometry file '{:s}'.", fname);
    GeoLib::IO::BoostXmlGmlInterface gml_reader(geo_objects);
    std::string geometry_file = BaseLib::joinPaths(dir_first, fname);
    if (!BaseLib::IsFileExisting(geometry_file))
    {
        // Fallback to reading gml from prj-file directory
        geometry_file = BaseLib::joinPaths(dir_second, fname);
        WARN("File {:s} not found in {:s}! Trying reading from {:s}.", fname,
             dir_first, dir_second);
        if (!BaseLib::IsFileExisting(geometry_file))
        {
            OGS_FATAL("Could not read geometry file {:s} in {:s}.", fname,
                      dir_second);
        }
    }
    gml_reader.readFile(geometry_file);
}

std::unique_ptr<MeshLib::Mesh> readSingleMesh(
    BaseLib::ConfigTree const& mesh_config_parameter,
    std::string const& directory)
{
    std::string const mesh_file = BaseLib::joinPaths(
        directory, mesh_config_parameter.getValue<std::string>());
    DBUG("Reading mesh file '{:s}'.", mesh_file);

    auto mesh = std::unique_ptr<MeshLib::Mesh>(MeshLib::IO::readMeshFromFile(
        mesh_file, true /* compute_element_neighbors */));
    if (!mesh)
    {
        std::filesystem::path abspath{mesh_file};
        try
        {
            abspath = std::filesystem::absolute(mesh_file);
        }
        catch (std::filesystem::filesystem_error const& e)
        {
            ERR("Determining the absolute path of '{}' failed: {}", mesh_file,
                e.what());
        }

        OGS_FATAL("Could not read mesh from '{:s}' file. No mesh added.",
                  abspath.string());
    }

#ifdef DOXYGEN_DOCU_ONLY
    //! \ogs_file_attr{prj__meshes__mesh__axially_symmetric}
    mesh_config_parameter.getConfigAttributeOptional<bool>("axially_symmetric");
#endif  // DOXYGEN_DOCU_ONLY

    if (auto const axially_symmetric =
            //! \ogs_file_attr{prj__mesh__axially_symmetric}
        mesh_config_parameter.getConfigAttributeOptional<bool>(
            "axially_symmetric"))
    {
        mesh->setAxiallySymmetric(*axially_symmetric);
        if (mesh->getDimension() == 3 && mesh->isAxiallySymmetric())
        {
            OGS_FATAL("3D mesh cannot be axially symmetric.");
        }
    }

    return mesh;
}

std::vector<std::unique_ptr<MeshLib::Mesh>> readMeshes(
    BaseLib::ConfigTree const& config, std::string const& directory,
    std::string const& project_directory)
{
    std::vector<std::unique_ptr<MeshLib::Mesh>> meshes;

    GeoLib::GEOObjects geoObjects;

    //! \ogs_file_param{prj__meshes}
    auto optional_meshes = config.getConfigSubtreeOptional("meshes");
    if (optional_meshes)
    {
        DBUG("Reading multiple meshes.");
        //! \ogs_file_param{prj__meshes__mesh}
        auto const configs = optional_meshes->getConfigParameterList("mesh");
        std::transform(configs.begin(), configs.end(),
                       std::back_inserter(meshes),
                       [&directory](auto const& mesh_config)
                       { return readSingleMesh(mesh_config, directory); });
        if (auto const geometry_file_name =
                //! \ogs_file_param{prj__geometry}
            config.getConfigParameterOptional<std::string>("geometry"))
        {
            readGeometry(*geometry_file_name, geoObjects, directory,
                         project_directory);
        }
    }
    else
    {  // Read single mesh with geometry.
        meshes.push_back(
            //! \ogs_file_param{prj__mesh}
            readSingleMesh(config.getConfigParameter("mesh"), directory));

        auto const geometry_file_name =
            //! \ogs_file_param{prj__geometry}
            config.getConfigParameter<std::string>("geometry");
        readGeometry(geometry_file_name, geoObjects, directory,
                     project_directory);
    }

    {  // generate meshes from geometries
        std::unique_ptr<MeshGeoToolsLib::SearchLength> search_length_algorithm =
            MeshGeoToolsLib::createSearchLengthAlgorithm(config, *meshes[0]);
        bool const multiple_nodes_allowed = false;
        auto additional_meshes =
            MeshGeoToolsLib::constructAdditionalMeshesFromGeoObjects(
                geoObjects, *meshes[0], std::move(search_length_algorithm),
                multiple_nodes_allowed);

        std::move(begin(additional_meshes), end(additional_meshes),
                  std::back_inserter(meshes));
    }

    auto mesh_names = MeshLib::views::names | ranges::to<std::vector>() |
                      ranges::actions::sort;
    auto const sorted_names = meshes | mesh_names;
    auto const unique_names = meshes | mesh_names | ranges::actions::unique;
    if (unique_names.size() < sorted_names.size())
    {
        WARN(
            "Mesh names aren't unique. From project file read mesh names are:");
        for (auto const& name : meshes | MeshLib::views::names)
        {
            INFO("- {}", name);
        }
    }

    auto const zero_mesh_field_data_by_material_ids =
        //! \ogs_file_param{prj__zero_mesh_field_data_by_material_ids}
        config.getConfigParameterOptional<std::vector<int>>(
            "zero_mesh_field_data_by_material_ids");
    if (zero_mesh_field_data_by_material_ids)
    {
        WARN(
            "Tag 'zero_mesh_field_data_by_material_ids` is experimental. Its "
            "name may be changed, or it may be removed due to its "
            "corresponding feature becomes a single tool. Please use it with "
            "care!");
        MeshToolsLib::zeroMeshFieldDataByMaterialIDs(
            *meshes[0], *zero_mesh_field_data_by_material_ids);
    }

    MeshLib::setMeshSpaceDimension(meshes);

    return meshes;
}

std::vector<GeoLib::NamedRaster> readRasters(
    BaseLib::ConfigTree const& config, std::string const& raster_directory,
    GeoLib::MinMaxPoints const& min_max_points)
{
    INFO("readRasters ...");
    std::vector<GeoLib::NamedRaster> named_rasters;

    //! \ogs_file_param{prj__rasters}
    auto optional_rasters = config.getConfigSubtreeOptional("rasters");
    if (optional_rasters)
    {
        //! \ogs_file_param{prj__rasters__raster}
        auto const configs = optional_rasters->getConfigSubtreeList("raster");
        std::transform(
            configs.begin(), configs.end(), std::back_inserter(named_rasters),
            [&raster_directory, &min_max_points](auto const& raster_config)
            {
                return GeoLib::IO::readRaster(raster_config, raster_directory,
                                              min_max_points);
            });
    }
    INFO("readRasters done");
    return named_rasters;
}

// for debugging raster reading implementation
// void writeRasters(std::vector<GeoLib::NamedRaster> const& named_rasters,
//                  std::string const& output_directory)
//{
//    for (auto const& named_raster : named_rasters)
//    {
// #if defined(USE_PETSC)
//        int my_mpi_rank;
//        MPI_Comm_rank(MPI_COMM_WORLD, &my_mpi_rank);
// #endif
//        FileIO::AsciiRasterInterface::writeRasterAsASC(
//            named_raster.raster, output_directory + "/" +
//                                     named_raster.raster_name +
// #if defined(USE_PETSC)
//                                     "_" + std::to_string(my_mpi_rank) +
// #endif
//                                     ".asc");
//    }
//}

std::vector<int> parseMaterialIdString(
    std::string const& material_id_string,
    MeshLib::PropertyVector<int> const* const material_ids)
{
    if (material_id_string == "*")
    {
        if (material_ids == nullptr)
        {
            OGS_FATAL(
                "MaterialIDs property is not defined in the mesh but it is "
                "required to parse '*' definition.");
        }

        std::vector<int> material_ids_of_this_medium =
            *material_ids |
            ranges::views::adjacent_remove_if(std::equal_to<>()) |
            ranges::to_vector;
        BaseLib::makeVectorUnique(material_ids_of_this_medium);
        DBUG("Catch all medium definition for material ids {}.",
             fmt::join(material_ids_of_this_medium, ", "));
        return material_ids_of_this_medium;
    }

    // Usual case of ids separated by comma.
    return BaseLib::splitMaterialIdString(material_id_string);
}

template <typename T, typename CreateMedium>
requires std::convertible_to<decltype(std::declval<CreateMedium>()(
                                 std::declval<int>())),
                             std::shared_ptr<T>>
void createMediumForId(int const id,
                       std::map<int, std::shared_ptr<T>>& _media,
                       std::vector<int> const& material_ids_of_this_medium,
                       CreateMedium&& create_medium)
{
    if (_media.find(id) != end(_media))
    {
        OGS_FATAL(
            "Multiple media were specified for the same material id "
            "'{:d}'. Keep in mind, that if no material id is "
            "specified, it is assumed to be 0 by default.",
            id);
    }

    if (id == material_ids_of_this_medium[0])
    {
        _media[id] = create_medium(id);
    }
    else
    {
        _media[id] = _media[material_ids_of_this_medium[0]];
    }
}

}  // namespace

ProjectData::ProjectData() = default;

ProjectData::ProjectData(BaseLib::ConfigTree const& project_config,
                         std::string const& project_directory,
                         std::string const& output_directory,
                         std::string const& mesh_directory,
                         [[maybe_unused]] std::string const& script_directory)
    : _mesh_vec(readMeshes(project_config, mesh_directory, project_directory)),
      _named_rasters(readRasters(project_config, project_directory,
                                 GeoLib::AABB(_mesh_vec[0]->getNodes().begin(),
                                              _mesh_vec[0]->getNodes().end())
                                     .getMinMaxPoints()))
{
    // for debugging raster reading implementation
    // writeRasters(_named_rasters, output_directory);
    if (auto const python_script =
            //! \ogs_file_param{prj__python_script}
        project_config.getConfigParameterOptional<std::string>("python_script"))
    {
        namespace py = pybind11;

#ifdef OGS_EMBED_PYTHON_INTERPRETER
        _py_scoped_interpreter.emplace(ApplicationsLib::setupEmbeddedPython());
#endif

        // Append to python's module search path
        auto py_path = py::module::import("sys").attr("path");
        py_path.attr("append")(script_directory);  // .prj or -s directory

        auto const script_path =
            BaseLib::joinPaths(script_directory, *python_script);

        // Evaluate in scope of main module
        py::object scope = py::module::import("__main__").attr("__dict__");
        // add (global) variables
        auto globals = py::dict(scope);
        globals["ogs_prj_directory"] = project_directory;
        globals["ogs_mesh_directory"] = mesh_directory;
        globals["ogs_script_directory"] = script_directory;
        try
        {
            py::eval_file(script_path, scope);
        }
        catch (py::error_already_set const& e)
        {
            OGS_FATAL("Error evaluating python script {}: {}", script_path,
                      e.what());
        }
    }

    //! \ogs_file_param{prj__curves}
    parseCurves(project_config.getConfigSubtreeOptional("curves"));

    auto parameter_names_for_transformation =
        //! \ogs_file_param{prj__parameters}
        parseParameters(project_config.getConfigSubtree("parameters"));

    _local_coordinate_system = ParameterLib::createCoordinateSystem(
        //! \ogs_file_param{prj__local_coordinate_system}
        project_config.getConfigSubtreeOptional("local_coordinate_system"),
        _parameters);

    for (auto& parameter : _parameters)
    {
        if (std::find(begin(parameter_names_for_transformation),
                      end(parameter_names_for_transformation),
                      parameter->name) !=
            end(parameter_names_for_transformation))
        {
            if (!_local_coordinate_system)
            {
                OGS_FATAL(
                    "The parameter '{:s}' is using the local coordinate system "
                    "but no local coordinate system was provided.",
                    parameter->name);
            }
            parameter->setCoordinateSystem(*_local_coordinate_system);
        }

        parameter->initialize(_parameters);
    }

    //! \ogs_file_param{prj__process_variables}
    parseProcessVariables(project_config.getConfigSubtree("process_variables"));

    //! \ogs_file_param{prj__media}
    parseMedia(project_config.getConfigSubtreeOptional("media"));

    //! \ogs_file_param{prj__linear_solvers}
    parseLinearSolvers(project_config.getConfigSubtree("linear_solvers"));

    auto chemical_solver_interface = parseChemicalSolverInterface(
        //! \ogs_file_param{prj__chemical_system}
        project_config.getConfigSubtreeOptional("chemical_system"),
        output_directory);

    //! \ogs_file_param{prj__processes}
    parseProcesses(project_config.getConfigSubtree("processes"),
                   project_directory, output_directory,
                   std::move(chemical_solver_interface));

    //! \ogs_file_param{prj__nonlinear_solvers}
    parseNonlinearSolvers(project_config.getConfigSubtree("nonlinear_solvers"));

    //! \ogs_file_param{prj__time_loop}
    parseTimeLoop(project_config.getConfigSubtree("time_loop"),
                  output_directory);
}

void ProjectData::parseProcessVariables(
    BaseLib::ConfigTree const& process_variables_config)
{
    DBUG("Parse process variables:");

    std::set<std::string> names;

    for (auto var_config
         //! \ogs_file_param{prj__process_variables__process_variable}
         : process_variables_config.getConfigSubtreeList("process_variable"))
    {
        // Either the mesh name is given, or the first mesh's name will be
        // taken. Taking the first mesh's value is deprecated.
        auto const mesh_name =
            //! \ogs_file_param{prj__process_variables__process_variable__mesh}
            var_config.getConfigParameter<std::string>("mesh",
                                                       _mesh_vec[0]->getName());

        auto& mesh = MeshLib::findMeshByName(_mesh_vec, mesh_name);

        auto pv = ProcessLib::ProcessVariable{var_config, mesh, _mesh_vec,
                                              _parameters, _curves};
        if (!names.insert(pv.getName()).second)
        {
            OGS_FATAL("A process variable with name `{:s}' already exists.",
                      pv.getName());
        }

        _process_variables.push_back(std::move(pv));
    }
}

std::vector<std::string> ProjectData::parseParameters(
    BaseLib::ConfigTree const& parameters_config)
{
    using namespace ProcessLib;

    std::set<std::string> names;
    std::vector<std::string> parameter_names_for_transformation;

    DBUG("Reading parameters:");
    for (auto parameter_config :
         //! \ogs_file_param{prj__parameters__parameter}
         parameters_config.getConfigSubtreeList("parameter"))
    {
        auto p = ParameterLib::createParameter(parameter_config, _mesh_vec,
                                               _named_rasters, _curves);
        if (!names.insert(p->name).second)
        {
            OGS_FATAL("A parameter with name `{:s}' already exists.", p->name);
        }

        auto const use_local_coordinate_system =
            //! \ogs_file_param{prj__parameters__parameter__use_local_coordinate_system}
            parameter_config.getConfigParameterOptional<bool>(
                "use_local_coordinate_system");
        if (!!use_local_coordinate_system && *use_local_coordinate_system)
        {
            parameter_names_for_transformation.push_back(p->name);
        }

        _parameters.push_back(std::move(p));
    }

    _parameters.push_back(
        std::make_unique<ParameterLib::ConstantParameter<double>>(
            ProcessLib::DeactivatedSubdomain::zero_parameter_name, 0.0));
    _parameters.push_back(
        std::make_unique<ParameterLib::ConstantParameter<double>>(
            ProcessLib::Process::constant_one_parameter_name, 1.0));

    return parameter_names_for_transformation;
}

void ProjectData::parseMedia(
    std::optional<BaseLib::ConfigTree> const& media_config)
{
    if (!media_config)
    {
        return;
    }

    DBUG("Reading media:");

    if (_mesh_vec.empty() || _mesh_vec[0] == nullptr)
    {
        ERR("A mesh is required to define medium materials.");
        return;
    }

    for (auto const& medium_config :
         //! \ogs_file_param{prj__media__medium}
         media_config->getConfigSubtreeList("medium"))
    {
        auto create_medium = [dim = _mesh_vec[0]->getDimension(),
                              &medium_config, this](int const id)
        {
            return MaterialPropertyLib::createMedium(
                id, _mesh_vec[0]->getDimension(), medium_config, _parameters,
                _local_coordinate_system ? &*_local_coordinate_system : nullptr,
                _curves);
        };

        auto const material_id_string =
            //! \ogs_file_attr{prj__media__medium__id}
            medium_config.getConfigAttribute<std::string>("id", "0");

        std::vector<int> const material_ids_of_this_medium =
            parseMaterialIdString(material_id_string,
                                  materialIDs(*_mesh_vec[0]));

        for (auto const& id : material_ids_of_this_medium)
        {
            createMediumForId(id, _media, material_ids_of_this_medium,
                              create_medium);
        }
    }

    if (_media.empty())
    {
        OGS_FATAL("No entity is found inside <media>.");
    }
}

std::unique_ptr<ChemistryLib::ChemicalSolverInterface>
ProjectData::parseChemicalSolverInterface(
    std::optional<BaseLib::ConfigTree> const& config,
    std::string const& output_directory)
{
    if (!config)
    {
        return nullptr;
    }

    std::unique_ptr<ChemistryLib::ChemicalSolverInterface>
        chemical_solver_interface;
#ifdef OGS_BUILD_PROCESS_COMPONENTTRANSPORT
    INFO(
        "Ready for initializing interface to a chemical solver for water "
        "chemistry calculation.");

    auto const chemical_solver =
        //! \ogs_file_attr{prj__chemical_system__chemical_solver}
        config->getConfigAttribute<std::string>("chemical_solver");

    if (boost::iequals(chemical_solver, "Phreeqc"))
    {
        INFO(
            "Configuring phreeqc interface for water chemistry calculation "
            "using file-based approach.");

        chemical_solver_interface = ChemistryLib::createChemicalSolverInterface<
            ChemistryLib::ChemicalSolver::Phreeqc>(_mesh_vec, _linear_solvers,
                                                   *config, output_directory);
    }
    else if (boost::iequals(chemical_solver, "PhreeqcKernel"))
    {
        OGS_FATAL(
            "The chemical solver option of PhreeqcKernel is not accessible for "
            "the time being. Please set 'Phreeqc'' as the chemical solver for "
            "reactive transport modeling.");
    }
    else if (boost::iequals(chemical_solver, "SelfContained"))
    {
        INFO(
            "Use self-contained chemical solver for water chemistry "
            "calculation.");

        chemical_solver_interface = ChemistryLib::createChemicalSolverInterface<
            ChemistryLib::ChemicalSolver::SelfContained>(
            _mesh_vec, _linear_solvers, *config, output_directory);
    }
    else
    {
        OGS_FATAL(
            "Unknown chemical solver. Please specify either Phreeqc or "
            "PhreeqcKernel as the solver for water chemistry calculation "
            "instead.");
    }
#else
    (void)output_directory;

    OGS_FATAL(
        "Found the type of the process to be solved is not component transport "
        "process. Please specify the process type to ComponentTransport. At "
        "the present, water chemistry calculation is only available for "
        "component transport process.");
#endif
    return chemical_solver_interface;
}

void ProjectData::parseProcesses(
    BaseLib::ConfigTree const& processes_config,
    std::string const& project_directory,
    std::string const& output_directory,
    [[maybe_unused]] std::unique_ptr<ChemistryLib::ChemicalSolverInterface>&&
        chemical_solver_interface)
{
    (void)project_directory;  // to avoid compilation warning
    (void)output_directory;   // to avoid compilation warning

    DBUG("Reading processes:");
    //! \ogs_file_param{prj__processes__process}
    for (auto process_config : processes_config.getConfigSubtreeList("process"))
    {
        auto const type =
            //! \ogs_file_param{prj__processes__process__type}
            process_config.peekConfigParameter<std::string>("type");

        auto const name =
            //! \ogs_file_param{prj__processes__process__name}
            process_config.getConfigParameter<std::string>("name");

        [[maybe_unused]] auto const integration_order =
            //! \ogs_file_param{prj__processes__process__integration_order}
            process_config.getConfigParameter<int>("integration_order");

        std::unique_ptr<ProcessLib::Process> process;

        auto jacobian_assembler = ProcessLib::createJacobianAssembler(
            //! \ogs_file_param{prj__processes__process__jacobian_assembler}
            process_config.getConfigSubtreeOptional("jacobian_assembler"));

#ifdef OGS_BUILD_PROCESS_STEADYSTATEDIFFUSION
        if (type == "STEADY_STATE_DIFFUSION")
        {
            // The existence check of the in the configuration referenced
            // process variables is checked in the physical process.
            // TODO at the moment we have only one mesh, later there can be
            // several meshes. Then we have to assign the referenced mesh
            // here.
            process =
                ProcessLib::SteadyStateDiffusion::createSteadyStateDiffusion(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _mesh_vec, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_LIQUIDFLOW
            if (type == "LIQUID_FLOW")
        {
            process = ProcessLib::LiquidFlow::createLiquidFlowProcess(
                name, *_mesh_vec[0], std::move(jacobian_assembler),
                _process_variables, _parameters, integration_order,
                process_config, _mesh_vec, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_STOKESFLOW
            if (type == "StokesFlow")
        {
            WARN(
                "The StokesFlow process is deprecated and will be removed in "
                "OGS-6.5.5.");
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process =
                        ProcessLib::StokesFlow::createStokesFlowProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters, integration_order,
                            process_config, _media);
                    break;
                default:
                    OGS_FATAL(
                        "StokesFlow process does not support given "
                        "dimension {:d}",
                        _mesh_vec[0]->getDimension());
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_TES
            if (type == "TES")
        {
            WARN(
                "The TES process is deprecated and will be removed in "
                "OGS-6.5.5.");
            process = ProcessLib::TES::createTESProcess(
                name, *_mesh_vec[0], std::move(jacobian_assembler),
                _process_variables, _parameters, integration_order,
                process_config);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_TH2M
            if (type == "TH2M")
        {
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::TH2M::createTH2MProcess<2>(
                        name, *_mesh_vec[0], std::move(jacobian_assembler),
                        _process_variables, _parameters,
                        _local_coordinate_system, integration_order,
                        process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::TH2M::createTH2MProcess<3>(
                        name, *_mesh_vec[0], std::move(jacobian_assembler),
                        _process_variables, _parameters,
                        _local_coordinate_system, integration_order,
                        process_config, _media);
                    break;
                default:
                    OGS_FATAL("TH2M process does not support given dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_HEATCONDUCTION
            if (type == "HEAT_CONDUCTION")
        {
            process = ProcessLib::HeatConduction::createHeatConductionProcess(
                name, *_mesh_vec[0], std::move(jacobian_assembler),
                _process_variables, _parameters, integration_order,
                process_config, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_HEATTRANSPORTBHE
            if (type == "HEAT_TRANSPORT_BHE")
        {
            if (_mesh_vec[0]->getDimension() != 3)
            {
                OGS_FATAL(
                    "HEAT_TRANSPORT_BHE can only work with a 3-dimensional "
                    "mesh! ");
            }

            process =
                ProcessLib::HeatTransportBHE::createHeatTransportBHEProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _curves, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_WELLBORESIMULATOR
            if (type == "WELLBORE_SIMULATOR")
        {
            if (_mesh_vec[0]->getDimension() != 1)
            {
                OGS_FATAL(
                    "WELLBORE_SIMULATOR can only work with a 1-dimensional "
                    "mesh!");
            }

            process =
                ProcessLib::WellboreSimulator::createWellboreSimulatorProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_HYDROMECHANICS
            if (type == "HYDRO_MECHANICS")
        {
            if (  //! \ogs_file_param{prj__processes__process__HYDRO_MECHANICS__dimension}
                process_config.getConfigParameterOptional<int>("dimension"))
            {
                OGS_FATAL(
                    "The 'dimension' tag has been removed in the merge-request "
                    "!4766."
                    "The dimension is now taken from the main mesh and the tag "
                    "must be"
                    "removed. There is a python script in the merge-request "
                    "description"
                    "for automatic conversion.");
            }
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process =
                        ProcessLib::HydroMechanics::createHydroMechanicsProcess<
                            2>(name, *_mesh_vec[0],
                               std::move(jacobian_assembler),
                               _process_variables, _parameters,
                               _local_coordinate_system, integration_order,
                               process_config, _media);
                    break;
                case 3:
                    process =
                        ProcessLib::HydroMechanics::createHydroMechanicsProcess<
                            3>(name, *_mesh_vec[0],
                               std::move(jacobian_assembler),
                               _process_variables, _parameters,
                               _local_coordinate_system, integration_order,
                               process_config, _media);
                    break;
                default:
                    OGS_FATAL(
                        "HYDRO_MECHANICS process does not support given "
                        "dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_LARGEDEFORMATION
            if (type == "LARGE_DEFORMATION")
        {
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::LargeDeformation::
                        createLargeDeformationProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::LargeDeformation::
                        createLargeDeformationProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                default:
                    OGS_FATAL(
                        "LARGE_DEFORMATION process does not support given "
                        "dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_LIE
            if (type == "HYDRO_MECHANICS_WITH_LIE")
        {
            if (  //! \ogs_file_param{prj__processes__process__HYDRO_MECHANICS_WITH_LIE__dimension}
                process_config.getConfigParameterOptional<int>("dimension"))
            {
                OGS_FATAL(
                    "The 'dimension' tag has been removed in the merge-request "
                    "!4766."
                    "The dimension is now taken from the main mesh and the tag "
                    "must be"
                    "removed. There is a python script in the merge-request "
                    "description"
                    "for automatic conversion.");
            }
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::LIE::HydroMechanics::
                        createHydroMechanicsProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                case 3:
                    process = ProcessLib::LIE::HydroMechanics::
                        createHydroMechanicsProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                default:
                    OGS_FATAL(
                        "HYDRO_MECHANICS_WITH_LIE process does not support "
                        "given dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_HT
            if (type == "HT")
        {
            process = ProcessLib::HT::createHTProcess(
                name, *_mesh_vec[0], std::move(jacobian_assembler),
                _process_variables, _parameters, integration_order,
                process_config, _mesh_vec, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_COMPONENTTRANSPORT
            if (type == "ComponentTransport")
        {
            process =
                ProcessLib::ComponentTransport::createComponentTransportProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _mesh_vec, _media,
                    std::move(chemical_solver_interface));
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_PHASEFIELD
            if (type == "PHASE_FIELD")
        {
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process =
                        ProcessLib::PhaseField::createPhaseFieldProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                case 3:
                    process =
                        ProcessLib::PhaseField::createPhaseFieldProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_RICHARDSCOMPONENTTRANSPORT
            if (type == "RichardsComponentTransport")
        {
            process = ProcessLib::RichardsComponentTransport::
                createRichardsComponentTransportProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_SMALLDEFORMATION
            if (type == "SMALL_DEFORMATION")
        {
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::SmallDeformation::
                        createSmallDeformationProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::SmallDeformation::
                        createSmallDeformationProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                default:
                    OGS_FATAL(
                        "SMALL_DEFORMATION process does not support given "
                        "dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_SMALLDEFORMATIONNONLOCAL
            if (type == "SMALL_DEFORMATION_NONLOCAL")
        {
            WARN(
                "The SMALL_DEFORMATION_NONLOCAL process is deprecated and will "
                "be removed in OGS-6.5.5.");
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::SmallDeformationNonlocal::
                        createSmallDeformationNonlocalProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                case 3:
                    process = ProcessLib::SmallDeformationNonlocal::
                        createSmallDeformationNonlocalProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                default:
                    OGS_FATAL(
                        "SMALL_DEFORMATION_NONLOCAL process does not support "
                        "given dimension {:d}",
                        _mesh_vec[0]->getDimension());
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_LIE
            if (type == "SMALL_DEFORMATION_WITH_LIE")
        {
            if (  //! \ogs_file_param{prj__processes__process__SMALL_DEFORMATION_WITH_LIE__dimension}
                process_config.getConfigParameterOptional<int>("dimension"))
            {
                OGS_FATAL(
                    "The 'dimension' tag has been removed in the merge-request "
                    "!4766."
                    "The dimension is now taken from the main mesh and the tag "
                    "must be"
                    "removed. There is a python script in the merge-request "
                    "description"
                    "for automatic conversion.");
            }
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::LIE::SmallDeformation::
                        createSmallDeformationProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                case 3:
                    process = ProcessLib::LIE::SmallDeformation::
                        createSmallDeformationProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                default:
                    OGS_FATAL(
                        "SMALL_DEFORMATION_WITH_LIE process does not support "
                        "given dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_THERMOHYDROMECHANICS
            if (type == "THERMO_HYDRO_MECHANICS")
        {
            if (  //! \ogs_file_param{prj__processes__process__THERMO_HYDRO_MECHANICS__dimension}
                process_config.getConfigParameterOptional<int>("dimension"))
            {
                OGS_FATAL(
                    "The 'dimension' tag has been removed in the merge-request "
                    "!4766."
                    "The dimension is now taken from the main mesh and the tag "
                    "must be"
                    "removed. There is a python script in the merge-request "
                    "description"
                    "for automatic conversion.");
            }
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::ThermoHydroMechanics::
                        createThermoHydroMechanicsProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::ThermoHydroMechanics::
                        createThermoHydroMechanicsProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                default:
                    OGS_FATAL(
                        "THERMO_HYDRO_MECHANICS process does not support given "
                        "dimension");
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_THERMOMECHANICALPHASEFIELD
            if (type == "THERMO_MECHANICAL_PHASE_FIELD")
        {
            WARN(
                "The THERMO_MECHANICAL_PHASE_FIELD process is deprecated and "
                "will be removed in OGS-6.5.5.");
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::ThermoMechanicalPhaseField::
                        createThermoMechanicalPhaseFieldProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
                case 3:
                    process = ProcessLib::ThermoMechanicalPhaseField::
                        createThermoMechanicalPhaseFieldProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config);
                    break;
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_THERMOMECHANICS
            if (type == "THERMO_MECHANICS")
        {
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::ThermoMechanics::
                        createThermoMechanicsProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::ThermoMechanics::
                        createThermoMechanicsProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
            }
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_RICHARDSFLOW
            if (type == "RICHARDS_FLOW")
        {
            process = ProcessLib::RichardsFlow::createRichardsFlowProcess(
                name, *_mesh_vec[0], std::move(jacobian_assembler),
                _process_variables, _parameters, integration_order,
                process_config, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_RICHARDSMECHANICS
            if (type == "RICHARDS_MECHANICS")
        {
            if (  //! \ogs_file_param{prj__processes__process__RICHARDS_MECHANICS__dimension}
                process_config.getConfigParameterOptional<int>("dimension"))
            {
                OGS_FATAL(
                    "The 'dimension' tag has been removed in the merge-request "
                    "!4766."
                    "The dimension is now taken from the main mesh and the tag "
                    "must be"
                    "removed. There is a python script in the merge-request "
                    "description"
                    "for automatic conversion.");
            }
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::RichardsMechanics::
                        createRichardsMechanicsProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::RichardsMechanics::
                        createRichardsMechanicsProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
            }
        }
        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")
        {
            switch (_mesh_vec[0]->getDimension())
            {
                case 2:
                    process = ProcessLib::ThermoRichardsMechanics::
                        createThermoRichardsMechanicsProcess<2>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
                case 3:
                    process = ProcessLib::ThermoRichardsMechanics::
                        createThermoRichardsMechanicsProcess<3>(
                            name, *_mesh_vec[0], std::move(jacobian_assembler),
                            _process_variables, _parameters,
                            _local_coordinate_system, integration_order,
                            process_config, _media);
                    break;
            }
        }
        else
#endif

#ifdef OGS_BUILD_PROCESS_TWOPHASEFLOWWITHPP
            if (type == "TWOPHASE_FLOW_PP")
        {
            process =
                ProcessLib::TwoPhaseFlowWithPP::createTwoPhaseFlowWithPPProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_TWOPHASEFLOWWITHPRHO
            if (type == "TWOPHASE_FLOW_PRHO")
        {
            WARN(
                "The TWOPHASE_FLOW_PRHO process is deprecated and will be "
                "removed in OGS-6.5.5.");
            process = ProcessLib::TwoPhaseFlowWithPrho::
                createTwoPhaseFlowWithPrhoProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _media);
        }
        else
#endif
#ifdef OGS_BUILD_PROCESS_THERMALTWOPHASEFLOWWITHPP
            if (type == "THERMAL_TWOPHASE_WITH_PP")
        {
            process = ProcessLib::ThermalTwoPhaseFlowWithPP::
                createThermalTwoPhaseFlowWithPPProcess(
                    name, *_mesh_vec[0], std::move(jacobian_assembler),
                    _process_variables, _parameters, integration_order,
                    process_config, _media);
        }
        else
#endif
        {
            OGS_FATAL("Unknown process type: {:s}", type);
        }

        if (ranges::contains(_processes, name,
                             [](std::unique_ptr<ProcessLib::Process> const& p)
                             { return p->name; }))
        {
            OGS_FATAL("The process name '{:s}' is not unique.", name);
        }
        _processes.push_back(std::move(process));
    }
}

void ProjectData::parseTimeLoop(BaseLib::ConfigTree const& config,
                                std::string const& output_directory)
{
    DBUG("Reading time loop configuration.");

    bool const compensate_non_equilibrium_initial_residuum = std::any_of(
        std::begin(_process_variables),
        std::end(_process_variables),
        [](auto const& process_variable)
        { return process_variable.compensateNonEquilibriumInitialResiduum(); });

    _time_loop = ProcessLib::createTimeLoop(
        config, output_directory, _processes, _nonlinear_solvers, _mesh_vec,
        compensate_non_equilibrium_initial_residuum);

    if (!_time_loop)
    {
        OGS_FATAL("Initialization of time loop failed.");
    }
}

void ProjectData::parseLinearSolvers(BaseLib::ConfigTree const& config)
{
    DBUG("Reading linear solver configuration.");

    //! \ogs_file_param{prj__linear_solvers__linear_solver}
    for (auto conf : config.getConfigSubtreeList("linear_solver"))
    {
        //! \ogs_file_param{prj__linear_solvers__linear_solver__name}
        auto const name = conf.getConfigParameter<std::string>("name");
        auto const linear_solver_parser =
            MathLib::LinearSolverOptionsParser<GlobalLinearSolver>{};
        auto const solver_options =
            linear_solver_parser.parseNameAndOptions("", &conf);

        BaseLib::insertIfKeyUniqueElseError(
            _linear_solvers,
            name,
            std::make_unique<GlobalLinearSolver>(std::get<0>(solver_options),
                                                 std::get<1>(solver_options)),
            "The linear solver name is not unique");
    }
}

void ProjectData::parseNonlinearSolvers(BaseLib::ConfigTree const& config)
{
    DBUG("Reading non-linear solver configuration.");

    //! \ogs_file_param{prj__nonlinear_solvers__nonlinear_solver}
    for (auto conf : config.getConfigSubtreeList("nonlinear_solver"))
    {
        auto const ls_name =
            //! \ogs_file_param{prj__nonlinear_solvers__nonlinear_solver__linear_solver}
            conf.getConfigParameter<std::string>("linear_solver");
        auto const& linear_solver = BaseLib::getOrError(
            _linear_solvers, ls_name,
            "A linear solver with the given name does not exist.");

        //! \ogs_file_param{prj__nonlinear_solvers__nonlinear_solver__name}
        auto const name = conf.getConfigParameter<std::string>("name");
        BaseLib::insertIfKeyUniqueElseError(
            _nonlinear_solvers,
            name,
            NumLib::createNonlinearSolver(*linear_solver, conf).first,
            "The nonlinear solver name is not unique");
    }
}

void ProjectData::parseCurves(std::optional<BaseLib::ConfigTree> const& config)
{
    if (!config)
    {
        return;
    }

    DBUG("Reading curves configuration.");

    //! \ogs_file_param{prj__curves__curve}
    for (auto conf : config->getConfigSubtreeList("curve"))
    {
        //! \ogs_file_param{prj__curves__curve__name}
        auto const name = conf.getConfigParameter<std::string>("name");
        BaseLib::insertIfKeyUniqueElseError(
            _curves,
            name,
            MathLib::createPiecewiseLinearCurve<
                MathLib::PiecewiseLinearInterpolation>(conf),
            "The curve name is not unique.");
    }
}

MeshLib::Mesh& ProjectData::getMesh(std::string const& mesh_name) const
{
    return MeshLib::findMeshByName(_mesh_vec, mesh_name);
}