diff --git a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp index 5d0dfe712..2f85a1df2 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp @@ -652,6 +652,7 @@ namespace Opm void handleWSOLVENT (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWTEMP (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWTEST (const HandlerContext&, const ParseContext&, ErrorGuard&); + void handleWTMULT (const HandlerContext&, const ParseContext&, ErrorGuard&); void handleWTRACER (const HandlerContext&, const ParseContext&, ErrorGuard&); }; } diff --git a/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp b/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp index aa7470256..673f6640a 100644 --- a/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp +++ b/opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp @@ -296,6 +296,7 @@ public: InjectionControls controls(const UnitSystem& unit_system, const SummaryState& st, double udq_default) const; bool updateUDQActive(const UDQConfig& udq_config, UDQActive& active) const; void update_uda(const UDQConfig& udq_config, UDQActive& udq_active, UDAControl control, const UDAValue& value); + void handleWTMULT(Well::WELTARGCMode cmode, double factor); template void serializeOp(Serializer& serializer) @@ -426,6 +427,7 @@ public: void setBHPLimit(const double limit); int productionControls() const { return this->m_productionControls; } + void handleWTMULT(Well::WELTARGCMode cmode, double factor); 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 3a0e61653..f62fd69ab 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/KeywordHandlers.cpp @@ -2009,6 +2009,82 @@ namespace { } +/* + The WTMULT keyword can optionally use UDA values in three different ways: + + 1. The target can be UDA - instead of the standard strings "ORAT", "GRAT", + "WRAT", ..., the keyword can be configured with a UDA which is evaluated to + an integer and then mapped to one of the common controls. + + 2. The scaling factor itself can be a UDA. + + 3. The target we aim to scale might already be specified as a UDA. + + The current implementation does not support UDA usage in any part of WTMULT + codepath. +*/ + + void Schedule::handleWTMULT(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) { + for (const auto& record : handlerContext.keyword) { + const auto& wellNamePattern = record.getItem().getTrimmedString(0); + const auto& control = record.getItem().get(0); + const auto& factor = record.getItem().get(0); + const auto& num = record.getItem().get(0); + + if (factor.is()) { + std::string reason = fmt::format("Use of UDA value: {} is not supported as multiplier", factor.get()); + throw OpmInputError(reason, handlerContext.keyword.location()); + } + + if (this->snapshots.back().udq().has_keyword(control)) { + std::string reason = fmt::format("Use of UDA value: {} is not supported for control target", control); + throw OpmInputError(reason, handlerContext.keyword.location()); + } + + if (num != 1) { + std::string reason = fmt::format("Only NUM=1 is supported in WTMULT keyword"); + throw OpmInputError(reason, handlerContext.keyword.location()); + } + + const auto cmode = Well::WELTARGCModeFromString(control); + if (cmode == Well::WELTARGCMode::GUID) + throw std::logic_error("Multiplying guide rate is not implemented"); + + const auto well_names = this->wellNames(wellNamePattern, handlerContext.currentStep, handlerContext.matching_wells); + if (well_names.empty()) + invalidNamePattern(wellNamePattern, handlerContext.currentStep, parseContext, errors, handlerContext.keyword); + + for (const auto& well_name : well_names) { + auto well = this->snapshots.back().wells.get(well_name); + if (well.isInjector()) { + bool update_well = true; + auto properties = std::make_shared(well.getInjectionProperties()); + properties->handleWTMULT( cmode, factor.get()); + + well.updateInjection(properties); + if (update_well) { + this->snapshots.back().events().addEvent(ScheduleEvents::INJECTION_UPDATE); + this->snapshots.back().wellgroup_events().addEvent(well_name, ScheduleEvents::INJECTION_UPDATE); + this->snapshots.back().wells.update(std::move(well)); + } + } else { + bool update_well = true; + auto properties = std::make_shared(well.getProductionProperties()); + properties->handleWTMULT( cmode, factor.get()); + + well.updateProduction(properties); + if (update_well) { + this->snapshots.back().events().addEvent(ScheduleEvents::PRODUCTION_UPDATE); + this->snapshots.back().wellgroup_events().addEvent(well_name, + ScheduleEvents::PRODUCTION_UPDATE); + this->snapshots.back().wells.update(std::move(well)); + } + } + } + } + } + + bool Schedule::handleNormalKeyword(const HandlerContext& handlerContext, const ParseContext& parseContext, ErrorGuard& errors) { using handler_function = void (Schedule::*)(const HandlerContext&, const ParseContext&, ErrorGuard&); static const std::unordered_map handler_functions = { @@ -2100,6 +2176,7 @@ namespace { { "WSOLVENT", &Schedule::handleWSOLVENT }, { "WTEMP" , &Schedule::handleWTEMP }, { "WTEST" , &Schedule::handleWTEST }, + { "WTMULT" , &Schedule::handleWTMULT }, { "WTRACER" , &Schedule::handleWTRACER }, }; diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellInjectionProperties.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellInjectionProperties.cpp index ac8feb27f..91d8541bb 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellInjectionProperties.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellInjectionProperties.cpp @@ -339,5 +339,39 @@ namespace Opm { } } + void Well::WellInjectionProperties::handleWTMULT(Well::WELTARGCMode cmode, double factor) { + if (cmode == Well::WELTARGCMode::BHP) + this->BHPTarget *= factor; + + else if (cmode == WELTARGCMode::ORAT) { + if (this->injectorType == InjectorType::OIL) + this->surfaceInjectionRate *= factor; + else + std::invalid_argument("Well type must be OIL to scale the oil rate"); + } + + else if (cmode == WELTARGCMode::WRAT) { + if (this->injectorType == InjectorType::WATER) + this->surfaceInjectionRate *= factor; + else + std::invalid_argument("Well type must be WATER to scale the water rate"); + } + + else if (cmode == WELTARGCMode::GRAT) { + if(this->injectorType == InjectorType::GAS) + this->surfaceInjectionRate *= factor; + else + std::invalid_argument("Well type must be GAS to scale the gas rate"); + } + + else if (cmode == WELTARGCMode::THP) + this->THPTarget *= factor; + + else if (cmode == WELTARGCMode::RESV) + this->reservoirInjectionRate*= factor; + + else throw std::invalid_argument("Invalid keyword (MODE) supplied"); + } + } diff --git a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellProductionProperties.cpp b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellProductionProperties.cpp index d6fc7dfec..a4a9a590f 100644 --- a/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellProductionProperties.cpp +++ b/src/opm/parser/eclipse/EclipseState/Schedule/Well/WellProductionProperties.cpp @@ -17,6 +17,7 @@ along with OPM. If not, see . */ +#include #include #include #include @@ -270,6 +271,38 @@ void Well::WellProductionProperties::handleWCONHIST(const std::optionalOilRate *= factor; + break; + case Well::WELTARGCMode::GRAT: + this->GasRate *= factor; + break; + case Well::WELTARGCMode::WRAT: + this->WaterRate *= factor; + break; + case Well::WELTARGCMode::LRAT: + this->LiquidRate *= factor; + break; + case Well::WELTARGCMode::RESV: + this->ResVRate *= factor; + break; + case Well::WELTARGCMode::BHP: + this->BHPTarget *= factor; + break; + case Well::WELTARGCMode::THP: + this->THPTarget *= factor; + break; + case Well::WELTARGCMode::LIFT: + this->ALQValue *= factor; + break; + default: + throw std::logic_error("Unhandled WTMULT control"); + } + } + + bool Well::WellProductionProperties::operator==(const Well::WellProductionProperties& other) const { return OilRate == other.OilRate diff --git a/tests/parser/ScheduleTests.cpp b/tests/parser/ScheduleTests.cpp index 677c649cd..0a1af82d9 100644 --- a/tests/parser/ScheduleTests.cpp +++ b/tests/parser/ScheduleTests.cpp @@ -856,15 +856,24 @@ DATES -- 1 / WELSPECS 'OP_1' 'OP' 9 9 1* 'OIL' 1* 1* 1* 1* 1* 1* 1* / + 'I1' 'I' 5 5 2522.5 'WATER' / / COMPDAT 'OP_1' 9 9 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / 'OP_1' 9 9 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 / - 'OP_1' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / + 'OP_1' 9 9 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / + 'I1' 8 8 1 1 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / + 'I1' 8 8 2 2 'OPEN' 1* 46.825 0.311 4332.346 1* 1* 'X' 22.123 / + 'I1' 8 8 3 9 'OPEN' 1* 32.948 0.311 3047.839 1* 1* 'X' 22.100 / / + WCONPROD 'OP_1' 'OPEN' 'ORAT' 0.000 0.000 0.000 5* / / + +WCONINJE + 'I1' 'WATER' 'OPEN' 'RATE' 200 1* 450.0 / +/ DATES -- 2 20 JAN 2010 / / @@ -878,6 +887,20 @@ WELTARG OP_1 THP 2000 / OP_1 GUID 2300.14 / / + +DATES + 1 FEB 2010 / +/ + +WTMULT +OP_1 ORAT 2 / +OP_1 GRAT 3 / +OP_1 WRAT 4 / +I1 WRAT 2 / +I1 BHP 3 / +I1 THP 4 / +/ + )"; const auto& schedule = make_schedule(input); @@ -910,8 +933,25 @@ WELTARG BOOST_CHECK (wpp_2.hasProductionControl( Opm::Well::ProducerCMode::ORAT) ); BOOST_CHECK (wpp_2.hasProductionControl( Opm::Well::ProducerCMode::RESV) ); + + const auto& well_3 = schedule.getWell("OP_1", 3); + const auto wpp_3 = well_3.getProductionProperties(); + const auto prod_controls3 = wpp_3.controls(st, 0); + + BOOST_CHECK_EQUAL(prod_controls3.oil_rate, 2 * 1300 * siFactorL); + BOOST_CHECK_EQUAL(prod_controls3.water_rate, 4 * 1400 * siFactorL); + BOOST_CHECK_EQUAL(prod_controls3.gas_rate, 3 * 1500.52 * siFactorG); + + + const auto& inj_controls2 = schedule.getWell("I1", 2).getInjectionProperties().controls(unitSystem, st, 0); + const auto& inj_controls3 = schedule.getWell("I1", 3).getInjectionProperties().controls(unitSystem, st, 0); + + BOOST_CHECK_EQUAL(inj_controls2.surface_rate * 2, inj_controls3.surface_rate); + BOOST_CHECK_EQUAL(inj_controls2.bhp_limit * 3, inj_controls3.bhp_limit); + BOOST_CHECK_EQUAL(inj_controls2.thp_limit * 4, inj_controls3.thp_limit); } + BOOST_AUTO_TEST_CASE(createDeckWithWeltArg_UDA) { std::string input = R"( START -- 0