From 6cf86b3884d4e3fc73ed5604a82d10aacf893ec6 Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Wed, 19 Nov 2025 10:12:21 +0100 Subject: [PATCH 1/8] Fixes for gliftopt item 3 maximum gas+alq --- .../wells/BlackoilWellModelGasLift_impl.hpp | 10 ---------- opm/simulators/wells/GasLiftGroupInfo.cpp | 17 ++++++++++++++--- .../wells/GasLiftSingleWellGeneric.cpp | 15 +++++++++++++-- .../wells/GasLiftSingleWellGeneric.hpp | 3 ++- opm/simulators/wells/GasLiftStage2.cpp | 11 +++++++++-- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp index b254919ee33..5263fda3d8f 100644 --- a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp @@ -248,16 +248,6 @@ gasLiftOptimizationStage1(const Simulator& simulator, group_alq_rates[j]); } } - if constexpr (glift_debug) { - int counter = 0; - if (comm.rank() == i) { - counter = wellState.gliftGetDebugCounter(); - } - counter = comm.sum(counter); - if (comm.rank() != i) { - wellState.gliftSetDebugCounter(counter); - } - } } } } diff --git a/opm/simulators/wells/GasLiftGroupInfo.cpp b/opm/simulators/wells/GasLiftGroupInfo.cpp index 30385ce8304..93f99cebc97 100644 --- a/opm/simulators/wells/GasLiftGroupInfo.cpp +++ b/opm/simulators/wells/GasLiftGroupInfo.cpp @@ -549,15 +549,19 @@ getProducerWellRates_(const Well* well, int well_index) if (controls.hasControl(Well::ProducerCMode::ORAT) && oil_rate > static_cast(controls.oil_rate)) { water_rate *= (static_cast(controls.oil_rate) / oil_rate); + gas_rate *= (static_cast(controls.oil_rate) / oil_rate); oil_rate = static_cast(controls.oil_rate); } - if (controls.hasControl(Well::ProducerCMode::GRAT)) { - gas_rate = std::min(static_cast(controls.gas_rate), gas_rate); + if (controls.hasControl(Well::ProducerCMode::GRAT) && gas_rate > static_cast(controls.gas_rate)) { + water_rate *= (static_cast(controls.gas_rate) / gas_rate); + oil_rate *= (static_cast(controls.gas_rate) / gas_rate); + gas_rate = static_cast(controls.gas_rate); } if (controls.hasControl(Well::ProducerCMode::WRAT) && water_rate > static_cast(controls.water_rate)) { oil_rate *= (static_cast(controls.water_rate) / water_rate); + gas_rate *= (static_cast(controls.water_rate) / water_rate); water_rate = static_cast(controls.water_rate); } if (controls.hasControl(Well::ProducerCMode::LRAT)) { @@ -566,6 +570,7 @@ getProducerWellRates_(const Well* well, int well_index) if (liquid_rate > liquid_rate_lim) { water_rate = water_rate / liquid_rate * liquid_rate_lim; oil_rate = oil_rate / liquid_rate * liquid_rate_lim; + gas_rate = gas_rate / liquid_rate * liquid_rate_lim; } } @@ -670,12 +675,17 @@ initializeGroupRatesRecursive_(const Group& group) if (oil_target && oil_rate > *oil_target) { water_rate *= (*oil_target/oil_rate); + gas_rate *= (*oil_target/oil_rate); oil_rate = *oil_target; } - if (gas_target && gas_rate > *gas_target) + if (gas_target && gas_rate > *gas_target) { + water_rate *= (*gas_target/gas_rate); + oil_rate *= (*gas_target/gas_rate); gas_rate = *gas_target; + } if (water_target && water_rate > *water_target) { oil_rate *= (*water_target/water_rate); + gas_rate *= (*water_target/water_rate); water_rate = *water_target; } if (liquid_target) { @@ -684,6 +694,7 @@ initializeGroupRatesRecursive_(const Group& group) if (liquid_rate > liquid_rate_limited) { oil_rate = oil_rate / liquid_rate * liquid_rate_limited; water_rate = water_rate / liquid_rate * liquid_rate_limited; + gas_rate = gas_rate / liquid_rate * liquid_rate_limited; } } } diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp index 50bf46fdde2..d5189e3466b 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp @@ -116,6 +116,11 @@ calcIncOrDecGradient(Scalar oil_rate, BasicRates oldrates = {oil_rate, gas_rate, water_rate, false}; const auto new_rates = updateRatesToGroupLimits_(oldrates, ratesLimited, gr_name_dont_limit); + auto delta_gas_rate = new_rates.gas - gas_rate; + if (increase && checkGroupTotalRateExceeded(delta_alq, delta_gas_rate, gr_name_dont_limit)) + return std::nullopt; + + if (!increase && new_rates.oil < 0) { return std::nullopt; } @@ -293,7 +298,7 @@ addOrSubtractAlqIncrement_(Scalar alq, bool increase) const if (limited && checkALQequal_(orig_alq, alq)) alq_opt = std::nullopt; - return {alq_opt, limited}; + return {alq_opt, limited && increase }; } template @@ -1841,10 +1846,16 @@ checkGroupALQrateExceeded(Scalar delta_alq, template bool GasLiftSingleWellGeneric:: checkGroupTotalRateExceeded(Scalar delta_alq, - Scalar delta_gas_rate) const + Scalar delta_gas_rate, + const std::string& gr_name_dont_limit) const { const auto& pairs = group_info_.getWellGroups(well_name_); for (const auto& [group_name, efficiency] : pairs) { + // in stage 2 we don't want to limit the rate to the group + // target we are trying to redistribute the gaslift within + if (gr_name_dont_limit == group_name) + continue; + auto max_total_rate_opt = group_info_.maxTotalGasRate(group_name); if (max_total_rate_opt) { Scalar alq = group_info_.alqRate(group_name) + efficiency * delta_alq; diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.hpp b/opm/simulators/wells/GasLiftSingleWellGeneric.hpp index c8827a83143..c2ff8fa4fad 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.hpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.hpp @@ -267,7 +267,8 @@ class GasLiftSingleWellGeneric : public GasLiftCommon bool checkGroupALQrateExceeded(Scalar delta_alq, const std::string& gr_name_dont_limit = "") const; bool checkGroupTotalRateExceeded(Scalar delta_alq, - Scalar delta_gas_rate) const; + Scalar delta_gas_rate, + const std::string& gr_name_dont_limit = "") const; std::pair, bool> addOrSubtractAlqIncrement_(Scalar alq, bool increase) const; diff --git a/opm/simulators/wells/GasLiftStage2.cpp b/opm/simulators/wells/GasLiftStage2.cpp index e604d7c6165..a7864e021ef 100644 --- a/opm/simulators/wells/GasLiftStage2.cpp +++ b/opm/simulators/wells/GasLiftStage2.cpp @@ -217,7 +217,7 @@ checkRateAlreadyLimited_(const std::string& well_name, well_name, state.alq(), (increase ? "incremental" : "decremental"), - (state.oilIsLimited() ? "oil" : (state.gasIsLimited() ? "gas" : "alq")) + (state.oilIsLimited() ? "oil" : (state.gasIsLimited() ? "gas" : (state.waterIsLimited() ? "water" : "alq"))) ); displayDebugMessage_(msg); return true; @@ -431,7 +431,14 @@ optimizeGroup_(const Group& group) OPM_TIMEFUNCTION(); const auto& group_name = group.name(); const auto prod_control = this->group_state_.production_control(group_name); - if ((prod_control != Group::ProductionCMode::NONE) && (prod_control != Group::ProductionCMode::FLD)) + const auto max_totalgas = getGroupMaxTotalGas_(group); + bool restricted_by_max_totalgas = false; + if (max_totalgas) { + auto [oil_rate, gas_rate, water_rate, alq] = getCurrentGroupRates_(group); + restricted_by_max_totalgas = (gas_rate + alq) > max_totalgas; + } + + if (restricted_by_max_totalgas || ((prod_control != Group::ProductionCMode::NONE) && (prod_control != Group::ProductionCMode::FLD))) { if (this->debug) { const std::string msg = fmt::format("optimizing (control = {})", Group::ProductionCMode2String(prod_control)); From abab405b97f93d1c5aa24d368459dee5dda6c945 Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Wed, 19 Nov 2025 16:00:12 +0100 Subject: [PATCH 2/8] Limit the rates with targets in consitent way --- .../wells/GasLiftSingleWellGeneric.cpp | 390 +++++------------- .../wells/GasLiftSingleWellGeneric.hpp | 37 +- 2 files changed, 98 insertions(+), 329 deletions(-) diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp index d5189e3466b..4b7b8bbf672 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp @@ -542,9 +542,7 @@ debugShowLimitingTargets_(const LimitedRates& rates) const { if (rates.limited()) { if (rates.oil_is_limited) { - const std::string msg = fmt::format("oil rate {} is limited by {} target", - rates.oil, - GasLiftGroupInfo::rateToString(*(rates.oil_limiting_target))); + const std::string msg = fmt::format("oil rate {} is limited by ORAT target", rates.oil); displayDebugMessage_(msg); } if (rates.gas_is_limited) { @@ -552,9 +550,7 @@ debugShowLimitingTargets_(const LimitedRates& rates) const displayDebugMessage_(msg); } if (rates.water_is_limited) { - const std::string msg = fmt::format("water rate {} is limited by {} target", - rates.water, - GasLiftGroupInfo::rateToString(*(rates.water_limiting_target))); + const std::string msg = fmt::format("water rate {} is limited by WRAT target", rates.water); displayDebugMessage_(msg); } } else { @@ -636,74 +632,6 @@ getBhpWithLimit_(Scalar bhp) const return {bhp, limited}; } -// TODO: what if the gas_rate_target_ has been defaulted -// (i.e. value == 0, meaning: "No limit") but the -// oil_rate_target_ has not been defaulted ? -// If the new_oil_rate exceeds the oil_rate_target_ it is cut back, -// but the same cut-back will not happen for the new_gas_rate -// Seems like an inconsistency, since alq should in this -// case also be adjusted (to the smaller value that would -// give oil target rate) but then the gas rate would also be smaller? -// The effect of not reducing the gas rate (if it should be -// reduced?) is that a too large value is used in the -// computation of the economic gradient making the gradient -// smaller than it should be since the term appears in the denominator. -template -std::pair -GasLiftSingleWellGeneric:: -getGasRateWithLimit_(const BasicRates& rates) const -{ - auto [rate, target_type] = getRateWithLimit_(Rate::gas, rates); - bool limited = target_type.has_value(); - return {rate, limited}; -} - -// NOTE: If the computed oil rate is larger than the target -// rate of the well, we reduce it to the target rate. This -// will make the economic gradient smaller than it would be -// if we did not reduce the rate, and it is less -// likely that the current gas lift increment will be -// accepted. -// TODO: If it still is accepted, we should ideally reduce the alq -// also since we also reduced the rate. This might involve -// some sort of iteration though.. -template -std::pair -GasLiftSingleWellGeneric:: -getOilRateWithLimit_(const BasicRates& rates) const -{ - auto [rate, target_type] = getRateWithLimit_(Rate::oil, rates); - bool limited = target_type.has_value(); - return {rate, limited}; -} - -template -std::pair::Rate>> -GasLiftSingleWellGeneric:: -getOilRateWithLimit2_(const BasicRates& rates) const -{ - return getRateWithLimit_(Rate::oil, rates); -} - -template -std::pair -GasLiftSingleWellGeneric:: -getWaterRateWithLimit_(const BasicRates& rates) const -{ - auto [rate, target_type] = getRateWithLimit_(Rate::water, rates); - bool limited = target_type.has_value(); - return {rate, limited}; -} - -template -std::pair::Rate>> -GasLiftSingleWellGeneric:: -getWaterRateWithLimit2_(const BasicRates& rates) const -{ - return getRateWithLimit_(Rate::water, rates); -} - template Scalar GasLiftSingleWellGeneric:: @@ -745,170 +673,7 @@ getProductionTarget_(Rate rate) const } template -std::pair::Rate>> -GasLiftSingleWellGeneric:: -getRateWithLimit_(Rate rate_type, const BasicRates& rates) const -{ - Scalar new_rate = getRate_(rate_type, rates); - // If "target_type" is empty at the end of this method, it means the rate - // was not limited. Otherwise, target_type gives the reason (the type of target) - // for why the rate was limited. - std::optional target_type; - - // we also need to limit the other rate (currently only for water and oil and not gas) - Scalar rate2 = 0.0; - if (rate_type == Rate::oil) { - rate2 = getRate_(Rate::water, rates); - } else if (rate_type == Rate::water) { - rate2 = getRate_(Rate::oil, rates); - } - - if (hasProductionControl_(rate_type)) { - auto target = getProductionTarget_(rate_type); - if (new_rate > target) { - const std::string msg = fmt::format("limiting {} rate to target: " - "computed rate: {}, target: {}", - GasLiftGroupInfo::rateToString(rate_type), - new_rate, - target); - displayDebugMessage_(msg); - rate2 *= target/new_rate; - new_rate = target; - target_type = rate_type; - } - } - if (((rate_type == Rate::oil) || (rate_type == Rate::water))) { - if (rate_type == Rate::oil) { - if(hasProductionControl_(Rate::water)) { - auto water_target = getProductionTarget_(Rate::water); - if (rate2 > water_target) { - new_rate *= (water_target / rate2); - target_type = Rate::water; - rate2 = water_target; - const std::string msg = fmt::format("limiting {} rate to {} due to WRAT target: " - "computed WRAT: {}, target WRAT: {}", - GasLiftGroupInfo::rateToString(rate_type), - new_rate, - rate2, - water_target); - displayDebugMessage_(msg); - } - } - } else { - if(hasProductionControl_(Rate::oil)) { - auto oil_target = getProductionTarget_(Rate::oil); - if (rate2 > oil_target) { - new_rate *= (oil_target / rate2); - target_type = Rate::oil; - rate2 = oil_target; - const std::string msg = fmt::format("limiting {} rate to {} due to ORAT target: " - "computed ORAT: {}, target ORAT: {}", - GasLiftGroupInfo::rateToString(rate_type), - new_rate, - rate2, - oil_target); - displayDebugMessage_(msg); - } - } - } - - if(hasProductionControl_(Rate::liquid)) { - // Note: Since "new_rate" was first updated for ORAT or WRAT, see first "if" - // statement in the method, the rate is limited due to LRAT only if - // it becomes less than the rate limited by a WRAT or ORAT target.. - Scalar liq_rate = new_rate + rate2; - - auto liq_target = getProductionTarget_(Rate::liquid); - if (liq_rate > liq_target) { - Scalar fraction = new_rate / liq_rate; - // NOTE: since - // fraction * liq_rate = new_rate, - // we must have - // fraction * liq_target < new_rate - // since - // liq_target < liq_rate - // therefore new_rate will become less than it original was and - // limited = true. - new_rate = fraction * liq_target; - target_type = Rate::liquid; - const std::string msg = fmt::format("limiting {} rate to {} due to LRAT target: " - "computed LRAT: {}, target LRAT: {}", - GasLiftGroupInfo::rateToString(rate_type), - new_rate, - liq_rate, - liq_target); - displayDebugMessage_(msg); - } - } - } - // TODO: Also check RESV target? - return {new_rate, target_type}; -} - -template -std::pair -GasLiftSingleWellGeneric:: -getOilRateWithGroupLimit_(Scalar new_oil_rate, - Scalar oil_rate, - const std::string& gr_name_dont_limit) const -{ - [[maybe_unused]] auto [rate, gr_name, efficiency] = getRateWithGroupLimit_(Rate::oil, new_oil_rate, oil_rate, gr_name_dont_limit); - bool limited = gr_name != nullptr; - return {rate, limited}; -} - -template -std::pair -GasLiftSingleWellGeneric:: -getGasRateWithGroupLimit_(Scalar new_gas_rate, - Scalar gas_rate, - const std::string& gr_name_dont_limit) const -{ - [[maybe_unused]] auto [rate, gr_name, efficiency] = getRateWithGroupLimit_(Rate::gas, new_gas_rate, gas_rate, gr_name_dont_limit); - bool limited = gr_name != nullptr; - return {rate, limited}; -} - -template -std::pair -GasLiftSingleWellGeneric:: -getWaterRateWithGroupLimit_(Scalar new_water_rate, - Scalar water_rate, - const std::string& gr_name_dont_limit) const -{ - [[maybe_unused]] auto [rate, gr_name, efficiency] = getRateWithGroupLimit_(Rate::water, new_water_rate, water_rate, gr_name_dont_limit); - bool limited = gr_name != nullptr; - return {rate, limited}; -} - -template -std::tuple -GasLiftSingleWellGeneric:: -getLiquidRateWithGroupLimit_(const Scalar new_oil_rate, - const Scalar oil_rate, - const Scalar new_water_rate, - const Scalar water_rate, - const std::string& gr_name_dont_limit) const -{ - auto liquid_rate = oil_rate + water_rate; - auto new_liquid_rate = new_oil_rate + new_water_rate; - auto [liquid_rate_limited, group_name, efficiency] - = getRateWithGroupLimit_(Rate::liquid, new_liquid_rate, liquid_rate, gr_name_dont_limit); - bool limited = group_name != nullptr; - if (limited) { - Scalar oil_fraction = oil_rate / liquid_rate; - Scalar delta_liquid = liquid_rate_limited - liquid_rate; - auto limited_oil_rate = oil_rate + oil_fraction * delta_liquid; - auto limited_water_rate = water_rate + (1.0 - oil_fraction) * delta_liquid; - return {limited_oil_rate, limited_water_rate, limited, limited}; - } - return {new_oil_rate, new_water_rate, limited, limited}; -} - -template -std::tuple +std::tuple GasLiftSingleWellGeneric:: getRateWithGroupLimit_(Rate rate_type, const Scalar new_rate, @@ -923,7 +688,7 @@ getRateWithGroupLimit_(Rate rate_type, // if delta_rate > 0 const auto& pairs = this->group_info_.getWellGroups(this->well_name_); Scalar limited_rate = new_rate; - Scalar gr_target{}, new_gr_rate{}, efficiency{}; + Scalar gr_target{}, new_gr_rate{}; const std::string* group_name = nullptr; for (const auto& [group_name_temp, efficiency_temp] : pairs) { // in stage 2 we don't want to limit the rate to the group @@ -941,23 +706,11 @@ getRateWithGroupLimit_(Rate rate_type, debugInfoGroupRatesExceedTarget(rate_type, group_name_temp, gr_rate_temp, gr_target_temp); } group_name = &group_name_temp; - efficiency = efficiency_temp; limited_rate = old_rate; gr_target = gr_target_temp; new_gr_rate = gr_rate_temp; break; } - Scalar new_gr_rate_temp = gr_rate_temp + efficiency_temp * delta_rate; - if (new_gr_rate_temp > gr_target_temp) { - Scalar limited_rate_temp = old_rate + (gr_target_temp - gr_rate_temp) / efficiency_temp; - if (limited_rate_temp < limited_rate) { - group_name = &group_name_temp; - efficiency = efficiency_temp; - limited_rate = limited_rate_temp; - gr_target = gr_target_temp; - new_gr_rate = new_gr_rate_temp; - } - } } } if (group_name) { @@ -972,10 +725,10 @@ getRateWithGroupLimit_(Rate rate_type, new_gr_rate); displayDebugMessage_(msg); } - return {limited_rate, group_name, efficiency}; + return {limited_rate, group_name}; } } - return {new_rate, /*group_name =*/nullptr, /*efficiency dummy value*/ 0.0}; + return {new_rate, /*group_name =*/nullptr}; } template @@ -1004,21 +757,58 @@ typename GasLiftSingleWellGeneric::LimitedRates GasLiftSingleWellGeneric:: getLimitedRatesFromRates_(const BasicRates& rates) const { - auto [oil_rate, oil_limiting_target] = getOilRateWithLimit2_(rates); - bool oil_is_limited = oil_limiting_target.has_value(); - auto [gas_rate, gas_is_limited] = getGasRateWithLimit_(rates); - auto [water_rate, water_limiting_target] = getWaterRateWithLimit2_(rates); - bool water_is_limited = water_limiting_target.has_value(); + Scalar oil_rate = getRate_(Rate::oil, rates); + Scalar gas_rate = getRate_(Rate::gas, rates); + Scalar water_rate = getRate_(Rate::water, rates); + bool oil_is_limited = false; + bool gas_is_limited = false; + bool water_is_limited = false; + if (hasProductionControl_(Rate::oil)) { + auto target = getProductionTarget_(Rate::oil); + if (oil_rate > target) { + gas_rate *= target / oil_rate; + water_rate *= target / oil_rate; + oil_rate = target; + oil_is_limited = true; + } + } + if (hasProductionControl_(Rate::gas)) { + auto target = getProductionTarget_(Rate::gas); + if (gas_rate > target) { + oil_rate *= target / gas_rate; + water_rate *= target / gas_rate; + gas_rate = target; + gas_is_limited = true; + } + } + if (hasProductionControl_(Rate::water)) { + auto target = getProductionTarget_(Rate::water); + if (water_rate > target) { + gas_rate *= target / water_rate; + oil_rate *= target / water_rate; + water_rate = target; + water_is_limited = true; + } + } + if (hasProductionControl_(Rate::liquid)) { + auto target = getProductionTarget_(Rate::liquid); + auto liq_rate = oil_rate + water_rate; + if (liq_rate > target) { + gas_rate *= target / liq_rate; + water_rate *= target / liq_rate; + oil_rate *= target / liq_rate; + water_is_limited = true; + oil_is_limited = true; + } + } return LimitedRates {oil_rate, gas_rate, water_rate, oil_is_limited, gas_is_limited, water_is_limited, - rates.bhp_is_limited, - oil_limiting_target, - water_limiting_target}; + rates.bhp_is_limited}; } template @@ -1583,36 +1373,52 @@ typename GasLiftSingleWellGeneric::LimitedRates GasLiftSingleWellGeneric:: updateRatesToGroupLimits_(const BasicRates& old_rates, const LimitedRates& rates, - const std::string& gr_name) const + const std::string& gr_name_dont_limit) const { LimitedRates new_rates = rates; - auto [new_oil_rate, oil_is_limited] = getOilRateWithGroupLimit_(new_rates.oil, old_rates.oil, gr_name); - auto mod_water_rate = new_rates.water; - if (oil_is_limited) { - new_rates.oil_limiting_target = Rate::oil; - mod_water_rate *= (new_oil_rate / new_rates.oil); - } - auto [new_gas_rate, gas_is_limited] = getGasRateWithGroupLimit_(new_rates.gas, old_rates.gas, gr_name); - auto [new_water_rate, water_is_limited] = getWaterRateWithGroupLimit_(mod_water_rate, old_rates.water, gr_name); - if (water_is_limited) { - new_rates.water_limiting_target = Rate::water; - new_oil_rate *= (new_water_rate / new_rates.water); - } - auto [new_oil_rate2, new_water_rate2, oil_is_limited2, water_is_limited2] - = getLiquidRateWithGroupLimit_(new_oil_rate, old_rates.oil, new_water_rate, old_rates.water, gr_name); - if (oil_is_limited2) { - new_rates.oil_limiting_target = Rate::liquid; - } - if (water_is_limited2) { - new_rates.water_limiting_target = Rate::liquid; - } - new_rates.oil = new_oil_rate2; - new_rates.gas = new_gas_rate; - new_rates.water = new_water_rate2; - new_rates.oil_is_limited = rates.oil_is_limited || oil_is_limited || oil_is_limited2; + auto oil_rate = rates.oil; + auto gas_rate = rates.gas; + auto water_rate = rates.water; + bool oil_is_limited = false; + bool gas_is_limited = false; + bool water_is_limited = false; + auto [oil_rate_new, gr_name_oil] = getRateWithGroupLimit_(Rate::oil, oil_rate, old_rates.oil, gr_name_dont_limit); + if (gr_name_oil) { + gas_rate *= oil_rate_new/oil_rate; + water_rate *= oil_rate_new/oil_rate; + oil_rate = oil_rate_new; + oil_is_limited = true; + } + auto [gas_rate_new, gr_name_gas] = getRateWithGroupLimit_(Rate::gas, gas_rate, old_rates.gas, gr_name_dont_limit); + if (gr_name_gas) { + oil_rate *= gas_rate_new/gas_rate; + water_rate *= gas_rate_new/gas_rate; + gas_rate = gas_rate_new; + gas_is_limited = true; + } + auto [water_rate_new, gr_name_water] = getRateWithGroupLimit_(Rate::water, water_rate, old_rates.water, gr_name_dont_limit); + if (gr_name_water) { + oil_rate *= water_rate_new/water_rate; + gas_rate *= water_rate_new/water_rate; + water_rate = water_rate_new; + water_is_limited = true; + } + auto liq_rate = water_rate + oil_rate; + auto [liq_rate_new, gr_name_liq] = getRateWithGroupLimit_(Rate::liquid, liq_rate, old_rates.water + old_rates.oil, gr_name_dont_limit); + if (gr_name_liq) { + oil_rate *= liq_rate_new/liq_rate; + gas_rate *= liq_rate_new/liq_rate; + water_rate *= liq_rate_new/liq_rate; + oil_is_limited = true; + water_is_limited = true; + } + new_rates.oil = oil_rate; + new_rates.gas = gas_rate; + new_rates.water = water_rate; + new_rates.oil_is_limited = rates.oil_is_limited || oil_is_limited; new_rates.gas_is_limited = rates.gas_is_limited || gas_is_limited; - new_rates.water_is_limited = rates.water_is_limited || water_is_limited || water_is_limited2; - if (oil_is_limited || oil_is_limited2 || gas_is_limited || water_is_limited || water_is_limited2) { + new_rates.water_is_limited = rates.water_is_limited || water_is_limited; + if (oil_is_limited || gas_is_limited || water_is_limited) { new_rates.limit_type = LimitedRates::LimitType::group; } return new_rates; @@ -1947,21 +1753,17 @@ checkRatesViolated(const LimitedRates& rates) const std::string target_type; std::string rate_type; if (rates.oil_is_limited) { - target_type = GasLiftGroupInfo::rateToString(*(rates.oil_limiting_target)); rate_type = "oil"; } else if (rates.gas_is_limited) { - target_type = "gas"; rate_type = "gas"; } else if (rates.water_is_limited) { - target_type = GasLiftGroupInfo::rateToString(*(rates.water_limiting_target)); rate_type = "water"; } - const std::string msg = fmt::format("iteration {} : {} rate was limited due to {} {} target. " + const std::string msg = fmt::format("iteration {} : {} rate was limited due to {} target. " "Stopping iteration", this->it, rate_type, - well_or_group, - target_type); + well_or_group); this->parent.displayDebugMessage_(msg); } return true; diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.hpp b/opm/simulators/wells/GasLiftSingleWellGeneric.hpp index c2ff8fa4fad..23266e0fbea 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.hpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.hpp @@ -184,15 +184,11 @@ class GasLiftSingleWellGeneric : public GasLiftCommon bool oil_is_limited_, bool gas_is_limited_, bool water_is_limited_, - bool bhp_is_limited_, - std::optional oil_limiting_target_, - std ::optional water_limiting_target_) + bool bhp_is_limited_) : BasicRates(oil_, gas_, water_, bhp_is_limited_) , oil_is_limited{oil_is_limited_} , gas_is_limited{gas_is_limited_} , water_is_limited{water_is_limited_} - , oil_limiting_target{oil_limiting_target_} - , water_limiting_target{water_limiting_target_} { set_initial_limit_type_(); } @@ -220,8 +216,6 @@ class GasLiftSingleWellGeneric : public GasLiftCommon bool oil_is_limited; bool gas_is_limited; bool water_is_limited; - std::optional oil_limiting_target; - std::optional water_limiting_target; private: void set_initial_limit_type_() @@ -317,7 +311,6 @@ class GasLiftSingleWellGeneric : public GasLiftCommon void displayWarning_(const std::string& warning); std::pair getBhpWithLimit_(Scalar bhp) const; - std::pair getGasRateWithLimit_(const BasicRates& rates) const; std::pair getGasRateWithGroupLimit_(Scalar new_gas_rate, Scalar gas_rate, const std::string& gr_name_dont_limit) const; @@ -327,22 +320,6 @@ class GasLiftSingleWellGeneric : public GasLiftCommon LimitedRates getLimitedRatesFromRates_(const BasicRates& rates) const; - std::tuple - getLiquidRateWithGroupLimit_(const Scalar new_oil_rate, - const Scalar oil_rate, - const Scalar new_water_rate, - const Scalar water_rate, - const std::string& gr_name_dont_limit) const; - - std::pair - getOilRateWithGroupLimit_(Scalar new_oil_rate, - Scalar oil_rate, - const std::string& gr_name_dont_limit) const; - - std::pair getOilRateWithLimit_(const BasicRates& rates) const; - - std::pair> - getOilRateWithLimit2_(const BasicRates& rates) const; Scalar getProductionTarget_(Rate rate) const; Scalar getRate_(Rate rate_type, const BasicRates& rates) const; @@ -350,22 +327,12 @@ class GasLiftSingleWellGeneric : public GasLiftCommon std::pair> getRateWithLimit_(Rate rate_type, const BasicRates& rates) const; - std::tuple + std::tuple getRateWithGroupLimit_(Rate rate_type, const Scalar new_rate, const Scalar old_rate, const std::string& gr_name_dont_limit) const; - std::pair - getWaterRateWithGroupLimit_(Scalar new_water_rate, - Scalar water_rate, - const std::string& gr_name_dont_limit) const; - - std::pair getWaterRateWithLimit_(const BasicRates& rates) const; - - std::pair> - getWaterRateWithLimit2_(const BasicRates& rates) const; - BasicRates getWellStateRates_() const; bool hasProductionControl_(Rate rate) const; From 0a7501ddac0b43c22fde353c94fd223d0fd03b6f Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Mon, 24 Nov 2025 12:04:30 +0100 Subject: [PATCH 3/8] update well potential for all well after gaslift --- .../wells/BlackoilWellModelGasLift_impl.hpp | 4 +++ .../wells/GasLiftSingleWellGeneric.cpp | 34 ++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp index 5263fda3d8f..8b51623555c 100644 --- a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp @@ -136,6 +136,10 @@ maybeDoGasLiftOptimize(const Simulator& simulator, num_wells_changed = glift_wells.size(); } num_wells_changed = simulator.vanguard().gridView().comm().sum(num_wells_changed); + + if (num_wells_changed > 0) { + updateWellPotentials(simulator, well_container, node_pressures, wellState, deferred_logger); + } return num_wells_changed > 0; } diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp index 4b7b8bbf672..eaa999d0d10 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp @@ -160,24 +160,26 @@ runOptimize(const int iteration_idx) Scalar alq = state->alq(); if (this->debug) logSuccess_(alq, iteration_idx); - this->well_state_.well(this->well_name_).alq_state.set(alq); + auto& ws = this->well_state_.well(this->well_name_); + ws.alq_state.set(alq); const auto& pu = this->well_state_.phaseUsageInfo(); - - std::vector well_pot(pu.numActivePhases(), 0.0); - if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) { - const int oil_pos = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx); - well_pot[oil_pos] = state->oilRate(); - } - if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) { - const int water_pos = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx); - well_pot[water_pos] = state->waterRate(); - } - if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) { - const int gas_pos = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx); - well_pot[gas_pos] = state->gasRate(); + const bool isThp = (ws.production_cmode == Well::ProducerCMode::THP); + if (isThp) { + std::vector well_pot(pu.numActivePhases(), 0.0); + if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) { + const int oil_pos = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx); + well_pot[oil_pos] = state->oilRate(); + } + if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) { + const int water_pos = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx); + well_pot[water_pos] = state->waterRate(); + } + if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) { + const int gas_pos = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx); + well_pot[gas_pos] = state->gasRate(); + } + ws.well_potentials = well_pot; } - - this->well_state_[this->well_name_].well_potentials = well_pot; } } } From 59ac8cf97c139def12e17bd421c0fcd5b3317e78 Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Mon, 24 Nov 2025 12:04:58 +0100 Subject: [PATCH 4/8] Enhance glift1 test to actually test gaslift optimization --- tests/GLIFT1.DATA | 16 +++- tests/test_glift1.cpp | 167 ++++++++++++++++++++++++++++-------------- 2 files changed, 126 insertions(+), 57 deletions(-) diff --git a/tests/GLIFT1.DATA b/tests/GLIFT1.DATA index 0385718d06d..454392eca53 100644 --- a/tests/GLIFT1.DATA +++ b/tests/GLIFT1.DATA @@ -267,7 +267,7 @@ COMPDAT WCONPROD -- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift - 'B-1H' OPEN ORAT 1500.0 1* 1* 3000.0 1* 100.0 30 1 1* / + 'B-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / / GCONINJE @@ -296,15 +296,29 @@ WLIFTOPT TSTEP 0.5 / +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN ORAT 2000 1* 1* 1* 1* 100.0 30 1 1* / +/ DATES 1 FEB 2020 / / +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN ORAT 4500 1* 1* 1* 1* 100.0 30 1 1* / +/ + DATES 1 MAR 2020 / / +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN GRAT 1* 1* 2.0e5 1* 1* 100.0 30 1 1* / +/ + DATES 1 APR 2020 / 1 MAY 2020 / diff --git a/tests/test_glift1.cpp b/tests/test_glift1.cpp index a4400d22395..9751f46bd6e 100644 --- a/tests/test_glift1.cpp +++ b/tests/test_glift1.cpp @@ -152,62 +152,117 @@ BOOST_AUTO_TEST_CASE(G1) simulator->setTimeStepSize(43200); // 12 hours simulator->model().newtonMethod().setIterationIndex(0); WellModel& well_model = simulator->problem().wellModel(); - int report_step_idx = 0; - well_model.beginReportStep(report_step_idx); - well_model.beginTimeStep(); - Opm::DeferredLogger deferred_logger; - well_model.calculateExplicitQuantities(deferred_logger); - well_model.prepareTimeStep(deferred_logger); - well_model.updateWellControls(deferred_logger); - const Opm::WellInterface* well_ptr = &well_model.getWell("B-1H"); - const StdWell *std_well = dynamic_cast(well_ptr); - - const auto& schedule = simulator->vanguard().schedule(); - auto wells_ecl = schedule.getWells(report_step_idx); - std::optional idx; - int num_producers = 0; - for(std::size_t i = 0; i < wells_ecl.size(); ++i) { - const auto &well = wells_ecl[i]; - if (well.isProducer()) { - idx = i; - num_producers++; + + // we tests 3 different setups given in the .DATA file + // 1) No rate limit -> well should be limited by alq + // 2) No alq is needed. ORAT limit -> oil_rate = oil_limit and gas_rate is scaled + // 3) Alq is needed. ORAT limit -> oil_rate = oil_limit and gas_rate is scaled + // 4) Alq is needed. GRAT limit -> gas_rate = gas_limit and oil_rate is scaled + for (int report_step_idx = 0; report_step_idx < 4; report_step_idx++ ) { + + well_model.beginReportStep(report_step_idx); + well_model.beginTimeStep(); + Opm::DeferredLogger deferred_logger; + well_model.calculateExplicitQuantities(deferred_logger); + well_model.prepareTimeStep(deferred_logger); + well_model.updateWellControls(deferred_logger); + const Opm::WellInterface* well_ptr = &well_model.getWell("B-1H"); + const StdWell *std_well = dynamic_cast(well_ptr); + const auto& schedule = simulator->vanguard().schedule(); + auto wells_ecl = schedule.getWells(report_step_idx); + std::optional idx; + int num_producers = 0; + for(std::size_t i = 0; i < wells_ecl.size(); ++i) { + const auto &well = wells_ecl[i]; + if (well.isProducer()) { + idx = i; + num_producers++; + } + } + BOOST_CHECK_EQUAL( num_producers, 1); + const auto &well = wells_ecl[*idx]; + BOOST_CHECK_EQUAL( well.name(), "B-1H"); + const auto& summary_state = simulator->vanguard().summaryState(); + WellState &well_state = well_model.wellState(); + const auto &group_state = well_model.groupState(); + GLiftEclWells ecl_well_map; + Opm::BlackoilWellModelGasLift:: + initGliftEclWellMap(well_model.localNonshutWells(), ecl_well_map); + const int iteration_idx = simulator->model().newtonMethod().numIterations(); + const auto& comm = simulator->vanguard().grid().comm(); + GasLiftGroupInfo group_info { + ecl_well_map, + schedule, + summary_state, + simulator->episodeIndex(), + iteration_idx, + deferred_logger, + well_state, + group_state, + comm, + /*glift_debug=*/false + }; + GLiftSyncGroups sync_groups; + GasLiftSingleWell glift {*std_well, *(simulator.get()), summary_state, + deferred_logger, well_state, group_state, group_info, sync_groups, + comm, /*glift_debug=*/false + }; + group_info.initialize(); + const auto& controls = well.productionControls(summary_state); + auto state = glift.runOptimize(iteration_idx); + auto pot = well_state[well.name()].well_potentials; + + const auto& glo = schedule.glo(report_step_idx); + const auto increment = glo.gaslift_increment(); + const auto gl_well = glo.well(well.name()); + + // no well limit -> alq limited + if (report_step_idx == 0) { + BOOST_CHECK(state->alqIsLimited()); + BOOST_CHECK(state->increase().has_value()); + BOOST_CHECK(!state->gasIsLimited()); + BOOST_CHECK(!state->oilIsLimited()); + BOOST_CHECK_CLOSE(state->alq(), *gl_well.max_rate(), 1e-8); + BOOST_CHECK_CLOSE(state->oilRate(), pot[1], 1e-8); + BOOST_CHECK_CLOSE(state->gasRate(), pot[2], 1e-8); + } + // ORAT limit no alq needed + if (report_step_idx == 1) { + BOOST_CHECK(!state->alqIsLimited()); + BOOST_CHECK(!state->gasIsLimited()); + BOOST_CHECK(state->oilIsLimited()); + // the oil rate is constraint by the oil target + BOOST_CHECK_CLOSE(state->oilRate(), controls.oil_rate, 1e-8); + // the gas rate is scaled by the oil target / oil potential + BOOST_CHECK_CLOSE(state->gasRate(), pot[2] * controls.oil_rate / pot[1], 1e-6); + BOOST_CHECK(!state->increase().has_value()); + BOOST_CHECK_CLOSE(state->alq(), 0.0, 1e-8); + } + // ORAT limit, alq needed + if (report_step_idx == 2) { + BOOST_CHECK(!state->alqIsLimited()); + BOOST_CHECK(!state->gasIsLimited()); + BOOST_CHECK(state->oilIsLimited()); + // the oil rate is constraint by the oil target + BOOST_CHECK_CLOSE(state->oilRate(), controls.oil_rate, 1e-8); + // the gas rate is scaled by the oil target / oil potential + BOOST_CHECK_CLOSE(state->gasRate(), pot[2] * controls.oil_rate / pot[1], 1e-6); + BOOST_CHECK(state->increase().has_value()); + // for this setup we needed one alq increment + BOOST_CHECK_CLOSE(state->alq(), increment*1, 1e-8); + } + // GRAT limit, alq needed + if (report_step_idx == 3) { + BOOST_CHECK(!state->alqIsLimited()); + BOOST_CHECK(state->gasIsLimited()); + BOOST_CHECK(!state->oilIsLimited()); + // the gas rate is constraint by the gas target + BOOST_CHECK_CLOSE(state->gasRate(), controls.gas_rate, 1e-8); + // the oil rate is scaled by the gas target / gas potential + BOOST_CHECK_CLOSE(state->oilRate(), pot[1] * controls.gas_rate / pot[2], 1e-6); + BOOST_CHECK(state->increase().has_value()); + // for this setup we needed five alq increment + BOOST_CHECK_CLOSE(state->alq(), increment*5, 1e-8); } } - BOOST_CHECK_EQUAL( num_producers, 1); - const auto &well = wells_ecl[*idx]; - BOOST_CHECK_EQUAL( well.name(), "B-1H"); - const auto& summary_state = simulator->vanguard().summaryState(); - WellState &well_state = well_model.wellState(); - const auto &group_state = well_model.groupState(); - GLiftEclWells ecl_well_map; - Opm::BlackoilWellModelGasLift:: - initGliftEclWellMap(well_model.localNonshutWells(), ecl_well_map); - const int iteration_idx = simulator->model().newtonMethod().numIterations(); - const auto& comm = simulator->vanguard().grid().comm(); - GasLiftGroupInfo group_info { - ecl_well_map, - schedule, - summary_state, - simulator->episodeIndex(), - iteration_idx, - deferred_logger, - well_state, - group_state, - comm, - /*glift_debug=*/false - }; - GLiftSyncGroups sync_groups; - GasLiftSingleWell glift {*std_well, *(simulator.get()), summary_state, - deferred_logger, well_state, group_state, group_info, sync_groups, - comm, /*glift_debug=*/false - }; - group_info.initialize(); - auto state = glift.runOptimize(iteration_idx); - BOOST_CHECK_CLOSE(state->oilRate(), 0.01736111111111111, 1e-8); - BOOST_CHECK_CLOSE(state->gasRate(), 1.6464, 1e-1); - BOOST_CHECK(state->oilIsLimited()); - BOOST_CHECK(!state->gasIsLimited()); - BOOST_CHECK(!state->alqIsLimited()); - BOOST_CHECK_CLOSE(state->alq(), 0.0, 1e-8); - BOOST_CHECK(!state->increase().has_value()); } From f9008d07d8939d07afdabc88452981be9624989d Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Tue, 25 Nov 2025 14:32:50 +0100 Subject: [PATCH 5/8] Add a test for GLIFTOPT in glift1 to test interaction with group control --- tests/GLIFT1.DATA | 130 ++++++++++++++++++++++++++++++++++++++- tests/test_glift1.cpp | 140 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 266 insertions(+), 4 deletions(-) diff --git a/tests/GLIFT1.DATA b/tests/GLIFT1.DATA index 454392eca53..8e916b2ff13 100644 --- a/tests/GLIFT1.DATA +++ b/tests/GLIFT1.DATA @@ -224,6 +224,7 @@ GRUPTREE 'F1' 'M5N' / 'B1' 'M5S' / + 'C1' 'M5N' / 'G1' 'M5S' / / @@ -244,6 +245,10 @@ INCLUDE WELSPECS --WELL GROUP IHEEL JHEEL DREF PHASE DRAD INFEQ SIINS XFLOW PRTAB DENS 'B-1H' 'B1' 11 3 1* OIL 1* 1* SHUT 1* 1* 1* / + 'B-2H' 'B1' 4 7 1* OIL 1* 1* SHUT 1* 1* 1* / + 'B-3H' 'B1' 11 12 1* OIL 1* 1* SHUT 1* 1* 1* / + 'C-1H' 'C1' 13 20 1* OIL 1* 1* SHUT 1* 1* 1* / + 'C-2H' 'C1' 12 27 1* OIL 1* 1* SHUT 1* 1* 1* / / WELSPECS @@ -256,6 +261,10 @@ WELSPECS COMPDAT --WELL I J K1 K2 OP/SH SATN TRAN WBDIA KH SKIN DFACT DIR PEQVR 'B-1H' 11 3 1 5 OPEN 1* 1* 0.216 1* 0 1* Z 1* / + 'B-2H' 4 7 1 6 OPEN 1* 1* 0.216 1* 0 1* Z 1* / + 'B-3H' 11 12 1 4 OPEN 1* 1* 0.216 1* 0 1* Z 1* / + 'C-1H' 13 20 1 4 OPEN 1* 1* 0.216 1* 0 1* Z 1* / + 'C-2H' 12 27 1 5 OPEN 1* 1* 0.216 1* 0 1* Z 1* / / COMPDAT @@ -321,11 +330,130 @@ WCONPROD DATES 1 APR 2020 / +/ + +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' SHUT THP 1* 1* 1* 1* 1* 100.0 30 1 1* / +/ + +DATES + 2 APR 2020 / +/ + +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-3H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / +/ + +--Add more wells to check interation with group control +WLIFTOPT + 'B-1H' YES 150000 1.01 -1.0 / + 'B-2H' YES 150000 1.01 -1.0 / + 'B-3H' YES 150000 1.01 -1.0 / + 'C-1H' YES 150000 1.01 -1.0 / + 'C-2H' YES 150000 1.01 -1.0 / +/ + +-- Group lift gas limits for gas lift optimization +GLIFTOPT + 'PLAT-A' 450000 / -- +/ + +DATES 1 MAY 2020 / +/ + +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-3H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / +/ + +WLIFTOPT + 'B-1H' YES 150000 1.01 -1.0 / + 'B-2H' YES 150000 1.01 -1.0 / + 'B-3H' YES 150000 1.01 -1.0 / + 'C-1H' YES 150000 1.01 -1.0 / + 'C-2H' YES 150000 1.01 -1.0 / +/ + +GLIFTOPT + 'PLAT-A' 450000 / -- +/ + +GCONPROD + 'PLAT-A' ORAT 25000 / +/ + +DATES 1 JUN 2020 / +/ + +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-3H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / +/ + +WLIFTOPT + 'B-1H' YES 150000 1.01 -1.0 / + 'B-2H' YES 150000 1.01 -1.0 / + 'B-3H' YES 150000 1.01 -1.0 / + 'C-1H' YES 150000 1.01 -1.0 / + 'C-2H' YES 150000 1.01 -1.0 / +/ + +GCONPROD + 'M5S' GRAT 2* 5e5 / + 'PLAT-A' ORAT 25000 / +/ +GLIFTOPT + 'PLAT-A' 450000 / -- +/ + +DATES 1 JLY 2020 / - 1 AUG 2020 / +/ +WCONPROD +-- Well_name Status Ctrl Orate Wrate Grate Lrate RFV FBHP WHP VFP Glift + 'B-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'B-3H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-1H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / + 'C-2H' OPEN THP 1* 1* 1* 1* 1* 100.0 30 1 1* / +/ + +WLIFTOPT + 'B-1H' YES 150000 1.01 -1.0 / + 'B-2H' YES 150000 1.01 -1.0 / + 'B-3H' YES 150000 1.01 -1.0 / + 'C-1H' YES 150000 1.01 -1.0 / + 'C-2H' YES 150000 1.01 -1.0 / +/ + +GCONPROD + 'M5S' NONE / + 'PLAT-A' NONE/ +/ + +GLIFTOPT + 'PLAT-A' 800000 800000 / -- +/ + +DATES + 1 AUG 2020 / / END diff --git a/tests/test_glift1.cpp b/tests/test_glift1.cpp index 9751f46bd6e..8a0c01b9957 100644 --- a/tests/test_glift1.cpp +++ b/tests/test_glift1.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #if HAVE_DUNE_FEM @@ -153,13 +155,13 @@ BOOST_AUTO_TEST_CASE(G1) simulator->model().newtonMethod().setIterationIndex(0); WellModel& well_model = simulator->problem().wellModel(); - // we tests 3 different setups given in the .DATA file + // we tests 4 different setups given in the .DATA file + // we only look at B-1H // 1) No rate limit -> well should be limited by alq // 2) No alq is needed. ORAT limit -> oil_rate = oil_limit and gas_rate is scaled // 3) Alq is needed. ORAT limit -> oil_rate = oil_limit and gas_rate is scaled // 4) Alq is needed. GRAT limit -> gas_rate = gas_limit and oil_rate is scaled for (int report_step_idx = 0; report_step_idx < 4; report_step_idx++ ) { - well_model.beginReportStep(report_step_idx); well_model.beginTimeStep(); Opm::DeferredLogger deferred_logger; @@ -174,7 +176,7 @@ BOOST_AUTO_TEST_CASE(G1) int num_producers = 0; for(std::size_t i = 0; i < wells_ecl.size(); ++i) { const auto &well = wells_ecl[i]; - if (well.isProducer()) { + if (well.isProducer() && well.name()=="B-1H") { idx = i; num_producers++; } @@ -266,3 +268,135 @@ BOOST_AUTO_TEST_CASE(G1) } } } + + +BOOST_AUTO_TEST_CASE(G2) +{ + //using TypeTag = Opm::Properties::TTag::FlowProblemBlackoil; + using TypeTag = Opm::Properties::TTag::TestGliftTypeTag; + //using EclProblem = Opm::EclProblem; + //using EclWellModel = typename EclProblem::EclWellModel; + using FluidSystem = Opm::GetPropType; + using IndexTraits = typename FluidSystem::IndexTraitsType; + using WellModel = Opm::BlackoilWellModel; + using WellState = Opm::WellState; + using StdWell = Opm::StandardWell; + using GasLiftSingleWell = Opm::GasLiftSingleWell; + using GasLiftGroupInfo = Opm::GasLiftGroupInfo; + using GasLiftSingleWellGeneric = Opm::GasLiftSingleWellGeneric; + using GLiftEclWells = typename GasLiftGroupInfo::GLiftEclWells; + using GLiftOptWells = typename Opm::BlackoilWellModelGasLift::GLiftOptWells; + using GLiftProdWells = typename Opm::BlackoilWellModelGasLift::GLiftProdWells; + using GLiftWellStateMap = typename Opm::BlackoilWellModelGasLift::GLiftWellStateMap; + using GasLiftStage2 = Opm::GasLiftStage2; + + + // we use the same file but start from report idx 4 + // where more wells and group controll are added + const std::string filename = "GLIFT1.DATA"; + using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups; + + auto simulator = initSimulator(filename.data()); + + simulator->model().applyInitialSolution(); + simulator->setEpisodeIndex(-1); + simulator->setEpisodeLength(0.0); + simulator->startNextEpisode(/*episodeStartTime=*/0.0, /*episodeLength=*/1e30); + simulator->setTimeStepSize(43200); // 12 hours + simulator->model().newtonMethod().setIterationIndex(0); + WellModel& well_model = simulator->problem().wellModel(); + + const auto& comm = simulator->vanguard().grid().comm(); + const auto& summary_state = simulator->vanguard().summaryState(); + + // we tests 4 different setups for stage 2 optimization + // starting from report step 5 as given in the .DATA file + // 1) No group rate limit -> group should be limited by max alq + // 2) Group is limited by ORAT -> sufficient ALQ to production the target + // 3) Group is limited by ORAT for PLAT-1 and GRAT for M5S + // 4) No group limits but restriction on total gas + for (int report_step_idx = 5; report_step_idx < 9; report_step_idx++ ) { + GLiftOptWells glift_wells; + GLiftProdWells prod_wells; + GLiftWellStateMap state_map; + simulator->setEpisodeIndex(report_step_idx); + well_model.beginReportStep(report_step_idx); + well_model.beginTimeStep(); + Opm::DeferredLogger deferred_logger; + well_model.calculateExplicitQuantities(deferred_logger); + const auto& schedule = simulator->vanguard().schedule(); + auto wells_ecl = schedule.getWells(report_step_idx); + GLiftEclWells ecl_well_map; + Opm::BlackoilWellModelGasLift:: + initGliftEclWellMap(well_model.localNonshutWells(), ecl_well_map); + const int iteration_idx = simulator->model().newtonMethod().numIterations(); + WellState& well_state = well_model.wellState(); + const auto& group_state = well_model.groupState(); + GasLiftGroupInfo group_info { + ecl_well_map, + schedule, + summary_state, + report_step_idx, + iteration_idx, + deferred_logger, + well_state, + group_state, + comm, + /*glift_debug=*/false + }; + group_info.initialize(); + GLiftSyncGroups sync_groups; + for (const auto& well : well_model.localNonshutWells()) { + if (group_info.hasWell(well->name())) { + auto glift = std::make_unique (*well, *(simulator.get()), summary_state, + deferred_logger, well_state, group_state, group_info, sync_groups, + comm, /*glift_debug=*/false + ); + const auto& controls = well->wellEcl().productionControls(summary_state); + auto state = glift->runOptimize(iteration_idx); + if (state) { + state_map.emplace(well->name(), std::move(state)); + glift_wells.emplace(well->name(), std::move(glift)); + } + prod_wells.insert({well->name(), well.get()}); + } + } + GasLiftStage2 glift2 {report_step_idx, + comm, + schedule, + summary_state, + deferred_logger, + well_state, + group_state, + prod_wells, + glift_wells, + group_info, + state_map, + /*glift_debug=*/false + }; + glift2.runOptimize(); + + // no group rate limits -> alq is limited by GLIFTOPT item 1 + if (report_step_idx == 5) { + BOOST_CHECK_CLOSE(group_info.alqRate("PLAT-A")*86400, 450000, 1e-8); + } + // group oil rate limit -> give sufficient alq to produce group target + if (report_step_idx == 6) { + BOOST_CHECK_CLOSE(group_info.alqRate("PLAT-A")*86400, 150000, 1e-8); + BOOST_CHECK(group_info.oilRate("PLAT-A") > *group_info.oilTarget("PLAT-A")); + } + + // same as above but with GRAT limit on sub-group (i.e need some more alq) + if (report_step_idx == 7) { + BOOST_CHECK_CLOSE(group_info.alqRate("PLAT-A")*86400, 187500, 1e-8); + BOOST_CHECK(group_info.oilRate("PLAT-A") > *group_info.oilTarget("PLAT-A")); + } + + // max alq + gas limit + if (report_step_idx == 8) { + BOOST_CHECK_CLOSE( (group_info.alqRate("PLAT-A") + group_info.gasRate("PLAT-A"))*86400, 800000, 1); + } + well_model.endTimeStep(); + well_model.endEpisode(); + } +} \ No newline at end of file From 39db86211692634e395b929523339b58bba15a6c Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Fri, 28 Nov 2025 14:29:18 +0100 Subject: [PATCH 6/8] Store and update well potentials during gaslift optimization --- .../wells/BlackoilWellModelGasLift_impl.hpp | 4 -- opm/simulators/wells/GasLiftGroupInfo.cpp | 6 +++ .../wells/GasLiftSingleWellGeneric.cpp | 47 ++++++++++++------- .../wells/GasLiftSingleWellGeneric.hpp | 24 ++++++++++ opm/simulators/wells/GasLiftStage2.cpp | 18 +++---- opm/simulators/wells/GasLiftWellState.hpp | 18 +++++++ 6 files changed, 88 insertions(+), 29 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp index 8b51623555c..5263fda3d8f 100644 --- a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp @@ -136,10 +136,6 @@ maybeDoGasLiftOptimize(const Simulator& simulator, num_wells_changed = glift_wells.size(); } num_wells_changed = simulator.vanguard().gridView().comm().sum(num_wells_changed); - - if (num_wells_changed > 0) { - updateWellPotentials(simulator, well_container, node_pressures, wellState, deferred_logger); - } return num_wells_changed > 0; } diff --git a/opm/simulators/wells/GasLiftGroupInfo.cpp b/opm/simulators/wells/GasLiftGroupInfo.cpp index 93f99cebc97..90c2ce8cc4d 100644 --- a/opm/simulators/wells/GasLiftGroupInfo.cpp +++ b/opm/simulators/wells/GasLiftGroupInfo.cpp @@ -547,6 +547,9 @@ getProducerWellRates_(const Well* well, int well_index) Scalar water_rate = water_pot; Scalar gas_rate = gas_pot; + // The well rates are potentially limited by the targets + // The other phases are scaled accordingly. i.e. we assume the phase fractions are the same + // This is not 100% true but the best we can do without solving the well equation if (controls.hasControl(Well::ProducerCMode::ORAT) && oil_rate > static_cast(controls.oil_rate)) { water_rate *= (static_cast(controls.oil_rate) / oil_rate); gas_rate *= (static_cast(controls.oil_rate) / oil_rate); @@ -673,6 +676,9 @@ initializeGroupRatesRecursive_(const Group& group) group.name(), oil_rate, gas_rate, water_rate, alq); } + // The group rates are potentially limited by the group targets + // The other phases are scaled accordingly. i.e. we assume the phase fractions are the same + // This is not 100% true but the best we can do without solving the well equation if (oil_target && oil_rate > *oil_target) { water_rate *= (*oil_target/oil_rate); gas_rate *= (*oil_target/oil_rate); diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp index eaa999d0d10..5ca1fe7079c 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp @@ -127,10 +127,13 @@ calcIncOrDecGradient(Scalar oil_rate, auto grad = calcEcoGradient_(oil_rate, new_rates.oil, gas_rate, new_rates.gas, increase); return GradInfo(grad, new_rates.oil, + rates.oil, new_rates.oil_is_limited, new_rates.gas, + rates.gas, new_rates.gas_is_limited, new_rates.water, + rates.water, new_rates.water_is_limited, new_alq, alq_is_limited); @@ -163,23 +166,23 @@ runOptimize(const int iteration_idx) auto& ws = this->well_state_.well(this->well_name_); ws.alq_state.set(alq); const auto& pu = this->well_state_.phaseUsageInfo(); - const bool isThp = (ws.production_cmode == Well::ProducerCMode::THP); - if (isThp) { - std::vector well_pot(pu.numActivePhases(), 0.0); - if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) { - const int oil_pos = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx); - well_pot[oil_pos] = state->oilRate(); - } - if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) { - const int water_pos = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx); - well_pot[water_pos] = state->waterRate(); - } - if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) { - const int gas_pos = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx); - well_pot[gas_pos] = state->gasRate(); - } - ws.well_potentials = well_pot; + // since alq is changed we also need to update the well potentials + // the well solution itself will be updated by solving the well equation + // after the gaslift optimization + std::vector well_pot(pu.numActivePhases(), 0.0); + if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) { + const int oil_pos = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx); + well_pot[oil_pos] = state->oilPot(); } + if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) { + const int water_pos = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx); + well_pot[water_pos] = state->waterPot(); + } + if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) { + const int gas_pos = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx); + well_pot[gas_pos] = state->gasPot(); + } + ws.well_potentials = well_pot; } } } @@ -300,6 +303,8 @@ addOrSubtractAlqIncrement_(Scalar alq, bool increase) const if (limited && checkALQequal_(orig_alq, alq)) alq_opt = std::nullopt; + // alq_is_limited is used to check if we can increase the alq or not + // i.e. only return alq_is_limited = true if both limited and increase are true return {alq_opt, limited && increase }; } @@ -766,6 +771,10 @@ getLimitedRatesFromRates_(const BasicRates& rates) const bool oil_is_limited = false; bool gas_is_limited = false; bool water_is_limited = false; + + // The well rates are potentially limited by the targets + // The other phases are scaled accordingly. i.e. we assume the phase fractions are the same + // This is not 100% true but the best we can do without solving the well equation if (hasProductionControl_(Rate::oil)) { auto target = getProductionTarget_(Rate::oil); if (oil_rate > target) { @@ -805,8 +814,11 @@ getLimitedRatesFromRates_(const BasicRates& rates) const } } return LimitedRates {oil_rate, + rates.oil, gas_rate, + rates.gas, water_rate, + rates.water, oil_is_limited, gas_is_limited, water_is_limited, @@ -1251,12 +1263,15 @@ runOptimizeLoop_(bool increase) increase_opt = std::nullopt; } ret_value = std::make_unique>(new_rates.oil, + new_rates.oil_pot, new_rates.oil_is_limited, new_rates.gas, + new_rates.gas_pot, new_rates.gas_is_limited, cur_alq, alq_is_limited, new_rates.water, + new_rates.water_pot, new_rates.water_is_limited, increase_opt); return ret_value; diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.hpp b/opm/simulators/wells/GasLiftSingleWellGeneric.hpp index 23266e0fbea..f48f10a8a67 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.hpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.hpp @@ -63,19 +63,25 @@ class GasLiftSingleWellGeneric : public GasLiftCommon GradInfo() = default; GradInfo(Scalar grad_, Scalar new_oil_rate_, + Scalar new_oil_pot_, bool oil_is_limited_, Scalar new_gas_rate_, + Scalar new_gas_pot_, bool gas_is_limited_, Scalar new_water_rate_, + Scalar new_water_pot_, bool water_is_limited_, Scalar alq_, bool alq_is_limited_) : grad{grad_} , new_oil_rate{new_oil_rate_} + , new_oil_pot{new_oil_pot_} , oil_is_limited{oil_is_limited_} , new_gas_rate{new_gas_rate_} + , new_gas_pot{new_gas_pot_} , gas_is_limited{gas_is_limited_} , new_water_rate{new_water_rate_} + , new_water_pot{new_water_pot_} , water_is_limited{water_is_limited_} , alq{alq_} , alq_is_limited{alq_is_limited_} @@ -83,10 +89,13 @@ class GasLiftSingleWellGeneric : public GasLiftCommon Scalar grad; Scalar new_oil_rate; + Scalar new_oil_pot; bool oil_is_limited; Scalar new_gas_rate; + Scalar new_gas_pot; bool gas_is_limited; Scalar new_water_rate; + Scalar new_water_pot; bool water_is_limited; Scalar alq; bool alq_is_limited; @@ -179,13 +188,19 @@ class GasLiftSingleWellGeneric : public GasLiftCommon { enum class LimitType {well, group, none}; LimitedRates(Scalar oil_, + Scalar oil_pot_, Scalar gas_, + Scalar gas_pot_, Scalar water_, + Scalar water_pot_, bool oil_is_limited_, bool gas_is_limited_, bool water_is_limited_, bool bhp_is_limited_) : BasicRates(oil_, gas_, water_, bhp_is_limited_) + , oil_pot(oil_pot_) + , gas_pot(gas_pot_) + , water_pot(water_pot_) , oil_is_limited{oil_is_limited_} , gas_is_limited{gas_is_limited_} , water_is_limited{water_is_limited_} @@ -194,10 +209,16 @@ class GasLiftSingleWellGeneric : public GasLiftCommon } LimitedRates(const BasicRates& rates, + Scalar oil_pot_, + Scalar gas_pot_, + Scalar water_pot_, bool oil_is_limited_, bool gas_is_limited_, bool water_is_limited_) : BasicRates(rates) + , oil_pot(oil_pot_) + , gas_pot(gas_pot_) + , water_pot(water_pot_) , oil_is_limited{oil_is_limited_} , gas_is_limited{gas_is_limited_} , water_is_limited{water_is_limited_} @@ -213,6 +234,9 @@ class GasLiftSingleWellGeneric : public GasLiftCommon // For a given ALQ value, were the rates limited due to group targets // or due to well targets? LimitType limit_type; + Scalar oil_pot; + Scalar gas_pot; + Scalar water_pot; bool oil_is_limited; bool gas_is_limited; bool water_is_limited; diff --git a/opm/simulators/wells/GasLiftStage2.cpp b/opm/simulators/wells/GasLiftStage2.cpp index a7864e021ef..9a3ad1817b1 100644 --- a/opm/simulators/wells/GasLiftStage2.cpp +++ b/opm/simulators/wells/GasLiftStage2.cpp @@ -123,20 +123,20 @@ addOrRemoveALQincrement_(GradMap &grad_map, well_name, (add ? "adding" : "subtracting"), old_alq, new_alq); this->displayDebugMessage_(msg); } - this->well_state_.well(well_name).alq_state.set(gi.alq); + auto& ws = this->well_state_.well(well_name); + ws.alq_state.set(gi.alq); std::vector well_pot(this->well_state_.numPhases(), 0.0); const auto& pu = this->well_state_.phaseUsageInfo(); if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) { - well_pot[pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx)] = gi.new_oil_rate; + well_pot[pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx)] = gi.new_oil_pot; } if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) { - well_pot[pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx)] = gi.new_water_rate; + well_pot[pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx)] = gi.new_water_pot; } if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) { - well_pot[pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx)] = gi.new_gas_rate; + well_pot[pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx)] = gi.new_gas_pot; } - - this->well_state_[well_name].well_potentials = well_pot; + ws.well_potentials = well_pot; } template @@ -861,10 +861,10 @@ computeDelta(const std::string& well_name, bool add) delta_water = factor * (gi.new_water_rate - state.waterRate()); delta_alq = factor * (gi.alq - state.alq()); } - state.update(gi.new_oil_rate, gi.oil_is_limited, - gi.new_gas_rate, gi.gas_is_limited, + state.update(gi.new_oil_rate, gi.new_oil_pot, gi.oil_is_limited, + gi.new_gas_rate, gi.new_gas_pot, gi.gas_is_limited, gi.alq, gi.alq_is_limited, - gi.new_water_rate, gi.water_is_limited, add); + gi.new_water_rate, gi.new_water_pot, gi.water_is_limited, add); } // and communicate the results diff --git a/opm/simulators/wells/GasLiftWellState.hpp b/opm/simulators/wells/GasLiftWellState.hpp index 1b14eed7036..f38b90b2262 100644 --- a/opm/simulators/wells/GasLiftWellState.hpp +++ b/opm/simulators/wells/GasLiftWellState.hpp @@ -30,21 +30,27 @@ class GasLiftWellState { public: GasLiftWellState(Scalar oil_rate, + Scalar oil_pot, bool oil_is_limited, Scalar gas_rate, + Scalar gas_pot, bool gas_is_limited, Scalar alq, bool alq_is_limited, Scalar water_rate, + Scalar water_pot, bool water_is_limited, std::optional increase) : oil_rate_{oil_rate} + , oil_pot_{oil_pot} , oil_is_limited_{oil_is_limited} , gas_rate_{gas_rate} + , gas_pot_{gas_pot} , gas_is_limited_{gas_is_limited} , alq_{alq} , alq_is_limited_{alq_is_limited} , water_rate_{water_rate} + , water_pot_{water_pot} , water_is_limited_{water_is_limited} , increase_{increase} {} @@ -54,41 +60,53 @@ class GasLiftWellState bool alqIsLimited() const { return alq_is_limited_; } bool gasIsLimited() const { return gas_is_limited_; } Scalar gasRate() const { return gas_rate_; } + Scalar gasPot() const { return gas_pot_; } std::pair getRates() { return {oil_rate_, gas_rate_}; } std::optional increase() const { return increase_; } bool oilIsLimited() const { return oil_is_limited_; } Scalar oilRate() const { return oil_rate_; } Scalar waterRate() const { return water_rate_; } + Scalar oilPot() const { return oil_pot_; } + Scalar waterPot() const { return water_pot_; } bool waterIsLimited() const { return water_is_limited_; } void update(Scalar oil_rate, + Scalar oil_pot, bool oil_is_limited, Scalar gas_rate, + Scalar gas_pot, bool gas_is_limited, Scalar alq, bool alq_is_limited, Scalar water_rate, + Scalar water_pot, Scalar water_is_limited, bool increase) { oil_rate_ = oil_rate; + oil_pot_ = oil_pot; oil_is_limited_ = oil_is_limited; gas_rate_ = gas_rate; + gas_pot_ = gas_pot; gas_is_limited_ = gas_is_limited; alq_ = alq; alq_is_limited_ = alq_is_limited; water_rate_ = water_rate; + water_pot_ = water_pot; water_is_limited_ = water_is_limited; increase_ = increase; } private: Scalar oil_rate_; + Scalar oil_pot_; bool oil_is_limited_; Scalar gas_rate_; + Scalar gas_pot_; bool gas_is_limited_; Scalar alq_; bool alq_is_limited_; Scalar water_rate_; + Scalar water_pot_; bool water_is_limited_; std::optional increase_; }; From ad6bc9add5352b1dfea7e9bff13d07d9f6ee3b2e Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Tue, 2 Dec 2025 12:16:35 +0100 Subject: [PATCH 7/8] Revert change where only alq_is_limited is true when increase is true --- opm/simulators/wells/GasLiftSingleWellGeneric.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp index 5ca1fe7079c..d0b6cf00b46 100644 --- a/opm/simulators/wells/GasLiftSingleWellGeneric.cpp +++ b/opm/simulators/wells/GasLiftSingleWellGeneric.cpp @@ -303,9 +303,7 @@ addOrSubtractAlqIncrement_(Scalar alq, bool increase) const if (limited && checkALQequal_(orig_alq, alq)) alq_opt = std::nullopt; - // alq_is_limited is used to check if we can increase the alq or not - // i.e. only return alq_is_limited = true if both limited and increase are true - return {alq_opt, limited && increase }; + return {alq_opt, limited}; } template From b21ac4f5af4d6dc749b666e5f235f900e711bb9e Mon Sep 17 00:00:00 2001 From: Tor Harald Sandve Date: Wed, 3 Dec 2025 13:38:07 +0100 Subject: [PATCH 8/8] Proper fix for alq_is_limited --- opm/simulators/wells/GasLiftStage2.cpp | 12 ------------ tests/test_glift1.cpp | 7 +++++++ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/opm/simulators/wells/GasLiftStage2.cpp b/opm/simulators/wells/GasLiftStage2.cpp index 9a3ad1817b1..935e108497c 100644 --- a/opm/simulators/wells/GasLiftStage2.cpp +++ b/opm/simulators/wells/GasLiftStage2.cpp @@ -197,18 +197,6 @@ checkRateAlreadyLimited_(const std::string& well_name, do_check = true; } } - else { - // If current_increase is not defined, it means that stage1 - // was unable to either increase nor decrease the ALQ. If the - // initial rates stored in "state" is limited, and if - // "increase" is true, it is not likely that adding ALQ will - // cause the new rates not to be limited. However, if - // "increase" is false, subtracting ALQ can make the new rates - // not limited. - if (increase) { - do_check = true; - } - } if (do_check) { if (state.gasIsLimited() || state.oilIsLimited() || state.alqIsLimited() || state.waterIsLimited()) { diff --git a/tests/test_glift1.cpp b/tests/test_glift1.cpp index 8a0c01b9957..c8946d30f00 100644 --- a/tests/test_glift1.cpp +++ b/tests/test_glift1.cpp @@ -384,6 +384,13 @@ BOOST_AUTO_TEST_CASE(G2) if (report_step_idx == 6) { BOOST_CHECK_CLOSE(group_info.alqRate("PLAT-A")*86400, 150000, 1e-8); BOOST_CHECK(group_info.oilRate("PLAT-A") > *group_info.oilTarget("PLAT-A")); + + // also test wells + std::vector> wells = {{"B-1H",25000}, {"B-2H",37500}, {"B-3H",25000}, + {"C-1H",37500}, {"C-2H",25000}}; + for (auto well: wells) { + BOOST_CHECK_CLOSE(state_map[well.first]->alq()*86400, well.second, 1e-8); + } } // same as above but with GRAT limit on sub-group (i.e need some more alq)