PYACTION: The actual python code is in an external file

With this commit the PYACTION keyword is changed, instead of embedding the
Python code directly in the .DATA file the keyword now points to an external
file which is loaded verbatim into the PyAction keyword.

In addition the PYACTION keyword has now got a name and a string indicating how
many times it should run.
This commit is contained in:
Joakim Hove 2020-03-16 11:34:12 +01:00
parent db72ff80ed
commit 7852203d39
11 changed files with 205 additions and 127 deletions

View File

@ -421,6 +421,10 @@ if(ENABLE_ECL_OUTPUT)
tests/SPE1CASE2.DATA
tests/SPE1CASE2_RESTART.DATA
tests/SPE1CASE2.X0060
tests/PYACTION.DATA
tests/act1.py
tests/EMBEDDED_PYTHON.DATA
tests/wclose.py
)
endif()

View File

@ -23,6 +23,7 @@
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/I.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/P.hpp>
#include <opm/parser/eclipse/Parser/ParserKeywords/G.hpp>
#include <opm/parser/eclipse/Parser/Parser.hpp>
#include <opm/parser/eclipse/Parser/ErrorGuard.hpp>
@ -159,6 +160,15 @@ int main(int argc, char** argv) {
copy_file(input_arg.parent_path(), fname, output_dir);
}
using PYACTION = Opm::ParserKeywords::PYACTION;
for (std::size_t pyaction_index = 0; pyaction_index < deck.count<PYACTION>(); pyaction_index++) {
const auto& pyaction_keyword = deck.getKeyword<PYACTION>(pyaction_index);
const auto& fname = pyaction_keyword.getRecord(1).getItem<PYACTION::FILENAME>().get<std::string>(0);
copy_file(input_arg.parent_path(), fname, output_dir);
}
using GDFILE = Opm::ParserKeywords::GDFILE;
if (deck.hasKeyword<GDFILE>()) {
const auto& gdfile_keyword = deck.getKeyword<GDFILE>();

View File

@ -28,8 +28,20 @@ namespace Opm {
class PyAction {
public:
explicit PyAction(const std::string& code_arg);
enum class RunCount {
single,
unlimited,
first_true
};
static RunCount from_string(std::string run_count);
static std::string load(const std::string& input_path, const std::string& fname);
PyAction(const std::string& name, RunCount run_count, const std::string& code);
const std::string& code() const;
const std::string& name() const;
PyAction::RunCount run_count() const;
~PyAction();
/*
@ -44,6 +56,8 @@ public:
*/
void * storage() const;
private:
std::string m_name;
RunCount m_run_count;
std::string input_code;
void * m_storage;
};

View File

@ -16,6 +16,7 @@
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fstream>
#ifdef EMBEDDED_PYTHON
#include <pybind11/embed.h>
@ -28,12 +29,45 @@ using dict = int;
#endif
#include <opm/parser/eclipse/Utility/String.hpp>
#include <opm/common/utility/FileSystem.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Action/PyAction.hpp>
namespace Opm {
PyAction::PyAction(const std::string& code_arg) :
input_code(code_arg),
PyAction::RunCount PyAction::from_string(std::string run_count) {
run_count = uppercase(run_count);
if (run_count == "SINGLE")
return RunCount::single;
if (run_count == "UNLIMITED")
return RunCount::unlimited;
if (run_count == "FIRST_TRUE")
return RunCount::first_true;
throw std::invalid_argument("RunCount string: " + run_count + " not recognized ");
}
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.c_str(), std::ios::in);
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),
m_storage( new py::dict() )
{}
@ -42,7 +76,13 @@ 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;
}
/*
The python variables are reference counted and when the Python dictionary

View File

@ -1 +1,4 @@
{"name" : "PYACTION", "sections" : ["SCHEDULE"], "code" : {"end" : "PYEND"}}
{"name" : "PYACTION", "sections" : ["SCHEDULE"], "size" : 2, "records": [
[{"name" : "NAME", "value_type" : "STRING"},
{"name" : "RUN_COUNT" , "value_type" : "STRING", "default" : "SINGLE"}],
[{"name" : "FILENAME", "value_type" : "STRING"}]]}

View File

@ -0,0 +1,70 @@
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
WCLOSE UNLIMITED /
'wclose.py' /
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

5
tests/PYACTION.DATA Normal file
View File

@ -0,0 +1,5 @@
SCHEDULE
PYACTION
ACT1 Single /
act1.py /

11
tests/act1.py Normal file
View File

@ -0,0 +1,11 @@
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

View File

@ -28,6 +28,7 @@
#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/Parser/ParserKeywords/P.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
@ -99,107 +100,16 @@ 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 deck = parser.parseFile("EMBEDDED_PYTHON.DATA");
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));
const auto& pyaction_kw = deck.getKeyword<ParserKeywords::PYACTION>(0);
const std::string& fname = pyaction_kw.getRecord(1).getItem(0).get<std::string>(0);
PyAction py_action("WCLOSE", PyAction::RunCount::unlimited, PyAction::load(deck.getInputPath(), fname));
st.update_well_var("PROD1", "WWCT", 0);
python.exec(py_action, ecl_state, schedule, 10, st);

View File

@ -22,12 +22,24 @@
#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/EclipseState/Schedule/Action/PyAction.hpp>
using namespace Opm;
BOOST_AUTO_TEST_CASE(ParsePYACTION) {
const std::string input_code = R"(from math import sin
Parser parser;
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 = PyAction::from_string(record0.getItem(1).get<std::string>(0));
std::string code = 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)))
#---
@ -40,31 +52,8 @@ B = A / 10
C = B * 20
)";
const std::string deck_string1 = R"(
SCHEDULE
PYACTION Her comes an ignored comment
)" + input_code + "PYEND";
const std::string deck_string2 = R"(
SCHEDULE
PYACTION -- Comment
)" + input_code + "PYEND" + "\nGRID";
Parser parser;
{
auto deck = parser.parseString(deck_string1);
const auto& parsed_code = deck.getKeyword("PYACTION").getRecord(0).getItem("code").get<std::string>(0);
BOOST_CHECK_EQUAL(parsed_code, input_code);
}
{
auto deck = parser.parseString(deck_string2);
const auto& parsed_code = deck.getKeyword("PYACTION").getRecord(0).getItem("code").get<std::string>(0);
BOOST_CHECK_EQUAL(parsed_code, input_code);
BOOST_CHECK( deck.hasKeyword("GRID"));
}
PyAction pyact(input_code);
PyAction pyaction("ACT1", run_count, code);
BOOST_CHECK_EQUAL(pyaction.name(), "ACT1");
BOOST_CHECK_EQUAL(pyaction.code(), literal_code);
BOOST_CHECK(pyaction.run_count() == PyAction::RunCount::single);
}

22
tests/wclose.py Normal file
View File

@ -0,0 +1,22 @@
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"])