From 420b66822b03684d862fc1ccfb13778814a62a3c Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Mon, 28 Jan 2019 10:01:27 +0100 Subject: [PATCH 1/3] Add iterators to the keywords in the body of ACTIONX --- .../eclipse/EclipseState/Schedule/Action/ActionX.hpp | 2 ++ .../eclipse/EclipseState/Schedule/Action/ActionX.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp index 2f4d4cc98..03c30865b 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp @@ -71,6 +71,8 @@ public: size_t max_run() const { return this->m_max_run; } double min_wait() const { return this->m_min_wait; } std::time_t start_time() const { return this->m_start_time; } + std::vector::const_iterator begin() const; + std::vector::const_iterator end() const; private: std::string m_name; diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp index 14ae34a60..f7a91525a 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp @@ -92,4 +92,12 @@ bool ActionX::ready(std::time_t sim_time) const { } +std::vector::const_iterator ActionX::begin() const { + return this->keywords.begin(); +} + +std::vector::const_iterator ActionX::end() const { + return this->keywords.end(); +} + } From d82be297456ee15c4c23713ab08ebcf6eaa96dad Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Mon, 28 Jan 2019 10:00:08 +0100 Subject: [PATCH 2/3] ACTIONX: Works with WELOPEN and msim --- CMakeLists.txt | 2 +- msim/include/opm/msim/msim.hpp | 3 +- msim/src/msim.cpp | 23 +- .../Schedule/Action/ActionAST.hpp | 2 +- .../EclipseState/Schedule/Action/ActionX.hpp | 4 +- .../EclipseState/Schedule/Schedule.hpp | 6 +- .../eclipse/EclipseState/Schedule/Well.hpp | 2 +- .../Schedule/Action/ActionAST.cpp | 17 +- .../EclipseState/Schedule/Action/ActionX.cpp | 8 +- .../EclipseState/Schedule/Schedule.cpp | 53 +- tests/msim/actionx1.include | 521 ++++++++++++++++++ tests/msim/test_msim_ACTIONX.cpp | 154 ++++++ tests/parser/ACTIONX.cpp | 25 +- 13 files changed, 769 insertions(+), 51 deletions(-) create mode 100644 tests/msim/actionx1.include create mode 100644 tests/msim/test_msim_ACTIONX.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd3635af8..25b4eef5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,7 +143,7 @@ if (ENABLE_MOCKSIM) set(_libs mocksim opmcommon ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) - foreach( test test_msim) + foreach( test test_msim test_msim_ACTIONX ) add_executable(${test} "tests/msim/${test}") target_link_libraries(${test} ${_libs}) add_test(NAME ${test} COMMAND ${test}) diff --git a/msim/include/opm/msim/msim.hpp b/msim/include/opm/msim/msim.hpp index 606ca7406..6646c2c70 100644 --- a/msim/include/opm/msim/msim.hpp +++ b/msim/include/opm/msim/msim.hpp @@ -32,7 +32,8 @@ public: void well_rate(const std::string& well, data::Rates::opt rate, std::function func); void solution(const std::string& field, std::function func); - void run(const Schedule& schedule, EclipseIO& io); + void run(Schedule& schedule, EclipseIO& io); + void post_step(Schedule& schedule, data::Solution& sol, data::Wells& well_data, size_t report_step, EclipseIO& io) const; private: void run_step(const Schedule& schedule, data::Solution& sol, data::Wells& well_data, size_t report_step, EclipseIO& io) const; diff --git a/msim/src/msim.cpp b/msim/src/msim.cpp index 34db19f32..fbe7bdddb 100644 --- a/msim/src/msim.cpp +++ b/msim/src/msim.cpp @@ -24,6 +24,8 @@ #include #include +#include +#include #include #include #include @@ -35,7 +37,7 @@ msim::msim(const EclipseState& state) : state(state) {} -void msim::run(const Schedule& schedule, EclipseIO& io) { +void msim::run(Schedule& schedule, EclipseIO& io) { const double week = 7 * 86400; data::Solution sol; data::Wells well_data; @@ -44,10 +46,29 @@ void msim::run(const Schedule& schedule, EclipseIO& io) { for (size_t report_step = 1; report_step < schedule.size(); report_step++) { double time_step = std::min(week, 0.5*schedule.stepLength(report_step - 1)); run_step(schedule, sol, well_data, report_step, time_step, io); + post_step(schedule, sol, well_data, report_step, io); } } +void msim::post_step(Schedule& schedule, data::Solution& sol, data::Wells& well_data, size_t report_step, EclipseIO& io) const { + const auto& actions = schedule.actions(); + if (actions.empty()) + return; + + const SummaryState& summary_state = io.summaryState(); + ActionContext context( summary_state ); + std::vector matching_wells; + + auto sim_time = schedule.simTime(report_step); + for (const auto& action : actions.pending(sim_time)) { + if (action->eval(sim_time, context, matching_wells)) + schedule.applyAction(report_step, *action, matching_wells); + } +} + + + void msim::run_step(const Schedule& schedule, data::Solution& sol, data::Wells& well_data, size_t report_step, EclipseIO& io) const { this->run_step(schedule, sol, well_data, report_step, schedule.stepLength(report_step - 1), io); } diff --git a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.hpp b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.hpp index 6f6f5cd16..f9b542b75 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.hpp @@ -44,7 +44,7 @@ private: unique_ptr, but that requires writing custom destructors - the use of a shared_ptr does not imply any shared ownership. */ - std::shared_ptr tree; + std::shared_ptr condition; }; } #endif diff --git a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp index 03c30865b..f75e85bfb 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp @@ -64,7 +64,7 @@ public: void addKeyword(const DeckKeyword& kw); bool ready(std::time_t sim_time) const; - bool eval(std::time_t sim_time, const ActionContext& context) const; + bool eval(std::time_t sim_time, const ActionContext& context, std::vector& wells) const; std::string name() const { return this->m_name; } @@ -81,7 +81,7 @@ private: std::time_t m_start_time; std::vector keywords; - ActionAST ast; + ActionAST condition; mutable size_t run_count = 0; mutable std::time_t last_run = 0; }; diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index f10be9d41..41a63e530 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -163,6 +163,8 @@ namespace Opm */ void filterConnections(const EclipseGrid& grid); size_t size() const; + + void applyAction(size_t reportStep, const ActionX& action, const std::vector& matching_wells); private: TimeMap m_timeMap; OrderedMap< Well > m_wells; @@ -182,7 +184,7 @@ namespace Opm WellProducer::ControlModeEnum m_controlModeWHISTCTL; Actions m_actions; - std::vector< Well* > getWells(const std::string& wellNamePattern); + std::vector< Well* > getWells(const std::string& wellNamePattern, const std::vector& matching_wells = {}); std::vector< Group* > getGroups(const std::string& groupNamePattern); void updateWellStatus( Well& well, size_t reportStep , WellCommon::StatusEnum status); @@ -212,7 +214,7 @@ namespace Opm void handleWSKPTAB( const DeckKeyword& keyword, const size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors); void handleWINJTEMP( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors); void handleWCONINJH( const SCHEDULESection&, const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors); - void handleWELOPEN( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors ); + void handleWELOPEN( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors, const std::vector& matching_wells = {}); void handleWELTARG( const SCHEDULESection&, const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors); void handleGCONINJE( const SCHEDULESection&, const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors); void handleGCONPROD( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors); diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well.hpp index 173aee918..29ff437c1 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well.hpp @@ -59,7 +59,7 @@ namespace Opm { const std::string& name() const; const size_t& seqIndex() const; std::size_t getTotNoConn() const; - void setTotNoConn(std::size_t noConn); + void setTotNoConn(std::size_t noConn); bool hasBeenDefined(size_t timeStep) const; const std::string getGroupName(size_t timeStep) const; void setGroupName(size_t timeStep , const std::string& groupName); diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.cpp index 6ff4e17ba..80460dc04 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.cpp @@ -32,16 +32,21 @@ namespace Opm { ActionAST::ActionAST(const std::vector& tokens) { - auto tree = ActionParser::parse(tokens); - this->tree.reset( new ASTNode(tree) ); + auto condition = ActionParser::parse(tokens); + this->condition.reset( new ASTNode(condition) ); } bool ActionAST::eval(const ActionContext& context, std::vector& matching_wells) const { - WellSet wells; - bool eval_result = this->tree->eval(context, wells); - matching_wells = wells.wells(); - return eval_result; + if (this->condition) { + WellSet wells; + bool eval_result = this->condition->eval(context, wells); + matching_wells = wells.wells(); + return eval_result; + } else + // In the case of missing condition we always evaluate to false. That + // is not crystal clear from the manual. + return false; } } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp index f7a91525a..09fec840f 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp @@ -52,7 +52,7 @@ ActionX::ActionX(const DeckKeyword& kw, std::time_t start_time) : for (const auto& token : record.getItem("CONDITION").getData()) tokens.push_back(token); } - this->ast = ActionAST(tokens); + this->condition = ActionAST(tokens); } @@ -62,15 +62,17 @@ void ActionX::addKeyword(const DeckKeyword& kw) { -bool ActionX::eval(std::time_t sim_time, const ActionContext& /* context */) const { +bool ActionX::eval(std::time_t sim_time, const ActionContext& context, std::vector& matching_wells) const { if (!this->ready(sim_time)) return false; - bool result = true; + + bool result = this->condition.eval(context, matching_wells); if (result) { this->run_count += 1; this->last_run = sim_time; } + return result; } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index 8a7c4d9ae..5cf70e85a 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -1147,7 +1147,7 @@ namespace Opm { } } - void Schedule::handleWELOPEN( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors) { + void Schedule::handleWELOPEN( const DeckKeyword& keyword, size_t currentStep, const ParseContext& parseContext, ErrorGuard& errors, const std::vector& matching_wells) { auto all_defaulted = []( const DeckRecord& rec ) { auto defaulted = []( const DeckItem& item ) { @@ -1163,7 +1163,7 @@ namespace Opm { const auto& wellNamePattern = record.getItem( "WELL" ).getTrimmedString(0); const auto& status_str = record.getItem( "STATUS" ).getTrimmedString( 0 ); - auto wells = getWells( wellNamePattern ); + auto wells = getWells( wellNamePattern, matching_wells ); if (wells.empty()) invalidNamePattern( wellNamePattern, parseContext, errors, keyword); @@ -1881,22 +1881,32 @@ namespace Opm { return { tmp.begin(), tmp.end() }; } - std::vector< Well* > Schedule::getWells(const std::string& wellNamePattern) { - size_t wildcard_pos = wellNamePattern.find("*"); + std::vector< Well* > Schedule::getWells(const std::string& wellNamePattern, const std::vector& matching_wells) { + // If we arrive here during the handling the body of a ACTIONX keyword + // we can arrive with wellname '?' and a set of matching wells. + if (wellNamePattern == "?") { + std::vector wells; + for (const auto& well_name : matching_wells) + wells.push_back( std::addressof(m_wells.get(well_name) )); - if( wildcard_pos != wellNamePattern.length()-1 ) { - if( !m_wells.hasKey( wellNamePattern ) ) return {}; - return { std::addressof( m_wells.get( wellNamePattern ) ) }; - } + return wells; + } else { + auto wildcard_pos = wellNamePattern.find("*"); - std::vector< Well* > wells; - for( auto& well : this->m_wells ) { - if( Well::wellNameInWellNamePattern( well.name(), wellNamePattern ) ) { - wells.push_back( std::addressof( well ) ); + if( wildcard_pos != wellNamePattern.length()-1 ) { + if( !m_wells.hasKey( wellNamePattern ) ) return {}; + return { std::addressof( m_wells.get( wellNamePattern ) ) }; } - } - return wells; + std::vector< Well* > wells; + for( auto& well : this->m_wells ) { + if( Well::wellNameInWellNamePattern( well.name(), wellNamePattern ) ) { + wells.push_back( std::addressof( well ) ); + } + } + + return wells; + } } void Schedule::addGroup(const std::string& groupName, size_t timeStep) { @@ -2162,5 +2172,20 @@ namespace Opm { return this->m_actions; } + + void Schedule::applyAction(size_t reportStep, const ActionX& action, const std::vector& matching_wells) { + ParseContext parseContext; + ErrorGuard errors; + + for (const auto& keyword : action) { + if (actionx_whitelist.find(keyword.name()) == actionx_whitelist.end()) + throw std::invalid_argument("The keyword: " + keyword.name() + " can not be handled in the ACTION body"); + + if (keyword.name() == "WELOPEN") + this->handleWELOPEN(keyword, reportStep, parseContext, errors, matching_wells); + } + + } + } diff --git a/tests/msim/actionx1.include b/tests/msim/actionx1.include new file mode 100644 index 000000000..7bd7accda --- /dev/null +++ b/tests/msim/actionx1.include @@ -0,0 +1,521 @@ +std::string actionx1 = R"( +-- 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 + 2 1 1 2 / + +UNIFOUT + +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 +/ +SCHEDULE +-- ------------------------------------------------------------------------- +RPTSCHED + 'PRES' 'SGAS' 'RS' 'WELLS' 'WELSPECS' / + +RPTRST + 'BASIC=1' / + + +-- 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 + + +ACTIONX + 'SHUT_WELL' 100000 / + WWCT * > 0.50 / +/ + +WELOPEN + '?' 'SHUT' / +/ + +ENDACTIO + + +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) + +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 +)"; \ No newline at end of file diff --git a/tests/msim/test_msim_ACTIONX.cpp b/tests/msim/test_msim_ACTIONX.cpp new file mode 100644 index 000000000..1539588ae --- /dev/null +++ b/tests/msim/test_msim_ACTIONX.cpp @@ -0,0 +1,154 @@ +/* + Copyright 2018 Statoil 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 . + */ + +#include +#include + +#define BOOST_TEST_MODULE ACTIONX_SIM + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace Opm; + + + +struct test_data { + Deck deck; + EclipseState state; + Schedule schedule; + SummaryConfig summary_config; + + test_data(const std::string& deck_string) : + deck( Parser().parseString(deck_string)), + state( this->deck ), + schedule( this->deck, this->state.getInputGrid(), this->state.get3DProperties(), this->state.runspec()), + summary_config( this->deck, this->schedule, this->state.getTableManager()) + { + } +}; + +double prod_opr(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) { + const auto& units = es.getUnits(); + double oil_rate = 1.0; + return -units.to_si(UnitSystem::measure::rate, oil_rate); +} + +double prod_wpr_P1(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) { + const auto& units = es.getUnits(); + double water_rate = 0.0; + return -units.to_si(UnitSystem::measure::rate, water_rate); +} + +double prod_wpr_P2(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) { + const auto& units = es.getUnits(); + double water_rate = 0.0; + if (report_step > 5) + water_rate = 2.0; // => WWCT = WWPR / (WOPR + WWPR) = 2/3 + + return -units.to_si(UnitSystem::measure::rate, water_rate); +} + +double prod_wpr_P3(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) { + const auto& units = es.getUnits(); + double water_rate = 0.0; + return -units.to_si(UnitSystem::measure::rate, water_rate); +} + +double prod_wpr_P4(const EclipseState& es, const Schedule& sched, const data::Solution& sol, size_t report_step, double seconds_elapsed) { + const auto& units = es.getUnits(); + double water_rate = 0.0; + if (report_step > 10) + water_rate = 2.0; + + return -units.to_si(UnitSystem::measure::rate, water_rate); +} + + +BOOST_AUTO_TEST_CASE(WELL_CLOSE_EXAMPLE) { +#include "actionx1.include" + + test_data td( actionx1 ); + msim sim(td.state); + { + test_work_area_type * work_area = test_work_area_alloc("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"); + const auto& w2 = td.schedule.getWell("P2"); + const auto& w3 = td.schedule.getWell("P3"); + const auto& w4 = td.schedule.getWell("P4"); + + BOOST_CHECK_EQUAL(w1->getStatus(15), WellCommon::StatusEnum::OPEN ); + BOOST_CHECK_EQUAL(w2->getStatus(15), WellCommon::StatusEnum::OPEN ); + BOOST_CHECK_EQUAL(w3->getStatus(15), WellCommon::StatusEnum::OPEN ); + BOOST_CHECK_EQUAL(w4->getStatus(15), WellCommon::StatusEnum::OPEN ); + } + + + sim.run(td.schedule, io); + { + const auto& w1 = td.schedule.getWell("P1"); + const auto& w2 = td.schedule.getWell("P2"); + const auto& w3 = td.schedule.getWell("P3"); + const auto& w4 = td.schedule.getWell("P4"); + + BOOST_CHECK_EQUAL(w1->getStatus(15), WellCommon::StatusEnum::OPEN ); + BOOST_CHECK_EQUAL(w3->getStatus(15), WellCommon::StatusEnum::OPEN ); + + BOOST_CHECK_EQUAL(w2->getStatus(5), WellCommon::StatusEnum::OPEN ); + BOOST_CHECK_EQUAL(w2->getStatus(6), WellCommon::StatusEnum::SHUT ); + + BOOST_CHECK_EQUAL(w4->getStatus(10), WellCommon::StatusEnum::OPEN ); + BOOST_CHECK_EQUAL(w4->getStatus(11), WellCommon::StatusEnum::SHUT ); + } + + + + test_work_area_free(work_area); + } +} diff --git a/tests/parser/ACTIONX.cpp b/tests/parser/ACTIONX.cpp index 9fc0aad49..63f79003d 100644 --- a/tests/parser/ACTIONX.cpp +++ b/tests/parser/ACTIONX.cpp @@ -145,6 +145,7 @@ BOOST_AUTO_TEST_CASE(TestActions) { Opm::SummaryState st; Opm::ActionContext context(st); Opm::Actions config; + std::vector matching_wells; BOOST_CHECK_EQUAL(config.size(), 0); BOOST_CHECK(config.empty()); @@ -165,33 +166,19 @@ BOOST_AUTO_TEST_CASE(TestActions) { config.add(action3); } Opm::ActionX& action2 = config.at("NAME"); + // The action2 instance has an empty condition, so it will never evaluate to true. BOOST_CHECK(action2.ready(util_make_date_utc(1,7,2000))); BOOST_CHECK(!action2.ready(util_make_date_utc(1,6,2000))); - BOOST_CHECK(!action2.eval(util_make_date_utc(1,6,2000), context)); - - BOOST_CHECK(action2.eval(util_make_date_utc(1,8,2000), context)); - BOOST_CHECK(!action2.ready(util_make_date_utc(1,8,2000))); - BOOST_CHECK(!action2.eval(util_make_date_utc(1,8,2000), context)); - - BOOST_CHECK(action2.ready(util_make_date_utc(3,8,2000))); - BOOST_CHECK(config.ready(util_make_date_utc(3,8,2000))); - BOOST_CHECK(action2.eval(util_make_date_utc(3,8,2000), context)); - - BOOST_CHECK(action2.ready(util_make_date_utc(5,8,2000))); - BOOST_CHECK(action2.eval(util_make_date_utc(5,8,2000), context)); - - BOOST_CHECK(!action2.ready(util_make_date_utc(7,8,2000))); - BOOST_CHECK(!action2.eval(util_make_date_utc(7,8,2000), context)); - BOOST_CHECK(config.ready(util_make_date_utc(7,8,2000))); + BOOST_CHECK(!action2.eval(util_make_date_utc(1,6,2000), context, matching_wells)); auto pending = config.pending( util_make_date_utc(7,8,2000)); - BOOST_CHECK_EQUAL( pending.size(), 1); + BOOST_CHECK_EQUAL( pending.size(), 2); for (auto& ptr : pending) { BOOST_CHECK( ptr->ready(util_make_date_utc(7,8,2000))); - BOOST_CHECK( ptr->eval(util_make_date_utc(7,8,2000), context)); + BOOST_CHECK( !ptr->eval(util_make_date_utc(7,8,2000), context, matching_wells)); } - BOOST_CHECK(!action2.eval(util_make_date_utc(7,8,2000), context )); + BOOST_CHECK(!action2.eval(util_make_date_utc(7,8,2000), context, matching_wells )); } From 2caf82baff600f5e917e28827adf3abdbb092de7 Mon Sep 17 00:00:00 2001 From: Joakim Hove Date: Sun, 3 Feb 2019 18:07:04 +0100 Subject: [PATCH 3/3] Move check for legitimate keywords to ActionX --- .../eclipse/EclipseState/Schedule/Action/ActionX.hpp | 2 +- .../eclipse/EclipseState/Schedule/Action/Actions.hpp | 1 - .../eclipse/EclipseState/Schedule/Action/ActionX.cpp | 7 +++++++ .../eclipse/EclipseState/Schedule/Schedule.cpp | 12 +++++------- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp index f75e85bfb..830320c3e 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp @@ -73,7 +73,7 @@ public: std::time_t start_time() const { return this->m_start_time; } std::vector::const_iterator begin() const; std::vector::const_iterator end() const; - + static bool valid_keyword(const std::string& keyword); private: std::string m_name; size_t m_max_run = 0; diff --git a/opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp b/opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp index 000a274a9..5b026cc6e 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp @@ -38,7 +38,6 @@ public: bool ready(std::time_t sim_time) const; ActionX& at(const std::string& name); std::vector pending(std::time_t sim_time) const; - private: std::map actions; }; diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp index 09fec840f..0aa4b7860 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.cpp @@ -17,6 +17,8 @@ along with OPM. If not, see . */ +#include + #include #include @@ -25,6 +27,11 @@ namespace Opm { +bool ActionX::valid_keyword(const std::string& keyword) { + static std::unordered_set actionx_whitelist = {"WELSPECS","WELOPEN"}; + return (actionx_whitelist.find(keyword) != actionx_whitelist.end()); +} + ActionX::ActionX(const std::string& name, size_t max_run, double min_wait, std::time_t start_time) : m_name(name), diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index 5cf70e85a..2f56a0b33 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include @@ -66,8 +65,6 @@ namespace Opm { - static std::set actionx_whitelist = {"WELSPECS","WELOPEN"}; - Schedule::Schedule( const Deck& deck, const EclipseGrid& grid, @@ -371,11 +368,12 @@ namespace Opm { if (action_keyword.name() == "ENDACTIO") break; - if (actionx_whitelist.find(action_keyword.name()) == actionx_whitelist.end()) { + if (ActionX::valid_keyword(action_keyword.name())) + action.addKeyword(action_keyword); + else { std::string msg = "The keyword " + action_keyword.name() + " is not supported in a ACTIONX block."; parseContext.handleError( ParseContext::ACTIONX_ILLEGAL_KEYWORD, msg, errors); - } else - action.addKeyword(action_keyword); + } } this->m_actions.add(action); } else @@ -2178,7 +2176,7 @@ namespace Opm { ErrorGuard errors; for (const auto& keyword : action) { - if (actionx_whitelist.find(keyword.name()) == actionx_whitelist.end()) + if (!ActionX::valid_keyword(keyword.name())) throw std::invalid_argument("The keyword: " + keyword.name() + " can not be handled in the ACTION body"); if (keyword.name() == "WELOPEN")