diff --git a/opm/parser/eclipse/EclipseState/Schedule/Events.hpp b/opm/parser/eclipse/EclipseState/Schedule/Events.hpp index 91aef38f3..25d9f93bb 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Events.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Events.hpp @@ -35,22 +35,22 @@ namespace Opm the Schedule object the NEW_WELL event is triggered every time a WELSPECS keyword is encountered. */ - NEW_WELL = 1, + NEW_WELL = (1 << 0), /* WHen the well data is updated with the WELSPECS keyword this event is triggered. Only applies to individual wells, and not the global Schedule object. */ - WELL_WELSPECS_UPDATE = 2, + WELL_WELSPECS_UPDATE = (1 << 1), - //WELL_POLYMER_UPDATE = 4, + //WELL_POLYMER_UPDATE = (1 << 2), /* The NEW_GROUP event is triggered by the WELSPECS and GRUPTREE keywords. */ - NEW_GROUP = 8, + NEW_GROUP = (1 << 3), /* The PRODUCTION_UPDATE event is triggered by the @@ -59,48 +59,53 @@ namespace Opm is changed. Quite simlar for INJECTION_UPDATE and POLYMER_UPDATE. */ - PRODUCTION_UPDATE = 16, - INJECTION_UPDATE = 32, - //POLYMER_UPDATES = 64, + PRODUCTION_UPDATE = (1 << 4), + INJECTION_UPDATE = (1 << 5), + //POLYMER_UPDATES = (1 << 6), /* This event is triggered if the well status is changed between {OPEN,SHUT,STOP,AUTO}. There are many keywords which can trigger a well status change. */ - WELL_STATUS_CHANGE = 128, + WELL_STATUS_CHANGE = (1 << 7), /* COMPDAT and WELOPEN */ - COMPLETION_CHANGE = 256, + COMPLETION_CHANGE = (1 << 8), /* The well group topolyg has changed. */ - GROUP_CHANGE = 512, + GROUP_CHANGE = (1 << 9), /* Geology modifier. */ - GEO_MODIFIER = 1024, + GEO_MODIFIER = (1 << 10), /* TUNING has changed */ - TUNING_CHANGE = 2048, + TUNING_CHANGE = (1 << 11), /* The VFP tables have changed */ - VFPINJ_UPDATE = 4096, - VFPPROD_UPDATE = 8192, + VFPINJ_UPDATE = (1 << 12), + VFPPROD_UPDATE = (1 << 13), /* GROUP production or injection targets has changed */ - GROUP_PRODUCTION_UPDATE = 16384, - GROUP_INJECTION_UPDATE = 32768 + GROUP_PRODUCTION_UPDATE = (1 << 14), + GROUP_INJECTION_UPDATE = (1 << 15), + + /* + * New explicit well productivity/injectivity assignment. + */ + WELL_PRODUCTIVITY_INDEX = (1 << 16), }; } diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index cbb938f24..dc5de6d42 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -521,6 +521,7 @@ namespace Opm void handleWECON (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWEFAC (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWELOPEN (const HandlerContext&, const ParseContext&, ErrorGuard&); + void handleWELPI (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWELSEGS (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWELSPECS (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWELTARG (const HandlerContext&, const ParseContext&, ErrorGuard&); diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp index 65da4cde2..49cc4c34f 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp @@ -122,6 +122,8 @@ namespace RestartIO { void setState(State state); void setComplnum(int compnum); void scaleWellPi(double wellPi); + bool prepareWellPIScaling(); + void applyWellPIScaling(const double scaleFactor); void updateSegmentRST(int segment_number_arg, double center_depth_arg); void updateSegment(int segment_number_arg, @@ -161,6 +163,7 @@ namespace RestartIO { serializer(m_perf_range); serializer(m_defaultSatTabId); serializer(segment_number); + serializer(m_subject_to_welpi); } private: @@ -238,6 +241,9 @@ namespace RestartIO { // 0 means the completion is not related to segment int segment_number = 0; + // Whether or not this Connection is subject to WELPI scaling. + bool m_subject_to_welpi = false; + static std::string CTFKindToString(const CTFKind); }; } diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp index be7b17dbf..08e552c41 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp @@ -21,8 +21,16 @@ #ifndef WELL2_HPP #define WELL2_HPP +#include #include +#include +#include +#include #include +#include +#include + +#include #include #include @@ -45,8 +53,6 @@ namespace Opm { class DeckRecord; class EclipseGrid; class DeckKeyword; -struct WellInjectionProperties; -class WellProductionProperties; class UDQActive; class UDQConfig; class SICD; @@ -539,6 +545,7 @@ public: bool updateEconLimits(std::shared_ptr econ_limits); bool updateProduction(std::shared_ptr production); bool updateInjection(std::shared_ptr injection); + bool updateWellProductivityIndex(const double prodIndex); bool updateWSEGSICD(const std::vector >& sicd_pairs); bool updateWSEGVALV(const std::vector >& valve_pairs); @@ -563,6 +570,7 @@ public: bool cmp_structure(const Well& other) const; bool operator==(const Well& data) const; void setInsertIndex(std::size_t index); + void applyWellProdIndexScaling(const double currentEffectivePI); template void serializeOp(Serializer& serializer) @@ -588,6 +596,7 @@ public: serializer(solvent_fraction); serializer(has_produced); serializer(prediction_mode); + serializer(productivity_index); serializer(econ_limits); serializer(foam_properties); serializer(polymer_properties); @@ -624,21 +633,21 @@ private: double solvent_fraction; bool has_produced = false; bool prediction_mode = true; - + std::optional productivity_index{ std::nullopt }; std::shared_ptr econ_limits; std::shared_ptr foam_properties; std::shared_ptr polymer_properties; std::shared_ptr brine_properties; std::shared_ptr tracer_properties; - std::shared_ptr connections; // The WellConnections object can not be const because of the filterConnections method - would be beneficial to rewrite to enable const + std::shared_ptr connections; // The WellConnections object cannot be const because of WELPI and the filterConnections method std::shared_ptr production; std::shared_ptr injection; std::shared_ptr segments; }; std::ostream& operator<<( std::ostream&, const Well::WellInjectionProperties& ); -std::ostream& operator<<( std::ostream&, const WellProductionProperties& ); +std::ostream& operator<<( std::ostream&, const Well::WellProductionProperties& ); int eclipseControlMode(const Well::InjectorCMode imode, const InjectorType itype); diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp index bc6406077..4871c6028 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp @@ -25,7 +25,13 @@ #include +#include +#include + +#include + namespace Opm { + class DeckRecord; class EclipseGrid; class FieldPropsManager; class WellConnections { @@ -93,6 +99,21 @@ namespace Opm { Connection::Order ordering() const { return this->m_ordering; } std::vector output(const EclipseGrid& grid) const; + /// Activate or reactivate WELPI scaling for this connection set. + /// + /// Following this call, any WELPI-based scaling will apply to all + /// connections whose properties are not reset in COMPDAT. + /// + /// Returns whether or not this call to prepareWellPIScaling() is + /// a state change (e.g., no WELPI to active WELPI or WELPI for + /// some connections to WELPI for all connections). + bool prepareWellPIScaling(); + + /// Scale pertinent connections' CF value by supplied value. Scaling + /// factor typically derived from 'WELPI' input keyword and a dynamic + /// productivity index calculation. + void applyWellPIScaling(const double scaleFactor); + template void serializeOp(Serializer& serializer) { diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp index 244f70f48..843ff960e 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp @@ -17,6 +17,7 @@ along with OPM. If not, see . */ +#include #include #include #include @@ -50,6 +51,7 @@ #include #include +#include #include #include #include @@ -1064,6 +1066,48 @@ namespace { return applyWELOPEN(handlerContext.keyword, handlerContext.currentStep, parseContext, errors); } + void Schedule::handleWELPI(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) { + // Keyword structure + // + // WELPI + // W1 123.45 / + // W2* 456.78 / + // *P 111.222 / + // **X* 333.444 / + // / + // + // Interpretation of productivity index (item 2) depends on well's preferred phase. + using WELL_NAME = ParserKeywords::WELPI::WELL_NAME; + using PI = ParserKeywords::WELPI::STEADY_STATE_PRODUCTIVITY_OR_INJECTIVITY_INDEX_VALUE; + + const auto& usys = handlerContext.section.unitSystem(); + const auto gasPI = UnitSystem::measure::gas_productivity_index; + const auto liqPI = UnitSystem::measure::liquid_productivity_index; + + for (const auto& record : handlerContext.keyword) { + const auto well_names = this->wellNames(record.getItem().getTrimmedString(0), + handlerContext.currentStep); + + if (well_names.empty()) + this->invalidNamePattern(record.getItem().getTrimmedString(0), + handlerContext.currentStep, parseContext, + errors, handlerContext.keyword); + + const auto rawProdIndex = record.getItem().get(0); + for (const auto& well_name : well_names) { + // All wells in a single record *hopefully* have the same preferred phase... + const auto& well = this->getWell(well_name, handlerContext.currentStep); + const auto unitPI = (well.getPreferredPhase() == Phase::GAS) ? gasPI : liqPI; + + auto well2 = std::make_shared(well); + if (well2->updateWellProductivityIndex(usys.to_si(unitPI, rawProdIndex))) + this->updateWell(std::move(well2), handlerContext.currentStep); + + this->addWellGroupEvent(well_name, ScheduleEvents::WELL_PRODUCTIVITY_INDEX, handlerContext.currentStep); + } + } + } + void Schedule::handleWELSEGS(const HandlerContext& handlerContext, const ParseContext&, ErrorGuard&) { const auto& record1 = handlerContext.keyword.getRecord(0); const auto& wname = record1.getItem("WELL").getTrimmedString(0); @@ -1680,7 +1724,7 @@ namespace { bool Schedule::handleNormalKeyword(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) { - using handler_function = std::function; + using handler_function = void (Schedule::*)(const HandlerContext&, const ParseContext&, ErrorGuard&); static const std::unordered_map handler_functions = { { "BRANPROP", &Schedule::handleBRANPROP }, { "COMPDAT" , &Schedule::handleCOMPDAT }, @@ -1734,6 +1778,7 @@ namespace { { "WECON" , &Schedule::handleWECON }, { "WEFAC" , &Schedule::handleWEFAC }, { "WELOPEN" , &Schedule::handleWELOPEN }, + { "WELPI" , &Schedule::handleWELPI }, { "WELSEGS" , &Schedule::handleWELSEGS }, { "WELSPECS", &Schedule::handleWELSPECS }, { "WELTARG" , &Schedule::handleWELTARG }, @@ -1757,27 +1802,24 @@ namespace { { "WTRACER" , &Schedule::handleWTRACER }, }; - const auto function_iterator = handler_functions.find(handlerContext.keyword.name()); - - if (function_iterator != handler_functions.end()) { - const auto& handler = function_iterator->second; - - try { - handler(this, handlerContext, parseContext, errors); - } catch (const OpmInputError&) { - throw; - } catch (const std::exception& e) { - const OpmInputError opm_error { e, handlerContext.keyword.location() } ; - - OpmLog::error(opm_error.what()); - - std::throw_with_nested(opm_error); - } - - return true; - } else { + auto function_iterator = handler_functions.find(handlerContext.keyword.name()); + if (function_iterator == handler_functions.end()) { return false; } + + try { + std::invoke(function_iterator->second, this, handlerContext, parseContext, errors); + } catch (const OpmInputError&) { + throw; + } catch (const std::exception& e) { + const OpmInputError opm_error { e, handlerContext.keyword.location() } ; + + OpmLog::error(opm_error.what()); + + std::throw_with_nested(opm_error); + } + + return true; } } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index cfb420f67..a73c920bc 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -1285,22 +1285,6 @@ namespace { well_pair.second->filterConnections(grid); } } - - for (auto& dynamic_pair : this->wells_static) { - auto& dynamic_state = dynamic_pair.second; - for (auto& well_pair : dynamic_state.unique()) { - if (well_pair.second) - well_pair.second->filterConnections(grid); - } - } - - for (auto& dynamic_pair : this->wells_static) { - auto& dynamic_state = dynamic_pair.second; - for (auto& well_pair : dynamic_state.unique()) { - if (well_pair.second) - well_pair.second->filterConnections(grid); - } - } } const VFPProdTable& Schedule::getVFPProdTable(int table_id, std::size_t timeStep) const { diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp index 9ccab6f61..72c2d5285 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp @@ -127,6 +127,7 @@ Connection::Connection(const RestartIO::RstConnection& rst_connection, const Ecl result.m_sort_value = 14; result.m_defaultSatTabId = true; result.segment_number = 16; + result.m_subject_to_welpi = true; return result; } @@ -247,7 +248,20 @@ const std::optional>& Connection::perf_range() const { this->m_CF *= wellPi; } + bool Connection::prepareWellPIScaling() { + const auto update = !this->m_subject_to_welpi; + this->m_subject_to_welpi = true; + + return update; + } + + void Connection::applyWellPIScaling(const double scaleFactor) { + if (! this->m_subject_to_welpi) + return; + + this->scaleWellPi(scaleFactor); + } std::string Connection::str() const { std::stringstream ss; @@ -283,7 +297,8 @@ const std::optional>& Connection::perf_range() const { && this->direction == rhs.direction && this->segment_number == rhs.segment_number && this->center_depth == rhs.center_depth - && this->m_sort_value == rhs.m_sort_value; + && this->m_sort_value == rhs.m_sort_value + && this->m_subject_to_welpi == rhs.m_subject_to_welpi; } bool Connection::operator!=( const Connection& rhs ) const { diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp index bd608fa42..d8152e305 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp @@ -375,6 +375,7 @@ Well Well::serializeObject() result.efficiency_factor = 8.0; result.solvent_fraction = 9.0; result.prediction_mode = false; + result.productivity_index = 10.0; result.econ_limits = std::make_shared(Opm::WellEconProductionLimits::serializeObject()); result.foam_properties = std::make_shared(WellFoamProperties::serializeObject()); result.polymer_properties = std::make_shared(WellPolymerProperties::serializeObject()); @@ -488,6 +489,15 @@ bool Well::updateInjection(std::shared_ptr injection_ar return false; } +bool Well::updateWellProductivityIndex(const double prodIndex) { + const auto update = this->productivity_index != prodIndex; + if (update) + this->productivity_index = prodIndex; + + // Note order here: We must always run prepareWellPIScaling(), but that operation + // *may* not lead to requiring updating the well state, so return 'update' if not. + return this->connections->prepareWellPIScaling() || update; +} bool Well::updateHasProduced() { if (this->wtype.producer() && this->status == Status::OPEN) { @@ -801,6 +811,21 @@ void Well::setInsertIndex(std::size_t index) { this->insert_index = index; } +void Well::applyWellProdIndexScaling(const double currentEffectivePI) { + if (this->connections->empty()) + // No connections for this well. Unexpected. + return; + + if (!this->productivity_index) + // WELPI not activated. Nothing to do. + return; + + if (this->productivity_index == currentEffectivePI) + // No change in scaling. + return; + + this->connections->applyWellPIScaling(*this->productivity_index / currentEffectivePI); +} const WellConnections& Well::getConnections() const { return *this->connections; @@ -1486,6 +1511,10 @@ bool Well::operator==(const Well& data) const { this->getFoamProperties() == data.getFoamProperties() && this->getStatus() == data.getStatus() && this->guide_rate == data.guide_rate && + this->solvent_fraction == data.solvent_fraction && + this->hasProduced() == data.hasProduced() && + this->predictionMode() == data.predictionMode() && + this->productivity_index == data.productivity_index && this->getTracerProperties() == data.getTracerProperties() && this->getProductionProperties() == data.getProductionProperties() && this->getInjectionProperties() == data.getInjectionProperties(); diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp index d4c8f97f8..2b8b0e819 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp @@ -17,10 +17,15 @@ along with OPM. If not, see . */ +#include #include #include -#include +#include #include +#include +#include +#include +#include #include #include @@ -181,6 +186,20 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction return out; } + bool WellConnections::prepareWellPIScaling() + { + auto update = false; + for (auto& conn : this->m_connections) + update = conn.prepareWellPIScaling() || update; + + return update; + } + + void WellConnections::applyWellPIScaling(const double scaleFactor) + { + for (auto& conn : this->m_connections) + conn.applyWellPIScaling(scaleFactor); + } void WellConnections::addConnection(int i, int j , int k , std::size_t global_index, @@ -528,7 +547,6 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction } } - size_t WellConnections::findClosestConnection(int oi, int oj, double oz, size_t start_pos) { size_t closest = std::numeric_limits::max(); @@ -568,11 +586,12 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction return !( *this == rhs ); } - void WellConnections::filter(const ActiveGridCells& grid) { - auto new_end = std::remove_if(m_connections.begin(), - m_connections.end(), - [&grid](const Connection& c) { return !grid.cellActive(c.getI(), c.getJ(), c.getK()); }); + auto isInactive = [&grid](const Connection& c) { + return !grid.cellActive(c.getI(), c.getJ(), c.getK()); + }; + + auto new_end = std::remove_if(m_connections.begin(), m_connections.end(), isInactive); m_connections.erase(new_end, m_connections.end()); } diff --git a/tests/parser/ConnectionTests.cpp b/tests/parser/ConnectionTests.cpp index 1494c0aa8..57f379b0f 100644 --- a/tests/parser/ConnectionTests.cpp +++ b/tests/parser/ConnectionTests.cpp @@ -17,6 +17,7 @@ along with OPM. If not, see . */ +#include #include #include #include @@ -28,6 +29,7 @@ #include #include +#include #include #include #include @@ -41,6 +43,30 @@ #include #include +#include + +namespace { + double cp_rm3_per_db() + { + return Opm::prefix::centi*Opm::unit::Poise * Opm::unit::cubic(Opm::unit::meter) + / (Opm::unit::day * Opm::unit::barsa); + } + +Opm::WellConnections loadCOMPDAT(const std::string& compdat_keyword) { + Opm::EclipseGrid grid(10,10,10); + Opm::TableManager tables; + Opm::Parser parser; + const auto deck = parser.parseString(compdat_keyword); + Opm::FieldPropsManager field_props(deck, Opm::Phases{true, true, true}, grid, Opm::TableManager()); + const auto& keyword = deck.getKeyword("COMPDAT", 0); + Opm::WellConnections connections(Opm::Connection::Order::TRACK, 10,10); + for (const auto& rec : keyword) + connections.loadCOMPDAT(rec, grid, field_props); + + return connections; +} +} + namespace Opm { inline std::ostream& operator<<( std::ostream& stream, const Connection& c ) { @@ -150,20 +176,6 @@ BOOST_AUTO_TEST_CASE(ActiveCompletions) { BOOST_CHECK_EQUAL( completion3, active_completions.get(1)); } -Opm::WellConnections loadCOMPDAT(const std::string& compdat_keyword) { - Opm::EclipseGrid grid(10,10,10); - Opm::TableManager tables; - Opm::Parser parser; - const auto deck = parser.parseString(compdat_keyword); - Opm::FieldPropsManager field_props(deck, Opm::Phases{true, true, true}, grid, Opm::TableManager()); - const auto& keyword = deck.getKeyword("COMPDAT", 0); - Opm::WellConnections connections(Opm::Connection::Order::TRACK, 10,10); - for (const auto& rec : keyword) - connections.loadCOMPDAT(rec, grid, field_props); - - return connections; -} - BOOST_AUTO_TEST_CASE(loadCOMPDATTEST) { Opm::UnitSystem units(Opm::UnitSystem::UnitType::UNIT_TYPE_METRIC); // Unit system used in deck FIRST_SIM.DATA. { @@ -336,3 +348,149 @@ BOOST_AUTO_TEST_CASE(loadCOMPDATTESTSPE9) { BOOST_CHECK_MESSAGE( !conn.ctfAssignedFromInput(), "Calculated SPE9 CTF values must NOT be assigned from input"); } } + +BOOST_AUTO_TEST_CASE(ApplyWellPI) { + const auto deck = Opm::Parser{}.parseString(R"(RUNSPEC +DIMENS +10 10 3 / + +START + 5 OCT 2020 / + +GRID +DXV + 10*100 / +DYV + 10*100 / +DZV + 3*10 / +DEPTHZ + 121*2000 / + +ACTNUM + 100*1 + 99*1 0 + 100*1 +/ + +PERMX + 300*100 / +PERMY + 300*100 / +PERMZ + 300*100 / +PORO + 300*0.3 / + +SCHEDULE +WELSPECS + 'P' 'G' 10 10 2005 'LIQ' / +/ + +COMPDAT + 'P' 0 0 1 3 OPEN 1 100 / +/ + +TSTEP + 10 +/ + +END +)"); + + const auto es = Opm::EclipseState{ deck }; + const auto sched = Opm::Schedule{ deck, es }; + + const auto expectCF = 100.0*cp_rm3_per_db(); + + auto connP = sched.getWell("P", 0).getConnections(); + for (const auto& conn : connP) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + + connP.applyWellPIScaling(2.0); // No "prepare" -> no change. + for (const auto& conn : connP) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + + // All CFs scaled by factor 2. + BOOST_CHECK_MESSAGE( connP.prepareWellPIScaling(), "First call to prepareWellPIScaling must be a state change"); + BOOST_CHECK_MESSAGE(!connP.prepareWellPIScaling(), "Second call to prepareWellPIScaling must NOT be a state change"); + connP.applyWellPIScaling(2.0); + for (const auto& conn : connP) { + BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10); + } + + // Reset CF -- simulating COMPDAT record (inactive cell) + connP.addConnection(9, 9, 1, // 10, 10, 2 + 199, + 2015.0, + Opm::Connection::State::OPEN, + 50.0*cp_rm3_per_db(), + 0.123, + 0.234, + 0.157, + 0.0, + 1); + + BOOST_REQUIRE_EQUAL(connP.size(), std::size_t{3}); + + BOOST_CHECK_CLOSE(connP[0].CF(), 2.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[1].CF(), 2.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + + // Should not apply to connection whose CF was manually specified + connP.applyWellPIScaling(2.0); + + BOOST_CHECK_CLOSE(connP[0].CF(), 4.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[1].CF(), 4.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + + // Prepare new scaling. Simulating new WELPI record. + // New scaling applies to all connections. + BOOST_CHECK_MESSAGE(connP.prepareWellPIScaling(), "Third call to prepareWellPIScaling must be a state change"); + connP.applyWellPIScaling(2.0); + + BOOST_CHECK_CLOSE(connP[0].CF(), 8.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[1].CF(), 8.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[2].CF(), 100.0*cp_rm3_per_db(), 1.0e-10); + + // Reset CF -- simulating COMPDAT record (active cell) + connP.addConnection(8, 9, 1, // 10, 10, 2 + 198, + 2015.0, + Opm::Connection::State::OPEN, + 50.0*cp_rm3_per_db(), + 0.123, + 0.234, + 0.157, + 0.0, + 1); + + BOOST_REQUIRE_EQUAL(connP.size(), std::size_t{4}); + connP.applyWellPIScaling(2.0); + + BOOST_CHECK_CLOSE(connP[0].CF(), 16.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[1].CF(), 16.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[2].CF(), 200.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(connP[3].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + + const auto& grid = es.getInputGrid(); + const auto actCells = Opm::ActiveGridCells { + std::size_t{10}, std::size_t{10}, std::size_t{3}, + grid.getActiveMap().data(), + grid.getNumActive() + }; + + connP.filter(actCells); + + BOOST_REQUIRE_EQUAL(connP.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(connP[0].CF(), 16.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[1].CF(), 16.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + + connP.applyWellPIScaling(2.0); + BOOST_CHECK_CLOSE(connP[0].CF(), 32.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[1].CF(), 32.0*expectCF , 1.0e-10); + BOOST_CHECK_CLOSE(connP[2].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); +} diff --git a/tests/parser/ScheduleTests.cpp b/tests/parser/ScheduleTests.cpp index c1ea2d02c..362d7ce28 100644 --- a/tests/parser/ScheduleTests.cpp +++ b/tests/parser/ScheduleTests.cpp @@ -17,19 +17,17 @@ along with OPM. If not, see . */ -#include +#include #include -#include +#include #define BOOST_TEST_MODULE ScheduleTests #include -#include #include #include - #include #include #include @@ -60,8 +58,20 @@ using namespace Opm; +namespace { + double liquid_PI_unit() + { + return UnitSystem::newMETRIC().to_si(UnitSystem::measure::liquid_productivity_index, 1.0); + } -Schedule make_schedule(const std::string& deck_string) { + double cp_rm3_per_db() + { + return prefix::centi*unit::Poise * unit::cubic(unit::meter) + / (unit::day * unit::barsa); + } +} + +static Schedule make_schedule(const std::string& deck_string) { const auto& deck = Parser{}.parseString(deck_string); auto python = std::make_shared(); EclipseGrid grid(10,10,10); @@ -408,7 +418,7 @@ BOOST_AUTO_TEST_CASE(CreateScheduleDeckWellsOrdered) { } -bool has_well( const std::vector& wells, const std::string& well_name) { +static bool has_well( const std::vector& wells, const std::string& well_name) { for (const auto& well : wells ) if (well.name( ) == well_name) return true; @@ -3159,7 +3169,7 @@ BOOST_AUTO_TEST_CASE(WTEST_CONFIG) { } -bool has(const std::vector& l, const std::string& s) { +static bool has(const std::vector& l, const std::string& s) { auto f = std::find(l.begin(), l.end(), s); return (f != l.end()); } @@ -3704,3 +3714,82 @@ WLIFTOPT BOOST_CHECK(w3.alloc_extra_gas()); } +BOOST_AUTO_TEST_CASE(WellPI) { + const auto deck = Parser{}.parseString(R"(RUNSPEC +START +7 OCT 2020 / + +DIMENS + 10 10 3 / + +GRID +DXV + 10*100.0 / +DYV + 10*100.0 / +DZV + 3*10.0 / + +DEPTHZ + 121*2000.0 / + +PERMX + 300*100.0 / +PERMY + 300*100.0 / +PERMZ + 300*10.0 / +PORO + 300*0.3 / + +SCHEDULE +WELSPECS + 'P' 'G' 10 10 2005 'LIQ' / +/ +COMPDAT + 'P' 0 0 1 3 OPEN 1 100 / +/ + +TSTEP + 10 +/ + +WELPI + 'P' 200.0 / +/ + +TSTEP + 10 +/ + +END +)"); + + const auto es = EclipseState{ deck }; + const auto sched = Schedule{ deck, es }; + + // Apply WELPI before seeing WELPI data + { + const auto expectCF = 100.0*cp_rm3_per_db(); + auto wellP = sched.getWell("P", 0); + + wellP.applyWellProdIndexScaling(2.7182818); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + } + + // Apply WELPI after seeing WELPI data. + { + const auto expectCF = (200.0 / 100.0) * 100.0*cp_rm3_per_db(); + auto wellP = sched.getWell("P", 1); + + wellP.applyWellProdIndexScaling(100.0*liquid_PI_unit()); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + } + + BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_PRODUCTIVITY_INDEX, 1), + "Must have WELL_PRODUCTIVITY_INDEX event at report step 1"); +} diff --git a/tests/parser/WellTests.cpp b/tests/parser/WellTests.cpp index 38ffbf075..916033f63 100644 --- a/tests/parser/WellTests.cpp +++ b/tests/parser/WellTests.cpp @@ -49,17 +49,13 @@ using namespace Opm; - -namespace Opm { -inline std::ostream& operator<<( std::ostream& stream, const Connection& c ) { - return stream << "(" << c.getI() << "," << c.getJ() << "," << c.getK() << ")"; +namespace { + double cp_rm3_per_db() + { + return prefix::centi*unit::Poise * unit::cubic(unit::meter) + / (unit::day * unit::barsa); + } } -inline std::ostream& operator<<( std::ostream& stream, const Well& well ) { - return stream << "(" << well.name() << ")"; -} -} - - BOOST_AUTO_TEST_CASE(WellCOMPDATtestTRACK) { Opm::Parser parser; @@ -1161,3 +1157,93 @@ WCONINJE } +BOOST_AUTO_TEST_CASE(WellPI) { + const auto deck = Parser{}.parseString(R"(RUNSPEC +START +7 OCT 2020 / + +DIMENS + 10 10 3 / + +GRID +DXV + 10*100.0 / +DYV + 10*100.0 / +DZV + 3*10.0 / + +DEPTHZ + 121*2000.0 / + +PERMX + 300*100.0 / +PERMY + 300*100.0 / +PERMZ + 300*10.0 / +PORO + 300*0.3 / + +SCHEDULE +WELSPECS + 'P' 'G' 10 10 2005 'LIQ' / +/ +COMPDAT + 'P' 0 0 1 3 OPEN 1 100 / +/ + +END +)"); + + const auto es = EclipseState{ deck }; + const auto sched = Schedule{ deck, es }; + + const auto expectCF = 100.0*cp_rm3_per_db(); + + auto wellP = sched.getWell("P", 0); + + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + + // Simulate applying WELPI before WELPI keyword. No effect. + wellP.applyWellProdIndexScaling(2.7182818); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + + // Simulate applying WELPI after seeing + // + // WELPI + // P 2 / + // / + // + // (ignoring units of measure) + BOOST_CHECK_MESSAGE( wellP.updateWellProductivityIndex(2.0), "First call to updateWellProductivityIndex() must be a state change"); + BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(2.0), "Second call to updateWellProductivityIndex() must NOT be a state change"); + + // Want PI=2, but actual/effective PI=1 => scale CF by 2.0/1.0. + wellP.applyWellProdIndexScaling(1.0); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10); + } + + // Repeated application of WELPI multiplies scaling factors. + wellP.applyWellProdIndexScaling(1.0); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10); + } + + // New WELPI record does not reset the scaling factors + wellP.updateWellProductivityIndex(3.0); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10); + } + + // Effective PI=desired PI => no scaling change + wellP.applyWellProdIndexScaling(3.0); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10); + } +}