/* 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 . */ #ifndef OPM_MAIN_HEADER_INCLUDED #define OPM_MAIN_HEADER_INCLUDED #include # ifndef FLOW_BLACKOIL_ONLY # include # include # include # include # include # include # include # include # include # include # endif #include #include #include #include #include #include #include #include #include #if HAVE_DUNE_FEM #include #else #include #endif #if HAVE_MPI #include #include #endif #include #include namespace Opm::Properties { // this is a dummy type tag that is used to setup the parameters before the actual // simulator. namespace TTag { struct FlowEarlyBird { using InheritsFrom = std::tuple; }; } } // namespace Opm::Properties namespace Opm { template void flowEbosSetDeck(std::unique_ptr deck, std::unique_ptr eclState, std::unique_ptr schedule, std::unique_ptr summaryConfig) { using Vanguard = GetPropType; Vanguard::setExternalDeck(std::move(deck)); Vanguard::setExternalEclState(std::move(eclState)); Vanguard::setExternalSchedule(std::move(schedule)); Vanguard::setExternalSummaryConfig(std::move(summaryConfig)); } // ----------------- Main program ----------------- template int flowEbosMain(int argc, char** argv, bool outputCout, bool outputFiles) { // we always want to use the default locale, and thus spare us the trouble // with incorrect locale settings. Opm::resetLocale(); # if HAVE_DUNE_FEM Dune::Fem::MPIManager::initialize(argc, argv); # else Dune::MPIHelper::instance(argc, argv); # endif Opm::FlowMainEbos mainfunc(argc, argv, outputCout, outputFiles); return mainfunc.execute(); } } namespace Opm { // ----------------- Main class ----------------- // For now, we will either be instantiated from main() in flow.cpp, // or from a Python pybind11 module.. // NOTE (March 2020): When used from a pybind11 module, we do not neccessarily // want to run the whole simulation by calling run(), it is also // useful to just run one report step at a time. According to these different // usage scenarios, we refactored the original run() in flow.cpp into this class. class Main { private: using FlowMainEbosType = Opm::FlowMainEbos; public: Main(int argc, char** argv) : argc_(argc), argv_(argv) { } Main(const std::string &filename) { deckFilename_.assign(filename); flowProgName_.assign("flow"); argc_ = 2; saveArgs_[0] = const_cast(flowProgName_.c_str()); saveArgs_[1] = const_cast(deckFilename_.c_str()); argv_ = saveArgs_; } Main(int argc, char** argv, std::unique_ptr deck, std::unique_ptr eclipseState, std::unique_ptr schedule, std::unique_ptr summaryConfig) : argc_(argc) , argv_(argv) , deck_(std::move(deck)) , eclipseState_(std::move(eclipseState)) , schedule_(std::move(schedule)) , summaryConfig_(std::move(summaryConfig)) { } int runDynamic() { int exitCode = EXIT_SUCCESS; if (initialize_(exitCode)) { return dispatchDynamic_(); } else { return exitCode; } } template int runStatic() { int exitCode = EXIT_SUCCESS; if (initialize_(exitCode)) { return dispatchStatic_(); } else { return exitCode; } } // To be called from the Python interface code. Only do the // initialization and then return a pointer to the FlowEbosMain // object that can later be accessed directly from the Python interface // to e.g. advance the simulator one report step std::unique_ptr initFlowEbosBlackoil(int& exitCode) { exitCode = EXIT_SUCCESS; if (initialize_(exitCode)) { // TODO: check that this deck really represents a blackoil // case. E.g. check that number of phases == 3 Opm::flowEbosBlackoilSetDeck( setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosBlackoilMainInit( argc_, argv_, outputCout_, outputFiles_); } else { //NOTE: exitCode was set by initialize_() above; return std::unique_ptr(); // nullptr } } private: int dispatchDynamic_() { const auto& phases = eclipseState_->runspec().phases(); // run the actual simulator // // TODO: make sure that no illegal combinations like thermal and twophase are // requested. if ( false ) {} #ifndef FLOW_BLACKOIL_ONLY // Twophase cases else if( phases.size() == 2 ) { // oil-gas if (phases.active( Opm::Phase::GAS )) { Opm::flowEbosGasOilSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosGasOilMain(argc_, argv_, outputCout_, outputFiles_); } // oil-water else if ( phases.active( Opm::Phase::WATER ) ) { Opm::flowEbosOilWaterSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosOilWaterMain(argc_, argv_, outputCout_, outputFiles_); } else { if (outputCout_) std::cerr << "No suitable configuration found, valid are Twophase (oilwater and oilgas), polymer, solvent, or blackoil" << std::endl; return EXIT_FAILURE; } } // Polymer case else if ( phases.active( Opm::Phase::POLYMER ) ) { if ( !phases.active( Opm::Phase::WATER) ) { if (outputCout_) std::cerr << "No valid configuration is found for polymer simulation, valid options include " << "oilwater + polymer and blackoil + polymer" << std::endl; return EXIT_FAILURE; } // Need to track the polymer molecular weight // for the injectivity study if ( phases.active( Opm::Phase::POLYMW ) ) { // only oil water two phase for now assert( phases.size() == 4); return Opm::flowEbosOilWaterPolymerInjectivityMain(argc_, argv_, outputCout_, outputFiles_); } if ( phases.size() == 3 ) { // oil water polymer case Opm::flowEbosOilWaterPolymerSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosOilWaterPolymerMain(argc_, argv_, outputCout_, outputFiles_); } else { Opm::flowEbosPolymerSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosPolymerMain(argc_, argv_, outputCout_, outputFiles_); } } // Foam case else if ( phases.active( Opm::Phase::FOAM ) ) { Opm::flowEbosFoamSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosFoamMain(argc_, argv_, outputCout_, outputFiles_); } // Brine case else if ( phases.active( Opm::Phase::BRINE ) ) { if ( !phases.active( Opm::Phase::WATER) ) { if (outputCout_) std::cerr << "No valid configuration is found for brine simulation, valid options include " << "oilwater + brine and blackoil + brine" << std::endl; return EXIT_FAILURE; } if ( phases.size() == 3 ) { // oil water brine case Opm::flowEbosOilWaterBrineSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosOilWaterBrineMain(argc_, argv_, outputCout_, outputFiles_); } else { Opm::flowEbosBrineSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosBrineMain(argc_, argv_, outputCout_, outputFiles_); } } // Solvent case else if ( phases.active( Opm::Phase::SOLVENT ) ) { Opm::flowEbosSolventSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosSolventMain(argc_, argv_, outputCout_, outputFiles_); } // Energy case else if (eclipseState_->getSimulationConfig().isThermal()) { Opm::flowEbosEnergySetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosEnergyMain(argc_, argv_, outputCout_, outputFiles_); } #endif // FLOW_BLACKOIL_ONLY // Blackoil case else if( phases.size() == 3 ) { Opm::flowEbosBlackoilSetDeck(setupTime_, std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosBlackoilMain(argc_, argv_, outputCout_, outputFiles_); } else { if (outputCout_) std::cerr << "No suitable configuration found, valid are Twophase, polymer, foam, brine, solvent, energy, blackoil." << std::endl; return EXIT_FAILURE; } } template int dispatchStatic_() { Opm::flowEbosSetDeck(std::move(deck_), std::move(eclipseState_), std::move(schedule_), std::move(summaryConfig_)); return Opm::flowEbosMain(argc_, argv_, outputCout_, outputFiles_); } /// \brief Initialize /// \param exitCode The exitCode of the program. /// \return Whether to actually run the simulator. I.e. true if parsing of command line /// was successful and no --help, --print-properties, or --print-parameters have been found. template bool initialize_(int& exitCode) { Dune::Timer externalSetupTimer; externalSetupTimer.start(); handleVersionCmdLine_(argc_, argv_); // MPI setup. #if HAVE_DUNE_FEM Dune::Fem::MPIManager::initialize(argc_, argv_); int mpiRank = Dune::Fem::MPIManager::rank(); #else // the design of the plain dune MPIHelper class is quite flawed: there is no way to // get the instance without having the argc and argv parameters available and it is // not possible to determine the MPI rank and size without an instance. (IOW: the // rank() and size() methods are supposed to be static.) const auto& mpiHelper = Dune::MPIHelper::instance(argc_, argv_); int mpiRank = mpiHelper.rank(); #endif // we always want to use the default locale, and thus spare us the trouble // with incorrect locale settings. Opm::resetLocale(); // this is a work-around for a catch 22: we do not know what code path to use without // parsing the deck, but we don't know the deck without having access to the // parameters and this requires to know the type tag to be used. To solve this, we // use a type tag just for parsing the parameters before we instantiate the actual // simulator object. (Which parses the parameters again, but since this is done in an // identical manner it does not matter.) typedef TypeTagEarlyBird PreTypeTag; using PreProblem = GetPropType; PreProblem::setBriefDescription("Flow, an advanced reservoir simulator for ECL-decks provided by the Open Porous Media project."); int status = Opm::FlowMainEbos::setupParameters_(argc_, argv_); if (status != 0) { // if setupParameters_ returns a value smaller than 0, there was no error, but // the program should abort. This is the case e.g. for the --help and the // --print-properties parameters. #if HAVE_MPI if (status < 0) MPI_Finalize(); // graceful stop for --help or --print-properties command line. else MPI_Abort(MPI_COMM_WORLD, status); #endif exitCode = (status > 0) ? status : EXIT_SUCCESS; return false; // Whether to run the simulator } FileOutputMode outputMode = FileOutputMode::OUTPUT_NONE; outputCout_ = false; if (mpiRank == 0) outputCout_ = EWOMS_GET_PARAM(PreTypeTag, bool, EnableTerminalOutput); std::string deckFilename; std::string outputDir; if ( eclipseState_ ) { deckFilename = eclipseState_->getIOConfig().fullBasePath(); outputDir = eclipseState_->getIOConfig().getOutputDir(); } else { deckFilename = EWOMS_GET_PARAM(PreTypeTag, std::string, EclDeckFileName); } using PreVanguard = GetPropType; try { deckFilename = PreVanguard::canonicalDeckPath(deckFilename).string(); } catch (const std::exception& e) { if ( mpiRank == 0 ) { std::cerr << "Exception received: " << e.what() << ". Try '--help' for a usage description.\n"; } #if HAVE_MPI MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); #endif exitCode = EXIT_FAILURE; return false; } if (outputCout_) { Opm::FlowMainEbos::printBanner(); } // Create Deck and EclipseState. try { auto python = std::make_shared(); const bool init_from_restart_file = !EWOMS_GET_PARAM(PreTypeTag, bool, SchedRestart); if (outputDir.empty()) outputDir = EWOMS_GET_PARAM(PreTypeTag, std::string, OutputDir); outputMode = setupLogging(mpiRank, deckFilename, outputDir, EWOMS_GET_PARAM(PreTypeTag, std::string, OutputMode), outputCout_, "STDOUT_LOGGER"); auto parseContext = std::make_unique(std::vector> {{Opm::ParseContext::PARSE_RANDOM_SLASH, Opm::InputError::IGNORE}, {Opm::ParseContext::PARSE_MISSING_DIMS_KEYWORD, Opm::InputError::WARN}, {Opm::ParseContext::SUMMARY_UNKNOWN_WELL, Opm::InputError::WARN}, {Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputError::WARN}}); if (EWOMS_GET_PARAM(PreTypeTag, bool, EclStrictParsing)) parseContext->update(Opm::InputError::DELAYED_EXIT1); Opm::FlowMainEbos::printPRTHeader(outputCout_); if (outputCout_) { OpmLog::info("Reading deck file '" + deckFilename + "'"); } readDeck(mpiRank, deckFilename, deck_, eclipseState_, schedule_, summaryConfig_, nullptr, python, std::move(parseContext), init_from_restart_file, outputCout_); if (outputCout_) { OpmLog::info("Done reading deck file."); } setupTime_ = externalSetupTimer.elapsed(); outputFiles_ = (outputMode != FileOutputMode::OUTPUT_NONE); } catch (const std::invalid_argument& e) { if (outputCout_) { std::cerr << "Failed to create valid EclipseState object." << std::endl; std::cerr << "Exception caught: " << e.what() << std::endl; } #if HAVE_MPI MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE); #endif exitCode = EXIT_FAILURE; return false; } exitCode = EXIT_SUCCESS; return true; } Opm::filesystem::path simulationCaseName_( const std::string& casename ) { namespace fs = Opm::filesystem; const auto exists = []( const fs::path& f ) -> bool { if( !fs::exists( f ) ) return false; if( fs::is_regular_file( f ) ) return true; return fs::is_symlink( f ) && fs::is_regular_file( fs::read_symlink( f ) ); }; auto simcase = fs::path( casename ); if( exists( simcase ) ) { return simcase; } for( const auto& ext : { std::string("data"), std::string("DATA") } ) { if( exists( simcase.replace_extension( ext ) ) ) { return simcase; } } throw std::invalid_argument( "Cannot find input case " + casename ); } // This function is an extreme special case, if the program has been invoked // *exactly* as: // // flow --version // // the call is intercepted by this function which will print "flow $version" // on stdout and exit(0). void handleVersionCmdLine_(int argc, char** argv) { for ( int i = 1; i < argc; ++i ) { if (std::strcmp(argv[i], "--version") == 0) { std::cout << "flow " << Opm::moduleVersionName() << std::endl; std::exit(EXIT_SUCCESS); } } } int argc_; char** argv_; bool outputCout_; bool outputFiles_; double setupTime_; std::string deckFilename_; std::string flowProgName_; char *saveArgs_[2]; std::unique_ptr deck_; std::unique_ptr eclipseState_; std::unique_ptr schedule_; std::unique_ptr summaryConfig_; }; } // namespace Opm #endif // OPM_MAIN_HEADER_INCLUDED