diff --git a/opm/parser/eclipse/EclipseState/Schedule/Events.hpp b/opm/parser/eclipse/EclipseState/Schedule/Events.hpp index 0496f3968..25d9f93bb 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Events.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Events.hpp @@ -106,12 +106,6 @@ namespace Opm * New explicit well productivity/injectivity assignment. */ WELL_PRODUCTIVITY_INDEX = (1 << 16), - - /* - * Well's internal WellConnections structure changed. - * Rerun WELPI scaling if applicable. - */ - WELL_CONNECTIONS_UPDATED = (1 << 17), }; } diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index 642ab7d72..67c2bda88 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -272,6 +272,7 @@ namespace Opm RestartConfig& restart(); void applyAction(std::size_t reportStep, const Action::ActionX& action, const Action::Result& result); + void applyWellProdIndexScaling(const std::string& well_name, const std::size_t reportStep, const double scalingFactor); int getNupcol(std::size_t reportStep) const; diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp index 49cc4c34f..ccf7a5513 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.hpp @@ -123,7 +123,7 @@ namespace RestartIO { void setComplnum(int compnum); void scaleWellPi(double wellPi); bool prepareWellPIScaling(); - void applyWellPIScaling(const double scaleFactor); + bool applyWellPIScaling(const double scaleFactor); void updateSegmentRST(int segment_number_arg, double center_depth_arg); void updateSegment(int segment_number_arg, diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp index ba3090422..de20113d1 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp @@ -598,8 +598,11 @@ public: bool updateHasProduced(); bool cmp_structure(const Well& other) const; bool operator==(const Well& data) const; + bool hasSameConnectionsPointers(const Well& other) const; void setInsertIndex(std::size_t index); - void applyWellProdIndexScaling(const double currentEffectivePI); + double getWellPIScalingFactor(const double currentEffectivePI) const; + void applyWellProdIndexScaling(const double scalingFactor, + std::vector& scalingApplicable); template void serializeOp(Serializer& serializer) diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp index 4871c6028..2efa11759 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.hpp @@ -111,8 +111,13 @@ namespace Opm { /// 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); + /// productivity index calculation. Applicability array specifies + /// whether or not a particular connection is exempt from scaling. + /// Empty array means "apply scaling to all eligible connections". + /// This array is updated on return (entries set to 'false' if + /// corresponding connection is not eligible). + void applyWellPIScaling(const double scaleFactor, + std::vector& scalingApplicable); 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 53541a907..ffcb1c664 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp @@ -154,10 +154,8 @@ namespace { auto connections = std::shared_ptr( new WellConnections( well2->getConnections())); connections->loadCOMPDAT(record, handlerContext.grid, handlerContext.fieldPropsManager); - if (well2->updateConnections(connections, handlerContext.grid, handlerContext.fieldPropsManager.get_int("PVTNUM"))) { - this->updateWell(well2, handlerContext.currentStep); - this->addWellGroupEvent(name, ScheduleEvents::WELL_CONNECTIONS_UPDATED, handlerContext.currentStep); - } + if (well2->updateConnections(connections, handlerContext.grid, handlerContext.fieldPropsManager.get_int("PVTNUM"))) + this->updateWell(std::move(well2), handlerContext.currentStep); this->addWellGroupEvent(name, ScheduleEvents::COMPLETION_CHANGE, handlerContext.currentStep); } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp index f800ad638..7896317c0 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Schedule.cpp @@ -17,16 +17,17 @@ along with OPM. If not, see . */ +#include #include - #include +#include #include #include #include #include #include +#include #include -#include #include @@ -666,10 +667,9 @@ private: { auto& dynamic_state = this->wells_static.at(wname); auto well_ptr = std::make_shared( *dynamic_state[currentStep] ); - if (well_ptr->handleWELOPEN(record, comp_status, action_mode)) { + if (well_ptr->handleWELOPEN(record, comp_status, action_mode)) // The updateWell call breaks test at line 825 and 831 in ScheduleTests - this->updateWell(well_ptr, currentStep); - } + this->updateWell(std::move(well_ptr), currentStep); } m_events.addEvent( ScheduleEvents::COMPLETION_CHANGE, currentStep ); @@ -1499,6 +1499,38 @@ private: } } + void Schedule::applyWellProdIndexScaling(const std::string& well_name, const std::size_t reportStep, const double scalingFactor) { + auto wstat = this->wells_static.find(well_name); + if (wstat == this->wells_static.end()) + return; + + auto unique_well_instances = wstat->second.unique(); + + auto end = unique_well_instances.end(); + auto start = std::lower_bound(unique_well_instances.begin(), end, reportStep, + [](const auto& time_well_pair, const auto lookup) -> bool + { + // time < reportStep + return time_well_pair.first < lookup; + }); + + if (start == end) + // Report step after last? + return; + + // Relies on wells_static being OrderedMap>> + // which means unique_well_instances is a vector>> + std::vector scalingApplicable; + auto wellPtr = start->second; + wellPtr->applyWellProdIndexScaling(scalingFactor, scalingApplicable); + + for (; start != end; ++start) + if (! wellPtr->hasSameConnectionsPointers(*start->second)) { + wellPtr = start->second; + wellPtr->applyWellProdIndexScaling(scalingFactor, scalingApplicable); + } + } + RestartConfig& Schedule::restart() { return this->restart_config; } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp index 72c2d5285..e7448222e 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Connection.cpp @@ -256,11 +256,12 @@ const std::optional>& Connection::perf_range() const { return update; } - void Connection::applyWellPIScaling(const double scaleFactor) { + bool Connection::applyWellPIScaling(const double scaleFactor) { if (! this->m_subject_to_welpi) - return; + return false; this->scaleWellPi(scaleFactor); + return true; } std::string Connection::str() const { diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp index 858f48d50..fac690927 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/Well.cpp @@ -821,12 +821,35 @@ const std::string& Well::name() const { return this->wname; } +bool Well::hasSameConnectionsPointers(const Well& other) const +{ + // Note: This is *supposed* to be a pointer comparison. We need to know + // if the two connection structures represent the exact same object, not + // just if they have the same value. + return this->connections == other.connections; +} void Well::setInsertIndex(std::size_t index) { this->insert_index = index; } -void Well::applyWellProdIndexScaling(const double currentEffectivePI) { +double Well::getWellPIScalingFactor(const double currentEffectivePI) const { + if (this->connections->empty()) + // No connections for this well. Unexpected. + return 1.0; + + if (!this->productivity_index) + // WELPI not activated. Nothing to do. + return 1.0; + + if (this->productivity_index->pi_value == currentEffectivePI) + // No change in scaling. + return 1.0; + + return this->productivity_index->pi_value / currentEffectivePI; +} + +void Well::applyWellProdIndexScaling(const double scalingFactor, std::vector& scalingApplicable) { if (this->connections->empty()) // No connections for this well. Unexpected. return; @@ -835,11 +858,11 @@ void Well::applyWellProdIndexScaling(const double currentEffectivePI) { // WELPI not activated. Nothing to do. return; - if (this->productivity_index->pi_value == currentEffectivePI) + if (scalingFactor == 1.0) // No change in scaling. return; - this->connections->applyWellPIScaling(this->productivity_index->pi_value / currentEffectivePI); + this->connections->applyWellPIScaling(scalingFactor, scalingApplicable); } const WellConnections& Well::getConnections() const { diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp index 2b8b0e819..70b41771a 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellConnections.cpp @@ -195,10 +195,17 @@ inline std::array< size_t, 3> directionIndices(const Opm::Connection::Direction return update; } - void WellConnections::applyWellPIScaling(const double scaleFactor) + void WellConnections::applyWellPIScaling(const double scaleFactor, + std::vector& scalingApplicable) { - for (auto& conn : this->m_connections) - conn.applyWellPIScaling(scaleFactor); + scalingApplicable.resize(std::max(scalingApplicable.size(), this->m_connections.size()), true); + + auto i = std::size_t{0}; + for (auto& conn : this->m_connections) { + if (scalingApplicable[i]) + scalingApplicable[i] = conn.applyWellPIScaling(scaleFactor); + ++i; + } } void WellConnections::addConnection(int i, int j , int k , diff --git a/tests/parser/ConnectionTests.cpp b/tests/parser/ConnectionTests.cpp index 57f379b0f..65f5ff795 100644 --- a/tests/parser/ConnectionTests.cpp +++ b/tests/parser/ConnectionTests.cpp @@ -408,17 +408,26 @@ END 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); + { + std::vector scalingApplicable; + + connP.applyWellPIScaling(2.0, scalingApplicable); // 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); + + { + std::vector scalingApplicable; + + connP.applyWellPIScaling(2.0, scalingApplicable); // No "prepare" -> no change. + for (const auto& conn : connP) { + BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10); + } } // Reset CF -- simulating COMPDAT record (inactive cell) @@ -440,20 +449,27 @@ END 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); + { + std::vector scalingApplicable; + connP.applyWellPIScaling(2.0, scalingApplicable); - 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); + 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); + { + std::vector scalingApplicable; + connP.applyWellPIScaling(2.0, scalingApplicable); + + 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 @@ -468,12 +484,16 @@ END 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); + { + std::vector scalingApplicable; + connP.applyWellPIScaling(2.0, scalingApplicable); + + 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 { @@ -489,8 +509,12 @@ END 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); + { + std::vector scalingApplicable; + + connP.applyWellPIScaling(2.0, scalingApplicable); + 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 b9f78cd90..c91f4efd1 100644 --- a/tests/parser/ScheduleTests.cpp +++ b/tests/parser/ScheduleTests.cpp @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -3779,10 +3780,15 @@ END const auto expectCF = 100.0*cp_rm3_per_db(); auto wellP = sched.getWell("P", 0); - wellP.applyWellProdIndexScaling(2.7182818); + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(2.7182818, scalingApplicable); for (const auto& conn : wellP.getConnections()) { BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); } + + for (const bool applicable : scalingApplicable) { + BOOST_CHECK_MESSAGE(! applicable, "No connection must be eligible for WELPI scaling"); + } } // Apply WELPI after seeing WELPI data. @@ -3790,10 +3796,18 @@ END 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()); + const auto scalingFactor = wellP.getWellPIScalingFactor(100.0*liquid_PI_unit()); + BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10); + + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable); for (const auto& conn : wellP.getConnections()) { BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); } + + for (const bool applicable : scalingApplicable) { + BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling"); + } } // Apply WELPI after new COMPDAT. @@ -3801,29 +3815,329 @@ END const auto expectCF = (200.0 / 100.0) * 100.0*cp_rm3_per_db(); auto wellP = sched.getWell("P", 2); - wellP.applyWellProdIndexScaling(100.0*liquid_PI_unit()); + const auto scalingFactor = wellP.getWellPIScalingFactor(100.0*liquid_PI_unit()); + BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10); + + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable); const auto& connP = wellP.getConnections(); BOOST_CHECK_CLOSE(connP[0].CF(), expectCF , 1.0e-10); BOOST_CHECK_CLOSE(connP[1].CF(), 50*cp_rm3_per_db(), 1.0e-10); BOOST_CHECK_CLOSE(connP[2].CF(), expectCF , 1.0e-10); + + BOOST_CHECK_MESSAGE(bool(scalingApplicable[0]), "Connection[0] must be eligible for WELPI scaling"); + BOOST_CHECK_MESSAGE(! scalingApplicable[1] , "Connection[1] must NOT be eligible for WELPI scaling"); + BOOST_CHECK_MESSAGE(bool(scalingApplicable[0]), "Connection[2] must be eligible for WELPI scaling"); } - - BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 0), - "Well P must have WELL_CONNECTIONS_UPDATED event at report step 0"); - - BOOST_CHECK_MESSAGE(!sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 1), - "Well P must NOT have WELL_CONNECTIONS_UPDATED event at report step 1"); - - BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 2), - "Well P must have WELL_CONNECTIONS_UPDATED event at report step 2"); - - BOOST_CHECK_MESSAGE(!sched.hasWellGroupEvent("P", ScheduleEvents::WELL_CONNECTIONS_UPDATED, 3), - "Well P must NOT have WELL_CONNECTIONS_UPDATED event at report step 3"); - - BOOST_CHECK_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::WELL_PRODUCTIVITY_INDEX, 1), - "Must have WELL_PRODUCTIVITY_INDEX event at report step 1"); } +BOOST_AUTO_TEST_CASE(Schedule_ApplyWellProdIndexScaling) { + 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 -- 0 + 'P' 'G' 10 10 2005 'LIQ' / +/ +COMPDAT + 'P' 0 0 1 3 OPEN 1 100 / +/ + +TSTEP -- 1 + 10 +/ + +WELPI -- 1 + 'P' 200.0 / +/ + +TSTEP -- 2 + 10 +/ + +COMPDAT -- 2 + 'P' 0 0 2 2 OPEN 1 50 / +/ + +TSTEP -- 3 + 10 +/ + +WELPI --3 + 'P' 50.0 / +/ + +TSTEP -- 4 + 10 +/ + +COMPDAT -- 4 + 'P' 10 9 2 2 OPEN 1 100 1.0 3* 'Y' / + 'P' 10 8 2 2 OPEN 1 75 1.0 3* 'Y' / + 'P' 10 7 2 2 OPEN 1 25 1.0 3* 'Y' / +/ + +TSTEP -- 5 + 10 +/ + +END +)"); + + const auto es = EclipseState{ deck }; + auto sched = Schedule{ deck, es }; + + BOOST_REQUIRE_EQUAL(sched.getTimeMap().size(), std::size_t{6}); + BOOST_REQUIRE_EQUAL(sched.getTimeMap().numTimesteps(), std::size_t{5}); + BOOST_REQUIRE_EQUAL(sched.getTimeMap().last(), std::size_t{5}); + + BOOST_REQUIRE_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::Events::WELL_PRODUCTIVITY_INDEX, 1), + "Schedule must have WELL_PRODUCTIVITY_INDEX Event at report step 1"); + + BOOST_REQUIRE_MESSAGE(sched.hasWellGroupEvent("P", ScheduleEvents::Events::WELL_PRODUCTIVITY_INDEX, 3), + "Schedule must have WELL_PRODUCTIVITY_INDEX Event at report step 3"); + + auto getScalingFactor = [&sched](const std::size_t report_step, const double wellPI) -> double + { + return sched.getWell("P", report_step).getWellPIScalingFactor(wellPI); + }; + + auto applyWellPIScaling = [&sched](const std::size_t report_step, const double scalingFactor) + { + sched.applyWellProdIndexScaling("P", report_step, scalingFactor); + }; + + auto getConnections = [&sched](const std::size_t report_step) + { + return sched.getWell("P", report_step).getConnections(); + }; + + // Apply WELPI scaling after end of time series => no change to CTFs + { + const auto report_step = std::size_t{1}; + const auto scalingFactor = getScalingFactor(report_step, 100.0*liquid_PI_unit()); + + BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10); + + applyWellPIScaling(1729, scalingFactor); + + { + const auto expectCF = 100.0*cp_rm3_per_db(); + + const auto& conns = getConnections(0); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 100.0*cp_rm3_per_db(); + + const auto& conns = getConnections(1); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 100.0*cp_rm3_per_db(); + + const auto& conns = getConnections(2); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 100.0*cp_rm3_per_db(); + + const auto& conns = getConnections(3); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto& conns = getConnections(4); + BOOST_REQUIRE_EQUAL(conns.size(), 6); + + BOOST_CHECK_CLOSE(conns[0].CF(), 100.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), 100.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[3].CF(), 100.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[4].CF(), 75.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[5].CF(), 25.0*cp_rm3_per_db(), 1.0e-10); + } + } + + // Apply WELPI scaling after first WELPI specification + { + const auto report_step = std::size_t{1}; + const auto scalingFactor = getScalingFactor(report_step, 100.0*liquid_PI_unit()); + + BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10); + + applyWellPIScaling(report_step, scalingFactor); + + { + const auto expectCF = 100.0*cp_rm3_per_db(); + + const auto& conns = getConnections(0); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 200.0*cp_rm3_per_db(); + + const auto& conns = getConnections(1); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 200.0*cp_rm3_per_db(); + + const auto& conns = getConnections(2); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 200.0*cp_rm3_per_db(); + + const auto& conns = getConnections(3); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 200.0*cp_rm3_per_db(); + + const auto& conns = getConnections(4); + BOOST_REQUIRE_EQUAL(conns.size(), 6); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[3].CF(), 100.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[4].CF(), 75.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[5].CF(), 25.0*cp_rm3_per_db(), 1.0e-10); + } + } + + // Apply WELPI scaling after second WELPI specification + { + const auto report_step = std::size_t{3}; + const auto scalingFactor = getScalingFactor(report_step, 200.0*liquid_PI_unit()); + + BOOST_CHECK_CLOSE(scalingFactor, 0.25, 1.0e-10); + + applyWellPIScaling(report_step, scalingFactor); + + { + const auto expectCF = 100.0*cp_rm3_per_db(); + + const auto& conns = getConnections(0); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 200.0*cp_rm3_per_db(); + + const auto& conns = getConnections(1); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 200.0*cp_rm3_per_db(); + + const auto& conns = getConnections(2); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 50.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 50.0*cp_rm3_per_db(); + + const auto& conns = getConnections(3); + BOOST_REQUIRE_EQUAL(conns.size(), 3); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 0.25*expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + } + + { + const auto expectCF = 50.0*cp_rm3_per_db(); + + const auto& conns = getConnections(4); + BOOST_REQUIRE_EQUAL(conns.size(), 6); + + BOOST_CHECK_CLOSE(conns[0].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[1].CF(), 0.25*expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[2].CF(), expectCF, 1.0e-10); + BOOST_CHECK_CLOSE(conns[3].CF(), 100.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[4].CF(), 75.0*cp_rm3_per_db(), 1.0e-10); + BOOST_CHECK_CLOSE(conns[5].CF(), 25.0*cp_rm3_per_db(), 1.0e-10); + } + } +} void cmp_vector(const std::vector&v1, const std::vector& v2) { BOOST_CHECK_EQUAL(v1.size(), v2.size()); @@ -3848,4 +4162,3 @@ BOOST_AUTO_TEST_CASE(VFPPROD_SCALING) { cmp_vector(gfr, vfp_table.getGFRAxis()); cmp_vector(alq, vfp_table.getALQAxis()); } - diff --git a/tests/parser/WellTests.cpp b/tests/parser/WellTests.cpp index 8ee1f505e..b7288cab7 100644 --- a/tests/parser/WellTests.cpp +++ b/tests/parser/WellTests.cpp @@ -17,9 +17,11 @@ along with OPM. If not, see . */ -#include #include +#include #include +#include +#include #define BOOST_TEST_MODULE WellTest #include @@ -1197,7 +1199,7 @@ COMPDAT END )"); - using WellPI = Well::WellProductivityIndex; + using WellPIType = Well::WellProductivityIndex; const auto es = EclipseState{ deck }; const auto sched = Schedule{ deck, es }; @@ -1211,9 +1213,16 @@ END } // 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); + { + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(2.7182818, scalingApplicable); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), expectCF, 1.0e-10); + } + + for (const bool applicable : scalingApplicable) { + BOOST_CHECK_MESSAGE(! applicable, "No connection must be eligible for WELPI scaling"); + } } // Simulate applying WELPI after seeing @@ -1223,37 +1232,125 @@ END // / // // (ignoring units of measure) - BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPI{ 2.0, Phase::GAS }), + BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPIType{ 2.0, Phase::GAS }), "First call to updateWellProductivityIndex() must be a state change"); - BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPI{ 2.0, Phase::GAS }), + BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPIType{ 2.0, Phase::GAS }), "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); + { + const auto scalingFactor = wellP.getWellPIScalingFactor(1.0); + BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10); + + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 2.0*expectCF, 1.0e-10); + } + + for (const bool applicable : scalingApplicable) { + BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling"); + } } // 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); + { + const auto scalingFactor = wellP.getWellPIScalingFactor(1.0); + BOOST_CHECK_CLOSE(scalingFactor, 2.0, 1.0e-10); + + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10); + } + + for (const bool applicable : scalingApplicable) { + BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling"); + } } // New WELPI record does not reset the scaling factors - wellP.updateWellProductivityIndex(WellPI{ 3.0, Phase::GAS }); + wellP.updateWellProductivityIndex(WellPIType{ 3.0, Phase::GAS }); 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); + { + const auto scalingFactor = wellP.getWellPIScalingFactor(3.0); + BOOST_CHECK_CLOSE(scalingFactor, 1.0, 1.0e-10); + + std::vector scalingApplicable; + wellP.applyWellProdIndexScaling(scalingFactor, scalingApplicable); + for (const auto& conn : wellP.getConnections()) { + BOOST_CHECK_CLOSE(conn.CF(), 4.0*expectCF, 1.0e-10); + } + + for (const bool applicable : scalingApplicable) { + BOOST_CHECK_MESSAGE(applicable, "All connections must be eligible for WELPI scaling"); + } } - BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPI{ 3.0, Phase::OIL }), + BOOST_CHECK_MESSAGE(wellP.updateWellProductivityIndex(WellPIType{ 3.0, Phase::OIL }), "Fourth call to updateWellProductivityIndex() must be a state change"); - BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPI{ 3.0, Phase::OIL }), + BOOST_CHECK_MESSAGE(!wellP.updateWellProductivityIndex(WellPIType{ 3.0, Phase::OIL }), "Fifth call to updateWellProductivityIndex() must NOT be a state change"); } + +BOOST_AUTO_TEST_CASE(Has_Same_Connections_Pointers) { + 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 wellP = sched.getWell("P", 0); + auto wellQ = wellP; + + BOOST_CHECK_MESSAGE(wellP.hasSameConnectionsPointers(wellQ), + "P and Q must have the same internal connections pointers"); + + auto connQ = std::make_shared(wellP.getConnections()); + wellQ.forceUpdateConnections(std::move(connQ)); + BOOST_CHECK_MESSAGE(! wellP.hasSameConnectionsPointers(wellQ), + "P and Q must NOT have the same internal connections pointers " + "after forcibly updating the connections structure"); + + BOOST_CHECK_MESSAGE(wellP.getConnections() == wellQ.getConnections(), + "P and Q must have same WellConnections VALUE"); +}