Merge pull request #1395 from joakim-hove/use-pyaction

Use pyaction
This commit is contained in:
Joakim Hove 2020-01-13 15:16:24 +01:00 committed by GitHub
commit 13b3d1e17b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 219 additions and 7 deletions

View File

@ -213,6 +213,7 @@ if(ENABLE_ECL_INPUT)
if (OPM_ENABLE_EMBEDDED_PYTHON)
set_source_files_properties(${PYTHON_CXX_SOURCE_FILES} PROPERTIES COMPILE_FLAGS -Wno-shadow)
set_source_files_properties(src/opm/parser/eclipse/Python/PythonInterp.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow)
set_source_files_properties(src/opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.cpp PROPERTIES COMPILE_FLAGS -Wno-shadow)
list( APPEND MAIN_SOURCE_FILES src/opm/parser/eclipse/Python/PythonInterp.cpp ${PYTHON_CXX_SOURCE_FILES})
endif()

View File

@ -30,8 +30,22 @@ class PyAction {
public:
explicit PyAction(const std::string& code_arg);
const std::string& code() const;
~PyAction();
/*
Storage is a void pointer to a Python dictionary: py::dict. It is represented
with a void pointer in this way to avoid adding the Pybind11 headers to this
file. Calling scope must do a cast before using the storage pointer:
py::dict * storage = static_cast<py::dict *>(py_action.storage());
The purpose of this dictionary is to allow PYACTION scripts to store state
between invocations.
*/
void * storage() const;
private:
std::string input_code;
void * m_storage;
};
}

View File

@ -29,7 +29,9 @@ namespace Opm {
class PythonInterp;
class Parser;
class Deck;
class SummaryState;
class Schedule;
class EclipseState;
/*
This class is a thin wrapper around the PythonInterp class. The Python class
@ -57,7 +59,7 @@ public:
Python();
bool exec(const std::string& python_code) const;
bool exec(const std::string& python_code, const Parser& parser, Deck& deck) const;
bool exec(const PyAction& py_action) const;
bool exec(const PyAction& py_action, EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) const;
explicit operator bool() const;
private:
std::shared_ptr<PythonInterp> interp;

View File

@ -51,5 +51,8 @@ void python::common::export_SummaryState(py::module& module) {
.def("elapsed", &SummaryState::get_elapsed)
.def_property_readonly("groups", groups)
.def_property_readonly("wells", wells)
.def("__contains__", &SummaryState::has)
.def("has_well_var", &SummaryState::has_well_var)
.def("has_group_var", &SummaryState::has_group_var)
.def("__getitem__", &SummaryState::get);
}

View File

@ -12,7 +12,8 @@ class TestSummaryState(unittest.TestCase):
st = opm.io.sim.SummaryState(datetime.datetime.now())
st.update("FOPT", 100)
self.assertEqual(st["FOPT"], 100)
self.assertTrue("FOPT" in st)
self.assertFalse("FWPR" in st)
with self.assertRaises(IndexError):
x = st["FWPR"]
@ -27,6 +28,9 @@ class TestSummaryState(unittest.TestCase):
st.update_group_var("G3", "GOPR", 300)
self.assertEqual(st.group_var("G3", "GOPR"), 300)
self.assertTrue(st.has_group_var("G1", "GOPR"))
self.assertFalse(st.has_well_var("OP1", "GOPR"))
groups = st.groups
self.assertEqual(len(groups), 3)
self.assertTrue( "G1" in groups )

View File

@ -17,12 +17,24 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef EMBEDDED_PYTHON
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
#else
namespace py {
using dict = int;
}
#endif
#include <opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp>
namespace Opm {
PyAction::PyAction(const std::string& code_arg) :
input_code(code_arg)
input_code(code_arg),
m_storage( new py::dict() )
{}
@ -31,4 +43,31 @@ const std::string& PyAction::code() const {
}
/*
The python variables are reference counted and when the Python dictionary
stored in this->m_storage is destroyed the Python runtime system is involved.
This will fail hard id the Python runtime system has not been initialized. If
the python runtime has not been initialized the Python dictionary object will
leak - the leakage is quite harmless, using the PyAction object without a
Python runtime system does not make any sense after all.
*/
PyAction::~PyAction() {
auto dict = static_cast<py::dict *>(this->m_storage);
#ifdef EMBEDDED_PYTHON
if (Py_IsInitialized())
delete dict;
#else
delete dict;
#endif
}
void * PyAction::storage() const {
return this->m_storage;
}
}

View File

