Run PYACTION keywords

The PYACTION keyword is implemented with a Python module with a run() function
in an external module.
This commit is contained in:
Joakim Hove 2020-03-22 21:02:32 +01:00
parent 685ab301d2
commit 315382bad8
26 changed files with 1007 additions and 199 deletions

View File

@ -52,6 +52,7 @@ if(ENABLE_ECL_INPUT)
src/opm/parser/eclipse/Deck/DeckSection.cpp
src/opm/parser/eclipse/Deck/UDAValue.cpp
src/opm/parser/eclipse/Python/Python.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.cpp
src/opm/parser/eclipse/EclipseState/AquiferConfig.cpp
src/opm/parser/eclipse/EclipseState/AquiferCT.cpp
src/opm/parser/eclipse/EclipseState/Aquifetp.cpp
@ -95,7 +96,6 @@ if(ENABLE_ECL_INPUT)
src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionValue.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Action/ASTNode.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Action/Condition.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.cpp
src/opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Events.cpp
src/opm/parser/eclipse/EclipseState/Schedule/Group/Group.cpp
@ -221,7 +221,10 @@ if(ENABLE_ECL_INPUT)
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})
list( APPEND MAIN_SOURCE_FILES
src/opm/parser/eclipse/Python/PythonInterp.cpp
src/opm/parser/eclipse/Python/PyRunModule.cpp
${PYTHON_CXX_SOURCE_FILES})
endif()
list( APPEND PYTHON_CXX_DEPENDS ${PYTHON_CXX_SOURCE_FILES}
@ -435,8 +438,13 @@ if(ENABLE_ECL_OUTPUT)
tests/PYACTION.DATA
tests/act1.py
tests/EXIT_TEST.DATA
tests/action_syntax_error.py
tests/action_missing_run.py
tests/EMBEDDED_PYTHON.DATA
tests/wclose.py
tests/msim/MSIM_PYACTION.DATA
tests/msim/action1.py
tests/msim/action2.py
)
endif()

View File

