From 546523e229c15a815c8a3456a4c4e3e7b5120807 Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Wed, 24 Mar 2021 17:24:42 +0100 Subject: [PATCH] Add new class RSTConfig for configuration of restart. --- CMakeLists_files.cmake | 2 + examples/opmhash.cpp | 2 +- .../EclipseState/Schedule/RSTConfig.hpp | 235 ++++++++ .../EclipseState/Schedule/Schedule.hpp | 24 +- .../EclipseState/Schedule/ScheduleState.hpp | 17 +- .../EclipseState/Schedule/KeywordHandlers.cpp | 23 +- .../EclipseState/Schedule/RSTConfig.cpp | 533 ++++++++++++++++++ .../EclipseState/Schedule/Schedule.cpp | 32 +- .../EclipseState/Schedule/ScheduleState.cpp | 75 ++- tests/parser/RestartConfigTests.cpp | 155 ++++- .../integration_tests/IOConfig/RPT_TEST2.DATA | 3 + .../integration/IOConfigIntegrationTest.cpp | 51 ++ 12 files changed, 1084 insertions(+), 68 deletions(-) create mode 100644 opm/parser/eclipse/EclipseState/Schedule/RSTConfig.hpp create mode 100644 src/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 7719a83b0..fa44bcfe3 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -130,6 +130,7 @@ if(ENABLE_ECL_INPUT) src/opm/parser/eclipse/EclipseState/Schedule/OilVaporizationProperties.cpp src/opm/parser/eclipse/EclipseState/Schedule/RFTConfig.cpp src/opm/parser/eclipse/EclipseState/Schedule/RPTConfig.cpp + src/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.cpp src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp src/opm/parser/eclipse/EclipseState/Schedule/ScheduleDeck.cpp src/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.cpp @@ -758,6 +759,7 @@ if(ENABLE_ECL_INPUT) opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp opm/parser/eclipse/EclipseState/Schedule/RFTConfig.hpp opm/parser/eclipse/EclipseState/Schedule/RPTConfig.hpp + opm/parser/eclipse/EclipseState/Schedule/RSTConfig.hpp opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp opm/parser/eclipse/EclipseState/Schedule/ScheduleDeck.hpp opm/parser/eclipse/EclipseState/Schedule/ScheduleState.hpp diff --git a/examples/opmhash.cpp b/examples/opmhash.cpp index 030e8122d..d86b1f6bb 100644 --- a/examples/opmhash.cpp +++ b/examples/opmhash.cpp @@ -19,7 +19,7 @@ #include #include - +#include #include #include diff --git a/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.hpp b/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.hpp new file mode 100644 index 000000000..da298c12b --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.hpp @@ -0,0 +1,235 @@ +/* + Copyright 2021 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 OPM_RST_CONFIG_HPP +#define OPM_RST_CONFIG_HPP + + +/* + The RestartConfig class internalizes information of when (at which + report steps) we should save restart files, and which properties + should be included in the restart files. The configuration of this + immensely complex, and this code is unfortunately also way too + complex. + + The most basic question to disentangle is the "When to write restart + files" versus "What data to store write in the restart file". As + expressed in the deck keywords this completely entangled, in this + implementation we have tried to disentangle it: + + Keywords involved + ----------------- + + RPTRST: This is the main keyword for configuring restart output; it + can be used to configure bothe when to write the files and which + properties should be included in the restart files. + + + RPTSCHED: The main purpose of the RPTSCHED keyword is to configure + output from the SCHEDULE section to the PRINT file. However the + mneomnic RESTART=n can be used to turn writing of restart files + on, and also for values > 2 to some configuration of what is + written to the restart file: + + RESTART=1 : As RPTRST,BASIC=1 + RESTART>1 : As RPTRST,BASIC=2 + RESTART>2 : Flow is added to restart file + RESTART>3 : Fluid in place is added to restart file + RESTART=6 : Restart file for every timestep. + + + + RPTSOL: The RPTSOL keyword is very similar to the RPTCHED keyword, + it configures output from the SOLUTION section to the PRINT file, + but just as the RPTSCHED keyword it accepts a RESTART=n mnenonic + which can be used similarly to the BASIC=n mnenonic of the RPTRST + keyword. In particular the writing of an initial restart files + with initial equilibrium solution is controlled by the RPTSOL + keyword. If the restart mneonic is greater than 2 that can be + used to configure FLOWS and FIP keywords in the restart file. + + RESTART=1 : As RPTRST,BASIC=1 + RESTART>1 : As RPTRST,BASIC=2 + RESTART>2 : Flow is added to restart file + RESTART>3 : Fluid in place is added to restart file + + + The basic rule in ECLIPSE is generally that the 'last keyword wins', + but for the RPTRST RPTSHCED combination a BASIC setting with n >= 3 + will override consecutive RESTART=n settings from RPTSCHED. + + + When to write restart files: + ---------------------------- + + When to write the restart file is governed by the BASIC=n setting in + the RPTRST keyword and the RESTART=n settings in the RPTSOL and + RPTSCHED keywords. The most common setting is 'ON' - i.e. BASIC=2 + which means write a restart file for every report step, that can be + turned off again with BASIC=0. For BASIC>2 there are varietes of + every n'th report step, and the first report step in every month and + every year. + + + Old style / new style + --------------------- + + All of the relevant keywords can be specified using a new style + based on string mneomnics and alternatively an old style represented + with a *strictly ordered* list of integers. For instance both of + these keywords request restart files written for every report step; + in addition to the fields required to actually restart the files + should contain the relative permeabilities KRO, KRW, KRG: + + RPTRST + BASIC=2 KRG KRW KRO / + + + RPTRST + 2 9*0 3*1 17*0 + + Integer controls and string mneomnics can not be mixed in the same + keyword, but they can be mixed in the same deck - and that is + actually quite common. + + + What is written to the restart file + ----------------------------------- + + The BASIC=n mneonics request the writing of a restart file which + should contain 'all properties required to restart', in addition you + can configure extra keywords to be added to the restart file. This + is configured by just adding a list as: + + RPTRST + BASIC=2 KRG KRW KRO / + + It is really *not clear* what is the correct persistence semantics + for these keywords, consider for insance the following series of keywords: + + -- Request restart file at every report step, the restart files + -- should contain additional properties KRO, KRG and KRW. + RPTRST + BASIC=2 KRG KRW KRO / + + -- Advance the simulator forward with TSTEP / DATES + TSTEP / DATES / WCONxxx + + -- Turn writing of restart files OFF using integer controls. + RPTRST + 0 / + + -- Advance the simulator forward with TSTEP / DATES + TSTEP / DATES / WCONxxx + + -- Turn writing of restart files ON using integer controls. + RPTRST + 2 / + + When writing of restart files is turned on again with the last + RPTRST keyword, should still the relative permeabilites KRO, KRW and + KRG be added to the restart files? The model we have implemented is: + + - The list of keywords written to the restart file is persisted + independtly of the BASIC=n setting. + + - Using string based mnonics you can *only add* kewyords to be + written to the files. To stop writing a keyword you must use an + integer control with value 0. + + Based on this best guess heuristic the final restart files will + still contain KRO, KRW and KRG. + + + + What is required to restart? + ---------------------------- + + A restart capable files is requested with the 'BASIC' mneomnic, but + exactly which properties the 'BASIC' keyword is expanded to is the + responsability of the simulator; i.e. for a black oil simulation you + will at the very least need the expansion: + + BASIC -> PRESSURE, SWAT, SGAS, RS, RV + + But this class just carries the boolean information: Yes - restart + is requested - expanding as illustrated is the responsability of the + simulator. + + + + + What is not supported? + ---------------------- + + The SAVE keyword is not supported in OPM at all, this implies that + the SAVE and SFREQ mneomics are not supported. +*/ + +#include +#include +#include + +#include + +namespace Opm { + +class ParseContext; +class ErrorGuard; + +class RSTConfig { +public: + RSTConfig() = default; + RSTConfig(const SOLUTIONSection& solution_section, const ParseContext& parseContext, ErrorGuard& errors); + void update(const DeckKeyword& keyword, const ParseContext& parseContext, ErrorGuard& errors); + void init_next(); + static RSTConfig first(const RSTConfig& src); + static RSTConfig serializeObject(); + + template + void serializeOp(Serializer& serializer) { + serializer(write_rst_file); + serializer.template map, false>(keywords); + serializer(basic); + serializer(freq); + serializer(save); + } + + bool operator==(const RSTConfig& other) const; + + std::optional write_rst_file; + std::map keywords; + std::optional basic; + std::optional freq; + bool save = false; + +private: + void handleRPTSOL(const DeckKeyword& keyword); + void handleRPTRST(const DeckKeyword& keyword, const ParseContext& parse_context, ErrorGuard& errors); + void handleRPTSCHED(const DeckKeyword& keyword, const ParseContext& parse_context, ErrorGuard& errors); + void update_schedule(const std::pair, std::optional>& basic_freq); +}; + + + +} //namespace Opm + + + +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index 20f8defda..55cee567d 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -24,9 +24,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -71,6 +71,7 @@ namespace Opm MessageLimits m_deck_message_limits; UnitSystem m_unit_system; Runspec m_runspec; + RSTConfig rst_config; ScheduleStatic() = default; @@ -80,13 +81,17 @@ namespace Opm ScheduleStatic(std::shared_ptr python_handle, const Deck& deck, - const Runspec& runspec) : + const Runspec& runspec, + const ParseContext& parseContext, + ErrorGuard& errors): m_python_handle(python_handle), m_input_path(deck.getInputPath()), m_deck_message_limits( deck ), m_unit_system( deck.getActiveUnitSystem() ), - m_runspec( runspec ) - {} + m_runspec( runspec ), + rst_config( SOLUTIONSection(deck), parseContext, errors ) + { + } template void serializeOp(Serializer& serializer) @@ -95,6 +100,7 @@ namespace Opm m_runspec.serializeOp(serializer); m_unit_system.serializeOp(serializer); serializer(this->m_input_path); + rst_config.serializeOp(serializer); } @@ -105,6 +111,7 @@ namespace Opm st.m_runspec = Runspec::serializeObject(); st.m_unit_system = UnitSystem::newFIELD(); st.m_input_path = "Some/funny/path"; + st.rst_config = RSTConfig::serializeObject(); return st; } @@ -112,6 +119,7 @@ namespace Opm return this->m_input_path == other.m_input_path && this->m_deck_message_limits == other.m_deck_message_limits && this->m_unit_system == other.m_unit_system && + this->rst_config == other.rst_config && this->m_runspec == other.m_runspec; } }; @@ -287,7 +295,6 @@ namespace Opm void serializeOp(Serializer& serializer) { m_sched_deck.serializeOp(serializer); - restart_config.serializeOp(serializer); serializer.vector(snapshots); m_static.serializeOp(serializer); serializer(m_restart_info); @@ -307,6 +314,7 @@ namespace Opm pack_unpack(serializer); pack_unpack(serializer); pack_unpack(serializer); + pack_unpack(serializer); pack_unpack_map(serializer); pack_unpack_map(serializer); @@ -451,13 +459,9 @@ namespace Opm ScheduleStatic m_static; std::pair m_restart_info; ScheduleDeck m_sched_deck; - RestartConfig restart_config; std::optional exit_status; std::vector snapshots; - RestartConfig& restart(); - const RestartConfig& restart() const; - void load_rst(const RestartIO::RstState& rst, const EclipseGrid& grid, const FieldPropsManager& fp); @@ -599,8 +603,10 @@ namespace Opm void handleMXUNSUPP (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleNODEPROP (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleNUPCOL (const HandlerContext&, const ParseContext&, ErrorGuard&); + void handleRPTRST (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleRPTSCHED (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleTUNING (const HandlerContext&, const ParseContext&, ErrorGuard&); + void handleSAVE (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleUDQ (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleVAPPARS (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleVFPINJ (const HandlerContext&, const ParseContext&, ErrorGuard&); diff --git a/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.hpp b/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.hpp index 686789f66..d9d7e9daa 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.hpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace { @@ -316,6 +317,10 @@ namespace Opm { Well::ProducerCMode whistctl() const; void update_whistctl(Well::ProducerCMode whistctl); + bool rst_file(const RSTConfig& rst_config) const; + void update_date(const time_point& prev_time); + void handleSAVE(); + /*********************************************************************/ ptr_member pavg; @@ -333,6 +338,7 @@ namespace Opm { ptr_member glo; ptr_member guide_rate; ptr_member rft_config; + ptr_member rst_config; template struct always_false1 : std::false_type {}; @@ -368,6 +374,8 @@ namespace Opm { return this->guide_rate; else if constexpr ( std::is_same_v ) return this->rft_config; + else if constexpr ( std::is_same_v ) + return this->rst_config; else static_assert(always_false1::value, "Template type not supported in get()"); } @@ -404,6 +412,8 @@ namespace Opm { return this->guide_rate; else if constexpr ( std::is_same_v ) return this->rft_config; + else if constexpr ( std::is_same_v ) + return this->rst_config; else static_assert(always_false1::value, "Template type not supported in get()"); } @@ -441,6 +451,7 @@ namespace Opm { serializer(m_year_num); serializer(m_first_in_year); serializer(m_first_in_month); + serializer(m_save_step); m_tuning.serializeOp(serializer); serializer(m_nupcol); m_oilvap.serializeOp(serializer); @@ -460,8 +471,10 @@ namespace Opm { std::size_t m_sim_step = 0; std::size_t m_month_num = 0; std::size_t m_year_num = 0; - bool m_first_in_month = true; - bool m_first_in_year = true; + bool m_first_in_month; + bool m_first_in_year; + std::optional m_save_step; + Tuning m_tuning; int m_nupcol; OilVaporizationProperties m_oilvap; diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp index 1bc9faa19..b9215ccd9 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp @@ -790,8 +790,27 @@ namespace { this->snapshots.back().update_nupcol(nupcol); } - void Schedule::handleRPTSCHED(const HandlerContext& handlerContext, const ParseContext&, ErrorGuard&) { + void Schedule::handleRPTSCHED(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) { this->snapshots.back().rpt_config.update( RPTConfig(handlerContext.keyword )); + auto rst_config = this->snapshots.back().rst_config(); + rst_config.update(handlerContext.keyword, parseContext, errors); + this->snapshots.back().rst_config.update(std::move(rst_config)); + } + + void Schedule::handleRPTRST(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) { + auto rst_config = this->snapshots.back().rst_config(); + rst_config.update(handlerContext.keyword, parseContext, errors); + this->snapshots.back().rst_config.update(std::move(rst_config)); + } + + /* + We do not really handle the SAVE keyword, we just interpret it as: Write a + normal restart file at this report step. + */ + void Schedule::handleSAVE(const HandlerContext& handlerContext, const ParseContext&, ErrorGuard&) { + auto rst_config = this->snapshots.back().rst_config(); + rst_config.save = true; + this->snapshots.back().rst_config.update(std::move(rst_config)); } void Schedule::handleTUNING(const HandlerContext& handlerContext, const ParseContext&, ErrorGuard&) { @@ -1972,7 +1991,9 @@ namespace { { "MULTZ-" , &Schedule::handleMXUNSUPP }, { "NODEPROP", &Schedule::handleNODEPROP }, { "NUPCOL" , &Schedule::handleNUPCOL }, + { "RPTRST" , &Schedule::handleRPTRST }, { "RPTSCHED", &Schedule::handleRPTSCHED }, + { "SAVE" , &Schedule::handleSAVE }, { "TUNING" , &Schedule::handleTUNING }, { "UDQ" , &Schedule::handleUDQ }, { "VAPPARS" , &Schedule::handleVAPPARS }, diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.cpp new file mode 100644 index 000000000..bdf0d8c0a --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/RSTConfig.cpp @@ -0,0 +1,533 @@ +/* + Copyright 2021 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 + + +namespace Opm { + +namespace { + +inline bool is_int( const std::string& x ) { + auto is_digit = []( char c ) { return std::isdigit( c ); }; + + return !x.empty() + && ( x.front() == '-' || is_digit( x.front() ) ) + && std::all_of( x.begin() + 1, x.end(), is_digit ); +} + +constexpr const char* SCHEDIntegerKeywords[] = { "PRES", // 1 + "SOIL", // 2 + "SWAT", // 3 + "SGAS", // 4 + "RS", // 5 + "RV", // 6 + "RESTART", // 7 + "FIP", // 8 + "WELLS", // 9 + "VFPPROD", // 10 + "SUMMARY", // 11 + "CPU", // 12 + "AQUCT", // 13 + "WELSPECS",// 14 + "NEWTON", // 15 + "POILD", // 16 + "PWAT", // 17 + "PWATD", // 18 + "PGAS", // 19 + "PGASD", // 20 + "FIPVE", // 21 + "WOC", // 22 + "GOC", // 23 + "WOCDIFF", // 24 + "GOCDIFF", // 25 + "WOCGOC", // 26 + "ODGAS", // 27 + "ODWAT", // 28 + "GDOWAT", // 29 + "WDOGAS", // 30 + "OILAPI", // 31 + "FIPITR", // 32 + "TBLK", // 33 + "PBLK", // 34 + "SALT", // 35 + "PLYADS", // 36 + "RK", // 37 + "FIPSALT", // 38 + "TUNING", // 39 + "GI", // 40 + "ROCKC", // 41 + "SPENWAT", // 42 + "FIPSOL", // 43 + "SURFBLK", // 44 + "SURFADS", // 45 + "FIPSURF", // 46 + "TRADS", // 47 + "VOIL", // 48 + "VWAT", // 49 + "VGAS", // 50 + "DENO", // 51 + "DENW", // 52 + "DENG", // 53 + "GASCONC", // 54 + "PB", // 55 + "PD", // 56 + "KRW", // 57 + "KRO", // 58 + "KRG", // 59 + "MULT", // 60 + "UNKNOWN", // 61 61 and 62 are not listed in the manual + "UNKNOWN", // 62 + "FOAM", // 63 + "FIPFOAM", // 64 + "TEMP", // 65 + "FIPTEMP", // 66 + "POTC", // 67 + "FOAMADS", // 68 + "FOAMDCY", // 69 + "FOAMMOB", // 70 + "RECOV", // 71 + "FLOOIL", // 72 + "FLOWAT", // 73 + "FLOGAS", // 74 + "SGTRAP", // 75 + "FIPRESV", // 76 + "FLOSOL", // 77 + "KRN", // 78 + "GRAD", // 79 + }; +constexpr const char* RSTIntegerKeywords[] = { "BASIC", // 1 + "FLOWS", // 2 + "FIP", // 3 + "POT", // 4 + "PBPD", // 5 + "FREQ", // 6 + "PRES", // 7 + "VISC", // 8 + "DEN", // 9 + "DRAIN", // 10 + "KRO", // 11 + "KRW", // 12 + "KRG", // 13 + "PORO", // 14 + "NOGRAD", // 15 + "NORST", // 16 NORST - not supported + "SAVE", // 17 + "SFREQ", // 18 SFREQ=?? - not supported + "ALLPROPS", // 19 + "ROCKC", // 20 + "SGTRAP", // 21 + "", // 22 - Blank - ignored. + "RSSAT", // 23 + "RVSAT", // 24 + "GIMULT", // 25 + "SURFBLK", // 26 + "", // 27 - PCOW, PCOG, special cased + "STREAM", // 28 STREAM=?? - not supported + "RK", // 29 + "VELOCITY", // 30 + "COMPRESS" }; // 31 + +bool is_RPTRST_mnemonic( const std::string& kw ) { + /* all eclipse 100 keywords we want to not simply ignore. The list is + * sorted, so we can use binary_search for log(n) lookup. It is important + * that the list is sorted, but these are all the keywords listed in the + * manual and unlikely to change at all + */ + static constexpr const char* valid[] = { + "ACIP", "ACIS", "ALLPROPS", "BASIC", "BG", "BO", + "BW", "CELLINDX", "COMPRESS", "CONV", "DEN", "DRAIN", + "DRAINAGE", "DYNREG", "FIP", "FLORES", "FLOWS", "FREQ", + "GIMULT", "HYDH", "HYDHFW", "KRG", "KRO", "KRW", + "NOGRAD", "NORST", "NPMREB", "PBPD", "PCOG", "PCOW", + "PERMREDN", "POIS", "PORO", "PORV", "POT", "PRES", + "RFIP", "RK", "ROCKC", "RPORV", "RSSAT", "RVSAT", + "SAVE", "SDENO", "SFIP", "SFREQ", "SGTRAP", "SIGM_MOD", + "STREAM", "SURFBLK", "TRAS", "VELGAS", "VELOCITY", "VELOIL", + "VELWAT", "VISC", + }; + + return std::binary_search( std::begin( valid ), std::end( valid ), kw ); +} + + +bool is_RPTSCHED_mnemonic( const std::string& kw ) { + static constexpr const char* valid[] = { + "ALKALINE", "ANIONS", "AQUCT", "AQUFET", "AQUFETP", "BFORG", + "CATIONS", "CPU", "DENG", "DENO", "DENW", "ESALPLY", + "ESALSUR", "FFORG", "FIP", "FIPFOAM", "FIPHEAT", "FIPRESV", + "FIPSALT", "FIPSOL", "FIPSURF", "FIPTEMP", "FIPTR", "FIPVE", + "FLOGAS", "FLOOIL", "FLOSOL", "FLOWAT", "FMISC", "FOAM", + "FOAMADS", "FOAMCNM", "FOAMDCY", "FOAMMOB", "GASCONC", "GASSATC", + "GDOWAT", "GI", "GOC", "GOCDIFF", "GRAD", "KRG", + "KRN", "KRO", "KRW", "MULT", "NEWTON", "NOTHING", + "NPMREB", "ODGAS", "ODWAT", "OILAPI", "PB", "PBLK", + "PBU", "PD", "PDEW", "PGAS", "PGASD", "PLYADS", + "POIL", "POILD", "POLYMER", "POTC", "POTG", "POTO", + "POTW", "PRES", "PRESSURE", "PWAT", "PWATD", "RECOV", + "RESTART", "ROCKC", "RS", "RSSAT", "RV", "RVSAT", + "SALT", "SGAS", "SGTRAP", "SIGM_MOD", "SOIL", "SSOL", + "SUMMARY", "SURFADS", "SURFBLK", "SWAT", "TBLK", "TEMP", + "TRACER", "TRADS", "TRDCY", "TUNING", "VFPPROD", "VGAS", + "VOIL", "VWAT", "WDOGAS", "WELLS", "WELSPECL", "WELSPECS", + "WOC", "WOCDIFF", "WOCGOC", + }; + + return std::binary_search( std::begin( valid ), std::end( valid ), kw ); +} + +inline std::map< std::string, int > +RPTSCHED_integer( const std::vector< int >& ints ) { + const size_t size = std::min( ints.size(), sizeof( SCHEDIntegerKeywords ) ); + + std::map< std::string, int > mnemonics; + for( size_t i = 0; i < size; ++i ) + mnemonics[ SCHEDIntegerKeywords[ i ] ] = ints[ i ]; + + return mnemonics; +} + +inline std::map< std::string, int > +RPTRST_integer( const std::vector< int >& ints ) { + const size_t PCO_index = 26; + const size_t BASIC_index = 0; + + std::map< std::string, int > mnemonics; + const size_t size = std::min( ints.size(), sizeof( RSTIntegerKeywords ) ); + + /* fun with special cases. Eclipse seems to ignore the BASIC=0, interpreting + * it as sort-of "don't modify". Handle this by *not* adding/updating the + * integer list sourced BASIC mnemonic, should it be zero. I'm not sure if + * this applies to other mnemonics, but the eclipse manual indicates that + * any zero here should disable the output. + * + * See https://github.com/OPM/opm-parser/issues/886 for reference + * + * The current treatment of a mix on RPTRST and RPTSCHED integer keywords is + * probably not correct, but it is extremely difficult to comprehend exactly + * how it should be. Current code is a rather arbitrary hack to get through + * the tests. + */ + + if (size >= 26) { + for( size_t i = 0; i < std::min( size, PCO_index ); ++i ) + mnemonics[ RSTIntegerKeywords[ i ] ] = ints[ i ]; + } else { + if( size > 0 && ints[ BASIC_index ] != 0) + mnemonics[ RSTIntegerKeywords[ BASIC_index ] ] = ints[ BASIC_index ]; + + for( size_t i = 1; i < std::min( size, PCO_index ); ++i ) + mnemonics[ RSTIntegerKeywords[ i ] ] = ints[ i ]; + } + + for( size_t i = PCO_index + 1; i < size; ++i ) + mnemonics[ RSTIntegerKeywords[ i ] ] = ints[ i ]; + + /* item 27 (index 26) sets both PCOW and PCOG, so we special case it */ + if( ints.size() >= PCO_index ) { + mnemonics[ "PCOW" ] = ints[ PCO_index ]; + mnemonics[ "PCOG" ] = ints[ PCO_index ]; + } + + return mnemonics; +} + + +template< typename F, typename G > +inline std::map< std::string, int > RPT( const DeckKeyword& keyword, + const ParseContext& parseContext, + ErrorGuard& errors, + F is_mnemonic, + G integer_mnemonic ) { + + std::vector items; + const auto& deck_items = keyword.getStringData(); + const auto ints = std::any_of( deck_items.begin(), deck_items.end(), is_int ); + const auto strs = !std::all_of( deck_items.begin(), deck_items.end(), is_int ); + + /* if any of the values are pure integers we assume this is meant to be the + * slash-terminated list of integers way of configuring. If integers and + * non-integers are mixed, this is an error; however if the error mode + * RPT_MIXED_STYLE is permissive we try some desperate heuristics to + * interpret this as list of mnemonics. See the the documentation of the + * RPT_MIXED_STYLE error handler for more details. + */ + auto stoi = []( const std::string& str ) { return std::stoi( str ); }; + if( !strs ) + return integer_mnemonic( fun::map( stoi, deck_items ) ); + + + if (ints && strs) { + const auto& location = keyword.location(); + std::string msg = "Error in keyword {keyword}, mixing mnemonics and integers is not allowed\n" + "In {file} line {line}."; + parseContext.handleError(ParseContext::RPT_MIXED_STYLE, msg, location, errors); + + std::vector stack; + for (size_t index=0; index < deck_items.size(); index++) { + if (is_int(deck_items[index])) { + + if (stack.size() < 2) + throw OpmInputError("Problem processing {keyword}\nIn {file} line {line}.", location); + + if (stack.back() == "=") { + stack.pop_back(); + std::string mnemonic = stack.back(); + stack.pop_back(); + + items.insert(items.begin(), stack.begin(), stack.end()); + stack.clear(); + items.push_back( mnemonic + "=" + deck_items[index]); + } else + throw OpmInputError("Problem processing {keyword}\nIn {file} line {line}.", location); + + } else + stack.push_back(deck_items[index]); + } + items.insert(items.begin(), stack.begin(), stack.end()); + } else + items = deck_items; + + std::map< std::string, int > mnemonics; + for( const auto& mnemonic : items ) { + const auto sep_pos = mnemonic.find_first_of( "= " ); + + std::string base = mnemonic.substr( 0, sep_pos ); + if( !is_mnemonic( base ) ) { + std::string msg_fmt = fmt::format("Error in keyword {{keyword}}, unrecognized mnemonic {}\nIn {{file}} line {{line}}.", base); + parseContext.handleError(ParseContext::RPT_UNKNOWN_MNEMONIC, msg_fmt, keyword.location(), errors); + continue; + } + + int val = 1; + if (sep_pos != std::string::npos) { + const auto value_pos = mnemonic.find_first_not_of("= ", sep_pos); + if (value_pos != std::string::npos) + val = std::stoi(mnemonic.substr(value_pos)); + } + + mnemonics.emplace( base, val ); + } + + return mnemonics; +} + +inline void expand_RPTRST_mnemonics(std::map< std::string, int >& mnemonics) { + const auto allprops_iter = mnemonics.find( "ALLPROPS"); + if (allprops_iter != mnemonics.end()) { + const auto value = allprops_iter->second; + mnemonics.erase( allprops_iter ); + + for (const auto& kw : {"BG","BO","BW","KRG","KRO","KRW","VOIL","VGAS","VWAT","DEN"}) + mnemonics[kw] = value; + } +} + +std::optional extract(std::map& mnemonics, const std::string& key) { + auto iter = mnemonics.find(key); + if (iter == mnemonics.end()) + return {}; + + int value = iter->second; + mnemonics.erase(iter); + return value; +} + + +inline std::pair< std::map< std::string, int >, std::pair, std::optional>> +RPTRST( const DeckKeyword& keyword, const ParseContext& parseContext, ErrorGuard& errors) { + auto mnemonics = RPT( keyword, parseContext, errors, is_RPTRST_mnemonic, RPTRST_integer ); + std::optional basic = extract(mnemonics, "BASIC"); + std::optional freq = extract(mnemonics, "FREQ"); + + expand_RPTRST_mnemonics( mnemonics ); + return {mnemonics, { basic, freq }}; +} + + +template +void update_optional(std::optional& target, const std::optional& src) { + if (src.has_value()) + target = src; +} + + +} + +// The handleRPTSOL() function is only invoked from the constructor which uses +// the SOLUTION section, and the only information actually extracted is whether +// to write the initial restart file. + +void RSTConfig::handleRPTSOL( const DeckKeyword& keyword) { + const auto& record = keyword.getRecord(0); + const auto& item = record.getItem(0); + for (const auto& mnemonic : item.getData()) { + auto mnemonic_RESTART_pos = mnemonic.find("RESTART="); + if (mnemonic_RESTART_pos != std::string::npos) { + std::string restart_no = mnemonic.substr(mnemonic_RESTART_pos + 8, mnemonic.size()); + auto restart = std::strtoul(restart_no.c_str(), nullptr, 10); + this->write_rst_file = (restart > 1); + return; + } + } + + + /* If no RESTART mnemonic is found, either it is not present or we might + have an old data set containing integer controls instead of mnemonics. + Restart integer switch is integer control nr 7 */ + + if (item.data_size() >= 7) { + const std::string& integer_control = item.get(6); + auto restart = std::strtoul(integer_control.c_str(), nullptr, 10); + this->write_rst_file = (restart > 1); + return; + } +} + +bool RSTConfig::operator==(const RSTConfig& other) const { + return this->write_rst_file == other.write_rst_file && + this->keywords == other.keywords && + this->basic == other.basic && + this->freq == other.freq && + this->save == other.save; +} + + +void RSTConfig::update_schedule(const std::pair, std::optional>& basic_freq) { + update_optional(this->basic, basic_freq.first); + update_optional(this->freq, basic_freq.second); + if (this->basic.has_value()) { + auto basic_value = this->basic.value(); + if (basic_value == 0) + this->write_rst_file = false; + else if (basic_value == 1 || basic_value == 2) + this->write_rst_file = true; + else + this->write_rst_file = {}; + } +} + + +void RSTConfig::handleRPTRST(const DeckKeyword& keyword, const ParseContext& parseContext, ErrorGuard& errors) { + const auto& [mnemonics, basic_freq] = RPTRST(keyword, parseContext, errors); + this->update_schedule(basic_freq); + for (const auto& [kw,num] : mnemonics) + this->keywords[kw] = num; +} + +void RSTConfig::handleRPTSCHED(const DeckKeyword& keyword, const ParseContext& parseContext, ErrorGuard& errors) { + auto mnemonics = RPT( keyword, parseContext, errors, is_RPTSCHED_mnemonic, RPTSCHED_integer ); + auto nothing = extract(mnemonics, "NOTHING"); + if (nothing.has_value()) { + this->basic = {}; + this->keywords.clear(); + } + + if (this->basic.value_or(2) <= 2) { + auto restart = extract(mnemonics, "RESTART"); + if (restart.has_value()) { + auto basic_value = std::min(2, restart.value()); + this->update_schedule({basic_value , 1}); + } + } + + for (const auto& [kw,num] : mnemonics) + this->keywords[kw] = num; +} + + +RSTConfig::RSTConfig(const SOLUTIONSection& solution_section, const ParseContext& parseContext, ErrorGuard& errors) +{ + this->write_rst_file = false; + if (solution_section.hasKeyword()) { + const auto& keyword = solution_section.getKeyword(); + this->handleRPTRST(keyword, parseContext, errors); + + // Guessing on eclipse rules for write of initial RESTART file (at time 0): + // Write of initial restart file is (due to the eclipse reference manual) + // governed by RPTSOL RESTART in solution section, + // if RPTSOL RESTART > 1 initial restart file is written. + // but - due to initial restart file written from Eclipse + // for data where RPTSOL RESTART not set - guessing that + // when RPTRST is set in SOLUTION (no basic though...) -> write inital restart. + this->write_rst_file = true; + } + + if (solution_section.hasKeyword()) { + const auto& keyword = solution_section.getKeyword(); + this->handleRPTSOL(keyword); + } +} + + +void RSTConfig::update(const DeckKeyword& keyword, const ParseContext& parseContext, ErrorGuard& errors) { + if (keyword.name() == ParserKeywords::RPTRST::keywordName) + this->handleRPTRST(keyword, parseContext, errors); + else if (keyword.name() == ParserKeywords::RPTSCHED::keywordName) { + this->handleRPTSCHED(keyword, parseContext, errors); + } else + throw std::logic_error("The RSTConfig object can only use RPTRST and RPTSCHED keywords"); +} + + +RSTConfig RSTConfig::serializeObject() { + RSTConfig rst_config; + rst_config.basic = 10; + rst_config.freq = {}; + rst_config.write_rst_file = true; + rst_config.save = true; + rst_config.keywords = {{"S1", 1}, {"S2", 2}}; + return rst_config; +} + +/* + The RPTRST keyword is treated differently in the SOLUTION section and in the + SCHEDULE section. This function takes a RSTConfig object created from the + solution section and creates a transformed copy suitable as the first + RSTConfig to represent the Schedule section. +*/ +RSTConfig RSTConfig::first(const RSTConfig& solution_config ) { + RSTConfig rst_config(solution_config); + auto basic = rst_config.basic; + if (!basic.has_value()) { + rst_config.write_rst_file = false; + return rst_config; + } + + auto basic_value = basic.value(); + if (basic_value == 0) + rst_config.write_rst_file = false; + else if (basic_value == 1 || basic_value == 2) + rst_config.write_rst_file = true; + else if (basic_value >= 3) + rst_config.write_rst_file = {}; + + return rst_config; +} + +} + + diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index fb3e100d6..5435a4564 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -108,10 +108,9 @@ namespace { const std::optional& output_interval, const RestartIO::RstState * rst) try : - m_static( python, deck, runspec ), + m_static( python, deck, runspec, parseContext, errors ), m_restart_info( restart_info(rst)), - m_sched_deck(deck, m_restart_info ), - restart_config(deck, m_restart_info, output_interval, parseContext, errors) + m_sched_deck(deck, m_restart_info ) { if (rst) { auto restart_step = this->m_restart_info.second; @@ -211,7 +210,6 @@ Schedule::Schedule(const Deck& deck, const EclipseState& es, const std::optional Schedule result; result.m_static = ScheduleStatic::serializeObject(); - result.restart_config = RestartConfig::serializeObject(); result.snapshots = { ScheduleState::serializeObject() }; return result; @@ -1206,28 +1204,28 @@ void Schedule::iterateScheduleSection(std::size_t load_start, std::size_t load_e } } - RestartConfig& Schedule::restart() { - return this->restart_config; - } + bool Schedule::write_rst_file(std::size_t report_step, bool ) const { + if (report_step == 0) + return this->m_static.rst_config.write_rst_file.value(); - const RestartConfig& Schedule::restart() const { - return this->restart_config; - } - -bool Schedule::write_rst_file(std::size_t report_step, bool log) const { - return this->restart_config.getWriteRestartFile(report_step, log); + const auto& rst_config = this->snapshots[report_step - 1].rst_config(); + const auto& state = this->snapshots[report_step]; + return state.rst_file(rst_config); } - const std::map< std::string, int >& Schedule::rst_keywords( size_t timestep ) const { - return this->restart_config.getRestartKeywords(timestep); + const std::map< std::string, int >& Schedule::rst_keywords( size_t report_step ) const { + if (report_step == 0) + return this->m_static.rst_config.keywords; + + const auto& keywords = this->snapshots[report_step - 1].rst_config().keywords; + return keywords; } bool Schedule::operator==(const Schedule& data) const { return this->m_restart_info == data.m_restart_info && this->m_static == data.m_static && - this->restart_config == data.restart_config && this->snapshots == data.snapshots; } @@ -1647,6 +1645,8 @@ void Schedule::create_first(const time_point& start_time, const std::optionalm_static.rst_config ) ); + //sched_state.update_date( start_time ); this->addGroup("FIELD", 0); } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.cpp index fb59e5d80..a2ca63618 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ScheduleState.cpp @@ -40,7 +40,7 @@ time_point clamp_time(time_point t) { return TimeService::from_time_t( TimeService::to_time_t( t ) ); } -std::pair date_diff(time_point t2, time_point t1) { +std::pair date_diff(const time_point& t2, const time_point& t1) { auto ts1 = TimeStampUTC(TimeService::to_time_t(t1)); auto ts2 = TimeStampUTC(TimeService::to_time_t(t2)); auto year_diff = ts2.year() - ts1.year(); @@ -54,8 +54,12 @@ std::pair date_diff(time_point t2, time_point t1) { ScheduleState::ScheduleState(const time_point& t1): - m_start_time(clamp_time(t1)) + m_start_time(clamp_time(t1)), + m_first_in_month(true), + m_first_in_year(true) { + auto ts1 = TimeStampUTC(TimeService::to_time_t(this->m_start_time)); + this->m_month_num = ts1.month() - 1; } ScheduleState::ScheduleState(const time_point& start_time, const time_point& end_time) : @@ -64,6 +68,19 @@ ScheduleState::ScheduleState(const time_point& start_time, const time_point& end this->m_end_time = clamp_time(end_time); } +void ScheduleState::update_date(const time_point& prev_time) { + auto [year_diff, month_diff] = date_diff(this->m_start_time, prev_time); + this->m_year_num += year_diff; + this->m_first_in_month = (month_diff > 0); + this->m_first_in_year = (year_diff > 0); + + auto ts1 = TimeStampUTC(TimeService::to_time_t(this->m_start_time)); + this->m_month_num = ts1.month() - 1; +} + + + + ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_time) : ScheduleState(src) { @@ -79,14 +96,15 @@ ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_t if (next_rft.has_value()) this->rft_config.update( std::move(*next_rft) ); - auto [year_diff, month_diff] = date_diff(this->m_start_time, src.m_start_time); - this->m_year_num += year_diff; - this->m_month_num += month_diff; - - this->m_first_in_month = (this->m_month_num > src.m_month_num); - this->m_first_in_year = (this->m_year_num > src.m_year_num); + this->update_date(src.m_start_time); + if (this->rst_config().save) { + auto new_rst = this->rst_config(); + new_rst.save = false; + this->rst_config.update( std::move(new_rst) ); + } } + ScheduleState::ScheduleState(const ScheduleState& src, const time_point& start_time, const time_point& end_time) : ScheduleState(src, start_time) { @@ -244,6 +262,7 @@ ScheduleState ScheduleState::serializeObject() { ts.guide_rate.update( GuideRateConfig::serializeObject() ); ts.glo.update( GasLiftOpt::serializeObject() ); ts.rft_config.update( RFTConfig::serializeObject() ); + ts.rst_config.update( RSTConfig::serializeObject() ); return ts; } @@ -285,4 +304,44 @@ const WellGroupEvents& ScheduleState::wellgroup_events() const { return this->m_wellgroup_events; } + +/* + Observe that the decision to write a restart file will typically be a + combination of the RST configuration from the previous report step, and the + first_in_year++ attributes of this report step. That is the reason the + function takes a RSTConfig argument - instead of using the rst_config member. + +*/ + +bool ScheduleState::rst_file(const RSTConfig& rst) const { + if (rst.save) + return true; + + if (rst.write_rst_file.has_value()) + return rst.write_rst_file.value(); + + auto freq = rst.freq.value_or(1); + auto basic = rst.basic.value(); + + if (basic == 3) + return (this->sim_step() % freq) == 0; + + if (basic == 4) { + if (!this->first_in_year()) + return false; + + return (this->m_year_num % freq) == 0; + } + + if (basic == 5) { + if (!this->first_in_month()) + return false; + + return (this->m_month_num % freq) == 0; + } + + throw std::logic_error(fmt::format("Unsupported BASIC={} value", basic)); +} + + } diff --git a/tests/parser/RestartConfigTests.cpp b/tests/parser/RestartConfigTests.cpp index 3bc882220..497290ee2 100644 --- a/tests/parser/RestartConfigTests.cpp +++ b/tests/parser/RestartConfigTests.cpp @@ -202,14 +202,13 @@ RESTART=1 } - BOOST_AUTO_TEST_CASE(RPTRST_AND_RPTSOL_SOLUTION) { const auto input = std::string { R"(RUNSPEC DIMENS 10 10 10 / START - 6 JLY 2020 / + 6 JUN 2020 / GRID DXV @@ -259,27 +258,45 @@ END auto sched = make_schedule(input, false); - for (const std::size_t stepID : { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 18 }) { + for (const std::size_t stepID : { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 18 }) { BOOST_CHECK_MESSAGE(! sched.write_rst_file(stepID), "Must not write restart information for excluded step " << stepID); } - for (const std::size_t stepID : { 0, 11, 14, 17 }) { + for (const std::size_t stepID : { 0, 1, 11, 14, 17 }) { BOOST_CHECK_MESSAGE(sched.write_rst_file(stepID), "Must write restart information for included step " << stepID); } - std::vector first_in_month{true, false, false, false, false, true, false, true, true, true, true, true, true, true, true, true, false, true, false}; - std::vector month_num{ 0,0,0,0,0, 1, 1, 2, 3, 4, 5, 6, 7, 10, 12, 17, 17, 18, 18 }; + std::vector> expected = {{true , true , TimeStampUTC(2020, 6, 6)}, // 0 + {true , false, TimeStampUTC(2020, 7, 7)}, // 1 + {false, false, TimeStampUTC(2020, 7, 10)}, // 2 + {false, false, TimeStampUTC(2020, 7, 20)}, // 3 + {false, false, TimeStampUTC(2020, 7, 30)}, // 4 + {true , false, TimeStampUTC(2020, 8, 5)}, // 5 + {false, false, TimeStampUTC(2020, 8, 20)}, // 6 + {true , false, TimeStampUTC(2020, 9, 5)}, // 7 + {true , false, TimeStampUTC(2020, 10, 1)}, // 8 + {true , false, TimeStampUTC(2020, 11, 1)}, // 9 + {true , false, TimeStampUTC(2020, 12, 1)}, // 10 + {true , true , TimeStampUTC(2021, 1, 5)}, // 11 + {true , false, TimeStampUTC(2021, 2, 1)}, // 12 + {true , false, TimeStampUTC(2021, 5, 17)}, // 13 + {true , false, TimeStampUTC(2021, 7, 6)}, // 14 + {true , false, TimeStampUTC(2021, 12, 1)}, // 15 + {false, false, TimeStampUTC(2021, 12, 31)}, // 16 + {true, true , TimeStampUTC(2022, 1, 21)}, // 17 + {false, false, TimeStampUTC(2022, 1, 31)}}; // 18 + for (std::size_t index = 0; index < sched.size(); index++) { const auto& state = sched[index]; - BOOST_CHECK_EQUAL( state.month_num(), month_num[index] ); - BOOST_CHECK_EQUAL( state.first_in_month(), first_in_month[index] ); + const auto& [first_in_month, first_in_year, ts] = expected[index]; - if (index == 0 || index == 11 || index == 17) - BOOST_CHECK( state.first_in_year()); - else - BOOST_CHECK(!state.first_in_year()); + printf("index: %ld \n", index); + BOOST_CHECK_EQUAL( state.month_num(), ts.month() - 1); + BOOST_CHECK_EQUAL( state.first_in_month(), first_in_month ); + BOOST_CHECK_EQUAL( state.first_in_year(), first_in_year); + BOOST_CHECK( ts == TimeStampUTC( TimeService::to_time_t(state.start_time() ))); } } @@ -416,23 +433,25 @@ PORO 1000*0.25 / SOLUTION RPTRST -- PRES,DEN,PCOW,PCOG,RK,VELOCITY,COMPRESS - 6*0 1 0 1 9*0 1 7*0 1 0 3*1 / + 6*0 1 0 1 9*0 1 7*0 1 0 3*1 / -- Static + SCHEDULE +-- 0 DATES -- 1 10 OKT 2008 / / -RPTSCHED +RPTSCHED -- 1 RESTART=1 / DATES -- 2 20 JAN 2010 / / -RPTRST -- RK,VELOCITY,COMPRESS +RPTRST -- RK,VELOCITY,COMPRESS --2 18*0 0 8*0 / DATES -- 3 20 FEB 2010 / / -RPTSCHED +RPTSCHED -- 3 RESTART=0 / )"; @@ -1256,28 +1275,28 @@ RPTRST BASIC=5 FREQ=2 / DATES - 22 MAY 1981 / - 23 MAY 1981 / - 24 MAY 1981 / - 1 JUN 1981 / - 1 JUL 1981 / -- write - 1 JAN 1982 / -- write - 2 JAN 1982 / - 1 FEB 1982 / - 1 MAR 1982 / -- write - 1 APR 1983 / -- write - 2 JUN 1983 / -- write + 22 MAY 1981 / -- 1 + 23 MAY 1981 / -- 2 + 24 MAY 1981 / -- 3 + 1 JUN 1981 / -- 4 + 1 JUL 1981 / -- 5 Write + 1 JAN 1982 / -- 6 Write + 2 JAN 1982 / -- 7 + 1 FEB 1982 / -- 8 + 1 MAR 1982 / -- 9 Write + 1 APR 1983 / --10 + 2 JUN 1983 / --11 / )"; auto sched = make_schedule(data); /* BASIC=5, restart file is written at the first report step of each month. */ - for( size_t ts : { 1, 2, 3, 4, 7, 8 } ) + for( size_t ts : { 1, 2, 3, 4, 7, 8, 10, 11 } ) BOOST_CHECK( !sched.write_rst_file( ts ) ); - for( size_t ts : { 5, 6, 9, 10, 11 } ) - BOOST_CHECK( sched.write_rst_file( ts ) ); + for( size_t ts : { 5, 6, 9} ) + BOOST_CHECK_MESSAGE( sched.write_rst_file( ts ) , "Restart file expected for step: " << ts); } BOOST_AUTO_TEST_CASE(BASIC_EQ_0) { @@ -1369,7 +1388,7 @@ BASIC=4 FREQ=2 DATES 22 MAY 1981 / / -RPTSCHED // BASIC >2, ignore RPTSCHED RESTART +RPTSCHED -- BASIC >2, ignore RPTSCHED RESTART RESTART=3, FREQ=1 / DATES @@ -1456,3 +1475,77 @@ TSTEP } + +BOOST_AUTO_TEST_CASE(RPTSCHED_INTEGER2) { + + const std::string deckData1 = R"( +RUNSPEC +START -- 0 +19 JUN 2007 / +DIMENS + 10 10 10 / +GRID + +DXV + 10*1 / + +DYV + 10*1 / + +DZV + 10*1 / + +DEPTHZ + 121*1 / + +PORO + 1000*0.25 / +SOLUTION +RPTRST -- PRES,DEN,PCOW,PCOG,RK,VELOCITY,COMPRESS + 1 5*0 1 0 1 9*0 1 7*0 1 0 3*1 / -- Static + +SCHEDULE +-- 0 +DATES -- 1 + 10 OKT 2008 / +/ +RPTSCHED +RESTART=1 +/ +DATES -- 2 + 20 JAN 2010 / +/ +RPTRST -- RK,VELOCITY,COMPRESS + 18*0 0 8*0 / +DATES -- 3 + 20 FEB 2010 / +/ +RPTSCHED +RESTART=0 +/ + +DATES -- 4 +1 MAR 2010 / +/ +)"; + + auto sched = make_schedule(deckData1, false); + + BOOST_CHECK_EQUAL( sched.size(), 5); + BOOST_CHECK( sched.write_rst_file( 0 ) ); + BOOST_CHECK( sched.write_rst_file( 1 ) ); + BOOST_CHECK( sched.write_rst_file( 2 ) ); + BOOST_CHECK( !sched.write_rst_file( 3 ) ); + + + const auto& kw_list1 = filter_keywords(sched.rst_keywords(1)); + const auto expected1 = {"BG","BO","BW","COMPRESS","DEN","KRG","KRO","KRW","PCOG","PCOW","PRES","RK","VELOCITY","VGAS","VOIL","VWAT"}; + BOOST_CHECK_EQUAL_COLLECTIONS( expected1.begin(), expected1.end(), + kw_list1.begin(), kw_list1.end() ); + + const auto& kw_list2 = filter_keywords( sched.rst_keywords(3)); + const auto expected2 = { "COMPRESS", "RK", "VELOCITY" }; + BOOST_CHECK_EQUAL_COLLECTIONS( expected2.begin(), expected2.end(), + kw_list2.begin(), kw_list2.end() ); +} + diff --git a/tests/parser/data/integration_tests/IOConfig/RPT_TEST2.DATA b/tests/parser/data/integration_tests/IOConfig/RPT_TEST2.DATA index f3a908109..043ee154b 100644 --- a/tests/parser/data/integration_tests/IOConfig/RPT_TEST2.DATA +++ b/tests/parser/data/integration_tests/IOConfig/RPT_TEST2.DATA @@ -199,6 +199,9 @@ DATES DATES 21 'AUG' 2001 / / + +SAVE + DATES 24 'AUG' 2001 / / diff --git a/tests/parser/integration/IOConfigIntegrationTest.cpp b/tests/parser/integration/IOConfigIntegrationTest.cpp index 1081e9b18..33bad8654 100644 --- a/tests/parser/integration/IOConfigIntegrationTest.cpp +++ b/tests/parser/integration/IOConfigIntegrationTest.cpp @@ -138,6 +138,7 @@ BOOST_AUTO_TEST_CASE( NorneRestartConfig ) { + BOOST_AUTO_TEST_CASE( RestartConfig2 ) { std::map rptConfig; @@ -145,6 +146,7 @@ BOOST_AUTO_TEST_CASE( RestartConfig2 ) { rptConfig.emplace(8 , boost::gregorian::date(2000,7,1)); rptConfig.emplace(27 , boost::gregorian::date(2001,1,1)); rptConfig.emplace(45 , boost::gregorian::date(2001,7,1)); + rptConfig.emplace(50 , boost::gregorian::date(2001,8,24)); rptConfig.emplace(61 , boost::gregorian::date(2002,1,1)); rptConfig.emplace(79 , boost::gregorian::date(2002,7,1)); rptConfig.emplace(89 , boost::gregorian::date(2003,1,1)); @@ -177,6 +179,55 @@ BOOST_AUTO_TEST_CASE( RestartConfig2 ) { EclipseState state( deck); Schedule schedule(deck, state, python); verifyRestartConfig(schedule, rptConfig); + + auto keywords0 = schedule.rst_keywords(0); + std::map expected0 = {{"BG", 1}, + {"BO", 1}, + {"BW", 1}, + {"KRG", 1}, + {"KRO", 1}, + {"KRW", 1}, + {"VOIL", 1}, + {"VGAS", 1}, + {"VWAT", 1}, + {"DEN", 1}, + {"RVSAT", 1}, + {"RSSAT", 1}, + {"PBPD", 1}, + {"NORST", 1}}; + for (const auto& [kw, num] : expected0) + BOOST_CHECK_EQUAL( keywords0.at(kw), num ); + + auto keywords1 = schedule.rst_keywords(1); + std::map expected1 = {{"BG", 1}, + {"BO", 1}, + {"BW", 1}, + {"KRG", 1}, + {"KRO", 1}, + {"KRW", 1}, + {"VOIL", 1}, + {"VGAS", 1}, + {"VWAT", 1}, + {"DEN", 1}, + {"RVSAT", 1}, + {"RSSAT", 1}, + {"PBPD", 1}, + {"NORST", 1}, + {"FIP", 3}, + {"WELSPECS", 1}, + {"WELLS", 0}, + {"NEWTON", 1}, + {"SUMMARY", 1}, + {"CPU", 1}, + {"CONV", 10}}; + + for (const auto& [kw, num] : expected1) + BOOST_CHECK_EQUAL( keywords1.at(kw), num ); + + BOOST_CHECK_EQUAL(expected1.size(), keywords1.size()); + + auto keywords10 = schedule.rst_keywords(10); + BOOST_CHECK( keywords10 == keywords1 ); }