@ -17,7 +17,8 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include "PythonInterp.hpp"
@ -40,8 +41,8 @@ bool Python::exec(const std::string& python_code, const Parser& parser, Deck& de
return true;
}
bool Python::exec(const PyAction& py_action) const {
this->interp->exec(py_action.code());
bool Python::exec(const PyAction& py_action, EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) const {
this->interp->exec(py_action, ecl_state, schedule, report_step, st);
return true;
}

View File

@ -46,6 +46,21 @@ bool PythonInterp::exec(const std::string& python_code, const Parser& parser, De
}
bool PythonInterp::exec(const PyAction& py_action, EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) {
auto context = py::module::import("context");
context.attr("schedule") = &schedule;
context.attr("sim") = &st;
context.attr("state") = &ecl_state;
context.attr("report_step") = report_step;
context.attr("storage") = static_cast<py::dict*>(py_action.storage());
py::exec(py_action.code(), py::globals(), py::dict(py::arg("context") = context));
return true;
}
bool PythonInterp::exec(const std::string& python_code) {
py::exec(python_code, py::globals());
return true;

View File

@ -24,6 +24,12 @@
#include <string>
#include <stdexcept>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#ifdef EMBEDDED_PYTHON
#include <pybind11/embed.h>
@ -44,6 +50,7 @@ class __attribute__ ((visibility("hidden"))) PythonInterp {
public:
bool exec(const std::string& python_code);
bool exec(const std::string& python_code, const Parser& parser, Deck& deck);
bool exec(const PyAction& py_action, EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st);
explicit operator bool() const { return true; }
private:
py::scoped_interpreter guard = {};
@ -62,6 +69,10 @@ public:
return this->fail();
}
bool exec(const PyAction&, EclipseState& ecl_state, Schedule&, std::size_t, SummaryState& ) {
return this->fail();
}
explicit operator bool() const { return false; }
private:
bool fail() { throw std::logic_error("The current opm code has been built without Python support;"); }

View File

@ -28,6 +28,8 @@
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
using namespace Opm;
@ -96,5 +98,125 @@ BOOST_AUTO_TEST_CASE(PYINPUT_BASIC) {
}
BOOST_AUTO_TEST_CASE(PYACTION) {
const std::string deck_string = R"(
RUNSPEC
DIMENS
10 10 3 /
GRID
DX
300*1000 /
DY
300*1000 /
DZ
100*20 100*30 100*50 /
TOPS
100*8325 /
PORO
300*0.3 /
PERMX
300*1 /
PERMY
300*1 /
PERMZ
300*1 /
SCHEDULE
PYACTION
import sys
sys.stdout.write("Running PYACTION\n")
if "FOPR" in context.sim:
sys.stdout.write("Have FOPR: {}\n".format( context.sim["FOPR"] ))
else:
sys.stdout.write("Missing FOPR\n")
grid = context.state.grid()
sys.stdout.write("Grid dimensions: ({},{},{})\n".format(grid.nx, grid.ny, grid.nz))
prod_well = context.schedule.get_well("PROD1", context.report_step)
sys.stdout.write("Well status: {}\n".format(prod_well.status()))
if not "list" in context.storage:
context.storage["list"] = []
context.storage["list"].append(context.report_step)
if context.sim.well_var("PROD1", "WWCT") > 0.80:
context.schedule.shut_well("PROD1", context.report_step)
context.schedule.open_well("PROD2", context.report_step)
context.sim.update("RUN_COUNT", 1)
print(context.storage["list"])
PYEND
WELSPECS
'PROD1' 'G1' 10 10 8400 'OIL' /
'PROD2' 'G1' 5 5 8400 'OIL' /
'INJ' 'G1' 1 1 8335 'GAS' /
/
COMPDAT
'PROD1' 10 10 3 3 'OPEN' 1* 1* 0.5 /
'PROD2' 5 5 3 3 'SHUT' 1* 1* 0.5 /
'INJ' 1 1 1 1 'OPEN' 1* 1* 0.5 /
/
WCONPROD
'PROD1' 'OPEN' 'ORAT' 20000 4* 1000 /
/
WCONINJE
'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
/
TSTEP
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31
31 28 31 30 31 30 31 31 30 31 30 31 /
END
)";
Parser parser;
auto deck = parser.parseString(deck_string);
auto ecl_state = EclipseState(deck);
auto schedule = Schedule(deck, ecl_state);
Python python;
SummaryState st(std::chrono::system_clock::now());
PyAction py_action(deck.getKeyword("PYACTION").getRecord(0).getItem("code").get<std::string>(0));
st.update_well_var("PROD1", "WWCT", 0);
python.exec(py_action, ecl_state, schedule, 10, st);
st.update("FOPR", 0);
python.exec(py_action, ecl_state, schedule, 10, st);
st.update("FOPR", 100);
st.update_well_var("PROD1", "WWCT", 0.90);
python.exec(py_action, ecl_state, schedule, 10, st);
const auto& well1 = schedule.getWell("PROD1", 10);
const auto& well2 = schedule.getWell("PROD2", 10);
BOOST_CHECK( well1.getStatus() == Well::Status::SHUT );
BOOST_CHECK( well2.getStatus() == Well::Status::OPEN );
BOOST_CHECK( st.has("RUN_COUNT") );
}
#endif