@ -21,6 +21,7 @@ namespace Opm {
class EclipseIO;
class ParseContext;
class Parser;
class Python;
class SummaryState;
class msim {
@ -33,7 +34,7 @@ public:
void well_rate(const std::string& well, data::Rates::opt rate, std::function<well_rate_function> func);
void solution(const std::string& field, std::function<solution_function> func);
void run(Schedule& schedule, EclipseIO& io, bool report_only);
void post_step(Schedule& schedule, const SummaryState& st, data::Solution& sol, data::Wells& well_data, size_t report_step) const;
void post_step(Schedule& schedule, SummaryState& st, data::Solution& sol, data::Wells& well_data, size_t report_step);
private:
void run_step(const Schedule& schedule, SummaryState& st, data::Solution& sol, data::Wells& well_data, size_t report_step, EclipseIO& io) const;

View File

@ -25,6 +25,7 @@
#include <opm/output/data/Solution.hpp>
#include <opm/output/data/Wells.hpp>
#include <opm/output/data/Groups.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionContext.hpp>
@ -43,6 +44,7 @@ void msim::run(Schedule& schedule, EclipseIO& io, bool report_only) {
const double week = 7 * 86400;
data::Solution sol;
SummaryState st(std::chrono::system_clock::from_time_t(schedule.getStartTime()));
Python python;
io.writeInitial();
for (size_t report_step = 1; report_step < schedule.size(); report_step++) {
@ -61,7 +63,7 @@ void msim::run(Schedule& schedule, EclipseIO& io, bool report_only) {
}
void msim::post_step(Schedule& schedule, const SummaryState& st, data::Solution& /* sol */, data::Wells& /* well_data */, size_t report_step) const {
void msim::post_step(Schedule& schedule, SummaryState& st, data::Solution& /* sol */, data::Wells& /* well_data */, size_t report_step) {
const auto& actions = schedule.actions(report_step);
if (actions.empty())
return;
@ -74,6 +76,9 @@ void msim::post_step(Schedule& schedule, const SummaryState& st, data::Solution&
if (result)
schedule.applyAction(report_step, *action, result);
}
for (const auto& pyaction : actions.pending_python())
pyaction->run(this->state, schedule, report_step, st);
}

View File

@ -23,11 +23,16 @@
#include <string>
#include <memory>
namespace Opm {
class Python;
class EclipseState;
class Schedule;
class SummaryState;
class PyRunModule;
namespace Action {
@ -41,49 +46,30 @@ public:
static RunCount from_string(std::string run_count);
static std::string load(const std::string& input_path, const std::string& fname);
PyAction() = default;
PyAction(const std::string& name, RunCount run_count, const std::string& code);
PyAction(const PyAction& other);
bool run(EclipseState&, Schedule&, std::size_t, SummaryState& ) const { return false; };
PyAction operator=(const PyAction& other);
~PyAction();
static PyAction serializeObject();
const std::string& code() const;
PyAction() = default;
PyAction(std::shared_ptr<const Python> python, const std::string& name, RunCount run_count, const std::string& module_file);
bool run(EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) const;
const std::string& name() const;
bool operator==(const PyAction& other) const;
PyAction::RunCount run_count() const;
bool active() const;
/*
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;
bool operator==(const PyAction& other) const;
template<class Serializer>
void serializeOp(Serializer& serializer)
{
serializer(m_name);
serializer(m_run_count);
serializer(input_code);
serializer(module_file);
serializer(m_active);
}
private:
void update(bool result) const;
mutable std::shared_ptr< PyRunModule > run_module;
std::string m_name;
RunCount m_run_count;
std::string input_code;
void * m_storage = nullptr;
std::string module_file;
mutable bool m_active = true;
};
}

View File

@ -263,7 +263,7 @@ namespace Opm
int getNupcol(size_t reportStep) const;
bool operator==(const Schedule& data) const;
std::shared_ptr<const Python> python() const;
/*
The cmp() function compares two schedule instances in a context aware
@ -318,6 +318,7 @@ namespace Opm
private:
template<class Key, class Value> using Map2 = std::map<Key,Value>;
std::shared_ptr<const Python> python_handle;
TimeMap m_timeMap;
WellMap wells_static;
GroupMap groups;
@ -371,7 +372,7 @@ namespace Opm
void updateUDQActive( std::size_t timeStep, std::shared_ptr<UDQActive> udq );
bool updateWellStatus( const std::string& well, size_t reportStep , Well::Status status, bool update_connections);
void addWellToGroup( const std::string& group_name, const std::string& well_name , size_t timeStep);
void iterateScheduleSection(const std::string& input_path, const ParseContext& parseContext , ErrorGuard& errors, const SCHEDULESection& , const EclipseGrid& grid,
void iterateScheduleSection(std::shared_ptr<const Python> python, const std::string& input_path, const ParseContext& parseContext , ErrorGuard& errors, const SCHEDULESection& , const EclipseGrid& grid,
const FieldPropsManager& fp);
void addACTIONX(const Action::ActionX& action, std::size_t currentStep);
void addGroupToGroup( const std::string& parent_group, const std::string& child_group, size_t timeStep);
@ -417,7 +418,7 @@ namespace Opm
void handleWEFAC( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors);
void handleTUNING( const DeckKeyword& keyword, size_t currentStep);
void handlePYACTION( const std::string& input_path, const DeckKeyword& keyword, size_t currentStep);
void handlePYACTION( std::shared_ptr<const Python> python, const std::string& input_path, const DeckKeyword& keyword, size_t currentStep);
void handleNUPCOL( const DeckKeyword& keyword, size_t currentStep);
void handleGRUPTREE( const DeckKeyword& keyword, size_t currentStep, const UnitSystem& unit_system, const ParseContext& parseContext, ErrorGuard& errors);
void handleGRUPNET( const DeckKeyword& keyword, size_t currentStep, const UnitSystem& unit_system);
@ -438,7 +439,8 @@ namespace Opm
void handleVFPINJ(const DeckKeyword& vfpprodKeyword, const UnitSystem& unit_system, size_t currentStep);
void checkUnhandledKeywords( const SCHEDULESection& ) const;
void checkIfAllConnectionsIsShut(size_t currentStep);
void handleKeyword(const std::string& input_path,
void handleKeyword(std::shared_ptr<const Python> python,
const std::string& input_path,
size_t currentStep,
const SCHEDULESection& section,
size_t keywordIdx,

View File

@ -17,14 +17,12 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_EMBEDDED_PYTHON
#define OPM_EMBEDDED_PYTHON
#ifndef OPM_PYTHON_HPP
#define OPM_PYTHON_HPP
#include <memory>
#include <string>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp>
namespace Opm {
class PythonInterp;
class Parser;
@ -33,6 +31,9 @@ class SummaryState;
class Schedule;
class EclipseState;
namespace Action {
class PyAction;
}
/*
This class is a thin wrapper around the PythonInterp class. The Python class
can always be safely instantiated, but the actual PythonInterp implementation
@ -136,6 +137,7 @@ public:
compiled with support for Python.
*/
static bool supported();
bool run_module(const std::string& path);
private:
std::shared_ptr<PythonInterp> interp;
};

View File

@ -47,7 +47,7 @@ size_t Actions::size() const {
bool Actions::empty() const {
return this->actions.empty();
return this->actions.empty() && this->pyactions.empty();
}

View File

@ -17,8 +17,10 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fstream>
#include <memory>
#ifdef EMBEDDED_PYTHON
#include "src/opm/parser/eclipse/Python/PyRunModule.hpp"
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
@ -26,8 +28,11 @@ namespace py = pybind11;
#include <opm/common/utility/String.hpp>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
namespace Opm {
namespace Action {
@ -50,88 +55,83 @@ PyAction::RunCount PyAction::from_string(std::string run_count) {
PyAction PyAction::serializeObject()
{
PyAction result;
result.m_name = "name";
result.m_run_count = RunCount::unlimited;
result.input_code = "import opm";
result.m_run_count = RunCount::first_true;
result.m_active = false;
result.module_file = "no.such.file.py";
return result;
}
std::string PyAction::load(const std::string& input_path, const std::string& fname) {
namespace fs = Opm::filesystem;
fs::path code_path = fs::path(input_path) / fs::path(fname);
if (fs::exists(code_path)) {
std::ifstream ifs(code_path);
return std::string{ std::istreambuf_iterator<char>{ifs}, {} };
} else
throw std::invalid_argument("No such file: " + fname);
}
PyAction::PyAction(const std::string& name, RunCount run_count, const std::string& code) :
m_name(name),
m_run_count(run_count),
input_code(code)
{
#ifdef EMBEDDED_PYTHON
this->m_storage = new py::dict();
#endif
}
PyAction::PyAction(const PyAction& other) :
PyAction(other.name(), other.run_count(), other.code())
{}
PyAction PyAction::operator=(const PyAction& other) {
return PyAction(other);
}
const std::string& PyAction::code() const {
return this->input_code;
}
const std::string& PyAction::name() const {
return this->m_name;
}
PyAction::RunCount PyAction::run_count() const {
return this->m_run_count;
}
bool PyAction::active() const {
return this->m_active;
}
/*
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 if 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() {
#ifdef EMBEDDED_PYTHON
auto dict = static_cast<py::dict *>(this->m_storage);
if (Py_IsInitialized())
delete dict;
#endif
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 &&
this->m_run_count == other.m_run_count &&
this->m_active == other.m_active &&
this->input_code == other.input_code;
this->m_run_count == other.m_run_count &&
this->m_active == other.m_active &&
this->module_file == other.module_file;
}
#ifndef EMBEDDED_PYTHON
bool PyAction::run(EclipseState&, Schedule&, std::size_t, SummaryState&) const
{
return false;
}
PyAction::PyAction(std::shared_ptr<const Python>, const std::string& name, RunCount run_count, const std::string& fname) :
m_name(name),
m_run_count(run_count),
module_file(fname)
{}
#else
PyAction::PyAction(std::shared_ptr<const Python> python, const std::string& name, RunCount run_count, const std::string& fname) :
run_module( std::make_shared<Opm::PyRunModule>(python, fname)),
m_name(name),
m_run_count(run_count),
module_file(fname)
{
}
bool PyAction::run(EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) const
{
/*
For PyAction instances which have been constructed the 'normal' way
through the four argument constructor the run_module member variable has
already been correctly initialized, however if this instance lives on a
rank != 0 process and has been created through deserialization it was
created without access to a Python handle and we must import the module
now.
*/
if (!this->run_module)
this->run_module = std::make_shared<Opm::PyRunModule>(schedule.python(), this->module_file);
return this->run_module->run(ecl_state, schedule, report_step, st);
}
void * PyAction::storage() const {
return this->m_storage;
}
#endif
}
}

View File

@ -128,6 +128,7 @@ std::pair<std::time_t, std::size_t> restart_info(const RestartIO::RstState * rst
ErrorGuard& errors,
[[maybe_unused]] std::shared_ptr<const Python> python,
const RestartIO::RstState * rst) :
python_handle(python),
m_timeMap( deck , restart_info( rst )),
m_oilvaporizationproperties( this->m_timeMap, OilVaporizationProperties(runspec.tabdims().getNumPVTTables()) ),
m_events( this->m_timeMap ),
@ -168,7 +169,7 @@ std::pair<std::time_t, std::size_t> restart_info(const RestartIO::RstState * rst
}
if (DeckSection::hasSCHEDULE(deck))
iterateScheduleSection( deck.getInputPath(), parseContext, errors, SCHEDULESection( deck ), grid, fp);
iterateScheduleSection( python, deck.getInputPath(), parseContext, errors, SCHEDULESection( deck ), grid, fp);
}
@ -285,7 +286,8 @@ Schedule::Schedule(const Deck& deck, const EclipseState& es, const ParseContext&
}
void Schedule::handleKeyword(const std::string& input_path,
void Schedule::handleKeyword(std::shared_ptr<const Python> python,
const std::string& input_path,
size_t currentStep,
const SCHEDULESection& section,
size_t keywordIdx,
@ -480,7 +482,7 @@ Schedule::Schedule(const Deck& deck, const EclipseState& es, const ParseContext&
handleNUPCOL(keyword, currentStep);
else if (keyword.name() == "PYACTION")
handlePYACTION(input_path, keyword, currentStep);
handlePYACTION(python, input_path, keyword, currentStep);
else if (geoModifiers.find( keyword.name() ) != geoModifiers.end()) {
bool supported = geoModifiers.at( keyword.name() );
@ -495,7 +497,7 @@ Schedule::Schedule(const Deck& deck, const EclipseState& es, const ParseContext&
}
void Schedule::iterateScheduleSection(const std::string& input_path, const ParseContext& parseContext , ErrorGuard& errors, const SCHEDULESection& section , const EclipseGrid& grid,
void Schedule::iterateScheduleSection(std::shared_ptr<const Opm::Python> python, const std::string& input_path, const ParseContext& parseContext , ErrorGuard& errors, const SCHEDULESection& section , const EclipseGrid& grid,
const FieldPropsManager& fp) {
const auto& unit_system = section.unitSystem();
std::vector<std::pair< const DeckKeyword* , size_t> > rftProperties;
@ -549,7 +551,7 @@ void Schedule::iterateScheduleSection(const std::string& input_path, const Parse
else {
if (currentStep >= this->m_timeMap.restart_offset() || skiprest_whitelist.count(keyword.name()))
this->handleKeyword(input_path, currentStep, section, keywordIdx, keyword, parseContext, errors, grid, fp, unit_system, rftProperties);
this->handleKeyword(python, input_path, currentStep, section, keywordIdx, keyword, parseContext, errors, grid, fp, unit_system, rftProperties);
else
OpmLog::info("Skipping keyword: " + keyword.name() + " while loading SCHEDULE section");
}
@ -647,8 +649,9 @@ void Schedule::iterateScheduleSection(const std::string& input_path, const Parse
}
}
void Schedule::handlePYACTION( const std::string& input_path, const DeckKeyword& keyword, size_t currentStep) {
if (!Python::supported()) {
void Schedule::handlePYACTION( std::shared_ptr<const Python> python, const std::string& input_path, const DeckKeyword& keyword, size_t currentStep) {
if (!python->enabled()) {
//Must have a real Python instance here - to ensure that IMPORT works
const auto& loc = keyword.location();
OpmLog::warning("This version of flow is built without support for Python. Keyword PYACTION in file: " + loc.filename + " line: " + std::to_string(loc.lineno) + " is ignored.");
return;
@ -657,9 +660,14 @@ void Schedule::iterateScheduleSection(const std::string& input_path, const Parse
using PY = ParserKeywords::PYACTION;
const auto& name = keyword.getRecord(0).getItem<PY::NAME>().get<std::string>(0);
const auto& run_count = Action::PyAction::from_string( keyword.getRecord(0).getItem<PY::RUN_COUNT>().get<std::string>(0) );
const auto& code = Action::PyAction::load( input_path, keyword.getRecord(1).getItem<PY::FILENAME>().get<std::string>(0) );
const auto& module_arg = keyword.getRecord(1).getItem<PY::FILENAME>().get<std::string>(0);
std::string module;
if (input_path.empty())
module = module_arg;
else
module = input_path + "/" + module_arg;
Action::PyAction pyaction(name, run_count, code);
Action::PyAction pyaction(python, name, run_count, module);
auto new_actions = std::make_shared<Action::Actions>( this->actions(currentStep) );
new_actions->add(pyaction);
this->m_actions.update(currentStep, new_actions);
@ -3075,6 +3083,11 @@ void Schedule::load_rst(const RestartIO::RstState& rst_state, const EclipseGrid&
m_events.addEvent( ScheduleEvents::TUNING_CHANGE , report_step);
}
std::shared_ptr<const Python> Schedule::python() const
{
return this->python_handle;
}
namespace {
/*
The insane trickery here (thank you Stackoverflow!) is to be able to provide a

View File

@ -45,7 +45,7 @@ struct python_module {
if (result == -1)
pybind11::pybind11_fail("Insufficient memory to add a new module");
}
};
};
}
}

View File

@ -0,0 +1,81 @@
/*
Copyright 2020 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 <http://www.gnu.org/licenses/>.
*/
#ifndef EMBEDDED_PYTHON
error BUG: The PyRunModule.hpp header should *not* be included in a configuration without EMBEDDED_PYTHON
#endif
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/common/utility/FileSystem.hpp>
#include "src/opm/parser/eclipse/Python/PyRunModule.hpp"
namespace Opm {
namespace fs = Opm::filesystem;
PyRunModule::PyRunModule(std::shared_ptr<const Python> python, const std::string& fname) {
if (python->enabled())
this->python_handle = python;
else
throw std::logic_error("Tried to make a PYACTION object with an invalid Python handle");
fs::path file(fname);
if (!fs::is_regular_file(file))
throw std::invalid_argument("No such module: " + fname);
std::string module_name = file.filename().stem();
std::string module_path = file.parent_path().string();
if (!module_path.empty()) {
py::module sys = py::module::import("sys");
py::list sys_path = sys.attr("path");
{
bool have_path = false;
for (const auto& elm : sys_path) {
const std::string& path_elm = static_cast<py::str>(elm);
if (path_elm == module_path)
have_path = true;
}
if (!have_path)
sys_path.append(py::str(module_path));
}
}
this->opm_embedded = py::module::import("opm_embedded");
this->module = py::module::import(module_name.c_str());
if (this->module.is_none())
throw std::runtime_error("Syntax error when loading Python module: " + fname);
if (py::hasattr(this->module, "run"))
this->run_function = this->module.attr("run");
if (this->run_function.is_none())
throw std::runtime_error("Python module: " + fname + " did not have run() method");
this->module.attr("storage") = this->storage;
}
bool PyRunModule::run(EclipseState& ecl_state, Schedule& sched, std::size_t report_step, SummaryState& st) {
py::object result = this->run_function(&ecl_state, &sched, report_step, &st);
return result.cast<bool>();
}
}

View File

@ -0,0 +1,56 @@
/*
Copyright 2020 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 <http://www.gnu.org/licenses/>.
*/
#ifndef EMBEDDED_PYTHON
error BUG: The PyRunModule.hpp header should *not* be included in a configuration without EMBEDDED_PYTHON
#endif
#ifndef OPM_PY_RUN_MODULE
#define OPM_PY_RUN_MODULE
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
#include <memory>
#include <string>
#include <opm/parser/eclipse/Python/Python.hpp>
namespace Opm {
class EclipseState;
class Schedule;
class SummaryState;
class PyRunModule {
public:
PyRunModule(std::shared_ptr<const Python> python, const std::string& fname);
bool run(EclipseState& ecl_state, Schedule& sched, std::size_t report_step, SummaryState& st);
private:
py::object run_function = py::none();
std::shared_ptr<const Python> python_handle;
py::module module;
py::module opm_embedded;
py::dict storage;
};
}
#endif

View File

@ -45,22 +45,14 @@ bool Python::supported() {
bool Python::exec(const std::string& python_code) const {
this->interp->exec(python_code);
return true;
return this->interp->exec(python_code);
}
bool Python::exec(const std::string& python_code, const Parser& parser, Deck& deck) const {
this->interp->exec(python_code, parser, deck);
return true;
return this->interp->exec(python_code, parser, deck);
}
bool Python::exec(const Action::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;
}
bool Python::enabled() const {
return bool( *this->interp );
}

View File

@ -21,9 +21,12 @@
#ifdef EMBEDDED_PYTHON
#include <pybind11/embed.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <pybind11/pytypes.h>
#include <opm/parser/eclipse/Deck/Deck.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/common/utility/FileSystem.hpp>
#include "python/cxx/export.hpp"
#include "PythonInterp.hpp"
@ -32,35 +35,35 @@
namespace py = pybind11;
namespace Opm {
OPM_EMBEDDED_MODULE(context, module) {
/*
OPM_EMBEDDED_MODULE create a Python of all the Python/C++ classes which are
generated in the python::common::export_all() function in the wrapping code.
*/
OPM_EMBEDDED_MODULE(opm_embedded, module) {
python::common::export_all(module);
}
bool PythonInterp::exec(const std::string& python_code, py::module& context) {
py::bool_ def_result = false;
context.attr("result") = &def_result;
py::exec(python_code, py::globals() , py::dict(py::arg("context") = context));
const auto& result = static_cast<py::bool_>(context.attr("result"));
return result;
}
bool PythonInterp::exec(const std::string& python_code, const Parser& parser, Deck& deck) {
if (!this->guard)
throw std::logic_error("Python interpreter not enabled");
auto context = py::module::import("context");
auto context = py::module::import("opm_embedded");
context.attr("deck") = &deck;
context.attr("parser") = &parser;
py::exec(python_code, py::globals(), py::dict(py::arg("context") = context));
return true;
}
bool PythonInterp::exec(const Action::PyAction& py_action, EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) {
if (!this->guard)
throw std::logic_error("Python interpreter not enabled");
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;
return this->exec(python_code, context);
}
@ -69,8 +72,8 @@ bool PythonInterp::exec(const std::string& python_code) {
if (!this->guard)
throw std::logic_error("Python interpreter not enabled");
py::exec(python_code, py::globals());
return true;
auto context = py::module::import("opm_embedded");
return this->exec(python_code, context);
}
PythonInterp::PythonInterp(bool enable) {
@ -83,5 +86,4 @@ PythonInterp::PythonInterp(bool enable) {
}
}
#endif

View File

@ -35,7 +35,6 @@
#include <pybind11/embed.h>
namespace py = pybind11;
#endif
@ -52,10 +51,12 @@ public:
explicit PythonInterp(bool enable);
bool exec(const std::string& python_code);
bool exec(const std::string& python_code, const Parser& parser, Deck& deck);
bool exec(const Action::PyAction& py_action, EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st);
static bool supported() { return true; };
explicit operator bool() const { return bool(this->guard); }
private:
void load_module(const std::string& python_file);
bool exec(const std::string& python_code, py::module& context);
std::unique_ptr<py::scoped_interpreter> guard;
};
@ -73,7 +74,7 @@ public:
return this->fail();
};
bool exec(const std::string& /* python_code*/ , const Parser& /* parser */, Deck& /* deck */) {
bool exec(const std::string&, const Parser&, Deck&) {
return this->fail();
}

View File

@ -1,5 +1,9 @@
from math import sin
import random
def run():
pass
print("sin(0) = {}".format(sin(0)))
#---
if random.random() > 0.25:

View File

@ -0,0 +1 @@
import math

View File

@ -0,0 +1,7 @@
import math
Bug here
def run():
pass

View File

@ -0,0 +1,549 @@
-- This reservoir simulation deck is made available under the Open Database
-- License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in
-- individual contents of the database are licensed under the Database Contents
-- License: http://opendatacommons.org/licenses/dbcl/1.0/
-- Copyright (C) 2015 Statoil
-- This simulation is based on the data given in
-- 'Comparison of Solutions to a Three-Dimensional
-- Black-Oil Reservoir Simulation Problem' by Aziz S. Odeh,
-- Journal of Petroleum Technology, January 1981
---------------------------------------------------------------------------
------------------------ SPE1 - CASE 1 ------------------------------------
---------------------------------------------------------------------------
RUNSPEC
-- -------------------------------------------------------------------------
TITLE
SPE1 - CASE 1
DIMENS
10 10 3 /
-- The number of equilibration regions is inferred from the EQLDIMS
-- keyword.
EQLDIMS
/
-- The number of PVTW tables is inferred from the TABDIMS keyword;
-- when no data is included in the keyword the default values are used.
TABDIMS
/
OIL
GAS
WATER
DISGAS
-- As seen from figure 4 in Odeh, GOR is increasing with time,
-- which means that dissolved gas is present
FIELD
START
1 'DEC' 2014 /
WELLDIMS
-- Item 1: maximum number of wells in the model
-- - there are two wells in the problem; injector and producer
-- Item 2: maximum number of grid blocks connected to any one well
-- - must be one as the wells are located at specific grid blocks
-- Item 3: maximum number of groups in the model
-- - we are dealing with only one 'group'
-- Item 4: maximum number of wells in any one group
-- - there must be two wells in a group as there are two wells in total
5 1 1 2 /
UNIFOUT
UDQDIMS
50 25 0 50 50 0 0 50 0 20 /
GRID
-- The INIT keyword is used to request an .INIT file. The .INIT file
-- is written before the simulation actually starts, and contains grid
-- properties and saturation tables as inferred from the input
-- deck. There are no other keywords which can be used to configure
-- exactly what is written to the .INIT file.
INIT
-- -------------------------------------------------------------------------
NOECHO
DX
-- There are in total 300 cells with length 1000ft in x-direction
300*1000 /
DY
-- There are in total 300 cells with length 1000ft in y-direction
300*1000 /
DZ
-- The layers are 20, 30 and 50 ft thick, in each layer there are 100 cells
100*20 100*30 100*50 /
TOPS
-- The depth of the top of each grid block
100*8325 /
PORO
-- Constant porosity of 0.3 throughout all 300 grid cells
300*0.3 /
PERMX
-- The layers have perm. 500mD, 50mD and 200mD, respectively.
100*500 100*50 100*200 /
PERMY
-- Equal to PERMX
100*500 100*50 100*200 /
PERMZ
-- Cannot find perm. in z-direction in Odeh's paper
-- For the time being, we will assume PERMZ equal to PERMX and PERMY:
100*500 100*50 100*200 /
ECHO
PROPS
-- -------------------------------------------------------------------------
PVTW
-- Item 1: pressure reference (psia)
-- Item 2: water FVF (rb per bbl or rb per stb)
-- Item 3: water compressibility (psi^{-1})
-- Item 4: water viscosity (cp)
-- Item 5: water 'viscosibility' (psi^{-1})
-- Using values from Norne:
-- In METRIC units:
-- 277.0 1.038 4.67E-5 0.318 0.0 /
-- In FIELD units:
4017.55 1.038 3.22E-6 0.318 0.0 /
ROCK
-- Item 1: reference pressure (psia)
-- Item 2: rock compressibility (psi^{-1})
-- Using values from table 1 in Odeh:
14.7 3E-6 /
SWOF
-- Column 1: water saturation
-- - this has been set to (almost) equally spaced values from 0.12 to 1
-- Column 2: water relative permeability
-- - generated from the Corey-type approx. formula
-- the coeffisient is set to 10e-5, S_{orw}=0 and S_{wi}=0.12
-- Column 3: oil relative permeability when only oil and water are present
-- - we will use the same values as in column 3 in SGOF.
-- This is not really correct, but since only the first
-- two values are of importance, this does not really matter
-- Column 4: water-oil capillary pressure (psi)
0.12 0 1 0
0.18 4.64876033057851E-008 1 0
0.24 0.000000186 0.997 0
0.3 4.18388429752066E-007 0.98 0
0.36 7.43801652892562E-007 0.7 0
0.42 1.16219008264463E-006 0.35 0
0.48 1.67355371900826E-006 0.2 0
0.54 2.27789256198347E-006 0.09 0
0.6 2.97520661157025E-006 0.021 0
0.66 3.7654958677686E-006 0.01 0
0.72 4.64876033057851E-006 0.001 0
0.78 0.000005625 0.0001 0
0.84 6.69421487603306E-006 0 0
0.91 8.05914256198347E-006 0 0
1 0.00001 0 0 /
SGOF
-- Column 1: gas saturation
-- Column 2: gas relative permeability
-- Column 3: oil relative permeability when oil, gas and connate water are present
-- Column 4: oil-gas capillary pressure (psi)
-- - stated to be zero in Odeh's paper
-- Values in column 1-3 are taken from table 3 in Odeh's paper:
0 0 1 0
0.001 0 1 0
0.02 0 0.997 0
0.05 0.005 0.980 0
0.12 0.025 0.700 0
0.2 0.075 0.350 0
0.25 0.125 0.200 0
0.3 0.190 0.090 0
0.4 0.410 0.021 0
0.45 0.60 0.010 0
0.5 0.72 0.001 0
0.6 0.87 0.0001 0
0.7 0.94 0.000 0
0.85 0.98 0.000 0
0.88 0.984 0.000 0 /
--1.00 1.0 0.000 0 /
-- Warning from Eclipse: first sat. value in SWOF + last sat. value in SGOF
-- must not be greater than 1, but Eclipse still runs
-- Flow needs the sum to be excactly 1 so I added a row with gas sat. = 0.88
-- The corresponding krg value was estimated by assuming linear rel. between
-- gas sat. and krw. between gas sat. 0.85 and 1.00 (the last two values given)
DENSITY
-- Density (lb per ft³) at surface cond. of
-- oil, water and gas, respectively (in that order)
-- Using values from Norne:
-- In METRIC units:
-- 859.5 1033.0 0.854 /
-- In FIELD units:
53.66 64.49 0.0533 /
PVDG
-- Column 1: gas phase pressure (psia)
-- Column 2: gas formation volume factor (rb per Mscf)
-- - in Odeh's paper the units are said to be given in rb per bbl,
-- but this is assumed to be a mistake: FVF-values in Odeh's paper
-- are given in rb per scf, not rb per bbl. This will be in
-- agreement with conventions
-- Column 3: gas viscosity (cP)
-- Using values from lower right table in Odeh's table 2:
14.700 166.666 0.008000
264.70 12.0930 0.009600
514.70 6.27400 0.011200
1014.7 3.19700 0.014000
2014.7 1.61400 0.018900
2514.7 1.29400 0.020800
3014.7 1.08000 0.022800
4014.7 0.81100 0.026800
5014.7 0.64900 0.030900
9014.7 0.38600 0.047000 /
PVTO
-- Column 1: dissolved gas-oil ratio (Mscf per stb)
-- Column 2: bubble point pressure (psia)
-- Column 3: oil FVF for saturated oil (rb per stb)
-- Column 4: oil viscosity for saturated oil (cP)
-- Use values from top left table in Odeh's table 2:
0.0010 14.7 1.0620 1.0400 /
0.0905 264.7 1.1500 0.9750 /
0.1800 514.7 1.2070 0.9100 /
0.3710 1014.7 1.2950 0.8300 /
0.6360 2014.7 1.4350 0.6950 /
0.7750 2514.7 1.5000 0.6410 /
0.9300 3014.7 1.5650 0.5940 /
1.2700 4014.7 1.6950 0.5100
9014.7 1.5790 0.7400 /
1.6180 5014.7 1.8270 0.4490
9014.7 1.7370 0.6310 /
-- It is required to enter data for undersaturated oil for the highest GOR
-- (i.e. the last row) in the PVTO table.
-- In order to fulfill this requirement, values for oil FVF and viscosity
-- at 9014.7psia and GOR=1.618 for undersaturated oil have been approximated:
-- It has been assumed that there is a linear relation between the GOR
-- and the FVF when keeping the pressure constant at 9014.7psia.
-- From Odeh we know that (at 9014.7psia) the FVF is 2.357 at GOR=2.984
-- for saturated oil and that the FVF is 1.579 at GOR=1.27 for undersaturated oil,
-- so it is possible to use the assumption described above.
-- An equivalent approximation for the viscosity has been used.
/
SOLUTION
-- -------------------------------------------------------------------------
EQUIL
-- Item 1: datum depth (ft)
-- Item 2: pressure at datum depth (psia)
-- - Odeh's table 1 says that initial reservoir pressure is
-- 4800 psi at 8400ft, which explains choice of item 1 and 2
-- Item 3: depth of water-oil contact (ft)
-- - chosen to be directly under the reservoir
-- Item 4: oil-water capillary pressure at the water oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 5: depth of gas-oil contact (ft)
-- - chosen to be directly above the reservoir
-- Item 6: gas-oil capillary pressure at gas-oil contact (psi)
-- - given to be 0 in Odeh's paper
-- Item 7: RSVD-table
-- Item 8: RVVD-table
-- Item 9: Set to 0 as this is the only value supported by OPM
-- Item #: 1 2 3 4 5 6 7 8 9
8400 4800 8450 0 8300 0 1 0 0 /
RSVD
-- Dissolved GOR is initially constant with depth through the reservoir.
-- The reason is that the initial reservoir pressure given is higher
---than the bubble point presssure of 4014.7psia, meaning that there is no
-- free gas initially present.
8300 1.270
8450 1.270 /
SUMMARY
-- -------------------------------------------------------------------------
FOPR
WGOR
/
WOPR
/
WWPR
/
WWCT
/
FGOR
-- 2a) Pressures of the cell where the injector and producer are located
BPR
1 1 1 /
10 10 3 /
/
-- 2b) Gas saturation at grid points given in Odeh's paper
BGSAT
1 1 1 /
1 1 2 /
1 1 3 /
10 1 1 /
10 1 2 /
10 1 3 /
10 10 1 /
10 10 2 /
10 10 3 /
/
-- In order to compare Eclipse with Flow:
WBHP
/
WGIR
'INJ'
/
WGIT
'INJ'
/
WGPR
/
WGPT
/
WOPR
/
WOPT
/
WWIR
/
WWIT
/
WWPR
/
WWPT
/
WUBHP
/
WUOPRL
/
WUWCT
/
FOPR
FUOPR
SCHEDULE
-- -------------------------------------------------------------------------
RPTSCHED
'PRES' 'SGAS' 'RS' 'WELLS' 'WELSPECS' /
RPTRST
'BASIC=1' /
UDQ
ASSIGN WUBHP 11 /
ASSIGN WUOPRL 20 /
ASSIGN WUBHP P2 12 /
ASSIGN WUBHP P3 13 /
ASSIGN WUBHP P4 14 /
UNITS WUBHP 'BARSA' /
UNITS WUOPRL 'SM3/DAY' /
DEFINE WUWCT WWPR / (WWPR + WOPR) /
UNITS WUWCT '1' /
DEFINE FUOPR SUM(WOPR) /
UNITS FUOPR 'SM3/DAY' /
/
-- If no resolution (i.e. case 1), the two following lines must be added:
DRSDT
0 /
-- if DRSDT is set to 0, GOR cannot rise and free gas does not
-- dissolve in undersaturated oil -> constant bubble point pressure
WELSPECS
-- Item #: 1 2 3 4 5 6
'P1' 'G1' 3 3 8400 'OIL' /
'P2' 'G1' 4 4 8400 'OIL' /
'P3' 'G1' 5 5 8400 'OIL' /
'P4' 'G1' 6 6 8400 'OIL' /
'INJ' 'G1' 1 1 8335 'GAS' /
/
-- Coordinates in item 3-4 are retrieved from Odeh's figure 1 and 2
-- Note that the depth at the midpoint of the well grid blocks
-- has been used as reference depth for bottom hole pressure in item 5
COMPDAT
-- Item #: 1 2 3 4 5 6 7 8 9
'P1' 3 3 3 3 'OPEN' 1* 1* 0.5 /
'P2' 4 4 3 3 'OPEN' 1* 1* 0.5 /
'P3' 5 5 3 3 'OPEN' 1* 1* 0.5 /
'P4' 6 6 3 3 'OPEN' 1* 1* 0.5 /
'INJ' 1 1 1 1 'OPEN' 1* 1* 0.5 /
/
-- Coordinates in item 2-5 are retreived from Odeh's figure 1 and 2
-- Item 9 is the well bore internal diameter,
-- the radius is given to be 0.25ft in Odeh's paper
WCONPROD
-- Item #:1 2 3 4 5 9
'P1' 'OPEN' 'ORAT' 5000 4* 1000 /
'P2' 'OPEN' 'ORAT' 5000 4* 1000 /
'P3' 'OPEN' 'ORAT' 5000 4* 1000 /
'P4' 'OPEN' 'ORAT' 5000 4* 1000 /
/
-- It is stated in Odeh's paper that the maximum oil prod. rate
-- is 20 000stb per day which explains the choice of value in item 4.
-- The items > 4 are defaulted with the exception of item 9,
-- the BHP lower limit, which is given to be 1000psia in Odeh's paper
WCONINJE
-- Item #:1 2 3 4 5 6 7
'INJ' 'GAS' 'OPEN' 'RATE' 100000 1* 9014 /
/
-- Stated in Odeh that gas inj. rate (item 5) is 100MMscf per day
-- BHP upper limit (item 7) should not be exceeding the highest
-- pressure in the PVT table=9014.7psia (default is 100 000psia)
PYACTION
ACTION1 SINGLE /
'action1.py' /
PYACTION
ACTION2 UNLIMITED /
'action2.py' /
PYACTION
ACTION3 FIRST_TRUE /
'action2.py' /
DATES
1 'JAN' 2015 /
/
DATES
1 'FEB' 2015 /
/
DATES
1 'MAR' 2015 /
/
DATES
1 'APR' 2015 /
/
DATES
1 'MAI' 2015 /
/
DATES
1 'JUN' 2015 /
/
DATES
1 'JUL' 2015 /
/
DATES
1 'AUG' 2015 /
/
DATES
1 'SEP' 2015 /
/
DATES
1 'OCT' 2015 /
/
DATES
1 'NOV' 2015 /
/
DATES
1 'DEC' 2015 /
/
DATES
1 'JAN' 2016 /
/
DATES
1 'FEB' 2016 /
/
DATES
1 'MAR' 2016 /
/
DATES
1 'APR' 2016 /
/
DATES
1 'MAI' 2016 /
/
DATES
1 'JUN' 2016 /
/
DATES
1 'JUL' 2016 /
/
DATES
1 'AUG' 2016 /
/
DATES
1 'SEP' 2016 /
/
DATES
1 'OCT' 2016 /
/
DATES
1 'NOV' 2016 /
/
DATES
1 'DEC' 2016 /
/
END

4
tests/msim/action1.py Normal file
View File

@ -0,0 +1,4 @@
import math
def run(ecl_state, schedule, report_step, summary_state):
pass

9
tests/msim/action2.py Normal file
View File

@ -0,0 +1,9 @@
def run(ecl_state, schedule, report_step, sim):
wells_shut = False
for well in sim.wells:
if sim.well_var(well, "WWCT") > 0.50:
schedule.shut_well(well, report_step)
wells_shut = True
return wells_shut

View File

@ -57,8 +57,8 @@ struct test_data {
Schedule schedule;
SummaryConfig summary_config;
test_data(const std::string& deck_string) :
deck( Parser().parseString(deck_string)),
test_data(const Deck& deck_arg) :
deck(deck_arg),
state( this->deck ),
python( std::make_shared<Python>() ),
schedule( this->deck, this->state, this->python),
@ -67,6 +67,12 @@ struct test_data {
auto& ioconfig = this->state.getIOConfig();
ioconfig.setBaseName("MSIM");
}
test_data(const std::string& deck_string) :
test_data( Parser().parseString(deck_string) )
{}
};
@ -240,6 +246,8 @@ BOOST_AUTO_TEST_CASE(WELL_CLOSE_EXAMPLE) {
}
}
BOOST_AUTO_TEST_CASE(UDQ_ASSIGN) {
#include "actionx1.include"
@ -365,3 +373,61 @@ BOOST_AUTO_TEST_CASE(UDA) {
}
}
}
#ifdef EMBEDDED_PYTHON
BOOST_AUTO_TEST_CASE(PYTHON_WELL_CLOSE_EXAMPLE) {
const auto& deck = Parser().parseFile("msim/MSIM_PYACTION.DATA");
test_data td( deck );
msim sim(td.state);
{
WorkArea work_area("test_msim");
EclipseIO io(td.state, td.state.getInputGrid(), td.schedule, td.summary_config);
sim.well_rate("P1", data::Rates::opt::oil, prod_opr);
sim.well_rate("P2", data::Rates::opt::oil, prod_opr);
sim.well_rate("P3", data::Rates::opt::oil, prod_opr);
sim.well_rate("P4", data::Rates::opt::oil, prod_opr);
sim.well_rate("P1", data::Rates::opt::wat, prod_wpr_P1);
sim.well_rate("P2", data::Rates::opt::wat, prod_wpr_P2);
sim.well_rate("P3", data::Rates::opt::wat, prod_wpr_P3);
sim.well_rate("P4", data::Rates::opt::wat, prod_wpr_P4);
{
const auto& w1 = td.schedule.getWell("P1", 15);
const auto& w2 = td.schedule.getWell("P2", 15);
const auto& w3 = td.schedule.getWell("P3", 15);
const auto& w4 = td.schedule.getWell("P4", 15);
BOOST_CHECK(w1.getStatus() == Well::Status::OPEN );
BOOST_CHECK(w2.getStatus() == Well::Status::OPEN );
BOOST_CHECK(w3.getStatus() == Well::Status::OPEN );
BOOST_CHECK(w4.getStatus() == Well::Status::OPEN );
}
sim.run(td.schedule, io, false);
{
const auto& w1 = td.schedule.getWell("P1", 15);
const auto& w3 = td.schedule.getWell("P3", 15);
BOOST_CHECK(w1.getStatus() == Well::Status::OPEN );
BOOST_CHECK(w3.getStatus() == Well::Status::OPEN );
}
{
const auto& w2_5 = td.schedule.getWell("P2", 5);
const auto& w2_6 = td.schedule.getWell("P2", 6);
BOOST_CHECK(w2_5.getStatus() == Well::Status::OPEN );
BOOST_CHECK(w2_6.getStatus() == Well::Status::SHUT );
}
{
const auto& w4_10 = td.schedule.getWell("P4", 10);
const auto& w4_11 = td.schedule.getWell("P4", 11);
BOOST_CHECK(w4_10.getStatus() == Well::Status::OPEN );
BOOST_CHECK(w4_11.getStatus() == Well::Status::SHUT );
}
}
}
#endif

View File

@ -27,7 +27,6 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <opm/common/utility/TimeService.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/common/OpmLog/Location.hpp>
#include <opm/parser/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
@ -153,6 +152,7 @@ BOOST_AUTO_TEST_CASE(TestActions) {
Opm::Action::Context context(st);
Opm::Action::Actions config;
std::vector<std::string> matching_wells;
auto python = std::make_shared<Opm::Python>();
BOOST_CHECK_EQUAL(config.size(), 0);
BOOST_CHECK(config.empty());
@ -172,10 +172,10 @@ BOOST_AUTO_TEST_CASE(TestActions) {
Opm::Action::ActionX action3("NAME3", 1000000, 0, asTimeT(TimeStampUTC(TimeStampUTC::YMD{ 2000, 7, 1 })) );
config.add(action3);
Opm::Action::PyAction py_action1("PYTHON1", Opm::Action::PyAction::RunCount::single, "import sys");
Opm::Action::PyAction py_action1(python, "PYTHON1", Opm::Action::PyAction::RunCount::single, "act1.py");
config.add(py_action1);
Opm::Action::PyAction py_action2("PYTHON2", Opm::Action::PyAction::RunCount::single, "import sys");
Opm::Action::PyAction py_action2(python, "PYTHON2", Opm::Action::PyAction::RunCount::single, "act1.py");
config.add(py_action2);
}
const Opm::Action::ActionX& action2 = config.get("NAME");

View File

@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE(INSTANTIATE) {
auto python = std::make_shared<Python>();
BOOST_CHECK(Python::supported());
BOOST_CHECK(python->enabled());
BOOST_CHECK_NO_THROW(python->exec("print('Hello world')"));
BOOST_CHECK_NO_THROW(python->exec("import sys"));
Parser parser;
Deck deck;
@ -70,6 +70,8 @@ context.deck.add(kw)
BOOST_CHECK( deck.hasKeyword("FIELD") );
}
BOOST_AUTO_TEST_CASE(PYINPUT_BASIC) {
Parser parser;
@ -108,9 +110,10 @@ BOOST_AUTO_TEST_CASE(PYINPUT_BASIC) {
}
BOOST_AUTO_TEST_CASE(PYACTION) {
Parser parser;
auto python = std::make_shared<Python>();
auto python = std::make_shared<Python>(Python::Enable::ON);
auto deck = parser.parseFile("EMBEDDED_PYTHON.DATA");
auto ecl_state = EclipseState(deck);
auto schedule = Schedule(deck, ecl_state, python);
@ -118,16 +121,17 @@ BOOST_AUTO_TEST_CASE(PYACTION) {
SummaryState st(std::chrono::system_clock::now());
const auto& pyaction_kw = deck.getKeyword<ParserKeywords::PYACTION>(0);
const std::string& fname = pyaction_kw.getRecord(1).getItem(0).get<std::string>(0);
Action::PyAction py_action("WCLOSE", Action::PyAction::RunCount::unlimited, Action::PyAction::load(deck.getInputPath(), fname));
Action::PyAction py_action(python, "WCLOSE", Action::PyAction::RunCount::unlimited, deck.makeDeckPath(fname));
st.update_well_var("PROD1", "WWCT", 0);
python->exec(py_action, ecl_state, schedule, 10, st);
py_action.run(ecl_state, schedule, 10, st);
st.update("FOPR", 0);
python->exec(py_action, ecl_state, schedule, 10, st);
py_action.run(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);
py_action.run(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 );
@ -155,7 +159,6 @@ BOOST_AUTO_TEST_CASE(Python_Constructor2) {
BOOST_CHECK(!python_cond2.enabled());
}
#endif

View File

@ -17,18 +17,21 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <iostream>
#include <memory>
#define BOOST_TEST_MODULE PY_ACTION_TESTER
#include <boost/test/unit_test.hpp>
#include <iostream>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/P.hpp>
#include <opm/parser/eclipse/Python/Python.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp>
using namespace Opm;
BOOST_AUTO_TEST_CASE(ParsePYACTION) {
Parser parser;
auto python = std::make_shared<Python>();
auto deck = parser.parseFile("PYACTION.DATA");
auto keyword = deck.getKeyword<ParserKeywords::PYACTION>(0);
@ -36,23 +39,34 @@ BOOST_AUTO_TEST_CASE(ParsePYACTION) {
const auto& record1 = keyword.getRecord(1);
auto run_count = Action::PyAction::from_string(record0.getItem(1).get<std::string>(0));
std::string code = Action::PyAction::load(deck.getInputPath(), record1.getItem(0).get<std::string>(0));
std::string literal_code =R"(from math import sin
import random
print("sin(0) = {}".format(sin(0)))
#---
if random.random() > 0.25:
print("Large outcome")
else:
print("Small result")
A = 100
B = A / 10
C = B * 20
)";
Action::PyAction pyaction("ACT1", run_count, code);
const std::string& ok_module = deck.makeDeckPath(record1.getItem(0).get<std::string>(0));
Action::PyAction pyaction(python, "ACT1", run_count, ok_module);
BOOST_CHECK_EQUAL(pyaction.name(), "ACT1");
BOOST_CHECK_EQUAL(pyaction.code(), literal_code);
BOOST_CHECK(pyaction.run_count() == Action::PyAction::RunCount::single);
}
#ifdef EMBEDDED_PYTHON
BOOST_AUTO_TEST_CASE(ParsePYACTION_Modules) {
Parser parser;
auto python = std::make_shared<Python>();
auto deck = parser.parseFile("PYACTION.DATA");
auto keyword = deck.getKeyword<ParserKeywords::PYACTION>(0);
const auto& record0 = keyword.getRecord(0);
const auto& record1 = keyword.getRecord(1);
const auto& name = record0.getItem(0).get<std::string>(0);
auto run_count = Action::PyAction::from_string(record0.getItem(1).get<std::string>(0));
const std::string& ok_module = deck.makeDeckPath(record1.getItem(0).get<std::string>(0));
Action::PyAction pyaction(python, "ACT1", run_count, ok_module);
const std::string& broken_module = deck.makeDeckPath("action_missing_run.py");
BOOST_CHECK_THROW(Action::PyAction(python , "ACT2", run_count, broken_module), std::runtime_error);
const std::string& broken_module2 = deck.makeDeckPath("action_syntax_error.py");
BOOST_CHECK_THROW(Action::PyAction(python , "ACT2", run_count, broken_module2), std::runtime_error);
const std::string& missing_module = deck.makeDeckPath("no_such_module.py");
BOOST_CHECK_THROW(Action::PyAction(python , "ACT2", run_count, missing_module), std::invalid_argument);
}
#endif

View File

@ -1,22 +1,24 @@
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))
def run(ecl_state, schedule, report_step, summary_state):
sys.stdout.write("Running PYACTION arg1:{}\n".format(ecl_state))
if "FOPR" in summary_state:
sys.stdout.write("Have FOPR: {}\n".format( summary_state["FOPR"] ))
else:
sys.stdout.write("Missing FOPR\n")
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)
grid = ecl_state.grid()
sys.stdout.write("Grid dimensions: ({},{},{})\n".format(grid.nx, grid.ny, grid.nz))
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"])
prod_well = schedule.get_well("PROD1", report_step)
sys.stdout.write("Well status: {}\n".format(prod_well.status()))
if not "list" in storage:
storage["list"] = []
storage["list"].append(report_step)
sys.stdout.write("storage[list]: {}\n".format(storage["list"]))
if summary_state.well_var("PROD1", "WWCT") > 0.80:
schedule.shut_well("PROD1", report_step)
schedule.open_well("PROD2", report_step)
summary_state.update("RUN_COUNT", 1)
return True