opm-simulators/opm/simulators/flow/FlowMain.hpp

549 lines
22 KiB
C++
Raw Normal View History

/*
Copyright 2013, 2014, 2015 SINTEF ICT, Applied Mathematics.
Copyright 2014 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2015 IRIS AS
Copyright 2014 STATOIL 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 <http://www.gnu.org/licenses/>.
*/
2024-01-31 07:14:50 -06:00
#ifndef OPM_FLOW_MAIN_HEADER_INCLUDED
#define OPM_FLOW_MAIN_HEADER_INCLUDED
#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
#include <opm/input/eclipse/EclipseState/IOConfig/IOConfig.hpp>
#include <opm/input/eclipse/EclipseState/InitConfig/InitConfig.hpp>
#include <opm/models/utils/start.hh>
#include <opm/simulators/flow/Banners.hpp>
#include <opm/simulators/flow/FlowUtils.hpp>
#include <opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp>
#if HAVE_DUNE_FEM
#include <dune/fem/misc/mpimanager.hh>
#else
#include <dune/common/parallel/mpihelper.hh>
#endif
#include <charconv>
2023-08-15 02:32:10 -05:00
#include <cstddef>
2023-02-28 06:04:22 -06:00
#include <memory>
namespace Opm::Parameters {
template<class TypeTag, class MyTypeTag>
struct OutputInterval { using type = Properties::UndefinedProperty; };
template<class TypeTag, class MyTypeTag>
struct EnableLoggingFalloutWarning { using type = Properties::UndefinedProperty; };
// Do not merge parallel output files or warn about them
template<class TypeTag>
struct EnableLoggingFalloutWarning<TypeTag, Properties::TTag::FlowProblem>
{ static constexpr bool value = false; };
template<class TypeTag>
struct OutputInterval<TypeTag, Properties::TTag::FlowProblem>
{ static constexpr int value = 1; };
} // namespace Opm::Parameters
namespace Opm {
class Deck;
2024-01-31 07:14:50 -06:00
// The FlowMain class is the black-oil simulator.
template <class TypeTag>
2024-01-31 07:14:50 -06:00
class FlowMain
{
public:
using MaterialLawManager = typename GetProp<TypeTag, Properties::MaterialLaw>::EclMaterialLawManager;
using ModelSimulator = GetPropType<TypeTag, Properties::Simulator>;
using Grid = GetPropType<TypeTag, Properties::Grid>;
using GridView = GetPropType<TypeTag, Properties::GridView>;
using Problem = GetPropType<TypeTag, Properties::Problem>;
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using FluidSystem = GetPropType<TypeTag, Properties::FluidSystem>;
using Simulator = SimulatorFullyImplicitBlackoil<TypeTag>;
2024-01-31 07:14:50 -06:00
FlowMain(int argc, char **argv, bool output_cout, bool output_files )
: argc_{argc}, argv_{argv},
output_cout_{output_cout}, output_files_{output_files}
{
}
// Read the command line parameters. Throws an exception if something goes wrong.
static int setupParameters_(int argc, char** argv, Parallel::Communication comm)
{
if (!Parameters::MetaData::registrationOpen()) {
// We have already successfully run setupParameters_().
// For the dynamically chosen runs (as from the main flow
// executable) we must run this function again with the
// real typetag to be used, as the first time was with the
// "FlowEarlyBird" typetag. However, for the static ones (such
// as 'flow_onephase_energy') it has already been run with the
// correct typetag.
return EXIT_SUCCESS;
}
// register the flow specific parameters
Parameters::registerParam<TypeTag, Parameters::OutputInterval>
("Specify the number of report steps between two consecutive writes of restart data");
Parameters::registerParam<TypeTag, Parameters::EnableLoggingFalloutWarning>
("Developer option to see whether logging was on non-root processors. "
"In that case it will be appended to the *.DBG or *.PRT files");
ThreadManager<TypeTag>::registerParameters();
Simulator::registerParameters();
// register the base parameters
registerAllParameters_<TypeTag>(/*finalizeRegistration=*/false);
// hide the parameters unused by flow. TODO: this is a pain to maintain
Parameters::Hide<Parameters::EnableGravity>();
Parameters::Hide<Parameters::EnableGridAdaptation>();
// this parameter is actually used in eWoms, but the flow well model
// hard-codes the assumption that the intensive quantities cache is enabled,
// so flow crashes. Let's hide the parameter for that reason.
Parameters::Hide<Parameters::EnableIntensiveQuantityCache>();
// thermodynamic hints are not implemented/required by the eWoms blackoil
// model
Parameters::Hide<Parameters::EnableThermodynamicHints>();
// in flow only the deck file determines the end time of the simulation
Parameters::Hide<Parameters::EndTime<Scalar>>();
// time stepping is not done by the eWoms code in flow
Parameters::Hide<Parameters::InitialTimeStepSize<Scalar>>();
Parameters::Hide<Parameters::MaxTimeStepDivisions>();
Parameters::Hide<Parameters::MaxTimeStepSize<Scalar>>();
Parameters::Hide<Parameters::MinTimeStepSize<Scalar>>();
Parameters::Hide<Parameters::PredeterminedTimeStepsFile>();
// flow also does not use the eWoms Newton method
Parameters::Hide<Parameters::NewtonMaxError<Scalar>>();
Parameters::Hide<Parameters::NewtonTolerance<Scalar>>();
Parameters::Hide<Parameters::NewtonTargetIterations>();
Parameters::Hide<Parameters::NewtonVerbose>();
Parameters::Hide<Parameters::NewtonWriteConvergence>();
// the default eWoms checkpoint/restart mechanism does not work with flow
Parameters::Hide<Parameters::RestartTime<Scalar>>();
Parameters::hideParam<TypeTag, Parameters::RestartWritingInterval>();
// hide all vtk related it is not currently possible to do this dependet on if the vtk writing is used
2024-04-05 05:53:20 -05:00
//if(not(Parameters::get<TypeTag,Properties::EnableVtkOutput>())){
Parameters::Hide<Parameters::VtkWriteOilFormationVolumeFactor>();
Parameters::Hide<Parameters::VtkWriteOilSaturationPressure>();
Parameters::Hide<Parameters::VtkWriteOilVaporizationFactor>();
Parameters::Hide<Parameters::VtkWritePorosity>();
Parameters::Hide<Parameters::VtkWritePotentialGradients>();
Parameters::Hide<Parameters::VtkWritePressures>();
Parameters::Hide<Parameters::VtkWritePrimaryVars>();
Parameters::Hide<Parameters::VtkWritePrimaryVarsMeaning>();
Parameters::Hide<Parameters::VtkWriteProcessRank>();
Parameters::Hide<Parameters::VtkWriteRelativePermeabilities>();
Parameters::Hide<Parameters::VtkWriteSaturatedGasOilVaporizationFactor>();
Parameters::Hide<Parameters::VtkWriteSaturatedOilGasDissolutionFactor>();
Parameters::Hide<Parameters::VtkWriteSaturationRatios>();
Parameters::Hide<Parameters::VtkWriteSaturations>();
Parameters::Hide<Parameters::VtkWriteTemperature>();
Parameters::Hide<Parameters::VtkWriteViscosities>();
Parameters::Hide<Parameters::VtkWriteWaterFormationVolumeFactor>();
Parameters::Hide<Parameters::VtkWriteGasDissolutionFactor>();
Parameters::Hide<Parameters::VtkWriteGasFormationVolumeFactor>();
Parameters::Hide<Parameters::VtkWriteGasSaturationPressure>();
Parameters::Hide<Parameters::VtkWriteIntrinsicPermeabilities>();
Parameters::Hide<Parameters::VtkWriteTracerConcentration>();
Parameters::Hide<Parameters::VtkWriteExtrusionFactor>();
Parameters::Hide<Parameters::VtkWriteFilterVelocities>();
Parameters::Hide<Parameters::VtkWriteDensities>();
Parameters::Hide<Parameters::VtkWriteDofIndex>();
Parameters::Hide<Parameters::VtkWriteMobilities>();
//}
Parameters::Hide<Parameters::VtkWriteAverageMolarMasses>();
Parameters::Hide<Parameters::VtkWriteFugacities>();
Parameters::Hide<Parameters::VtkWriteFugacityCoeffs>();
Parameters::Hide<Parameters::VtkWriteMassFractions>();
Parameters::Hide<Parameters::VtkWriteMolarities>();
Parameters::Hide<Parameters::VtkWriteMoleFractions>();
Parameters::Hide<Parameters::VtkWriteTotalMassFractions>();
Parameters::Hide<Parameters::VtkWriteTotalMoleFractions>();
2024-04-04 06:31:57 -05:00
Parameters::Hide<Parameters::VtkWriteTortuosities>();
Parameters::Hide<Parameters::VtkWriteDiffusionCoefficients>();
Parameters::Hide<Parameters::VtkWriteEffectiveDiffusionCoefficients>();
// hide average density option
Parameters::Hide<Parameters::UseAverageDensityMsWells>();
2022-10-14 03:46:45 -05:00
Parameters::endRegistration();
int mpiRank = comm.rank();
// read in the command line parameters
int status = ::Opm::setupParameters_<TypeTag>(argc, const_cast<const char**>(argv), /*doRegistration=*/false, /*allowUnused=*/true, /*handleHelp=*/(mpiRank==0));
if (status == 0) {
// deal with unknown parameters.
int unknownKeyWords = 0;
if (mpiRank == 0) {
unknownKeyWords = Parameters::printUnused(std::cerr);
}
int globalUnknownKeyWords = comm.sum(unknownKeyWords);
unknownKeyWords = globalUnknownKeyWords;
if ( unknownKeyWords )
{
if ( mpiRank == 0 )
{
std::string msg = "Aborting simulation due to unknown "
"parameters. Please query \"flow --help\" for "
"supported command line parameters.";
if (OpmLog::hasBackend("STREAMLOG"))
{
OpmLog::error(msg);
}
else {
std::cerr << msg << std::endl;
}
}
return EXIT_FAILURE;
}
// deal with --print-parameters and unknown parameters.
if (Parameters::Get<Parameters::PrintParameters>() == 1) {
if (mpiRank == 0) {
Parameters::printValues();
}
return -1;
}
}
return status;
}
/// This is the main function of Flow. It runs a complete simulation with the
/// given grid and simulator classes, based on the user-specified command-line
/// input.
int execute()
{
2024-01-31 07:14:50 -06:00
return execute_(&FlowMain::runSimulator, /*cleanup=*/true);
}
int executeInitStep()
{
2024-01-31 07:14:50 -06:00
return execute_(&FlowMain::runSimulatorInit, /*cleanup=*/false);
}
// Returns true unless "EXIT" was encountered in the schedule
// section of the input datafile.
int executeStep()
{
return simulator_->runStep(*simtimer_);
}
// Called from Python to cleanup after having executed the last
// executeStep()
int executeStepsCleanup()
{
SimulatorReport report = simulator_->finalize();
runSimulatorAfterSim_(report);
return report.success.exit_status;
}
ModelSimulator* getSimulatorPtr()
{
return modelSimulator_.get();
}
SimulatorTimer* getSimTimer()
{
return simtimer_.get();
}
/// Get the size of the previous report step
double getPreviousReportStepSize()
{
return simtimer_->stepLengthTaken();
}
private:
// called by execute() or executeInitStep()
2024-01-31 07:14:50 -06:00
int execute_(int (FlowMain::* runOrInitFunc)(), bool cleanup)
{
auto logger = [this](const std::exception& e, const std::string& message_start) {
std::ostringstream message;
message << message_start << e.what();
if (this->output_cout_) {
// in some cases exceptions are thrown before the logging system is set
// up.
if (OpmLog::hasBackend("STREAMLOG")) {
OpmLog::error(message.str());
}
else {
std::cout << message.str() << "\n";
}
}
2023-07-19 07:05:19 -05:00
detail::checkAllMPIProcesses();
return EXIT_FAILURE;
};
try {
// deal with some administrative boilerplate
Dune::Timer setupTimerAfterReadingDeck;
setupTimerAfterReadingDeck.start();
int status = setupParameters_(this->argc_, this->argv_, FlowGenericVanguard::comm());
if (status) {
return status;
}
setupParallelism();
setupModelSimulator();
createSimulator();
this->deck_read_time_ = modelSimulator_->vanguard().setupTime();
this->total_setup_time_ = setupTimerAfterReadingDeck.elapsed() + this->deck_read_time_;
// if run, do the actual work, else just initialize
int exitCode = (this->*runOrInitFunc)();
if (cleanup) {
executeCleanup_();
}
return exitCode;
}
catch (const TimeSteppingBreakdown& e) {
Do a graceful exit instead of MPI_Abort for expected exceptions. Instead of unconditionally issuing MPI_Abort if we encounter a fatal exception, we try to test whether all processes have experienced this exception and if this is the case just terminate nomally with a exit code that signals an error. We still use MPI_Abort if not all processes get an exception as this is the only way to make sure that the program aborts. This approach also works around issues in some MPI implementations that might not correctly return the error. Multiple messages like this are gone now: ``` -------------------------------------------------------------------------- MPI_ABORT was invoked on rank 1 in communicator MPI_COMM_WORLD with errorcode 1. NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes. You may or may not see output from other processes, depending on exactly when Open MPI kills them. -------------------------------------------------------------------------- [smaug.dr-blatt.de:129359] 1 more process has sent help message help-mpi-api.txt / mpi-abort [smaug.dr-blatt.de:129359] Set MCA parameter "orte_base_help_aggregate" to 0 to see all help / error messages ``` Bu we still see something like this: ``` -------------------------------------------------------------------------- Primary job terminated normally, but 1 process returned a non-zero exit code. Per user-direction, the job has been aborted. -------------------------------------------------------------------------- -------------------------------------------------------------------------- mpirun detected that one or more processes exited with non-zero status, thus causing the job to be terminated. The first process to do so was: Process name: [[35057,1],0] Exit code: 1 -------------------------------------------------------------------------- ```
2023-07-19 05:15:35 -05:00
auto exitCode = logger(e, "Simulation aborted: ");
executeCleanup_();
return exitCode;
}
catch (const std::exception& e) {
Do a graceful exit instead of MPI_Abort for expected exceptions. Instead of unconditionally issuing MPI_Abort if we encounter a fatal exception, we try to test whether all processes have experienced this exception and if this is the case just terminate nomally with a exit code that signals an error. We still use MPI_Abort if not all processes get an exception as this is the only way to make sure that the program aborts. This approach also works around issues in some MPI implementations that might not correctly return the error. Multiple messages like this are gone now: ``` -------------------------------------------------------------------------- MPI_ABORT was invoked on rank 1 in communicator MPI_COMM_WORLD with errorcode 1. NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes. You may or may not see output from other processes, depending on exactly when Open MPI kills them. -------------------------------------------------------------------------- [smaug.dr-blatt.de:129359] 1 more process has sent help message help-mpi-api.txt / mpi-abort [smaug.dr-blatt.de:129359] Set MCA parameter "orte_base_help_aggregate" to 0 to see all help / error messages ``` Bu we still see something like this: ``` -------------------------------------------------------------------------- Primary job terminated normally, but 1 process returned a non-zero exit code. Per user-direction, the job has been aborted. -------------------------------------------------------------------------- -------------------------------------------------------------------------- mpirun detected that one or more processes exited with non-zero status, thus causing the job to be terminated. The first process to do so was: Process name: [[35057,1],0] Exit code: 1 -------------------------------------------------------------------------- ```
2023-07-19 05:15:35 -05:00
auto exitCode = logger(e, "Simulation aborted as program threw an unexpected exception: ");
executeCleanup_();
return exitCode;
}
}
void executeCleanup_() {
// clean up
mergeParallelLogFiles();
}
2019-10-03 05:13:13 -05:00
protected:
void setupParallelism()
{
// determine the rank of the current process and the number of processes
// involved in the simulation. MPI must have already been initialized
// here. (yes, the name of this method is misleading.)
auto comm = FlowGenericVanguard::comm();
mpi_rank_ = comm.rank();
mpi_size_ = comm.size();
2019-10-03 05:13:13 -05:00
#if _OPENMP
// If openMP is available, default to 2 threads per process unless
// OMP_NUM_THREADS is set or command line --threads-per-process used.
// Issue a warning if both OMP_NUM_THREADS and --threads-per-process are set,
// but let the environment variable take precedence.
constexpr int default_threads = 2;
const int requested_threads = Parameters::Get<Parameters::ThreadsPerProcess>();
int threads = requested_threads > 0 ? requested_threads : default_threads;
const char* env_var = getenv("OMP_NUM_THREADS");
if (env_var) {
int omp_num_threads = -1;
auto result = std::from_chars(env_var, env_var + std::strlen(env_var), omp_num_threads);
if (result.ec == std::errc() && omp_num_threads > 0) {
// Set threads to omp_num_threads if it was successfully parsed and is positive
threads = omp_num_threads;
// Warning in 'Main.hpp', where this code is duplicated
// if (requested_threads > 0) {
// OpmLog::warning("Environment variable OMP_NUM_THREADS takes precedence over the --threads-per-process cmdline argument.");
// }
} else {
OpmLog::warning("Invalid value for OMP_NUM_THREADS environment variable.");
}
}
// We are not limiting this to the number of processes
// reported by OpenMP as on some hardware (and some OpenMPI
// versions) this will be 1 when run with mpirun
omp_set_num_threads(threads);
2019-10-03 05:13:13 -05:00
#endif
using ThreadManager = GetPropType<TypeTag, Properties::ThreadManager>;
ThreadManager::init(false);
2019-10-03 05:13:13 -05:00
}
void mergeParallelLogFiles()
{
// force closing of all log files.
OpmLog::removeAllBackends();
if (mpi_rank_ != 0 || mpi_size_ < 2 || !this->output_files_ || !modelSimulator_) {
return;
}
detail::mergeParallelLogFiles(eclState().getIOConfig().getOutputDir(),
Parameters::Get<Parameters::EclDeckFileName>(),
Parameters::get<TypeTag, Parameters::EnableLoggingFalloutWarning>());
}
void setupModelSimulator()
{
modelSimulator_ = std::make_unique<ModelSimulator>(FlowGenericVanguard::comm(), /*verbose=*/false);
modelSimulator_->executionTimer().start();
modelSimulator_->model().applyInitialSolution();
}
const EclipseState& eclState() const
{ return modelSimulator_->vanguard().eclState(); }
EclipseState& eclState()
{ return modelSimulator_->vanguard().eclState(); }
const Schedule& schedule() const
{ return modelSimulator_->vanguard().schedule(); }
2017-10-29 15:06:19 -05:00
// Run the simulator.
int runSimulator()
{
2024-01-31 07:14:50 -06:00
return runSimulatorInitOrRun_(&FlowMain::runSimulatorRunCallback_);
}
int runSimulatorInit()
{
2024-01-31 07:14:50 -06:00
return runSimulatorInitOrRun_(&FlowMain::runSimulatorInitCallback_);
}
private:
// Callback that will be called from runSimulatorInitOrRun_().
int runSimulatorRunCallback_()
{
SimulatorReport report = simulator_->run(*simtimer_);
runSimulatorAfterSim_(report);
return report.success.exit_status;
}
// Callback that will be called from runSimulatorInitOrRun_().
int runSimulatorInitCallback_()
{
simulator_->init(*simtimer_);
return EXIT_SUCCESS;
}
// Output summary after simulation has completed
void runSimulatorAfterSim_(SimulatorReport &report)
{
if (! this->output_cout_) {
return;
}
const int threads
#if !defined(_OPENMP) || !_OPENMP
= 1;
#else
= omp_get_max_threads();
#endif
printFlowTrailer(mpi_size_, threads, total_setup_time_, deck_read_time_, report, simulator_->model().localAccumulatedReports());
detail::handleExtraConvergenceOutput(report,
Parameters::get<TypeTag, Parameters::OutputExtraConvergenceInfo>(),
R"(OutputExtraConvergenceInfo (--output-extra-convergence-info))",
eclState().getIOConfig().getOutputDir(),
eclState().getIOConfig().getBaseName());
}
// Run the simulator.
2024-01-31 07:14:50 -06:00
int runSimulatorInitOrRun_(int (FlowMain::* initOrRunFunc)())
{
const auto& schedule = this->schedule();
auto& ioConfig = eclState().getIOConfig();
simtimer_ = std::make_unique<SimulatorTimer>();
// initialize variables
const auto& initConfig = eclState().getInitConfig();
2023-08-15 02:32:10 -05:00
simtimer_->init(schedule, static_cast<std::size_t>(initConfig.getRestartStep()));
if (this->output_cout_) {
std::ostringstream oss;
// This allows a user to catch typos and misunderstandings in the
// use of simulator parameters.
if (Parameters::printUnused(oss)) {
std::cout << "----------------- Unrecognized parameters: -----------------\n";
std::cout << oss.str();
std::cout << "----------------------------------------------------------------" << std::endl;
}
}
if (!ioConfig.initOnly()) {
if (this->output_cout_) {
std::string msg;
msg = "\n\n================ Starting main simulation loop ===============\n";
OpmLog::info(msg);
}
return (this->*initOrRunFunc)();
}
else {
if (this->output_cout_) {
std::cout << "\n\n================ Simulation turned off ===============\n" << std::flush;
}
return EXIT_SUCCESS;
}
}
protected:
/// This is the main function of Flow.
// Create simulator instance.
// Writes to:
// simulator_
void createSimulator()
{
// Create the simulator instance.
simulator_ = std::make_unique<Simulator>(*modelSimulator_);
}
Grid& grid()
{ return modelSimulator_->vanguard().grid(); }
private:
std::unique_ptr<ModelSimulator> modelSimulator_;
int mpi_rank_ = 0;
int mpi_size_ = 1;
std::any parallel_information_;
std::unique_ptr<Simulator> simulator_;
std::unique_ptr<SimulatorTimer> simtimer_;
int argc_;
char **argv_;
bool output_cout_;
bool output_files_;
double total_setup_time_ = 0.0;
double deck_read_time_ = 0.0;
};
} // namespace Opm
2024-01-31 07:14:50 -06:00
#endif // OPM_FLOW_MAIN_HEADER_INCLUDED