diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index a8d80b0c1..0e11e86af 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory( pybind11 ) +add_subdirectory( simulators ) diff --git a/python/simulators/CMakeLists.txt b/python/simulators/CMakeLists.txt new file mode 100644 index 000000000..7a8f1d018 --- /dev/null +++ b/python/simulators/CMakeLists.txt @@ -0,0 +1,11 @@ +pybind11_add_module(simulators simulators.cpp) + +set_target_properties( simulators PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/python/simulators ) + +target_sources(simulators + PRIVATE + ../../flow/flow_ebos_blackoil.cpp) + +target_link_libraries( simulators PRIVATE opmsimulators ) + +install(TARGETS simulators DESTINATION ${PYTHON_INSTALL_PREFIX}/simulators) diff --git a/python/simulators/simulators.cpp b/python/simulators/simulators.cpp new file mode 100644 index 000000000..41c1634a6 --- /dev/null +++ b/python/simulators/simulators.cpp @@ -0,0 +1,356 @@ +#include "config.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#if HAVE_DUNE_FEM +#include +#else +#include +#endif + +#include +#include +#include + +namespace py = pybind11; + +BEGIN_PROPERTIES + +// this is a dummy type tag that is used to setup the parameters before the actual +// simulator. +NEW_TYPE_TAG(FlowEarlyBird, INHERITS_FROM(EclFlowProblem)); + +END_PROPERTIES + +namespace detail +{ + boost::filesystem::path simulationCaseName( const std::string& casename ) { + namespace fs = boost::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); + } + } + } +} + +enum class FileOutputMode { + //! \brief No output to files. + OUTPUT_NONE = 0, + //! \brief Output only to log files, no eclipse output. + OUTPUT_LOG_ONLY = 1, + //! \brief Output to all files. + OUTPUT_ALL = 3 +}; + +void ensureOutputDirExists(const std::string& cmdline_output_dir) +{ + if (!boost::filesystem::is_directory(cmdline_output_dir)) { + try { + boost::filesystem::create_directories(cmdline_output_dir); + } + catch (...) { + throw std::runtime_error("Creation of output directory '" + cmdline_output_dir + "' failed\n"); + } + } +} + +// Setup the OpmLog backends +FileOutputMode setupLogging(int mpi_rank_, const std::string& deck_filename, const std::string& cmdline_output_dir, const std::string& cmdline_output, bool output_cout_, const std::string& stdout_log_id) { + + if (!cmdline_output_dir.empty()) { + ensureOutputDirExists(cmdline_output_dir); + } + + // create logFile + using boost::filesystem::path; + path fpath(deck_filename); + std::string baseName; + std::ostringstream debugFileStream; + std::ostringstream logFileStream; + + // Strip extension "." or ".DATA" + std::string extension = boost::to_upper_copy(fpath.extension().string()); + if (extension == ".DATA" || extension == ".") { + baseName = boost::to_upper_copy(fpath.stem().string()); + } else { + baseName = boost::to_upper_copy(fpath.filename().string()); + } + + std::string output_dir = cmdline_output_dir; + if (output_dir.empty()) { + output_dir = absolute(path(baseName).parent_path()).string(); + std::cout << "Output dir = " << output_dir << std::endl; + } + + logFileStream << output_dir << "/" << baseName; + debugFileStream << output_dir << "/" << baseName; + + if (mpi_rank_ != 0) { + // Added rank to log file for non-zero ranks. + // This prevents message loss. + debugFileStream << "." << mpi_rank_; + // If the following file appears then there is a bug. + logFileStream << "." << mpi_rank_; + } + logFileStream << ".PRT"; + debugFileStream << ".DBG"; + + FileOutputMode output; + { + static std::map stringToOutputMode = + { {"none", FileOutputMode::OUTPUT_NONE }, + {"false", FileOutputMode::OUTPUT_LOG_ONLY }, + {"log", FileOutputMode::OUTPUT_LOG_ONLY }, + {"all" , FileOutputMode::OUTPUT_ALL }, + {"true" , FileOutputMode::OUTPUT_ALL }}; + auto outputModeIt = stringToOutputMode.find(cmdline_output); + if (outputModeIt != stringToOutputMode.end()) { + output = outputModeIt->second; + } + else { + output = FileOutputMode::OUTPUT_ALL; + std::cerr << "Value " << cmdline_output << + " is not a recognized output mode. Using \"all\" instead." + << std::endl; + } + } + + if (output > FileOutputMode::OUTPUT_NONE) { + std::shared_ptr prtLog = std::make_shared(logFileStream.str(), Opm::Log::NoDebugMessageTypes, false, output_cout_); + Opm::OpmLog::addBackend("ECLIPSEPRTLOG", prtLog); + prtLog->setMessageLimiter(std::make_shared()); + prtLog->setMessageFormatter(std::make_shared(false)); + } + + if (output >= FileOutputMode::OUTPUT_LOG_ONLY) { + std::string debugFile = debugFileStream.str(); + std::shared_ptr debugLog = std::make_shared(debugFileStream.str(), Opm::Log::DefaultMessageTypes, false, output_cout_); + Opm::OpmLog::addBackend("DEBUGLOG", debugLog); + } + + if (mpi_rank_ == 0) { + std::shared_ptr streamLog = std::make_shared(std::cout, Opm::Log::StdoutMessageTypes); + Opm::OpmLog::addBackend(stdout_log_id, streamLog); + streamLog->setMessageFormatter(std::make_shared(true)); + } + return output; +} + +void setupMessageLimiter(const Opm::MessageLimits msgLimits, const std::string& stdout_log_id) { + std::shared_ptr stream_log = Opm::OpmLog::getBackend(stdout_log_id); + + const std::map limits = {{Opm::Log::MessageType::Note, + msgLimits.getCommentPrintLimit(0)}, + {Opm::Log::MessageType::Info, + msgLimits.getMessagePrintLimit(0)}, + {Opm::Log::MessageType::Warning, + msgLimits.getWarningPrintLimit(0)}, + {Opm::Log::MessageType::Error, + msgLimits.getErrorPrintLimit(0)}, + {Opm::Log::MessageType::Problem, + msgLimits.getProblemPrintLimit(0)}, + {Opm::Log::MessageType::Bug, + msgLimits.getBugPrintLimit(0)}}; + stream_log->setMessageLimiter(std::make_shared(10, limits)); +} + +class BlackOilSimulator +{ +public: + BlackOilSimulator() + { + argc_ = 2; + argv_ = new char*[2]; + argv_[0] = new char[200]; + char argv0[] = "flow"; + std::strcpy(argv_[0], argv0); + argv_[1] = new char[200]; + } + + ~BlackOilSimulator() + { + delete[] argv_[0]; + delete[] argv_[1]; + delete[] argv_; + } + + void setDeck( const Opm::Deck& deck ) + { + deck_ = std::make_shared< Opm::Deck >(deck); + } + + void setEclipseState( const Opm::EclipseState& eclipseState ) + { + eclipseState_ = std::make_shared< Opm::EclipseState >(eclipseState); + } + + void setSchedule( const Opm::Schedule& schedule ) + { + schedule_ = std::make_shared< Opm::Schedule >(schedule); + } + + void setSummaryConfig( const Opm::SummaryConfig& summaryConfig ) + { + summaryConfig_ = std::make_shared< Opm::SummaryConfig >(summaryConfig); + } + + int run() + { + int argc = argc_; + char **argv = argv_; + + Dune::Timer externalSetupTimer; + externalSetupTimer.start(); + + detail::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 TTAG(FlowEarlyBird) PreTypeTag; + typedef GET_PROP_TYPE(PreTypeTag, Problem) PreProblem; + + 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 + MPI_Finalize(); +#endif + return (status >= 0)?status:0; + } + + FileOutputMode outputMode = FileOutputMode::OUTPUT_NONE; + bool outputCout = false; + if (mpiRank == 0) + outputCout = EWOMS_GET_PARAM(PreTypeTag, bool, EnableTerminalOutput); + + std::string deckFilename = eclipseState_->getIOConfig().fullBasePath(); + std::string outputDir = eclipseState_->getIOConfig().getOutputDir(); + + if (outputCout) { + Opm::FlowMainEbos::printBanner(); + } + Opm::ErrorGuard errorGuard; + outputMode = setupLogging(mpiRank, + deckFilename, + outputDir, + EWOMS_GET_PARAM(PreTypeTag, std::string, OutputMode), + outputCout, "STDOUT_LOGGER"); + + if (mpiRank == 0) { + setupMessageLimiter(schedule_->getMessageLimits(), "STDOUT_LOGGER"); + } + + const auto& phases = Opm::Runspec(*deck_).phases(); + bool outputFiles = (outputMode != FileOutputMode::OUTPUT_NONE); + + // Blackoil case + if( phases.size() == 3 ) { + Opm::flowEbosBlackoilSetDeck(externalSetupTimer.elapsed(), *deck_, *eclipseState_, *schedule_, *summaryConfig_); + return Opm::flowEbosBlackoilMain(argc, argv, outputCout, outputFiles); + } + else + { + if (outputCout) + std::cerr << "Only blackoil configuration is supported!" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; + } + +private: + int argc_; + char **argv_; + + std::shared_ptr deck_; + std::shared_ptr eclipseState_; + std::shared_ptr schedule_; + std::shared_ptr summaryConfig_; +}; + +PYBIND11_MODULE(simulators, m) +{ + py::class_(m, "BlackOilSimulator") + .def(py::init<>()) + .def("run", &BlackOilSimulator::run) + .def("setDeck", &BlackOilSimulator::setDeck) + .def("setEclipseState", &BlackOilSimulator::setEclipseState) + .def("setSchedule", &BlackOilSimulator::setSchedule) + .def("setSummaryConfig", &BlackOilSimulator::setSummaryConfig); +}