Enable running normal ACTIONX keywords from PYACTION

The run() function invoked by PYACTION has got a new fifth argument which is a
callable the script should utilize to apply the keywords from an ACTIONX
keyword. The callable is implemented as C++ lambda which will run the
Schedule::iterateScheduleSection() method and make sure an updated
SimulatorUpdate variable can be passed back to the simulator.
This commit is contained in:
Joakim Hove 2022-01-24 13:47:05 +01:00
parent 4224e26321
commit 46b663218a
15 changed files with 693 additions and 17 deletions

View File

@ -547,8 +547,10 @@ if(ENABLE_ECL_OUTPUT)
tests/ACTIONX_M1.X0010
tests/wclose.py
tests/msim/MSIM_PYACTION.DATA
tests/msim/MSIM_PYACTION_ACTIONX.DATA
tests/msim/action1.py
tests/msim/action2.py
tests/msim/action3.py
tests/VFP_CASE.DATA)
endif()

View File

@ -90,7 +90,7 @@ void msim::post_step(Schedule& schedule, SummaryState& st, data::Solution& /* so
}
for (const auto& pyaction : actions.pending_python())
pyaction->run(this->state, schedule, report_step, st);
schedule.runPyAction(report_step, *pyaction, this->state, st);
}

View File

@ -22,9 +22,10 @@
#define PYACTION_HPP_
#include <string>
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace Opm {
@ -49,7 +50,8 @@ public:
static PyAction serializeObject();
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;
bool run(EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st,
const std::function<void(const std::string&, const std::vector<std::string>&)>& actionx_callback) const;
const std::string& name() const;
bool active() const;
bool operator==(const PyAction& other) const;

View File

@ -256,6 +256,7 @@ namespace Opm
void shut_well(const std::string& well_name, std::size_t report_step);
void stop_well(const std::string& well_name, std::size_t report_step);
void open_well(const std::string& well_name, std::size_t report_step);
void applyWellProdIndexScaling(const std::string& well_name, const std::size_t reportStep, const double scalingFactor);
std::vector<const Group*> getChildGroups2(const std::string& group_name, std::size_t timeStep) const;
std::vector<Well> getChildWells2(const std::string& group_name, std::size_t timeStep) const;
@ -279,9 +280,22 @@ namespace Opm
bool write_rst_file(std::size_t report_step) const;
const std::map< std::string, int >& rst_keywords( size_t timestep ) const;
/*
The applyAction() is invoked from the simulator *after* an ACTIONX has
evaluated to true. The return value is a small structure with
'information' which the simulator should take into account when
updating internal datastructures after the ACTIONX keywords have been
applied.
*/
SimulatorUpdate applyAction(std::size_t reportStep, const Action::ActionX& action, const std::vector<std::string>& matching_wells, const std::unordered_map<std::string, double>& wellpi);
/*
The runPyAction() will run the Python script in a PYACTION keyword. In
the case of Schedule updates the recommended way of doing that from
PYACTION is to invoke a "normal" ACTIONX keyword internally from the
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);
void applyWellProdIndexScaling(const std::string& well_name, const std::size_t reportStep, const double scalingFactor);
const GasLiftOpt& glo(std::size_t report_step) const;
@ -587,6 +601,7 @@ namespace Opm
bool must_write_rst_file(std::size_t report_step) const;
void applyEXIT(const DeckKeyword&, std::size_t currentStep);
SimulatorUpdate applyAction(std::size_t reportStep, const std::string& action_name, const std::vector<std::string>& matching_wells);
/**
* Handles a "normal" keyword. A normal keyword is one that can be handled by a function with the standard set of arguments (the ones that are passed to this function).

View File

@ -29,6 +29,7 @@ error BUG: The PyRunModule.hpp header should *not* be included in a configuratio
#include <filesystem>
#include <pybind11/stl.h>
namespace Opm {
namespace fs = std::filesystem;
@ -73,9 +74,17 @@ PyRunModule::PyRunModule(std::shared_ptr<const Python> python, const std::string
this->module.attr("storage") = this->storage;
}
namespace {
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);
py::cpp_function py_actionx_callback(const std::function<void(const std::string&, const std::vector<std::string>&)>& actionx_callback) {
return py::cpp_function( actionx_callback );
}
}
bool PyRunModule::run(EclipseState& ecl_state, Schedule& sched, std::size_t report_step, SummaryState& st, const std::function<void(const std::string&, const std::vector<std::string>&)>& actionx_callback) {
auto cpp_callback = py_actionx_callback(actionx_callback);
py::object result = this->run_function(&ecl_state, &sched, report_step, &st, cpp_callback);
return result.cast<bool>();
}

View File

@ -27,6 +27,7 @@ error BUG: The PyRunModule.hpp header should *not* be included in a configuratio
#include <pybind11/pybind11.h>
namespace py = pybind11;
#include <functional>
#include <memory>
#include <string>
#include <opm/input/eclipse/Python/Python.hpp>
@ -42,7 +43,7 @@ class __attribute__((visibility("default"))) 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);
bool run(EclipseState& ecl_state, Schedule& sched, std::size_t report_step, SummaryState& st, const std::function<void(const std::string&, const std::vector<std::string>&)>& actionx_callback);
private:
py::object run_function = py::none();

View File

@ -113,7 +113,8 @@ PyAction::PyAction(std::shared_ptr<const Python> python, const std::string& name
}
bool PyAction::run(EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st) const
bool PyAction::run(EclipseState& ecl_state, Schedule& schedule, std::size_t report_step, SummaryState& st,
const std::function<void(const std::string&, const std::vector<std::string>&)>& actionx_callback) const
{
/*
For PyAction instances which have been constructed the 'normal' way
@ -126,7 +127,7 @@ bool PyAction::run(EclipseState& ecl_state, Schedule& schedule, std::size_t repo
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);
return this->run_module->run(ecl_state, schedule, report_step, st, actionx_callback);
}

View File

@ -1341,10 +1341,101 @@ File {} line {}.)", pattern, location.keyword, location.filename, location.linen
}
/*
This function will typically be called from the apply_action_callback()
which is invoked in a PYACTION plugin, i.e. the arguments here are
supplied by the user in a script - can very well be wrong.
*/
SimulatorUpdate Schedule::applyAction(std::size_t reportStep, const std::string& action_name, const std::vector<std::string>& matching_wells) {
const auto& actions = this->snapshots[reportStep].actions();
if (actions.has(action_name)) {
const auto& action = this->snapshots[reportStep].actions()[action_name];
std::vector<std::string> well_names;
for (const auto& wname : matching_wells) {
if (this->hasWell(wname, reportStep))
well_names.push_back(wname);
else
OpmLog::error(fmt::format("Tried to apply action: {} on non existing well: {}", action_name, wname));
}
return this->applyAction(reportStep, action, well_names, {});
} else {
OpmLog::error(fmt::format("Tried to apply action unknown action: {}", action_name));
return {};
}
}
/*
The runPyAction() method is a utility to run PYACTION keywords. The
PYACTION keywords contain a link to file with Python code and a function
run() which will eventually be invoked.
In principal the python code can do "anything" - but when it comes to
modifications of the Schedule information - e.g. by opening or closing
wells, the recommended way to do it is unfortunately to utilize the normal
ACTIONX machinery and apply a ACTIONX keyword in order to invoke the
keywords in the ACTIONX block. In order to set this up correctly we need
to align three different systems:
1. The Python code executes normally, and will possibly decide to
apply an ACTIONX keyword.
2. When an AXTIONX keyword is applied the Schedule implementation will
need to add the new keywords to the correct ScheduleBlock and
reiterate the Schedule section.
3. As part of the Schedule iteration we record which changes which must
be taken into account in the simulator afterwards. These changes are
recorded in a Action::SimulatorUpdate instance.
An important part of the implementation is the lambda
'apply_action_callback' which is used from Python to call back in to C++
in order to run the ACTIONX keywords. The sequence of calls goes like
this:
1. The simulator calls the method Schedule::runPyAction()
2. The Schedule::runPyAction() method creates a SimulatorUpdate
instance and captures a reference to that in the lambda
apply_action_callback. When calling the pyaction.run() method the
apply_action_callback is passed as a callable all the way down to
the python run() method.
3. In python the apply_action_callback comes in as the parameter
'actionx_callback' in the run() function. If the python code decides
to invoke the keywords from an actionx it will be like:
def run(ecl_state, schedule, report_step, summary_state, actionx_callback):
...
...
wells = ["W1", "W2"]
actionx_callback("ACTION_NAME", wells)
Observe that the wells argument must be a Python lvalue (otherwise
hard crash???)
4. The callable will go back into C++ and eventually reach the
Schedule::applyAction() which will invoke the
Schedule::iterateScheduleSection() and return an update
SimulatorUpdate which will be assigned to the instance in the
Schedule::runPyAction().
5. When the pyaction.run() method returns the Schedule structure and
the sim_update variable have been correctly updated, and the
sim_update is returned to the simulator.
*/
SimulatorUpdate Schedule::runPyAction(std::size_t reportStep, const Action::PyAction& pyaction, EclipseState& ecl_state, SummaryState& summary_state) {
SimulatorUpdate sim_update;
pyaction.run(ecl_state, *this, reportStep, summary_state);
auto apply_action_callback = [&sim_update, &reportStep, this](const std::string& action_name, const std::vector<std::string>& matching_wells) {
sim_update = this->applyAction(reportStep, action_name, matching_wells);
};
pyaction.run(ecl_state, *this, reportStep, summary_state, apply_action_callback);
return sim_update;
}

View File

@ -0,0 +1,491 @@
-- 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
--
-- Here in this case the deck has been modified by adding a PYACTION and ACTIONX
-- keyword. The deck is used in a integration test with the dummy simulator
-- msim.
---------------------------------------------------------------------------
------------------------ 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)
ACTIONX
CLOSEWELL 0 /
DAY = 60 /
/
WELOPEN
'?' 'SHUT' /
/
ENDACTIO
PYACTION
ACTION2 UNLIMITED /
'action3.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 /
/
END

