diff --git a/NumLib/StaggeredCoupling/CreateStaggeredCoupling-impl.h b/NumLib/StaggeredCoupling/CreateStaggeredCoupling-impl.h new file mode 100644 index 0000000000000000000000000000000000000000..0bf073387909fef494e02585cd6ec9272a2ab08b --- /dev/null +++ b/NumLib/StaggeredCoupling/CreateStaggeredCoupling-impl.h @@ -0,0 +1,230 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2023, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + * Created on November 21, 2023, 5:12 PM + */ + +#pragma once + +#include <numeric> + +#include "BaseLib/Error.h" +#include "CreateStaggeredCoupling.h" +#include "StaggeredCoupling.h" + +namespace NumLib +{ +using CouplingNodeVariant = std::variant<CouplingNode, RootCouplingNode>; + +template <typename ProcessData> +void checkLocalCouplingParameters( + std::vector<std::unique_ptr<ProcessData>> const& per_process_data, + std::vector<LocalCouplingParameters> const& all_local_coupling_parameters) +{ + // Check whether the process names in the sub-coupling definitions exist in + // the process data. + for (auto const& local_coupling_parameters : all_local_coupling_parameters) + { + for (auto const& process_name : local_coupling_parameters.process_names) + { + if (std::none_of( + per_process_data.begin(), + per_process_data.end(), + [&process_name](auto const& process_data) + { return process_data->process_name == process_name; })) + { + OGS_FATAL( + "The given process name '{}' for the element " + "'time_loop/global_process_coupling/" + "local_coupling_processes/process_name' is not found " + "in the element 'time_loop/global_process_coupling/" + "local_coupling_processes/process_name' in the project " + "file.", + process_name); + } + } + } +} + +/// Create coupling nodes that do not have local-coupling nodes. +template <typename ProcessData> +std::vector<CouplingNodeVariant> createRegularCouplingNodes( + std::vector<std::unique_ptr<ProcessData>> const& per_process_data, + std::vector<LocalCouplingParameters> const& all_local_coupling_parameters, + int const global_max_coupling_iterations, + std::vector<std::unique_ptr<NumLib::ConvergenceCriterion>>& + global_coupling_conv_criteria) +{ + std::vector<CouplingNodeVariant> coupling_nodes; + + for (auto const& process_data : per_process_data) + { + auto const& process_name = process_data->process_name; + + // If process_data->process_name occurs in local_coupling_parameters + // do nothing + if (std::any_of( + all_local_coupling_parameters.begin(), + all_local_coupling_parameters.end(), + [&process_name](auto const& local_coupling_parameters) + { + auto const& process_names = + local_coupling_parameters.process_names; + return std::find(process_names.begin(), process_names.end(), + process_name) != process_names.end(); + })) + { + continue; + } + + std::string const used_process_name = + process_name.empty() ? "not given" : process_name; + CouplingNode regular_node{ + used_process_name, + std::move(global_coupling_conv_criteria[process_data->process_id]), + global_max_coupling_iterations, + process_data->process_id, + }; + coupling_nodes.emplace_back(std::move(regular_node)); + } + + return coupling_nodes; +} + +/// Create coupling nodes that have local-coupling nodes. +template <typename ProcessData> +std::vector<CouplingNodeVariant> createRootCouplingNodes( + std::vector<std::unique_ptr<ProcessData>> const& per_process_data, + std::vector<LocalCouplingParameters> const& all_local_coupling_parameters, + int const global_max_coupling_iterations, + std::vector<std::unique_ptr<NumLib::ConvergenceCriterion>>& + global_coupling_conv_criteria) +{ + std::vector<CouplingNodeVariant> coupling_nodes; + + for (auto const& local_coupling_parameters : all_local_coupling_parameters) + { + RootCouplingNode root_node = {global_max_coupling_iterations, {}}; + + for (auto const& local_process_name : + local_coupling_parameters.process_names) + { + if (auto it = std::find_if( + per_process_data.begin(), + per_process_data.end(), + [&local_process_name](auto const& process_data) { + return process_data->process_name == local_process_name; + }); + it != per_process_data.end()) + { + auto const& process_data = *it; + + CouplingNode regular_node{ + process_data->process_name, + std::move(global_coupling_conv_criteria[process_data + ->process_id]), + local_coupling_parameters.max_iterations, + process_data->process_id}; + + root_node.sub_coupling_nodes.emplace_back( + std::move(regular_node)); + } + } + coupling_nodes.emplace_back(std::move(root_node)); + } + + return coupling_nodes; +} + +template <typename ProcessData> +std::vector<CouplingNodeVariant> createCouplingNodes( + std::vector<std::unique_ptr<ProcessData>> const& per_process_data, + std::vector<LocalCouplingParameters> const& all_local_coupling_parameters, + int const global_max_coupling_iterations, + std::vector<std::unique_ptr<NumLib::ConvergenceCriterion>>& + global_coupling_conv_criteria) +{ + checkLocalCouplingParameters(per_process_data, + all_local_coupling_parameters); + + // First, get the coupling nodes that do not have local-coupling nodes. + std::vector<CouplingNodeVariant> coupling_nodes = + createRegularCouplingNodes( + per_process_data, all_local_coupling_parameters, + global_max_coupling_iterations, global_coupling_conv_criteria); + + // Second, get the coupling nodes that have local-coupling nodes. + std::vector<CouplingNodeVariant> root_coupling_nodes = + createRootCouplingNodes(per_process_data, all_local_coupling_parameters, + global_max_coupling_iterations, + global_coupling_conv_criteria); + + std::size_t const num_coupling_nodes = + coupling_nodes.size() + + std::accumulate( + root_coupling_nodes.begin(), + root_coupling_nodes.end(), + 0, + [](std::size_t accumulated_sizes, const auto& coupling_node) + { + return accumulated_sizes + + std::get<RootCouplingNode>(coupling_node) + .sub_coupling_nodes.size(); + }); + + if (num_coupling_nodes != per_process_data.size()) + { + OGS_FATAL( + "The number of all coupling nodes including sub-nodes is not " + "identical to the number of the processes! Please check the " + "element by tag global_process_coupling in the project file."); + } + + if (coupling_nodes.empty()) + { + coupling_nodes = std::move(root_coupling_nodes); + } + else + { + coupling_nodes.reserve(coupling_nodes.size() + + root_coupling_nodes.size()); + + std::move(std::begin(root_coupling_nodes), + std::end(root_coupling_nodes), + std::back_inserter(coupling_nodes)); + } + + return coupling_nodes; +} + +/// Create a StaggeredCoupling instance from the given configuration. +template <typename ProcessData> +std::unique_ptr<StaggeredCoupling> createStaggeredCoupling( + BaseLib::ConfigTree const& config, + std::vector<std::unique_ptr<ProcessData>> const& per_process_data) +{ + auto [global_coupling_conv_criteria, all_local_coupling_parameters, + max_coupling_iterations] = parseCoupling(config); + + if (per_process_data.size() != global_coupling_conv_criteria.size()) + { + OGS_FATAL( + "The number of convergence criteria of the global " + "staggered coupling loop is not identical to the number of the " + "processes! Please check the element by tag " + "global_process_coupling in the project file."); + } + + auto coupling_nodes = createCouplingNodes( + per_process_data, all_local_coupling_parameters, + max_coupling_iterations, global_coupling_conv_criteria); + + return std::make_unique<StaggeredCoupling>(max_coupling_iterations, + std::move(coupling_nodes)); +} +} // namespace NumLib diff --git a/NumLib/StaggeredCoupling/CreateStaggeredCoupling.cpp b/NumLib/StaggeredCoupling/CreateStaggeredCoupling.cpp new file mode 100644 index 0000000000000000000000000000000000000000..acdd6d96c62b7b39b2855aa4326c6d17755799fb --- /dev/null +++ b/NumLib/StaggeredCoupling/CreateStaggeredCoupling.cpp @@ -0,0 +1,145 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2023, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + * Created on November 21, 2023, 3:37 PM + */ + +#include "CreateStaggeredCoupling.h" + +#include "BaseLib/ConfigTree.h" +#include "BaseLib/Error.h" +#include "NumLib/ODESolver/ConvergenceCriterion.h" +#include "StaggeredCoupling.h" + +namespace NumLib +{ + +/// The function returns a set of process names and the maximum iteration number +/// of a sub-coupling or nothing. +std::vector<LocalCouplingParameters> parseLocalCoupling( + BaseLib::ConfigTree const& config, const std::size_t max_process_number) +{ + auto const local_coupling_configs = + //! \ogs_file_param{prj__time_loop__global_process_coupling__local_coupling_processes} + config.getConfigSubtreeList("local_coupling_processes"); + + if (local_coupling_configs.empty()) + { + return {}; + } + + std::vector<LocalCouplingParameters> all_local_coupling_parameters; + std::vector<std::string> all_local_process_names; + for (auto const& local_coupling_config : local_coupling_configs) + { + std::vector<std::string> process_names; + + for ( + auto name : + local_coupling_config + //! \ogs_file_param{prj__time_loop__global_process_coupling__local_coupling_processes__process_name} + .getConfigParameterList<std::string>("process_name")) + { + if (std::find(process_names.begin(), process_names.end(), name) != + process_names.end()) + { + OGS_FATAL( + "The name of locally coupled process, {}, is not unique.", + name); + } + process_names.push_back(name); + + all_local_process_names.push_back(name); + } + + if (process_names.size() > max_process_number) + { + OGS_FATAL( + "The number of the locally coupled processes is greater " + "than the number of total coupled processes. " + "Please check the number of elements in the tag " + "'time_loop/global_process_coupling/" + "local_coupling_processes' in the project file."); + } + + INFO("There are {:d} locally coupled processes.", process_names.size()); + + int max_iterations = + local_coupling_config + //! \ogs_file_param{prj__time_loop__global_process_coupling__local_coupling_processes__max_iter} + .getConfigParameter<int>("max_iter"); + + all_local_coupling_parameters.push_back( + {process_names, max_iterations}); + } + + // std::adjacent_find only finds equal elements directly next to each other. + // Therefore, a copy of the vector is sorted first and then it is checked + // for duplicated element. + std::vector<std::string> copy_all_local_process_names = + all_local_process_names; + std::sort(copy_all_local_process_names.begin(), + copy_all_local_process_names.end()); + if (auto it = std::adjacent_find(copy_all_local_process_names.begin(), + copy_all_local_process_names.end()); + it != copy_all_local_process_names.end()) + { + OGS_FATAL( + "There are process names appearing in multiple tags of " + "'time_loop/global_process_coupling/local_coupling_processes'. For " + "example, name {}", + *it); + } + + return all_local_coupling_parameters; +} + +std::tuple<std::vector<std::unique_ptr<NumLib::ConvergenceCriterion>>, + std::vector<LocalCouplingParameters>, + int> +parseCoupling(BaseLib::ConfigTree const& config) +{ + auto const& coupling_config + //! \ogs_file_param{prj__time_loop__global_process_coupling} + = config.getConfigSubtreeOptional("global_process_coupling"); + + std::vector<std::unique_ptr<NumLib::ConvergenceCriterion>> + global_coupling_conv_criteria; + + std::vector<LocalCouplingParameters> all_local_coupling_parameters; + + int max_coupling_iterations = 1; + if (coupling_config) + { + max_coupling_iterations + //! \ogs_file_param{prj__time_loop__global_process_coupling__max_iter} + = coupling_config->getConfigParameter<int>("max_iter"); + + auto const& coupling_convergence_criteria_config = + //! \ogs_file_param{prj__time_loop__global_process_coupling__convergence_criteria} + coupling_config->getConfigSubtree("convergence_criteria"); + + auto coupling_convergence_criterion_config = + //! \ogs_file_param{prj__time_loop__global_process_coupling__convergence_criteria__convergence_criterion} + coupling_convergence_criteria_config.getConfigSubtreeList( + "convergence_criterion"); + std::transform(coupling_convergence_criterion_config.begin(), + coupling_convergence_criterion_config.end(), + std::back_inserter(global_coupling_conv_criteria), + [](BaseLib::ConfigTree const& c) + { return NumLib::createConvergenceCriterion(c); }); + + all_local_coupling_parameters = parseLocalCoupling( + *coupling_config, global_coupling_conv_criteria.size()); + } + + return {std::move(global_coupling_conv_criteria), + std::move(all_local_coupling_parameters), max_coupling_iterations}; +} + +} // namespace NumLib diff --git a/NumLib/StaggeredCoupling/CreateStaggeredCoupling.h b/NumLib/StaggeredCoupling/CreateStaggeredCoupling.h new file mode 100644 index 0000000000000000000000000000000000000000..98161d88edc9fa25ae930ce8e08d474d435914d0 --- /dev/null +++ b/NumLib/StaggeredCoupling/CreateStaggeredCoupling.h @@ -0,0 +1,47 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2023, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + * Created on November 21, 2023, 3:37 PM + */ + +#pragma once + +#include <memory> +#include <vector> + +namespace BaseLib +{ +class ConfigTree; +} + +namespace NumLib +{ +class ConvergenceCriterion; + +class StaggeredCoupling; + +struct LocalCouplingParameters +{ + std::vector<std::string> process_names; + int max_iterations; +}; + +std::tuple<std::vector<std::unique_ptr<NumLib::ConvergenceCriterion>>, + std::vector<LocalCouplingParameters>, + int> +parseCoupling(BaseLib::ConfigTree const& config); + +/// Create a StaggeredCoupling instance from the given configuration. +template <typename ProcessData> +std::unique_ptr<StaggeredCoupling> createStaggeredCoupling( + BaseLib::ConfigTree const& config, + std::vector<std::unique_ptr<ProcessData>> const& per_process_data); + +} // namespace NumLib + +#include "CreateStaggeredCoupling-impl.h" \ No newline at end of file