From 38757367953809e32362753d1f24e54bbe03dbec Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Thu, 2 Nov 2023 14:41:14 +0100 Subject: [PATCH] implement support for FBHPDEF --- opm/input/eclipse/Schedule/Schedule.hpp | 2 + opm/input/eclipse/Schedule/ScheduleState.hpp | 25 +++++ opm/input/eclipse/Schedule/Well/Well.hpp | 50 +++++++++- .../eclipse/Schedule/KeywordHandlers.cpp | 80 +++++++++++++-- src/opm/input/eclipse/Schedule/Schedule.cpp | 1 + .../input/eclipse/Schedule/ScheduleState.cpp | 4 + src/opm/input/eclipse/Schedule/Well/Well.cpp | 5 +- .../Schedule/Well/WellInjectionProperties.cpp | 15 ++- .../Well/WellProductionProperties.cpp | 27 +++-- .../share/keywords/000_Eclipse100/F/FBHPDEF | 6 +- tests/parser/WellTests.cpp | 98 ++++++++++++++++++- 11 files changed, 286 insertions(+), 27 deletions(-) diff --git a/opm/input/eclipse/Schedule/Schedule.hpp b/opm/input/eclipse/Schedule/Schedule.hpp index df3dae2fd..269b2d414 100644 --- a/opm/input/eclipse/Schedule/Schedule.hpp +++ b/opm/input/eclipse/Schedule/Schedule.hpp @@ -367,6 +367,7 @@ namespace Opm this->template pack_unpack(serializer); this->template pack_unpack(serializer); this->template pack_unpack(serializer); + this->template pack_unpack(serializer); this->template pack_unpack_map(serializer); this->template pack_unpack_map(serializer); @@ -712,6 +713,7 @@ namespace Opm void handleDRVDT (HandlerContext&); void handleDRVDTR (HandlerContext&); void handleEXIT (HandlerContext&); + void handleFBHPDEF (HandlerContext&); void handleGCONINJE (HandlerContext&); void handleGCONPROD (HandlerContext&); void handleGCONSALE (HandlerContext&); diff --git a/opm/input/eclipse/Schedule/ScheduleState.hpp b/opm/input/eclipse/Schedule/ScheduleState.hpp index ee92072d4..cbb765dc0 100644 --- a/opm/input/eclipse/Schedule/ScheduleState.hpp +++ b/opm/input/eclipse/Schedule/ScheduleState.hpp @@ -279,7 +279,28 @@ namespace Opm { std::unordered_map> m_data; }; + struct BHPDefaults { + std::optional prod_target; + std::optional inj_limit; + static BHPDefaults serializationTestObject() + { + return BHPDefaults{1.0, 2.0}; + } + + bool operator==(const BHPDefaults& rhs) const + { + return this->prod_target == rhs.prod_target + && this->inj_limit == rhs.inj_limit; + } + + template + void serializeOp(Serializer& serializer) + { + serializer(prod_target); + serializer(inj_limit); + } + }; ScheduleState() = default; explicit ScheduleState(const time_point& start_time); @@ -382,6 +403,8 @@ namespace Opm { ptr_member rft_config; ptr_member rst_config; + ptr_member bhp_defaults; + template ptr_member& get() { return const_cast&>(std::as_const(*this).template get()); @@ -428,6 +451,8 @@ namespace Opm { return this->rft_config; else if constexpr ( std::is_same_v ) return this->rst_config; + else if constexpr ( std::is_same_v ) + return this->bhp_defaults; else static_assert(always_false1::value, "Template type not supported in get()"); } diff --git a/opm/input/eclipse/Schedule/Well/Well.hpp b/opm/input/eclipse/Schedule/Well/Well.hpp index d20b51376..4aca23351 100644 --- a/opm/input/eclipse/Schedule/Well/Well.hpp +++ b/opm/input/eclipse/Schedule/Well/Well.hpp @@ -168,8 +168,29 @@ public: static WellInjectionProperties serializationTestObject(); void handleWELTARG(WELTARGCMode cmode, const UDAValue& new_arg, double SIFactorP); - void handleWCONINJE(const DeckRecord& record, bool availableForGroupControl, const std::string& well_name); - void handleWCONINJH(const DeckRecord& record, const bool is_producer, const std::string& well_name, const KeywordLocation& loc); + + //! \brief Handle a WCONINJE keyword. + //! \param record The deck record to use + //! \param bhp_def The default BHP target in input units + //! \param availableForGroupControl True if available for group control + //! \param well_name Name of well + void handleWCONINJE(const DeckRecord& record, + const double bhp_def, + bool availableForGroupControl, + const std::string& well_name); + + //! \brief Handle a WCONINJH keyword. + //! \param record The deck record to use + //! \param bhp_def The default BHP limit in SI units + //! \param is_producer True if well is a producer + //! \param well_name Name of well + //! \param loc Location of keyword for logging purpuses + void handleWCONINJH(const DeckRecord& record, + const double bhp_def, + const bool is_producer, + const std::string& well_name, + const KeywordLocation& loc); + bool hasInjectionControl(InjectorCMode controlModeArg) const { if (injectionControls & static_cast(controlModeArg)) return true; @@ -240,6 +261,7 @@ public: // BHP and THP limit double bhp_hist_limit = 0.0; double thp_hist_limit = 0.0; + bool bhp_hist_limit_defaulted = true; // Tracks whether value was defaulted or not // historical BHP and THP under historical mode double BHPH = 0.0; @@ -273,8 +295,28 @@ public: // this is used to check whether the specified control mode is an effective history matching production mode static bool effectiveHistoryProductionControl(ProducerCMode cmode); - void handleWCONPROD( const std::optional& alq_type, const UnitSystem& unit_system, const std::string& well, const DeckRecord& record); - void handleWCONHIST( const std::optional& alq_type, const UnitSystem& unit_system, const DeckRecord& record); + + //! \brief Handle WCONPROD keyword. + //! \param alq_type ALQ type + //! \param bhp_def Default BHP target in SI units + //! \param unit_system Unit system to use + //! \param well Well name + //! \param record Deck record to use + void handleWCONPROD(const std::optional& alq_type, + const double bhp_def, + const UnitSystem& unit_system, + const std::string& well, + const DeckRecord& record); + + //! \brief Handle WCONHIST keyword. + //! \param alq_type ALQ type + //! \param bhp_def Default BHP limit in SI units + //! \param unit_system Unit system to use + //! \param record Deck record to use + void handleWCONHIST(const std::optional& alq_type, + const double bhp_def, + const UnitSystem& unit_system, + const DeckRecord& record); void handleWELTARG( WELTARGCMode cmode, const UDAValue& new_arg, double SiFactorP); void resetDefaultBHPLimit(); void clearControls(); diff --git a/src/opm/input/eclipse/Schedule/KeywordHandlers.cpp b/src/opm/input/eclipse/Schedule/KeywordHandlers.cpp index b0109e021..5da0cc761 100644 --- a/src/opm/input/eclipse/Schedule/KeywordHandlers.cpp +++ b/src/opm/input/eclipse/Schedule/KeywordHandlers.cpp @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include @@ -465,6 +466,20 @@ File {} line {}.)", wname, location.keyword, location.filename, location.lineno) this->applyEXIT(handlerContext.keyword, handlerContext.currentStep); } + void Schedule::handleFBHPDEF(HandlerContext& handlerContext) + { + using FBHP = ParserKeywords::FBHPDEF; + const auto& record = handlerContext.keyword.getRecord(0); + ScheduleState::BHPDefaults bhp_defaults; + const auto& prod_limit = record.getItem(); + const auto& inj_limit = record.getItem(); + if (!(prod_limit.defaultApplied(0) && inj_limit.defaultApplied(0))) { + bhp_defaults.prod_target = prod_limit.getSIDouble(0); + bhp_defaults.inj_limit = inj_limit.getSIDouble(0); + } + this->snapshots.back().bhp_defaults.update(std::move(bhp_defaults)); + } + void Schedule::handleGCONINJE(HandlerContext& handlerContext) { using GI = ParserKeywords::GCONINJE; auto current_step = handlerContext.currentStep; @@ -1300,10 +1315,22 @@ File {} line {}.)", wname, location.keyword, location.filename, location.lineno) throw OpmInputError(reason, handlerContext.keyword.location()); } } - properties->handleWCONHIST(alq_type, this->m_static.m_unit_system, record); + double default_bhp; + if (this->snapshots.back().bhp_defaults.get().prod_target) { + default_bhp = *this->snapshots.back().bhp_defaults.get().prod_target; + } else { + default_bhp = UnitSystem::newMETRIC().to_si(UnitSystem::measure::pressure, + ParserKeywords::FBHPDEF::TARGET_BHP::defaultValue); + } + + properties->handleWCONHIST(alq_type, + default_bhp, + this->m_static.m_unit_system, record); if (switching_from_injector) { - properties->resetDefaultBHPLimit(); + if (properties->bhp_hist_limit_defaulted) { + properties->setBHPLimit(default_bhp); + } auto inj_props = std::make_shared(well2.getInjectionProperties()); inj_props->resetBHPLimit(); @@ -1375,10 +1402,23 @@ File {} line {}.)", wname, location.keyword, location.filename, location.lineno) throw OpmInputError(reason, handlerContext.keyword.location()); } } - properties->handleWCONPROD(alq_type, this->m_static.m_unit_system, well_name, record); + + double default_bhp_target; + if (this->snapshots.back().bhp_defaults.get().prod_target) { + default_bhp_target = *this->snapshots.back().bhp_defaults.get().prod_target; + } else { + default_bhp_target = UnitSystem::newMETRIC().to_si(UnitSystem::measure::pressure, + ParserKeywords::WCONPROD::BHP::defaultValue.get()); + } + + properties->handleWCONPROD(alq_type, default_bhp_target, + this->m_static.m_unit_system, + well_name, record); if (switching_from_injector) { - properties->resetDefaultBHPLimit(); + if (properties->bhp_hist_limit_defaulted) { + properties->setBHPLimit(default_bhp_target); + } update_well = true; this->snapshots.back().wellgroup_events().addEvent( well2.name(), ScheduleEvents::WELL_SWITCHED_INJECTOR_PRODUCER); } @@ -1428,7 +1468,21 @@ File {} line {}.)", wname, location.keyword, location.filename, location.lineno) auto injection = std::make_shared(well2.getInjectionProperties()); auto previousInjectorType = injection->injectorType; - injection->handleWCONINJE(record, well2.isAvailableForGroupControl(), well_name); + + double default_bhp_limit; + if (this->snapshots.back().bhp_defaults.get().inj_limit) { + default_bhp_limit = this->m_static.m_unit_system.from_si(UnitSystem::measure::pressure, + *this->snapshots.back().bhp_defaults.get().inj_limit); + } else { + default_bhp_limit = UnitSystem::newMETRIC().to_si(UnitSystem::measure::pressure, + ParserKeywords::WCONINJE::BHP::defaultValue.get()); + default_bhp_limit = this->m_static.m_unit_system.from_si(UnitSystem::measure::pressure, + default_bhp_limit); + } + + injection->handleWCONINJE(record, default_bhp_limit, + well2.isAvailableForGroupControl(), well_name); + const bool switching_from_producer = well2.isProducer(); if (well2.updateInjection(injection)) update_well = true; @@ -1497,9 +1551,20 @@ File {} line {}.)", wname, location.keyword, location.filename, location.lineno) auto well2 = this->snapshots.back().wells.get( well_name ); auto injection = std::make_shared(well2.getInjectionProperties()); auto previousInjectorType = injection->injectorType; - injection->handleWCONINJH(record, well2.isProducer(), well_name, handlerContext.keyword.location()); - const bool switching_from_producer = well2.isProducer(); + double default_bhp_limit; + if (this->snapshots.back().bhp_defaults.get().inj_limit) { + default_bhp_limit = *this->snapshots.back().bhp_defaults.get().inj_limit; + } else { + default_bhp_limit = UnitSystem::newMETRIC().to_si(UnitSystem::measure::pressure, + 6891.2); + } + + injection->handleWCONINJH(record, default_bhp_limit, + well2.isProducer(), well_name, + handlerContext.keyword.location()); + + const bool switching_from_producer = well2.isProducer(); if (well2.updateInjection(injection)) update_well = true; @@ -2789,6 +2854,7 @@ Well{0} entered with 'FIELD' parent group: { "DRVDTR" , &Schedule::handleDRVDTR }, { "ENDBOX" , &Schedule::handleGEOKeyword}, { "EXIT", &Schedule::handleEXIT }, + { "FBHPDEF", &Schedule::handleFBHPDEF }, { "GCONINJE", &Schedule::handleGCONINJE }, { "GCONPROD", &Schedule::handleGCONPROD }, { "GCONSALE", &Schedule::handleGCONSALE }, diff --git a/src/opm/input/eclipse/Schedule/Schedule.cpp b/src/opm/input/eclipse/Schedule/Schedule.cpp index ee254d32a..1a7447651 100644 --- a/src/opm/input/eclipse/Schedule/Schedule.cpp +++ b/src/opm/input/eclipse/Schedule/Schedule.cpp @@ -2427,6 +2427,7 @@ void Schedule::create_first(const time_point& start_time, const std::optionalm_static.sumthin); sched_state.rptonly(this->m_static.rptonly); + sched_state.bhp_defaults.update( ScheduleState::BHPDefaults() ); //sched_state.update_date( start_time ); this->addGroup("FIELD", 0); } diff --git a/src/opm/input/eclipse/Schedule/ScheduleState.cpp b/src/opm/input/eclipse/Schedule/ScheduleState.cpp index db4f98f0b..03f8b38b3 100644 --- a/src/opm/input/eclipse/Schedule/ScheduleState.cpp +++ b/src/opm/input/eclipse/Schedule/ScheduleState.cpp @@ -19,6 +19,8 @@ #include +#include + #include #include #include @@ -307,6 +309,7 @@ bool ScheduleState::operator==(const ScheduleState& other) const { this->guide_rate.get() == other.guide_rate.get() && this->rft_config.get() == other.rft_config.get() && this->udq.get() == other.udq.get() && + this->bhp_defaults.get() == other.bhp_defaults.get() && this->wells == other.wells && this->groups == other.groups && this->vfpprod == other.vfpprod && @@ -338,6 +341,7 @@ ScheduleState ScheduleState::serializationTestObject() { ts.m_sumthin = 12.345; ts.m_rptonly = true; + ts.bhp_defaults.update( BHPDefaults::serializationTestObject() ); ts.pavg.update( PAvg::serializationTestObject() ); ts.wtest_config.update( WellTestConfig::serializationTestObject() ); ts.gconsump.update( GConSump::serializationTestObject() ); diff --git a/src/opm/input/eclipse/Schedule/Well/Well.cpp b/src/opm/input/eclipse/Schedule/Well/Well.cpp index d76c7e42f..2db6b0500 100644 --- a/src/opm/input/eclipse/Schedule/Well/Well.cpp +++ b/src/opm/input/eclipse/Schedule/Well/Well.cpp @@ -427,8 +427,11 @@ Well::Well(const RestartIO::RstWell& rst_well, if (! i->predictionMode) { if (i->controlMode == Well::InjectorCMode::BHP) i->bhp_hist_limit = rst_well.hist_bhp_target; - else + else { + // defaults (ie FBHPDEF) are not stored in the restart file. + // we thus use an empirically obtained default value. i->resetDefaultHistoricalBHPLimit(); + } } else if (this->isAvailableForGroupControl()) i->addInjectionControl(Well::InjectorCMode::GRUP); diff --git a/src/opm/input/eclipse/Schedule/Well/WellInjectionProperties.cpp b/src/opm/input/eclipse/Schedule/Well/WellInjectionProperties.cpp index 631244de9..c995b36a8 100644 --- a/src/opm/input/eclipse/Schedule/Well/WellInjectionProperties.cpp +++ b/src/opm/input/eclipse/Schedule/Well/WellInjectionProperties.cpp @@ -85,7 +85,11 @@ namespace Opm { return result; } - void Well::WellInjectionProperties::handleWCONINJE(const DeckRecord& record, bool availableForGroupControl, const std::string& well_name) { + void Well::WellInjectionProperties::handleWCONINJE(const DeckRecord& record, + const double bhp_def, + bool availableForGroupControl, + const std::string& well_name) + { this->injectorType = InjectorTypeFromString( record.getItem("TYPE").getTrimmedString(0) ); this->predictionMode = true; @@ -118,7 +122,11 @@ namespace Opm { current behavoir agrees with the behavior of Eclipse when BHPLimit is not specified while employed during group control. */ - this->BHPTarget = record.getItem("BHP").get(0); + if (record.getItem("BHP").defaultApplied(0)) { + this->BHPTarget.update(bhp_def); + } else { + this->BHPTarget = record.getItem("BHP").get(0); + } this->addInjectionControl(InjectorCMode::BHP); if (availableForGroupControl) @@ -182,6 +190,7 @@ namespace Opm { void Well::WellInjectionProperties::handleWCONINJH(const DeckRecord& record, + const double bhp_def, const bool is_producer, const std::string& well_name, const KeywordLocation& loc) @@ -232,7 +241,7 @@ namespace Opm { if (switching_from_prediction || switching_from_BHP_control || switching_from_producer) { - this->resetDefaultHistoricalBHPLimit(); + this->bhp_hist_limit = bhp_def; } // otherwise, we keep its previous BHP limit } diff --git a/src/opm/input/eclipse/Schedule/Well/WellProductionProperties.cpp b/src/opm/input/eclipse/Schedule/Well/WellProductionProperties.cpp index 7ffe4c1c5..58206d061 100644 --- a/src/opm/input/eclipse/Schedule/Well/WellProductionProperties.cpp +++ b/src/opm/input/eclipse/Schedule/Well/WellProductionProperties.cpp @@ -160,13 +160,22 @@ namespace Opm { - void Well::WellProductionProperties::handleWCONPROD(const std::optional& alq_type, const UnitSystem& unit_system_arg, const std::string& /* well */, const DeckRecord& record) + void Well::WellProductionProperties::handleWCONPROD(const std::optional& alq_type, + const double bhp_def, + const UnitSystem& unit_system_arg, + const std::string& /* well */, + const DeckRecord& record) { this->predictionMode = true; this->init_vfp(alq_type, unit_system_arg, record); this->init_rates(record); - this->BHPTarget = record.getItem("BHP").get(0); + if (record.getItem("BHP").defaultApplied(0)) { + this->BHPTarget.update(unit_system_arg.from_si(UnitSystem::measure::pressure, + bhp_def)); + } else { + this->BHPTarget = record.getItem("BHP").get(0); + } this->THPTarget = record.getItem("THP").get(0); this->LiquidRate = record.getItem("LRAT").get(0); this->ResVRate = record.getItem("RESV").get(0); @@ -209,7 +218,10 @@ namespace Opm { originate from the WCONHIST keyword. Predictions are handled with the default constructor and the handleWCONPROD() method. */ -void Well::WellProductionProperties::handleWCONHIST(const std::optional& alq_type, const UnitSystem& unit_system_arg, const DeckRecord& record) +void Well::WellProductionProperties::handleWCONHIST(const std::optional& alq_type, + const double bhp_def, + const UnitSystem& unit_system_arg, + const DeckRecord& record) { this->init_rates(record); this->init_vfp(alq_type, unit_system_arg, record); @@ -220,11 +232,9 @@ void Well::WellProductionProperties::handleWCONHIST(const std::optionalpredictionMode) - this->resetDefaultBHPLimit(); - - if (this->controlMode == ProducerCMode::BHP) - this->resetDefaultBHPLimit(); + if (this->predictionMode || this->controlMode == ProducerCMode::BHP) { + this->setBHPLimit(bhp_def); + } this->init_history(record); } @@ -258,6 +268,7 @@ void Well::WellProductionProperties::handleWCONHIST(const std::optionalbhp_hist_limit = new_arg.get() * SiFactorP; this->addProductionControl( ProducerCMode::BHP ); + this->bhp_hist_limit_defaulted = false; } else if (cmode == WELTARGCMode::THP){ this->THPTarget.update_value( new_arg ); diff --git a/src/opm/input/eclipse/share/keywords/000_Eclipse100/F/FBHPDEF b/src/opm/input/eclipse/share/keywords/000_Eclipse100/F/FBHPDEF index 94479f592..073353490 100644 --- a/src/opm/input/eclipse/share/keywords/000_Eclipse100/F/FBHPDEF +++ b/src/opm/input/eclipse/share/keywords/000_Eclipse100/F/FBHPDEF @@ -8,12 +8,14 @@ { "name": "TARGET_BHP", "value_type": "DOUBLE", - "dimension": "Length" + "dimension": "Pressure", + "default": 1.01325 }, { "name": "LIMIT_BHP", "value_type": "DOUBLE", - "dimension": "Pressure" + "dimension": "Pressure", + "default": 6895 } ] } diff --git a/tests/parser/WellTests.cpp b/tests/parser/WellTests.cpp index 3e7deedf4..e37f847a0 100644 --- a/tests/parser/WellTests.cpp +++ b/tests/parser/WellTests.cpp @@ -47,6 +47,8 @@ #include #include +#include + using namespace Opm; namespace { @@ -589,7 +591,9 @@ namespace { auto deck = parser.parseString(input); const auto& record = deck["WCONHIST"].back().getRecord(0); Opm::Well::WellProductionProperties hist(unit_system, "W"); - hist.handleWCONHIST(alq_type, unit_system, record); + hist.handleWCONHIST(alq_type, + Opm::ParserKeywords::FBHPDEF::TARGET_BHP::defaultValue * unit::barsa, + unit_system, record); return hist; @@ -642,7 +646,9 @@ namespace { const auto& kwd = deck["WCONPROD"].back(); const auto& record = kwd.getRecord(0); Opm::Well::WellProductionProperties pred(unit_system, "W"); - pred.handleWCONPROD(alq_type, unit_system, "WELL", record); + pred.handleWCONPROD(alq_type, + Opm::ParserKeywords::FBHPDEF::TARGET_BHP::defaultValue * unit::barsa, + unit_system, "WELL", record); return pred; } @@ -1870,3 +1876,91 @@ END } } } + +BOOST_AUTO_TEST_CASE(FBHPDEF_Basic) +{ + const auto deck = Parser{}.parseString(R"(RUNSPEC +DIMENS +10 10 3 / +GRID +DXV +10*100.0 / +DYV +10*100.0 / +DZV +3*5.0 / +DEPTHZ +121*2000 / +PERMX +300*100.0 / +COPY +PERMX PERMY / +PERMX PERMZ / +/ +MULTIPLY +PERMZ 0.1 / +/ +PORO +300*0.3 / +SCHEDULE + +WELSPECS + 'P' 'G' 10 10 1* 'OIL' / + 'I' 'G' 1 1 1* 'GAS' / + 'I2' 'W' 1 1 1* 'WATER' / + 'I3' 'W' 1 1 1* 'WATER' / +/ +COMPDAT + 'P' 10 10 1 3 'OPEN' / + 'I' 1 1 1 1 'OPEN' / + 'I2' 1 1 1 1 'OPEN' / + 'I3' 1 1 1 1 'OPEN' / +/ + +WCONINJH + I3 WATER OPEN 116281 1* 0 / +/ + +FBHPDEF + 5.0 20.0 / + +WCONPROD + 'P' 'OPEN' 'LRAT' 1* 1* 1* 1234.567 1* 1* / +/ + +WCONINJH + I2 WATER OPEN 116281 1* 0 / +/ + +FBHPDEF + 2.0 30.0 / + +WCONINJE + 'I' 'GAS' 'OPEN' 'RATE' 20.0E3 / +/ + +TSTEP +30.0 / +WELSPECS + 'P' 'G1' / +/ +TSTEP + 30.0 / +END +)"); + + const auto es = EclipseState { deck }; + const auto sched = Schedule { deck, es }; + + const auto& wellP = sched.getWell("P", 0); + BOOST_CHECK_EQUAL(wellP.getProductionProperties().BHPTarget.get(), 5.0); + + const auto& wellI = sched.getWell("I", 0); + BOOST_CHECK_CLOSE(wellI.getInjectionProperties().BHPTarget.get(), 30.0, 1e-12); + + const auto& wellI2 = sched.getWell("I2", 0); + BOOST_CHECK_EQUAL(wellI2.getInjectionProperties().bhp_hist_limit, 20.0 * unit::barsa); + + const auto& wellI3 = sched.getWell("I3", 0); + BOOST_CHECK_CLOSE(wellI3.getInjectionProperties().bhp_hist_limit, 6891.2 * unit::barsa, 1e-12); +}