diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index e8c6be8b2..c8d100823 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -74,6 +74,8 @@ if(ENABLE_ECL_INPUT) src/opm/parser/eclipse/EclipseState/IOConfig/IOConfig.cpp src/opm/parser/eclipse/EclipseState/IOConfig/RestartConfig.cpp src/opm/parser/eclipse/EclipseState/Runspec.cpp + src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp + src/opm/parser/eclipse/EclipseState/Schedule/Actions.cpp src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp src/opm/parser/eclipse/EclipseState/Schedule/Connection.cpp src/opm/parser/eclipse/EclipseState/Schedule/WellConnections.cpp @@ -438,6 +440,8 @@ if(ENABLE_ECL_INPUT) opm/parser/eclipse/EclipseState/Aquancon.hpp opm/parser/eclipse/EclipseState/AquiferCT.hpp opm/parser/eclipse/EclipseState/Aquifetp.hpp + opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp + opm/parser/eclipse/EclipseState/Schedule/Actions.hpp opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp diff --git a/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp b/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp new file mode 100644 index 000000000..bb507704a --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/ActionContext.hpp @@ -0,0 +1,43 @@ +/* + Copyright 2018 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 ActionContext_HPP +#define ActionContext_HPP + +#include +#include + +namespace Opm { + +/* + This class is extremely work in progress, the api is hopefully sane; the + implementation complete garbage. +*/ + +class ActionContext { +public: + double get(const std::string& func, const std::string& arg) const; + void add(const std::string& func, const std::string& arg, double value); + +private: + std::map values; +}; +} +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp b/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp index 70443ea23..1f3b62f2c 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/ActionX.hpp @@ -22,6 +22,10 @@ #define ActionX_HPP_ #include +#include +#include + +#include namespace Opm { /* @@ -49,21 +53,33 @@ namespace Opm { */ class DeckKeyword; +class ActionContext; class ActionX { public: - ActionX(const std::string& name, size_t max_run, double max_wait); - explicit ActionX(const DeckKeyword& kw); + ActionX(const std::string& name, size_t max_run, double max_wait, std::time_t start_time); + ActionX(const DeckKeyword& kw, std::time_t start_time); + ActionX(const DeckRecord& record, std::time_t start_time); void addKeyword(const DeckKeyword& kw); + bool ready(std::time_t sim_time) const; + bool eval(std::time_t sim_time, const ActionContext& context); + + + std::string name() const { return this->m_name; } + size_t max_run() const { return this->m_max_run; } + double min_wait() const { return this->m_min_wait; } + std::time_t start_time() const { return this->m_start_time; } - const std::string& name() const; private: std::string m_name; - size_t max_run; - double max_wait; + size_t m_max_run; + double m_min_wait; + std::time_t m_start_time; std::vector keywords; + size_t run_count = 0; + std::time_t last_run = 0; }; } diff --git a/opm/parser/eclipse/EclipseState/Schedule/Actions.hpp b/opm/parser/eclipse/EclipseState/Schedule/Actions.hpp new file mode 100644 index 000000000..4b2ed4bc5 --- /dev/null +++ b/opm/parser/eclipse/EclipseState/Schedule/Actions.hpp @@ -0,0 +1,45 @@ +/* + Copyright 2018 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 ActionCOnfig_HPP +#define ActionCOnfig_HPP + +#include +#include +#include + +#include + +namespace Opm { + +class Actions { +public: + Actions() = default; + size_t size() const; + void add(const ActionX& action); + bool ready(std::time_t sim_time) const; + ActionX& at(const std::string& name); + std::vector pending(std::time_t sim_time); + +private: + std::map actions; +}; +} +#endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index 56605c77a..36e7b11ad 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace Opm { @@ -53,6 +54,7 @@ namespace Opm class TimeMap; class UnitSystem; class EclipseState; + class Actions; class Schedule { public: @@ -104,6 +106,7 @@ namespace Opm const OilVaporizationProperties& getOilVaporizationProperties(size_t timestep) const; const WellTestConfig& wtestConfig(size_t timestep) const; + const Actions& actionConfig() const; const GroupTree& getGroupTree(size_t t) const; size_t numGroups() const; @@ -145,6 +148,7 @@ namespace Opm DynamicState> wtest_config; WellProducer::ControlModeEnum m_controlModeWHISTCTL; + Actions actions; std::vector< Well* > getWells(const std::string& wellNamePattern); std::vector< Group* > getGroups(const std::string& groupNamePattern); diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp new file mode 100644 index 000000000..d13bcd9e0 --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ActionContext.cpp @@ -0,0 +1,32 @@ +/* + Copyright 2018 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 + +namespace Opm { + + void ActionContext::add(const std::string& func, const std::string& arg, double value) { + this->values[func + ":" + arg] = value; + } + + + double ActionContext::get(const std::string& func, const std::string& arg) const { + return this->values.at( func + ":" + arg ); + } +} diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp index 9ba30d99d..070455461 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/ActionX.cpp @@ -26,27 +26,62 @@ namespace Opm { -ActionX::ActionX(const std::string& name, size_t max_run, double max_wait) : +ActionX::ActionX(const std::string& name, size_t max_run, double min_wait, std::time_t start_time) : m_name(name), - max_run(max_run), - max_wait(max_wait) + m_max_run(max_run), + m_min_wait(min_wait), + m_start_time(start_time) {} -ActionX::ActionX(const DeckKeyword& kw) { - const auto& record = kw.getRecord(0); - this->m_name = record.getItem("NAME").getTrimmedString(0); - this->max_run = record.getItem("NUM").get(0); - this->max_wait = record.getItem("MAX_WAIT").getSIDouble(0); -} +ActionX::ActionX(const DeckRecord& record, std::time_t start_time) : + ActionX( record.getItem("NAME").getTrimmedString(0), + record.getItem("NUM").get(0), + record.getItem("MIN_WAIT").getSIDouble(0), + start_time ) + +{} + + +ActionX::ActionX(const DeckKeyword& kw, std::time_t start_time) : + ActionX(kw.getRecord(0), start_time) +{} void ActionX::addKeyword(const DeckKeyword& kw) { this->keywords.push_back(kw); } -const std::string& ActionX::name() const { - return this->m_name; + + +bool ActionX::eval(std::time_t sim_time, const ActionContext& context) { + if (!this->ready(sim_time)) + return false; + bool result = true; + + if (result) { + this->run_count += 1; + this->last_run = sim_time; + } + return result; } + +bool ActionX::ready(std::time_t sim_time) const { + if (this->run_count >= this->max_run()) + return false; + + if (sim_time < this->start_time()) + return false; + + if (this->run_count == 0) + return true; + + if (this->min_wait() <= 0) + return true; + + return std::difftime(sim_time, this->last_run) > this->min_wait(); +} + + } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Actions.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Actions.cpp new file mode 100644 index 000000000..35a4f42ed --- /dev/null +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Actions.cpp @@ -0,0 +1,63 @@ +/* + Copyright 2018 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 + +namespace Opm { + +size_t Actions::size() const { + return this->actions.size(); +} + + +void Actions::add(const ActionX& action) { + auto iter = this->actions.find(action.name()); + if (iter != this->actions.end()) + this->actions.erase(iter); + + this->actions.insert(std::pair(action.name(), action)); +} + +ActionX& Actions::at(const std::string& name) { + return this->actions.at(name); +} + + +bool Actions::ready(std::time_t sim_time) const { + for (const auto& pair : this->actions) { + if (pair.second.ready(sim_time)) + return true; + } + return false; +} + + +std::vector Actions::pending(std::time_t sim_time) { + std::vector action_vector; + for (auto& pair : this->actions) { + auto& action = pair.second; + if (action.ready(sim_time)) + action_vector.push_back( &action ); + } + return action_vector; +} + + +} diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index b270be145..ed8eb2a80 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -301,7 +301,7 @@ namespace Opm { while (true) { const auto& keyword = section.getKeyword(keywordIdx); if (keyword.name() == "ACTIONX") { - ActionX action(keyword); + ActionX action(keyword, this->m_timeMap.getStartTime(currentStep + 1)); while (true) { keywordIdx++; if (keywordIdx == section.size()) @@ -317,6 +317,7 @@ namespace Opm { } else action.addKeyword(action_keyword); } + this->actions.add(action); } else this->handleKeyword(currentStep, section, keywordIdx, keyword, parseContext, grid, eclipseProperties, unit_system, rftProperties); @@ -1893,6 +1894,8 @@ namespace Opm { } + + size_t Schedule::size() const { return this->m_timeMap.size(); } diff --git a/src/opm/parser/eclipse/share/keywords/000_Eclipse100/A/ACTIONX b/src/opm/parser/eclipse/share/keywords/000_Eclipse100/A/ACTIONX index 1e8db2a58..6435de47f 100644 --- a/src/opm/parser/eclipse/share/keywords/000_Eclipse100/A/ACTIONX +++ b/src/opm/parser/eclipse/share/keywords/000_Eclipse100/A/ACTIONX @@ -2,7 +2,7 @@ "name" : "ACTIONX", "sections" : ["SCHEDULE"], "records" : [ [{"name" : "NAME", "value_type" : "STRING"}, {"name" : "NUM" , "value_type" : "INT", "default" : 1}, - {"name" : "MAX_WAIT" , "value_type" : "DOUBLE", "default" : 0, "dimension" : "Time"}], + {"name" : "MIN_WAIT" , "value_type" : "DOUBLE", "default" : 0, "dimension" : "Time"}], [{"name" : "CONDITION", "value_type" : "RAW_STRING", "size_type" : "ALL"}] ] } diff --git a/tests/parser/ACTIONX.cpp b/tests/parser/ACTIONX.cpp index 8fec284ae..a8485adf9 100644 --- a/tests/parser/ACTIONX.cpp +++ b/tests/parser/ACTIONX.cpp @@ -26,8 +26,12 @@ #include #include +#include + #include #include +#include +#include #include #include #include @@ -45,13 +49,13 @@ ACTIONX WWCT OPX > 0.75 / / )"}; - ActionX action1("NAME", 10, 100); + ActionX action1("NAME", 10, 100, 0); BOOST_CHECK_EQUAL(action1.name(), "NAME"); const auto deck = Parser{}.parseString( action_kw ); const auto& kw = deck.getKeyword("ACTIONX"); - ActionX action2(kw); + ActionX action2(kw, 0); BOOST_CHECK_EQUAL(action2.name(), "ACTION"); } @@ -64,6 +68,9 @@ ACTIONX 'ACTION' / WWCT OPX > 0.75 / / + +TSTEP + 10 / )"}; const auto WITH_WELSPECS = std::string{ R"( @@ -83,6 +90,9 @@ WELSPECS / ENDACTIO + +TSTEP + 10 / )"}; const auto WITH_GRID = std::string{ R"( @@ -101,6 +111,9 @@ PORO 100*0.78 / ENDACTIO + +TSTEP + 10 / )"}; Opm::Parser parser; auto deck1 = parser.parseString(MISSING_END, Opm::ParseContext()); @@ -117,8 +130,70 @@ ENDACTIO BOOST_CHECK( !sched.hasWell("W1") ); BOOST_CHECK( sched.hasWell("W2")); - // The deck3 contains the 'GRID' keyword in the ACTIONX block - that is not a whitelisted keyword. ParseContext parseContext( {{ParseContext::ACTIONX_ILLEGAL_KEYWORD, InputError::THROW_EXCEPTION}} ); BOOST_CHECK_THROW(Schedule(deck3, grid1, eclipseProperties, Phases(true,true,true), parseContext), std::invalid_argument); } + + +BOOST_AUTO_TEST_CASE(TestActions) { + Opm::ActionContext context; + Opm::Actions config; + BOOST_CHECK_EQUAL(config.size(), 0); + + Opm::ActionX action1("NAME", 10, 100, 0); + config.add(action1); + BOOST_CHECK_EQUAL(config.size(), 1); + + + double min_wait = 86400; + size_t max_eval = 3; + { + Opm::ActionX action("NAME", max_eval, min_wait, util_make_date_utc(1, 7, 2000)); + config.add(action); + BOOST_CHECK_EQUAL(config.size(), 1); + + + Opm::ActionX action3("NAME3", 1000000, 0, util_make_date_utc(1,7,2000)); + config.add(action3); + } + Opm::ActionX& action2 = config.at("NAME"); + BOOST_CHECK(action2.ready(util_make_date_utc(1,7,2000))); + BOOST_CHECK(!action2.ready(util_make_date_utc(1,6,2000))); + BOOST_CHECK(!action2.eval(util_make_date_utc(1,6,2000), context)); + + BOOST_CHECK(action2.eval(util_make_date_utc(1,8,2000), context)); + BOOST_CHECK(!action2.ready(util_make_date_utc(1,8,2000))); + BOOST_CHECK(!action2.eval(util_make_date_utc(1,8,2000), context)); + + BOOST_CHECK(action2.ready(util_make_date_utc(3,8,2000))); + BOOST_CHECK(config.ready(util_make_date_utc(3,8,2000))); + BOOST_CHECK(action2.eval(util_make_date_utc(3,8,2000), context)); + + BOOST_CHECK(action2.ready(util_make_date_utc(5,8,2000))); + BOOST_CHECK(action2.eval(util_make_date_utc(5,8,2000), context)); + + BOOST_CHECK(!action2.ready(util_make_date_utc(7,8,2000))); + BOOST_CHECK(!action2.eval(util_make_date_utc(7,8,2000), context)); + BOOST_CHECK(config.ready(util_make_date_utc(7,8,2000))); + + auto pending = config.pending( util_make_date_utc(7,8,2000)); + BOOST_CHECK_EQUAL( pending.size(), 1); + for (auto& ptr : pending) { + BOOST_CHECK( ptr->ready(util_make_date_utc(7,8,2000))); + BOOST_CHECK( ptr->eval(util_make_date_utc(7,8,2000), context)); + } + + BOOST_CHECK(!action2.eval(util_make_date_utc(7,8,2000), context )); +} + + + +BOOST_AUTO_TEST_CASE(TestContext) { + Opm::ActionContext context; + + BOOST_REQUIRE_THROW(context.get("func", "arg"), std::out_of_range); + + context.add("FUNC", "ARG", 100); + BOOST_CHECK_EQUAL(context.get("FUNC", "ARG"), 100); +}