From 343f14dacfec96845078b42eaba2b3bab3a76537 Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Fri, 28 Jan 2022 09:28:07 +0100 Subject: [PATCH] Record and act on return value from PYACTION --- CMakeLists_files.cmake | 1 + msim/src/msim.cpp | 4 +-- opm/input/eclipse/Schedule/Action/Actions.hpp | 2 +- .../eclipse/Schedule/Action/PyAction.hpp | 3 +- opm/input/eclipse/Schedule/Action/State.hpp | 6 ++++ opm/input/eclipse/Schedule/Schedule.hpp | 2 +- .../input/eclipse/Schedule/Action/Actions.cpp | 4 +-- .../eclipse/Schedule/Action/PyAction.cpp | 22 ++++++++------ .../input/eclipse/Schedule/Action/State.cpp | 18 +++++++++++- src/opm/input/eclipse/Schedule/Schedule.cpp | 6 ++-- tests/EMBEDDED_PYTHON.DATA | 12 ++++++++ tests/msim/MSIM_PYACTION.DATA | 2 +- tests/msim/action_count.py | 11 +++++++ tests/msim/test_msim_ACTIONX.cpp | 1 + tests/parser/ACTIONX.cpp | 3 +- tests/parser/EmbeddedPython.cpp | 29 ++++++++++++++++++- 16 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 tests/msim/action_count.py diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index cea90529c..ed370bf25 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -551,6 +551,7 @@ if(ENABLE_ECL_OUTPUT) tests/msim/action1.py tests/msim/action2.py tests/msim/action3.py + tests/msim/action_count.py tests/VFP_CASE.DATA) endif() diff --git a/msim/src/msim.cpp b/msim/src/msim.cpp index e00cb7728..d51d8e8b9 100644 --- a/msim/src/msim.cpp +++ b/msim/src/msim.cpp @@ -89,8 +89,8 @@ void msim::post_step(data::Solution& /* sol */, data::Wells& /* well_data */, da this->schedule.applyAction(report_step, *action, result.wells(), {}); } - for (const auto& pyaction : actions.pending_python()) - this->schedule.runPyAction(report_step, *pyaction, this->state, this->st); + for (const auto& pyaction : actions.pending_python(this->action_state)) + this->schedule.runPyAction(report_step, *pyaction, this->action_state, this->state, this->st); } diff --git a/opm/input/eclipse/Schedule/Action/Actions.hpp b/opm/input/eclipse/Schedule/Action/Actions.hpp index a8b4e261b..e7e958ee9 100644 --- a/opm/input/eclipse/Schedule/Action/Actions.hpp +++ b/opm/input/eclipse/Schedule/Action/Actions.hpp @@ -55,7 +55,7 @@ public: const ActionX& operator[](const std::string& name) const; const ActionX& operator[](std::size_t index) const; std::vector pending(const State& state, std::time_t sim_time) const; - std::vector pending_python() const; + std::vector pending_python(const State& state) const; bool has(const std::string& name) const; std::vector::const_iterator begin() const; diff --git a/opm/input/eclipse/Schedule/Action/PyAction.hpp b/opm/input/eclipse/Schedule/Action/PyAction.hpp index cf02d046c..7744da8c7 100644 --- a/opm/input/eclipse/Schedule/Action/PyAction.hpp +++ b/opm/input/eclipse/Schedule/Action/PyAction.hpp @@ -36,6 +36,7 @@ class SummaryState; class PyRunModule; namespace Action { +class State; class PyAction { public: @@ -53,7 +54,7 @@ public: bool run(EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st, const std::function&)>& actionx_callback) const; const std::string& name() const; - bool active() const; + bool ready(const State& state) const; bool operator==(const PyAction& other) const; template diff --git a/opm/input/eclipse/Schedule/Action/State.hpp b/opm/input/eclipse/Schedule/Action/State.hpp index 9fb247f0c..2c7842388 100644 --- a/opm/input/eclipse/Schedule/Action/State.hpp +++ b/opm/input/eclipse/Schedule/Action/State.hpp @@ -35,6 +35,8 @@ namespace Action { class ActionX; class Actions; +class PyAction; + class State { struct RunState { @@ -79,9 +81,11 @@ struct RunState { public: void add_run(const ActionX& action, std::time_t sim_time, Result result); + void add_run(const PyAction& action, bool result); std::size_t run_count(const ActionX& action) const; std::time_t run_time(const ActionX& action) const; std::optional result(const std::string& action) const; + std::optional python_result(const std::string& action) const; void load_rst(const Actions& action_config, const RestartIO::RstState& rst_state); template @@ -89,6 +93,7 @@ public: { serializer.map(this->run_state); serializer.map(this->last_result); + serializer.template map, false>(this->m_python_result); } @@ -100,6 +105,7 @@ private: static action_id make_id(const ActionX& action); std::map run_state; std::map last_result; + std::map m_python_result; }; diff --git a/opm/input/eclipse/Schedule/Schedule.hpp b/opm/input/eclipse/Schedule/Schedule.hpp index 33558bda3..28125cb8b 100644 --- a/opm/input/eclipse/Schedule/Schedule.hpp +++ b/opm/input/eclipse/Schedule/Schedule.hpp @@ -295,7 +295,7 @@ namespace Opm Python code. he return value from runPyAction() comes from such a internal ACTIONX. */ - SimulatorUpdate runPyAction(std::size_t reportStep, const Action::PyAction& pyaction, EclipseState& ecl_state, SummaryState& summary_state); + SimulatorUpdate runPyAction(std::size_t reportStep, const Action::PyAction& pyaction, Action::State& action_state, EclipseState& ecl_state, SummaryState& summary_state); const GasLiftOpt& glo(std::size_t report_step) const; diff --git a/src/opm/input/eclipse/Schedule/Action/Actions.cpp b/src/opm/input/eclipse/Schedule/Action/Actions.cpp index dfbab48d1..e370d2089 100644 --- a/src/opm/input/eclipse/Schedule/Action/Actions.cpp +++ b/src/opm/input/eclipse/Schedule/Action/Actions.cpp @@ -109,10 +109,10 @@ bool Actions::ready(const State& state, std::time_t sim_time) const { return false; } -std::vector Actions::pending_python() const { +std::vector Actions::pending_python(const State& state) const { std::vector pyaction_vector; for (const auto& pyaction : this->pyactions) { - if (pyaction.active()) + if (pyaction.ready(state)) pyaction_vector.push_back( &pyaction ); } return pyaction_vector; diff --git a/src/opm/input/eclipse/Schedule/Action/PyAction.cpp b/src/opm/input/eclipse/Schedule/Action/PyAction.cpp index e70b8c57d..4c60607ef 100644 --- a/src/opm/input/eclipse/Schedule/Action/PyAction.cpp +++ b/src/opm/input/eclipse/Schedule/Action/PyAction.cpp @@ -31,6 +31,7 @@ namespace py = pybind11; #include #include #include +#include #include @@ -64,8 +65,18 @@ PyAction PyAction::serializeObject() return result; } -bool PyAction::active() const { - return this->m_active; +bool PyAction::ready(const State& state) const { + if (this->m_run_count == RunCount::unlimited) + return true; + + auto last_result = state.python_result(this->m_name); + if (!last_result.has_value()) + return true; + + if (this->m_run_count == RunCount::first_true && last_result.value() == false) + return true; + + return false; } @@ -73,13 +84,6 @@ const std::string& PyAction::name() const { return this->m_name; } -void PyAction::update(bool result) const { - if (this->m_run_count == RunCount::single) - this->m_active = false; - - if (this->m_run_count == RunCount::first_true && result) - this->m_active = false; -} bool PyAction::operator==(const PyAction& other) const { return this->m_name == other.m_name && diff --git a/src/opm/input/eclipse/Schedule/Action/State.cpp b/src/opm/input/eclipse/Schedule/Action/State.cpp index a9b17adce..3087cd5ff 100644 --- a/src/opm/input/eclipse/Schedule/Action/State.cpp +++ b/src/opm/input/eclipse/Schedule/Action/State.cpp @@ -59,6 +59,10 @@ void State::add_run(const ActionX& action, std::time_t run_time, Result result) this->last_result.insert_or_assign(action.name(), std::move(result)); } +void State::add_run(const PyAction& action, bool result) { + this->m_python_result.insert_or_assign( action.name(), result ); +} + std::optional State::result(const std::string& action) const { auto iter = this->last_result.find(action); @@ -69,6 +73,16 @@ std::optional State::result(const std::string& action) const { } +std::optional State::python_result(const std::string& action) const { + auto iter = this->m_python_result.find(action); + if (iter == this->m_python_result.end()) + return std::nullopt; + + return iter->second; +} + + + /* When restoring from restart file we initialize the number of times it has run and the last run time. From the evaluation only the 'true' evaluation is @@ -86,7 +100,8 @@ void State::load_rst(const Actions& action_config, const RestartIO::RstState& rs bool State::operator==(const State& other) const { return this->run_state == other.run_state && - this->last_result == other.last_result; + this->last_result == other.last_result && + this->m_python_result == other.m_python_result; } @@ -94,6 +109,7 @@ State State::serializeObject() { State st; st.run_state.insert(std::make_pair( std::make_pair("ACTION", 100), RunState::serializeObject())); st.last_result.insert( std::make_pair("ACTION", Result::serializeObject())); + st.m_python_result.insert( std::make_pair("PYACTION", false) ); return st; } diff --git a/src/opm/input/eclipse/Schedule/Schedule.cpp b/src/opm/input/eclipse/Schedule/Schedule.cpp index 3b8d97dd9..a601d89fe 100644 --- a/src/opm/input/eclipse/Schedule/Schedule.cpp +++ b/src/opm/input/eclipse/Schedule/Schedule.cpp @@ -58,6 +58,7 @@ #include #include +#include #include #include #include @@ -1428,14 +1429,15 @@ File {} line {}.)", pattern, location.keyword, location.filename, location.linen */ - SimulatorUpdate Schedule::runPyAction(std::size_t reportStep, const Action::PyAction& pyaction, EclipseState& ecl_state, SummaryState& summary_state) { + SimulatorUpdate Schedule::runPyAction(std::size_t reportStep, const Action::PyAction& pyaction, Action::State& action_state, EclipseState& ecl_state, SummaryState& summary_state) { SimulatorUpdate sim_update; auto apply_action_callback = [&sim_update, &reportStep, this](const std::string& action_name, const std::vector& matching_wells) { sim_update = this->applyAction(reportStep, action_name, matching_wells); }; - pyaction.run(ecl_state, *this, reportStep, summary_state, apply_action_callback); + auto result = pyaction.run(ecl_state, *this, reportStep, summary_state, apply_action_callback); + action_state.add_run(pyaction, result); return sim_update; } diff --git a/tests/EMBEDDED_PYTHON.DATA b/tests/EMBEDDED_PYTHON.DATA index 4e73a873d..75cb818ff 100644 --- a/tests/EMBEDDED_PYTHON.DATA +++ b/tests/EMBEDDED_PYTHON.DATA @@ -34,6 +34,18 @@ PYACTION WCLOSE UNLIMITED / 'wclose.py' / +PYACTION + UNLIMITED UNLIMITED / + 'wclose.py' / + +PYACTION + SINGLE SINGLE / + 'wclose.py' / + +PYACTION + FIRST_TRUE FIRST_TRUE / + 'wclose.py' / + WELSPECS 'PROD1' 'G1' 10 10 8400 'OIL' / 'PROD2' 'G1' 5 5 8400 'OIL' / diff --git a/tests/msim/MSIM_PYACTION.DATA b/tests/msim/MSIM_PYACTION.DATA index fe9e559d8..63941c7f3 100644 --- a/tests/msim/MSIM_PYACTION.DATA +++ b/tests/msim/MSIM_PYACTION.DATA @@ -447,7 +447,7 @@ PYACTION PYACTION ACTION3 FIRST_TRUE / - 'action2.py' / + 'action_count.py' / DATES 1 'JAN' 2015 / diff --git a/tests/msim/action_count.py b/tests/msim/action_count.py new file mode 100644 index 000000000..79cf3246e --- /dev/null +++ b/tests/msim/action_count.py @@ -0,0 +1,11 @@ +import math + +def run(ecl_state, schedule, report_step, summary_state, actionx_callback): + if not "run_count" in summary_state: + summary_state["run_count"] = 0 + summary_state["run_count"] += 1 + + if summary_state.elapsed() > (365 + 15)*24*3600: + return True + + return False diff --git a/tests/msim/test_msim_ACTIONX.cpp b/tests/msim/test_msim_ACTIONX.cpp index 96cc10e1c..8e217f1ed 100644 --- a/tests/msim/test_msim_ACTIONX.cpp +++ b/tests/msim/test_msim_ACTIONX.cpp @@ -524,6 +524,7 @@ BOOST_AUTO_TEST_CASE(PYTHON_WELL_CLOSE_EXAMPLE) { BOOST_CHECK(w4_11.getStatus() == Well::Status::SHUT ); } } + BOOST_CHECK_EQUAL( sim.st.get("run_count"), 13); } BOOST_AUTO_TEST_CASE(PYTHON_ACTIONX) { diff --git a/tests/parser/ACTIONX.cpp b/tests/parser/ACTIONX.cpp index 1b58c184a..bdd7147cd 100644 --- a/tests/parser/ACTIONX.cpp +++ b/tests/parser/ACTIONX.cpp @@ -276,7 +276,7 @@ BOOST_AUTO_TEST_CASE(TestActions) { BOOST_CHECK(!action2.eval(context)); - const auto& python_actions = config.pending_python(); + const auto& python_actions = config.pending_python(action_state); BOOST_CHECK_EQUAL(python_actions.size(), 2U); } @@ -977,6 +977,7 @@ BOOST_AUTO_TEST_CASE(ActionState) { auto res = st.result("NAME-HIDDEN"); BOOST_CHECK(!res.has_value()); + } BOOST_AUTO_TEST_CASE(MANUAL4_QUOTE) { diff --git a/tests/parser/EmbeddedPython.cpp b/tests/parser/EmbeddedPython.cpp index 2ee33728a..e47c9b746 100644 --- a/tests/parser/EmbeddedPython.cpp +++ b/tests/parser/EmbeddedPython.cpp @@ -32,6 +32,7 @@ #include #include #include +#include using namespace Opm; @@ -124,7 +125,7 @@ BOOST_AUTO_TEST_CASE(PYACTION) { const std::string& fname = pyaction_kw.getRecord(1).getItem(0).get(0); Action::PyAction py_action(python, "WCLOSE", Action::PyAction::RunCount::unlimited, deck.makeDeckPath(fname)); auto actionx_callback = [] (const std::string&, const std::vector&) { ;}; - + Action::State action_state; st.update_well_var("PROD1", "WWCT", 0); py_action.run(ecl_state, schedule, 10, st, actionx_callback); @@ -141,6 +142,32 @@ BOOST_AUTO_TEST_CASE(PYACTION) { BOOST_CHECK( well1.getStatus() == Well::Status::SHUT ); BOOST_CHECK( well2.getStatus() == Well::Status::OPEN ); BOOST_CHECK( st.has("RUN_COUNT") ); + + std::map action_map; + for (const auto * p : schedule[0].actions().pending_python(action_state)) + action_map.emplace( p->name(), *p ); + + const auto& pyaction_unlimited = action_map.at("UNLIMITED"); + const auto& pyaction_single = action_map.at("SINGLE"); + const auto& pyaction_first_true = action_map.at("FIRST_TRUE"); + + auto actions = schedule[0].actions(); + BOOST_CHECK( actions.pending_python(action_state).size() == 4); + + action_state.add_run( py_action, true); + BOOST_CHECK( actions.pending_python(action_state).size() == 4); + + action_state.add_run( pyaction_unlimited, true); + BOOST_CHECK( actions.pending_python(action_state).size() == 4); + + action_state.add_run( pyaction_single, false); + BOOST_CHECK( actions.pending_python(action_state).size() == 3); + + action_state.add_run( pyaction_first_true, false); + BOOST_CHECK( actions.pending_python(action_state).size() == 3); + + action_state.add_run( pyaction_first_true, true); + BOOST_CHECK( actions.pending_python(action_state).size() == 2); }