View File

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

View File

@ -1,4 +1,4 @@
def run(ecl_state, schedule, report_step, sim):
def run(ecl_state, schedule, report_step, sim, actionx_callback):
wells_shut = False
for well in sim.wells:

15
tests/msim/action3.py Normal file
View File

@ -0,0 +1,15 @@
import math
def run(ecl_state, schedule, report_step, summary_state, actionx_callback):
wells = []
if report_step == 1:
wells = ["P1"]
elif report_step == 2:
wells = ["P2"]
elif report_step == 3:
wells = ["P3"]
elif report_step == 4:
wells = ["P4"]
actionx_callback("CLOSEWELL", wells)

View File

@ -526,5 +526,51 @@ BOOST_AUTO_TEST_CASE(PYTHON_WELL_CLOSE_EXAMPLE) {
}
}
BOOST_AUTO_TEST_CASE(PYTHON_ACTIONX) {
const auto& deck = Parser().parseFile("msim/MSIM_PYACTION_ACTIONX.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", 0);
const auto& w2 = td.schedule.getWell("P2", 0);
const auto& w3 = td.schedule.getWell("P3", 0);
const auto& w4 = td.schedule.getWell("P4", 0);
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", 1);
const auto& w2 = td.schedule.getWell("P2", 2);
const auto& w3 = td.schedule.getWell("P3", 3);
const auto& w4 = td.schedule.getWell("P4", 4);
BOOST_CHECK(w1.getStatus() == Well::Status::SHUT );
BOOST_CHECK(w2.getStatus() == Well::Status::SHUT );
BOOST_CHECK(w3.getStatus() == Well::Status::SHUT );
BOOST_CHECK(w4.getStatus() == Well::Status::SHUT );
}
}
}
#endif

