diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake
index af9783ff1..f2e0c9fc7 100644
--- a/CMakeLists_files.cmake
+++ b/CMakeLists_files.cmake
@@ -41,6 +41,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/core/props/satfunc/RelpermDiagnostics.cpp
opm/simulators/timestepping/SimulatorReport.cpp
opm/simulators/flow/countGlobalCells.cpp
+ opm/simulators/flow/ConvergenceOutputConfiguration.cpp
opm/simulators/flow/KeywordValidation.cpp
opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.cpp
opm/simulators/flow/ValidationFunctions.cpp
@@ -171,6 +172,7 @@ endif()
list (APPEND TEST_SOURCE_FILES
tests/test_ALQState.cpp
tests/test_blackoil_amg.cpp
+ tests/test_convergenceoutputconfiguration.cpp
tests/test_convergencereport.cpp
tests/test_deferredlogger.cpp
tests/test_eclinterregflows.cpp
@@ -271,6 +273,7 @@ list (APPEND PUBLIC_HEADER_FILES
opm/simulators/flow/countGlobalCells.hpp
opm/simulators/flow/BlackoilModelEbos.hpp
opm/simulators/flow/BlackoilModelParametersEbos.hpp
+ opm/simulators/flow/ConvergenceOutputConfiguration.hpp
opm/simulators/flow/FlowMainEbos.hpp
opm/simulators/flow/Main.hpp
opm/simulators/flow/NonlinearSolverEbos.hpp
diff --git a/opm/simulators/flow/ConvergenceOutputConfiguration.cpp b/opm/simulators/flow/ConvergenceOutputConfiguration.cpp
new file mode 100644
index 000000000..384752a66
--- /dev/null
+++ b/opm/simulators/flow/ConvergenceOutputConfiguration.cpp
@@ -0,0 +1,128 @@
+/*
+ Copyright 2022 Equinor ASA.
+
+ This file is part of the Open Porous Media project (OPM).
+
+ OPM is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OPM is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OPM. If not, see .
+*/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace {
+ std::vector tokenizeOptionValues(std::string_view options)
+ {
+ const auto split = std::regex { R"(\s*,\s*)" };
+ return {
+ std::cregex_token_iterator {
+ options.begin(), options.end(), split, -1
+ },
+ std::cregex_token_iterator {}
+ };
+ }
+
+ void reportUnsupportedOptionValuesAndThrow(std::vector unsupp,
+ std::string_view optionName)
+ {
+ std::sort(unsupp.begin(), unsupp.end());
+ auto u = std::unique(unsupp.begin(), unsupp.end());
+
+ const auto pl = (std::distance(unsupp.begin(), u) != 1) ? "s" : "";
+
+ if (optionName.empty()) {
+ throw std::invalid_argument {
+ fmt::format("Unsupported convergence output "
+ "option value{}: {}\n"
+ "Supported values are \"none\", "
+ "\"steps\", and \"iterations\"",
+ pl, fmt::join(unsupp.begin(), u, ", "))
+ };
+ }
+
+ throw std::invalid_argument {
+ fmt::format("Option {}:\n - Unsupported value{}: {}\n"
+ " - Supported values are \"none\", "
+ "\"steps\", and \"iterations\"",
+ optionName, pl,
+ fmt::join(unsupp.begin(), u, ", "))
+ };
+ }
+
+ std::vector
+ getOptions(std::string_view options, std::string_view optionName)
+ {
+ using Option = Opm::ConvergenceOutputConfiguration::Option;
+
+ auto opt = std::vector{};
+
+ const auto values = std::unordered_map {
+ { "none" , Option::None },
+ { "step" , Option::Steps }, // Alias for 'steps' (plural)
+ { "steps" , Option::Steps },
+ { "iteration" , Option::Iterations }, // Alias for 'iterations' (plural)
+ { "iterations", Option::Iterations },
+ };
+
+ auto unsupp = std::vector{};
+ for (const auto& value : tokenizeOptionValues(options)) {
+ if (auto option = values.find(value); option != values.end()) {
+ opt.push_back(option->second);
+ }
+ else {
+ unsupp.push_back(value);
+ }
+ }
+
+ if (! unsupp.empty()) {
+ reportUnsupportedOptionValuesAndThrow(std::move(unsupp), optionName);
+ }
+
+ return opt;
+ }
+} // Anonymous namespace
+
+// ===========================================================================
+// Public Interface Below Separator
+// ===========================================================================
+
+Opm::ConvergenceOutputConfiguration::
+ConvergenceOutputConfiguration(std::string_view options,
+ std::string_view optionName)
+{
+ auto is_none = false;
+ for (const auto& option : getOptions(options, optionName)) {
+ if (option == Option::None) {
+ is_none = true;
+ break;
+ }
+
+ this->flag_ |= static_cast(option);
+ }
+
+ if (is_none) {
+ // Recall: "none" overrides all other options.
+ this->flag_ = std::byte{0};
+ }
+}
diff --git a/opm/simulators/flow/ConvergenceOutputConfiguration.hpp b/opm/simulators/flow/ConvergenceOutputConfiguration.hpp
new file mode 100644
index 000000000..78f30bd7a
--- /dev/null
+++ b/opm/simulators/flow/ConvergenceOutputConfiguration.hpp
@@ -0,0 +1,93 @@
+/*
+ Copyright 2022 Equinor ASA.
+
+ This file is part of the Open Porous Media project (OPM).
+
+ OPM is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OPM is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OPM. If not, see .
+*/
+
+#ifndef CONVERGENCE_OUTPUT_CONFIGURATION_HPP
+#define CONVERGENCE_OUTPUT_CONFIGURATION_HPP
+
+#include
+#include
+
+namespace Opm {
+
+/// Parse comma separated option strings into a runtime configuration object
+/// for whether to output additional convergence information and, if so,
+/// what information to output.
+///
+/// Supported option string values are
+///
+/// * "none" -- Dont want any additional convergence output.
+///
+/// * "steps" -- Want additional convergence output pertaining to the
+/// converged solution at the end of each timestep.
+///
+/// * "iterations" -- Want additional convergence output pertaining to each
+/// non-linar ieration in each timestep.
+///
+/// Option value "none" overrides all other options. In other words, if the
+/// user requests "none", then there will be no additional convergence
+/// output, even if there are other options in the option string.
+class ConvergenceOutputConfiguration
+{
+public:
+ /// Option values.
+ ///
+ /// None overrides all other options. In other words, if the user
+ /// requests None, then there will be no additional convergence output,
+ /// even if there are other options in the option string.
+ enum class Option : unsigned char {
+ None = 0,
+ Steps = 1 << 1,
+ Iterations = 1 << 2,
+ };
+
+ /// Constructor
+ ///
+ /// Parses comma separated option string into runtime configuration
+ /// option.
+ ///
+ /// \param[in] options Comma separated option string.
+ ///
+ /// \param[in] optionName Name of command line option whose value is \p
+ /// options. Used as diagnostic information only, and only if
+ /// specified.
+ explicit ConvergenceOutputConfiguration(std::string_view options,
+ std::string_view optionName = "");
+
+ /// Whether or not user wants any additional convergence output at all.
+ bool any() const
+ {
+ return this->flag_ != std::byte{0};
+ }
+
+ /// Whether or not user wants specific convergence output.
+ ///
+ /// \param[in] opt Specific convergence output type.
+ bool want(const Option opt) const
+ {
+ return std::to_integer(this->flag_ & static_cast(opt)) != 0;
+ }
+
+private:
+ /// Option flags. Treated as a small bitset.
+ std::byte flag_{0};
+};
+
+} // namespace Opm
+
+#endif // CONVERGENCE_OUTPUT_CONFIGURATION_HPP
diff --git a/opm/simulators/flow/FlowMainEbos.hpp b/opm/simulators/flow/FlowMainEbos.hpp
index 484e03ef7..dbdaa8a4c 100644
--- a/opm/simulators/flow/FlowMainEbos.hpp
+++ b/opm/simulators/flow/FlowMainEbos.hpp
@@ -25,6 +25,7 @@
#include
+#include
#include
#include
#include
@@ -574,27 +575,37 @@ namespace Opm
// Output summary after simulation has completed
void runSimulatorAfterSim_(SimulatorReport &report)
{
- if (this->output_cout_) {
- std::ostringstream ss;
- ss << "\n\n================ End of simulation ===============\n\n";
- ss << fmt::format("Number of MPI processes: {:9}\n", mpi_size_ );
-#if _OPENMP
- int threads = omp_get_max_threads();
+ if (! this->output_cout_) {
+ return;
+ }
+
+ const int threads
+#if !defined(_OPENMP) || !_OPENMP
+ = 1;
#else
- int threads = 1;
+ = omp_get_max_threads();
#endif
- ss << fmt::format("Threads per MPI process: {:9}\n", threads);
- report.reportFullyImplicit(ss);
- OpmLog::info(ss.str());
- const std::string dir = eclState().getIOConfig().getOutputDir();
+
+ std::ostringstream ss;
+ ss << "\n\n================ End of simulation ===============\n\n";
+ ss << fmt::format("Number of MPI processes: {:9}\n", mpi_size_ );
+ ss << fmt::format("Threads per MPI process: {:9}\n", threads);
+ report.reportFullyImplicit(ss);
+ OpmLog::info(ss.str());
+
+ const auto extraConvOutput = ConvergenceOutputConfiguration {
+ EWOMS_GET_PARAM(TypeTag, std::string, ExtraConvergenceOutput),
+ R"(ExtraConvergenceOutput (--extra-convergence-output))"
+ };
+
+ if (extraConvOutput.want(ConvergenceOutputConfiguration::Option::Steps)) {
namespace fs = ::std::filesystem;
- fs::path output_dir(dir);
- {
- std::string filename = eclState().getIOConfig().getBaseName() + ".INFOSTEP";
- fs::path fullpath = output_dir / filename;
- std::ofstream os(fullpath.string());
- report.fullReports(os);
- }
+
+ const auto infostep = fs::path { eclState().getIOConfig().getOutputDir() } /
+ fs::path { eclState().getIOConfig().getBaseName() }.concat(".INFOSTEP");
+
+ std::ofstream os(infostep);
+ report.fullReports(os);
}
}
diff --git a/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp b/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp
index b4922883d..83cdb890c 100644
--- a/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp
+++ b/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp
@@ -44,6 +44,12 @@ struct EnableTuning {
using type = UndefinedProperty;
};
+template
+struct ExtraConvergenceOutput
+{
+ using type = UndefinedProperty;
+};
+
template
struct EnableTerminalOutput {
static constexpr bool value = true;
@@ -57,6 +63,12 @@ struct EnableTuning {
static constexpr bool value = false;
};
+template
+struct ExtraConvergenceOutput
+{
+ static constexpr auto* value = "none";
+};
+
} // namespace Opm::Properties
namespace Opm {
@@ -137,6 +149,15 @@ public:
"Use adaptive time stepping between report steps");
EWOMS_REGISTER_PARAM(TypeTag, bool, EnableTuning,
"Honor some aspects of the TUNING keyword.");
+ EWOMS_REGISTER_PARAM(TypeTag, std::string, ExtraConvergenceOutput,
+ "Provide additional convergence output "
+ "files for diagnostic purposes. "
+ "\"none\" gives no extra output and "
+ "overrides all other options, "
+ "\"steps\" generates an INFOSTEP file, "
+ "\"iterations\" generates an INFOITER file. "
+ "Combine options with commas, e.g., "
+ "\"steps,iterations\" for multiple outputs.");
}
/// Run the simulation.
diff --git a/tests/test_convergenceoutputconfiguration.cpp b/tests/test_convergenceoutputconfiguration.cpp
new file mode 100644
index 000000000..5e83a4de8
--- /dev/null
+++ b/tests/test_convergenceoutputconfiguration.cpp
@@ -0,0 +1,160 @@
+/*
+ Copyright 2022 Equinor.
+
+ This file is part of the Open Porous Media project (OPM).
+
+ OPM is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OPM is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OPM. If not, see .
+*/
+
+#include
+
+#define BOOST_TEST_MODULE TestConvergenceOutputConfiguration
+
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+BOOST_AUTO_TEST_SUITE(Common_Operations)
+
+BOOST_AUTO_TEST_CASE(None)
+{
+ const auto config = Opm::ConvergenceOutputConfiguration{"none"};
+ BOOST_CHECK_MESSAGE(! config.any(),
+ "Configuration object with option value "
+ "\"none\" must NOT activate output");
+}
+
+BOOST_AUTO_TEST_CASE(Steps)
+{
+ const auto config = Opm::ConvergenceOutputConfiguration{"steps"};
+ BOOST_CHECK_MESSAGE(config.any(),
+ "Configuration object with supported "
+ "option value must activate option");
+ BOOST_CHECK_MESSAGE(config.want(Opm::ConvergenceOutputConfiguration::Option::Steps),
+ "Configuration object with \"steps\" "
+ "option value must activate Steps option");
+}
+
+BOOST_AUTO_TEST_CASE(Steps_Alias)
+{
+ const auto config = Opm::ConvergenceOutputConfiguration{"step"};
+ BOOST_CHECK_MESSAGE(config.any(),
+ "Configuration object with supported "
+ "option value must activate option");
+ BOOST_CHECK_MESSAGE(config.want(Opm::ConvergenceOutputConfiguration::Option::Steps),
+ "Configuration object with \"step\" "
+ "option value must activate Steps option");
+}
+
+BOOST_AUTO_TEST_CASE(Iterations)
+{
+ const auto config = Opm::ConvergenceOutputConfiguration{"iterations"};
+ BOOST_CHECK_MESSAGE(config.any(),
+ "Configuration object with supported "
+ "option value must activate option");
+ BOOST_CHECK_MESSAGE(config.want(Opm::ConvergenceOutputConfiguration::Option::Iterations),
+ "Configuration object with \"iterations\" "
+ "option value must activate Steps option");
+}
+
+BOOST_AUTO_TEST_CASE(Iterations_Alias)
+{
+ const auto config = Opm::ConvergenceOutputConfiguration{"iteration"};
+ BOOST_CHECK_MESSAGE(config.any(),
+ "Configuration object with supported "
+ "option value must activate option");
+ BOOST_CHECK_MESSAGE(config.want(Opm::ConvergenceOutputConfiguration::Option::Iterations),
+ "Configuration object with \"iterations\" "
+ "option value must activate Steps option");
+}
+
+BOOST_AUTO_TEST_CASE(Combinations)
+{
+ const auto steps_iter = Opm::ConvergenceOutputConfiguration{"steps,iterations"};
+ BOOST_CHECK_MESSAGE(steps_iter.any(),
+ "Configuration object with supported "
+ "option value must activate option");
+ BOOST_CHECK_MESSAGE(steps_iter.want(Opm::ConvergenceOutputConfiguration::Option::Steps),
+ "Configuration object with \"steps\" "
+ "option value must activate Steps option");
+ BOOST_CHECK_MESSAGE(steps_iter.want(Opm::ConvergenceOutputConfiguration::Option::Iterations),
+ "Configuration object with \"iterations\" "
+ "option value must activate Steps option");
+
+ const auto iter_steps = Opm::ConvergenceOutputConfiguration{"iterations,steps"};
+ BOOST_CHECK_MESSAGE(iter_steps.any(),
+ "Configuration object with supported "
+ "option value must activate option");
+ BOOST_CHECK_MESSAGE(iter_steps.want(Opm::ConvergenceOutputConfiguration::Option::Steps),
+ "Configuration object with \"steps\" "
+ "option value must activate Steps option");
+ BOOST_CHECK_MESSAGE(iter_steps.want(Opm::ConvergenceOutputConfiguration::Option::Iterations),
+ "Configuration object with \"iterations\" "
+ "option value must activate Steps option");
+
+ const auto none_iter_steps = Opm::ConvergenceOutputConfiguration{"none,iterations,steps"};
+ BOOST_CHECK_MESSAGE(! none_iter_steps.any(),
+ "Configuration object with any option "
+ "value \"none\" must NOT activate output");
+
+ const auto iter_none_steps = Opm::ConvergenceOutputConfiguration{"iterations,none,steps"};
+ BOOST_CHECK_MESSAGE(! iter_none_steps.any(),
+ "Configuration object with any option "
+ "value \"none\" must NOT activate output");
+
+ const auto steps_iter_none = Opm::ConvergenceOutputConfiguration{"steps,iterations, none"};
+ BOOST_CHECK_MESSAGE(! steps_iter_none.any(),
+ "Configuration object with any option "
+ "value \"none\" must NOT activate output");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Common_Operations
+
+// ---------------------------------------------------------------------------
+
+BOOST_AUTO_TEST_SUITE(Failed_Construction)
+
+BOOST_AUTO_TEST_CASE(Misprint)
+{
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"nonce"},
+ std::invalid_argument);
+
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"stepS"},
+ std::invalid_argument);
+
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"steps, iter"},
+ std::invalid_argument);
+
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"steps, iterations, non"},
+ std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(Unknown)
+{
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"Hello"},
+ std::invalid_argument);
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"meow"},
+ std::invalid_argument);
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{""},
+ std::invalid_argument);
+ BOOST_CHECK_THROW(Opm::ConvergenceOutputConfiguration{"xyz,zy;;;"},
+ std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END()