// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- // vi: set et ts=4 sw=4 sts=4: /* 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 2 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 . Consult the COPYING file in the top-level source directory of this module for the precise wording of the license and the list of copyright holders. */ /*! * \file * \copydoc Ewoms::EclBaseVanguard */ #ifndef EWOMS_ECL_BASE_VANGUARD_HH #define EWOMS_ECL_BASE_VANGUARD_HH #include #include #include #include #include #include #include #include #include #include #include #if HAVE_MPI #include #endif // HAVE_MPI #include #include #include namespace Ewoms { template class EclBaseVanguard; } BEGIN_PROPERTIES NEW_TYPE_TAG(EclBaseVanguard); // declare the properties required by the for the ecl simulator vanguard NEW_PROP_TAG(Grid); NEW_PROP_TAG(EquilGrid); NEW_PROP_TAG(Scalar); NEW_PROP_TAG(EclDeckFileName); NEW_PROP_TAG(OutputDir); NEW_PROP_TAG(EnableOpmRstFile); NEW_PROP_TAG(EclOutputInterval); NEW_PROP_TAG(IgnoreKeywords); SET_STRING_PROP(EclBaseVanguard, IgnoreKeywords, ""); SET_STRING_PROP(EclBaseVanguard, EclDeckFileName, ""); SET_INT_PROP(EclBaseVanguard, EclOutputInterval, -1); // use the deck-provided value SET_BOOL_PROP(EclBaseVanguard, EnableOpmRstFile, true); END_PROPERTIES namespace Ewoms { /*! * \ingroup EclBlackOilSimulator * * \brief Helper class for grid instantiation of ECL file-format using problems. */ template class EclBaseVanguard : public BaseVanguard { typedef BaseVanguard ParentType; typedef typename GET_PROP_TYPE(TypeTag, Vanguard) Implementation; typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar; typedef typename GET_PROP_TYPE(TypeTag, Simulator) Simulator; public: typedef typename GET_PROP_TYPE(TypeTag, Grid) Grid; typedef typename GET_PROP_TYPE(TypeTag, GridView) GridView; protected: static const int dimension = Grid::dimension; public: /*! * \brief Register the common run-time parameters for all ECL simulator vanguards. */ static void registerParameters() { EWOMS_REGISTER_PARAM(TypeTag, std::string, EclDeckFileName, "The name of the file which contains the ECL deck to be simulated"); EWOMS_REGISTER_PARAM(TypeTag, int, EclOutputInterval, "The number of report steps that ought to be skipped between two writes of ECL results"); EWOMS_REGISTER_PARAM(TypeTag, bool, EnableOpmRstFile, "Include OPM-specific keywords in the ECL restart file to enable restart of OPM simulators from these files"); EWOMS_REGISTER_PARAM(TypeTag, std::string, IgnoreKeywords, "List of Eclipse keywords which should be ignored. As a ':' separated string."); } /*! * \brief Returns the canonical path to a deck file. * * The input can either be the canonical deck file name or the name of the case * (i.e., without the .DATA extension) */ static boost::filesystem::path canonicalDeckPath(const std::string& caseName) { const auto fileExists = [](const boost::filesystem::path& f) -> bool { if (!boost::filesystem::exists(f)) return false; if (boost::filesystem::is_regular_file(f)) return true; return boost::filesystem::is_symlink(f) && boost::filesystem::is_regular_file(boost::filesystem::read_symlink(f)); }; auto simcase = boost::filesystem::path(caseName); if (fileExists(simcase)) return simcase; for (const auto& ext : { std::string("data"), std::string("DATA") }) { if (fileExists(simcase.replace_extension(ext))) return simcase; } throw std::invalid_argument("Cannot find input case '"+caseName+"'"); } /*! * \brief Set the Opm::EclipseState and the Opm::Deck object which ought to be used * when the simulator vanguard is instantiated. * * This is basically an optimization: In cases where the ECL input deck must be * examined to decide which simulator ought to be used, this avoids having to parse * the input twice. When this method is used, the caller is responsible for lifetime * management of these two objects, i.e., they are not allowed to be deleted as long * as the simulator vanguard object is alive. */ static void setExternalDeck(Opm::Deck* deck, Opm::EclipseState* eclState) { externalDeck_ = deck; externalEclState_ = eclState; } /*! * \brief Create the grid for problem data files which use the ECL file format. * * This is the file format used by the commercial ECLiPSE simulator. Usually it uses * a cornerpoint description of the grid. */ EclBaseVanguard(Simulator& simulator) : ParentType(simulator) { int myRank = 0; #if HAVE_MPI MPI_Comm_rank(MPI_COMM_WORLD, &myRank); #endif std::string fileName = EWOMS_GET_PARAM(TypeTag, std::string, EclDeckFileName); if (fileName == "") throw std::runtime_error("No input deck file has been specified as a command line argument," " or via '--ecl-deck-file-name=CASE.DATA'"); fileName = canonicalDeckPath(fileName).string(); // compute the base name of the input file name const char directorySeparator = '/'; long int i; for (i = fileName.size(); i >= 0; -- i) if (fileName[i] == directorySeparator) break; std::string baseName = fileName.substr(i + 1, fileName.size()); // remove the extension from the input file for (i = baseName.size(); i >= 0; -- i) if (baseName[i] == '.') break; std::string rawCaseName; if (i < 0) rawCaseName = baseName; else rawCaseName = baseName.substr(0, i); // transform the result to ALL_UPPERCASE caseName_ = rawCaseName; std::transform(caseName_.begin(), caseName_.end(), caseName_.begin(), ::toupper); typedef std::pair ParseModePair; typedef std::vector ParseModePairs; ParseModePairs tmp; tmp.emplace_back(Opm::ParseContext::PARSE_RANDOM_SLASH, Opm::InputError::IGNORE); tmp.emplace_back(Opm::ParseContext::PARSE_MISSING_DIMS_KEYWORD, Opm::InputError::WARN); tmp.emplace_back(Opm::ParseContext::SUMMARY_UNKNOWN_WELL, Opm::InputError::WARN); tmp.emplace_back(Opm::ParseContext::SUMMARY_UNKNOWN_GROUP, Opm::InputError::WARN); Opm::ParseContext parseContext(tmp); const std::string ignoredKeywords = EWOMS_GET_PARAM(TypeTag, std::string, IgnoreKeywords); if (ignoredKeywords.size() > 0) { size_t pos, offset = 0; while (true) { pos = ignoredKeywords.find(':', offset); if (pos == std::string::npos) { parseContext.ignoreKeyword(ignoredKeywords.substr(offset)); break; } parseContext.ignoreKeyword(ignoredKeywords.substr(offset, pos - offset)); offset = pos + 1; } } if (!externalDeck_) { if (myRank == 0) std::cout << "Reading the deck file '" << fileName << "'" << std::endl; Opm::Parser parser; internalDeck_.reset(new Opm::Deck(parser.parseFile(fileName , parseContext))); internalEclState_.reset(new Opm::EclipseState(*internalDeck_, parseContext)); deck_ = &(*internalDeck_); eclState_ = &(*internalEclState_); } else { assert(externalDeck_); assert(externalEclState_); deck_ = externalDeck_; eclState_ = externalEclState_; } if (!externalEclSchedule_) { // create the schedule object. Note that if eclState is supposed to represent // the internalized version of the deck, this constitutes a layering // violation. internalEclSchedule_.reset(new Opm::Schedule(*deck_, *eclState_, parseContext)); eclSchedule_ = &(*internalEclSchedule_); } else eclSchedule_ = externalEclSchedule_; if (!externalEclSummaryConfig_) { // create the schedule object. Note that if eclState is supposed to represent // the internalized version of the deck, this constitutes a layering // violation. internalEclSummaryConfig_.reset(new Opm::SummaryConfig(*deck_, *eclSchedule_, eclState_->getTableManager(), parseContext)); eclSummaryConfig_ = &(*internalEclSummaryConfig_); } else eclSummaryConfig_ = externalEclSummaryConfig_; // Possibly override IOConfig setting for how often RESTART files should get // written to disk (every N report step) int outputInterval = EWOMS_GET_PARAM(TypeTag, int, EclOutputInterval); if (outputInterval >= 0) eclState_->getRestartConfig().overrideRestartWriteInterval(outputInterval); asImp_().createGrids_(); asImp_().filterConnections_(); asImp_().updateOutputDir_(); asImp_().finalizeInit_(); } /*! * \brief Return a reference to the parsed ECL deck. */ const Opm::Deck& deck() const { return *deck_; } Opm::Deck& deck() { return *deck_; } /*! * \brief Return a reference to the internalized ECL deck. */ const Opm::EclipseState& eclState() const { return *eclState_; } Opm::EclipseState& eclState() { return *eclState_; } /*! * \brief Return a reference to the object that managages the ECL schedule. */ const Opm::Schedule& schedule() const { return *eclSchedule_; } Opm::Schedule& schedule() { return *eclSchedule_; } /*! * \brief Set the schedule object. * * The lifetime of this object is not managed by the vanguard, i.e., the object must * stay valid until after the vanguard gets destroyed. */ static void setExternalSchedule(Opm::Schedule* schedule) { externalEclSchedule_ = schedule; } /*! * \brief Return a reference to the object that determines which quantities ought to * be put into the ECL summary output. */ const Opm::SummaryConfig& summaryConfig() const { return *eclSummaryConfig_; } /*! * \brief Set the summary configuration object. * * The lifetime of this object is not managed by the vanguard, i.e., the object must * stay valid until after the vanguard gets destroyed. */ static void setExternalSummaryConfig(Opm::SummaryConfig* summaryConfig) { externalEclSummaryConfig_ = summaryConfig; } /*! * \brief Returns the name of the case. * * i.e., the all-uppercase version of the file name from which the * deck is loaded with the ".DATA" suffix removed. */ const std::string& caseName() const { return caseName_; } /*! * \brief Returns the number of logically Cartesian cells in each direction */ const std::array& cartesianDimensions() const { return asImp_().cartesianIndexMapper().cartesianDimensions(); } /*! * \brief Returns the overall number of cells of the logically Cartesian grid */ int cartesianSize() const { return asImp_().cartesianIndexMapper().cartesianSize(); } /*! * \brief Returns the overall number of cells of the logically EquilCartesian grid */ int equilCartesianSize() const { return asImp_().equilCartesianIndexMapper().cartesianSize(); } /*! * \brief Returns the Cartesian cell id for identifaction with ECL data */ unsigned cartesianIndex(unsigned compressedCellIdx) const { return asImp_().cartesianIndexMapper().cartesianIndex(compressedCellIdx); } /*! * \brief Return the index of the cells in the logical Cartesian grid */ unsigned cartesianIndex(const std::array& coords) const { unsigned cartIndex = coords[0]; int factor = cartesianDimensions()[0]; for (unsigned i = 1; i < dimension; ++i) { cartIndex += coords[i]*factor; factor *= cartesianDimensions()[i]; } return cartIndex; } /*! * \brief Extract Cartesian index triplet (i,j,k) of an active cell. * * \param [in] cellIdx Active cell index. * \param [out] ijk Cartesian index triplet */ void cartesianCoordinate(unsigned cellIdx, std::array& ijk) const { return asImp_().cartesianIndexMapper().cartesianCoordinate(cellIdx, ijk); } /*! * \brief Returns the Cartesian cell id given an element index for the grid used for equilibration */ unsigned equilCartesianIndex(unsigned compressedEquilCellIdx) const { return asImp_().equilCartesianIndexMapper().cartesianIndex(compressedEquilCellIdx); } /*! * \brief Extract Cartesian index triplet (i,j,k) of an active cell of the grid used for EQUIL. * * \param [in] cellIdx Active cell index. * \param [out] ijk Cartesian index triplet */ void equilCartesianCoordinate(unsigned cellIdx, std::array& ijk) const { return asImp_().equilCartesianIndexMapper().cartesianCoordinate(cellIdx, ijk); } /*! * \brief Return the names of the wells which do not penetrate any cells on the local * process. * * This is a kludge around the fact that for distributed grids, not all wells are * seen by all proccesses. */ std::unordered_set defunctWellNames() const { return std::unordered_set(); } private: void updateOutputDir_() { // update the location for output std::string outputDir = EWOMS_GET_PARAM(TypeTag, std::string, OutputDir); auto& ioConfig = eclState_->getIOConfig(); if (outputDir == "") // If no output directory parameter is specified, use the output directory // which Opm::IOConfig thinks that should be used. Normally this is the // directory in which the input files are located. outputDir = ioConfig.getOutputDir(); // ensure that the output directory exists and that it is a directory if (!boost::filesystem::is_directory(outputDir)) { try { boost::filesystem::create_directories(outputDir); } catch (...) { throw std::runtime_error("Creation of output directory '"+outputDir+"' failed\n"); } } // specify the directory output. This is not a very nice mechanism because // the eclState is supposed to be immutable here, IMO. ioConfig.setOutputDir(outputDir); ioConfig.setEclCompatibleRST(!EWOMS_GET_PARAM(TypeTag, bool, EnableOpmRstFile)); } Implementation& asImp_() { return *static_cast(this); } const Implementation& asImp_() const { return *static_cast(this); } std::string caseName_; static Opm::Deck* externalDeck_; static Opm::EclipseState* externalEclState_; static Opm::Schedule* externalEclSchedule_; static Opm::SummaryConfig* externalEclSummaryConfig_; std::unique_ptr internalDeck_; std::unique_ptr internalEclState_; std::unique_ptr internalEclSchedule_; std::unique_ptr internalEclSummaryConfig_; // these two attributes point either to the internal or to the external version of the // Deck and EclipsState objects. Opm::Deck* deck_; Opm::EclipseState* eclState_; Opm::Schedule* eclSchedule_; Opm::SummaryConfig* eclSummaryConfig_; }; template Opm::Deck* EclBaseVanguard::externalDeck_ = nullptr; template Opm::EclipseState* EclBaseVanguard::externalEclState_; template Opm::Schedule* EclBaseVanguard::externalEclSchedule_ = nullptr; template Opm::SummaryConfig* EclBaseVanguard::externalEclSummaryConfig_ = nullptr; } // namespace Ewoms #endif