View File

@ -123,15 +123,18 @@ BOOST_AUTO_TEST_CASE(PYACTION) {
const auto& pyaction_kw = deck.get<ParserKeywords::PYACTION>().front();
const std::string& fname = pyaction_kw.getRecord(1).getItem(0).get<std::string>(0);
Action::PyAction py_action(python, "WCLOSE", Action::PyAction::RunCount::unlimited, deck.makeDeckPath(fname));
auto actionx_callback = [] (const std::string&, const std::vector<std::string>&) { ;};
st.update_well_var("PROD1", "WWCT", 0);
py_action.run(ecl_state, schedule, 10, st);
py_action.run(ecl_state, schedule, 10, st, actionx_callback);
st.update("FOPR", 0);
py_action.run(ecl_state, schedule, 10, st);
py_action.run(ecl_state, schedule, 10, st, actionx_callback);
st.update("FOPR", 100);
st.update_well_var("PROD1", "WWCT", 0.90);
py_action.run(ecl_state, schedule, 10, st);
py_action.run(ecl_state, schedule, 10, st, actionx_callback);
const auto& well1 = schedule.getWell("PROD1", 10);
const auto& well2 = schedule.getWell("PROD2", 10);

View File

@ -1,6 +1,6 @@
import sys
def run(ecl_state, schedule, report_step, summary_state):
def run(ecl_state, schedule, report_step, summary_state, actionx_callback):
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"] ))