commit
13b3d1e17b
@ -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()
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 )
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;"); }
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user