From be9fa5e1d8520c851b41c56d9eef42b4e44297b4 Mon Sep 17 00:00:00 2001 From: Kai Bao Date: Wed, 5 Nov 2025 15:43:39 +0100 Subject: [PATCH 1/5] adding the group name to the SingleWellState:;group_target so we might be able to use it to identifiy the NONE control group --- opm/simulators/wells/BlackoilWellModelGeneric.cpp | 2 +- opm/simulators/wells/GroupStateHelper.cpp | 10 +++++----- opm/simulators/wells/GroupStateHelper.hpp | 14 +++++++------- opm/simulators/wells/SingleWellState.hpp | 2 +- opm/simulators/wells/WellGroupControls.cpp | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index 577bc2a12ca..58beb9ea328 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -1336,7 +1336,7 @@ updateAndCommunicateGroupData(const int reportStepIdx, const Scalar efficiencyFactor = well->wellEcl().getEfficiencyFactor() * ws.efficiency_scaling_factor; // Translate injector type from control to Phase. - std::optional group_target; + std::optional> group_target; if (well->isProducer()) { group_target = group_state_helper.getWellGroupTargetProducer( well->name(), diff --git a/opm/simulators/wells/GroupStateHelper.cpp b/opm/simulators/wells/GroupStateHelper.cpp index cddb2cc15f4..aa14cd44f3e 100644 --- a/opm/simulators/wells/GroupStateHelper.cpp +++ b/opm/simulators/wells/GroupStateHelper.cpp @@ -198,7 +198,7 @@ GroupStateHelper::checkGroupConstraintsInj(const std::strin if (this->schedule_.hasWell(name) && this->wellState().well(name).group_target) { // for wells we already have computed the target Scalar scale = 1.0; - const Scalar group_target_rate_available = *this->wellState().well(name).group_target; + const Scalar group_target_rate_available = (*this->wellState().well(name).group_target).second; const Scalar current_well_rate_available = tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. if (current_well_rate_available > 1e-12) { @@ -371,7 +371,7 @@ GroupStateHelper::checkGroupConstraintsProd(const std::stri // Switch sign since 'rates' are negative for producers. const Scalar current_well_rate_available = -tcalc.calcModeRateFromRates(rates); - const Scalar group_target_rate_available = *this->wellState().well(name).group_target; + const Scalar group_target_rate_available = (*this->wellState().well(name).group_target).second; Scalar scale = 1.0; if (current_well_rate_available > 1e-12) { scale = group_target_rate_available / current_well_rate_available; @@ -617,7 +617,7 @@ GroupStateHelper::getWellGroupTargetInjector(const std::str } template -std::optional +std::optional> GroupStateHelper::getWellGroupTargetProducer(const std::string& name, const std::string& parent, const Group& group, @@ -740,8 +740,8 @@ GroupStateHelper::getWellGroupTargetProducer(const std::str target *= local_fraction_lambda(chain[ii + 1], name); } } - // Avoid negative target rates coming from too large local reductions. - return std::max(Scalar(0.0), target / efficiency_factor); + // Avoid negative target rates comming from too large local reductions. + return {group.name(), std::max(Scalar(0.0), target / efficiency_factor)}; } template diff --git a/opm/simulators/wells/GroupStateHelper.hpp b/opm/simulators/wells/GroupStateHelper.hpp index 8f3634d7d3e..7ebd383ce38 100644 --- a/opm/simulators/wells/GroupStateHelper.hpp +++ b/opm/simulators/wells/GroupStateHelper.hpp @@ -151,13 +151,13 @@ class GroupStateHelper const std::vector& resv_coeff, DeferredLogger& deferred_logger) const; - std::optional getWellGroupTargetProducer(const std::string& name, - const std::string& parent, - const Group& group, - const Scalar* rates, - const Scalar efficiency_factor, - const std::vector& resv_coeff, - DeferredLogger& deferred_logger) const; + std::optional> getWellGroupTargetProducer(const std::string& name, + const std::string& parent, + const Group& group, + const Scalar* rates, + const Scalar efficiency_factor, + const std::vector& resv_coeff, + DeferredLogger& deferred_logger) const; GuideRate::RateVector getWellRateVector(const std::string& name) const; diff --git a/opm/simulators/wells/SingleWellState.hpp b/opm/simulators/wells/SingleWellState.hpp index 73a12d5901b..bb3332c6b03 100644 --- a/opm/simulators/wells/SingleWellState.hpp +++ b/opm/simulators/wells/SingleWellState.hpp @@ -127,7 +127,7 @@ class SingleWellState { std::vector prev_surface_rates; PerfData perf_data; bool trivial_group_target; - std::optional group_target; + std::optional> group_target; SegmentState segments; Events events; WellInjectorCMode injection_cmode{WellInjectorCMode::CMODE_UNDEFINED}; diff --git a/opm/simulators/wells/WellGroupControls.cpp b/opm/simulators/wells/WellGroupControls.cpp index 9ead1b8590d..b3098cb0277 100644 --- a/opm/simulators/wells/WellGroupControls.cpp +++ b/opm/simulators/wells/WellGroupControls.cpp @@ -126,7 +126,7 @@ getGroupInjectionControl(const Group& group, const auto target_rate = well_state.well(well_.indexOfWell()).group_target; if (target_rate) { - control_eq = injection_rate - *target_rate; + control_eq = injection_rate - target_rate->second; } else { const auto& controls = well.injectionControls(summaryState); control_eq = bhp - controls.bhp_limit; @@ -197,7 +197,7 @@ getGroupInjectionTargetRate(const Group& group, return std::nullopt; } - return well_state.well(well_.indexOfWell()).group_target; + return well_state.well(well_.indexOfWell()).group_target->second; } template @@ -275,7 +275,7 @@ getGroupProductionControl(const Group& group, const auto target_rate = well_state.well(well_.indexOfWell()).group_target; if (target_rate) { const auto current_rate = -tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. - control_eq = current_rate - *target_rate; + control_eq = current_rate - target_rate->second; } else { const auto& controls = well.productionControls(summaryState); control_eq = bhp - controls.bhp_limit; @@ -342,7 +342,7 @@ ::TargetCalculator tcalc(currentGroupControl, if (!target_rate) { return 1.0; } - if (*target_rate == 0.0) { + if (target_rate->second == 0.0) { return 0.0; } const auto& ws = well_state.well(well_.indexOfWell()); @@ -350,7 +350,7 @@ ::TargetCalculator tcalc(currentGroupControl, const auto current_rate = -tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. Scalar scale = 1.0; if (current_rate > 1e-14) - scale = *target_rate / current_rate; + scale = target_rate->second / current_rate; return scale; } From 7385b3c35d1f54cc12400a586a0ddc26e12ed9b3 Mon Sep 17 00:00:00 2001 From: Kai Bao Date: Thu, 6 Nov 2025 11:59:23 +0100 Subject: [PATCH 2/5] marking some groups are NONE group control based whether the group contribute to some SingleWellState::group_target --- .../wells/BlackoilWellModelGeneric.cpp | 76 +++++++++++++++++++ .../wells/BlackoilWellModelGeneric.hpp | 2 + .../wells/BlackoilWellModel_impl.hpp | 2 + opm/simulators/wells/GroupState.cpp | 7 ++ opm/simulators/wells/GroupState.hpp | 1 + opm/simulators/wells/GroupStateHelper.cpp | 12 +-- opm/simulators/wells/GroupStateHelper.hpp | 28 +++---- 7 files changed, 108 insertions(+), 20 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index 58beb9ea328..5c384ad4953 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -1947,6 +1947,82 @@ operator==(const BlackoilWellModelGeneric& rhs) const && this->gen_gaslift_ == rhs.gen_gaslift_; } +template +void +BlackoilWellModelGeneric:: +updateNONEProductionGroups(DeferredLogger& deferred_logger) +{ + auto& group_state = this->groupState(); + const auto& prod_group_controls = group_state.get_production_controls(); + if (prod_group_controls.empty()) { + return; + } + + const auto& well_state = this->wellState(); + // numbers of the group production controls + // one group can have multiple controls, including NONE + const std::size_t size_pc = prod_group_controls.size(); + // Collect groups that currently provide production targets to any well on this rank + std::unordered_set targeted_production_groups; // TODO: better name + targeted_production_groups.reserve(size_pc); + + for (const auto& wname : well_state.wells()) { + const auto& ws = well_state.well(wname); + if (ws.producer && ws.production_cmode == WellProducerCMode::GRUP) { + const auto& group_target = ws.group_target; + if (group_target.has_value()) { + targeted_production_groups.insert(group_target->first); + } + } + } + + // parallel communication to synchronize production groups used on all processes + std::vector gnames; + gnames.reserve(size_pc); + for (const auto& gprod : prod_group_controls) { + gnames.push_back(gprod.first); + } + + std::vector local_used(size_pc, 0); + for (std::size_t i = 0; i < size_pc; ++i) { + if (targeted_production_groups.find(gnames[i]) != targeted_production_groups.end()) { + local_used[i] = 1; + } + } + + std::vector global_used(size_pc, 0); + + // with the following code, we only need to communicate once + if (comm_.size() > 1 && size_pc > 0) { + std::vector gathered(comm_.size() * size_pc, 0); + comm_.allgather(local_used.data(), static_cast(size_pc), gathered.data()); + + for (int r = 0; r < comm_.size(); ++r) { + const std::size_t base = static_cast(r) * size_pc; + for (std::size_t i = 0; i < size_pc; ++i) { + global_used[i] = global_used[i] || gathered[base + i]; + } + } + } else { + global_used = local_used; + } + + for (std::size_t i = 0; i < size_pc; ++i) { + if (global_used[i] > 0) { + continue; + } + const auto& gname = gnames[i]; + if (group_state.production_control(gname) != Group::ProductionCMode::NONE) { + if (comm_.rank() == 0) { + const std::string msg = fmt::format("Production group {} has no constraints active, setting control mode to NONE", gname); + deferred_logger.info(msg); + } + group_state.production_control(gname, Group::ProductionCMode::NONE); + } + } +} + + template class BlackoilWellModelGeneric; #if FLOW_INSTANTIATE_FLOAT diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.hpp b/opm/simulators/wells/BlackoilWellModelGeneric.hpp index 826f24fed68..6a69a175d1d 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.hpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.hpp @@ -508,6 +508,8 @@ class BlackoilWellModelGeneric void assignMassGasRate(data::Wells& wsrpt, const Scalar gasDensity) const; + void updateNONEProductionGroups(DeferredLogger& deferred_logger); + Schedule& schedule_; const SummaryState& summaryState_; diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index b1e6ca65db4..4c80ed8428b 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -742,6 +742,8 @@ namespace Opm { this->calculateProductivityIndexValues(local_deferredLogger); + this->updateNONEProductionGroups(local_deferredLogger); + this->commitWGState(); const Opm::Parallel::Communication& comm = grid().comm(); diff --git a/opm/simulators/wells/GroupState.cpp b/opm/simulators/wells/GroupState.cpp index 391ec9a8e11..11ecb077858 100644 --- a/opm/simulators/wells/GroupState.cpp +++ b/opm/simulators/wells/GroupState.cpp @@ -390,6 +390,13 @@ GroupState::production_control(const std::string& gname) const return group_iter->second; } +template +const std::map& +GroupState::get_production_controls() const +{ + return this->production_controls; +}; + //------------------------------------------------------------------------- template diff --git a/opm/simulators/wells/GroupState.hpp b/opm/simulators/wells/GroupState.hpp index ffa655faabf..a307cd3aa66 100644 --- a/opm/simulators/wells/GroupState.hpp +++ b/opm/simulators/wells/GroupState.hpp @@ -97,6 +97,7 @@ class GroupState { bool has_production_control(const std::string& gname) const; void production_control(const std::string& gname, Group::ProductionCMode cmode); Group::ProductionCMode production_control(const std::string& gname) const; + const std::map& get_production_controls() const; bool has_injection_control(const std::string& gname, Phase phase) const; void injection_control(const std::string& gname, Phase phase, Group::InjectionCMode cmode); diff --git a/opm/simulators/wells/GroupStateHelper.cpp b/opm/simulators/wells/GroupStateHelper.cpp index aa14cd44f3e..48e0fddf033 100644 --- a/opm/simulators/wells/GroupStateHelper.cpp +++ b/opm/simulators/wells/GroupStateHelper.cpp @@ -483,7 +483,7 @@ GroupStateHelper::getProductionGroupRateVector(const std::s } template -std::optional +std::optional> GroupStateHelper::getWellGroupTargetInjector(const std::string& name, const std::string& parent, const Group& group, @@ -612,8 +612,8 @@ GroupStateHelper::getWellGroupTargetInjector(const std::str target *= local_fraction_lambda(chain[ii + 1], chain[ii + 1]); } } - // Avoid negative target rates comming from too large local reductions. - return std::max(Scalar(0.0), target / efficiency_factor); + // Avoid negative target rates coming from too large local reductions. + return std::make_pair(group.name(), std::max(Scalar(0.0), target / efficiency_factor)); } template @@ -630,7 +630,7 @@ GroupStateHelper::getWellGroupTargetProducer(const std::str // 'parent' will be the name of 'group'. But if we recurse, 'name' and // 'parent' will stay fixed while 'group' will be higher up // in the group tree. - // Eficiencyfactor is the well efficiency factor for the first group the well is + // Efficiency factor is the well efficiency factor for the first group the well is // part of. Later it is the accumulated factor including the group efficiency factor // of the child of group. OPM_TIMEFUNCTION(); @@ -740,8 +740,8 @@ GroupStateHelper::getWellGroupTargetProducer(const std::str target *= local_fraction_lambda(chain[ii + 1], name); } } - // Avoid negative target rates comming from too large local reductions. - return {group.name(), std::max(Scalar(0.0), target / efficiency_factor)}; + // Avoid negative target rates coming from too large local reductions. + return std::make_pair(group.name(), std::max(Scalar(0.0), target / efficiency_factor)); } template diff --git a/opm/simulators/wells/GroupStateHelper.hpp b/opm/simulators/wells/GroupStateHelper.hpp index 7ebd383ce38..e5a0990fec9 100644 --- a/opm/simulators/wells/GroupStateHelper.hpp +++ b/opm/simulators/wells/GroupStateHelper.hpp @@ -142,22 +142,22 @@ class GroupStateHelper GuideRate::RateVector getProductionGroupRateVector(const std::string& group_name) const; - std::optional getWellGroupTargetInjector(const std::string& name, - const std::string& parent, - const Group& group, - const Scalar* rates, - const Phase injection_phase, - const Scalar efficiency_factor, - const std::vector& resv_coeff, - DeferredLogger& deferred_logger) const; + std::optional> getWellGroupTargetInjector(const std::string& name, + const std::string& parent, + const Group& group, + const Scalar* rates, + const Phase injection_phase, + const Scalar efficiency_factor, + const std::vector& resv_coeff, + DeferredLogger& deferred_logger) const; std::optional> getWellGroupTargetProducer(const std::string& name, - const std::string& parent, - const Group& group, - const Scalar* rates, - const Scalar efficiency_factor, - const std::vector& resv_coeff, - DeferredLogger& deferred_logger) const; + const std::string& parent, + const Group& group, + const Scalar* rates, + const Scalar efficiency_factor, + const std::vector& resv_coeff, + DeferredLogger& deferred_logger) const; GuideRate::RateVector getWellRateVector(const std::string& name) const; From 9745d501ccf3d83ed6b0458560606e45d94b56b3 Mon Sep 17 00:00:00 2001 From: Kai Bao Date: Thu, 13 Nov 2025 10:10:38 +0100 Subject: [PATCH 3/5] using struct for SingleWellState::group_target hopefully more readable and more extensiable. --- .../wells/BlackoilWellModelGeneric.cpp | 5 +-- opm/simulators/wells/GroupStateHelper.cpp | 14 ++++---- opm/simulators/wells/GroupStateHelper.hpp | 34 ++++++++++--------- opm/simulators/wells/SingleWellState.hpp | 17 +++++++++- opm/simulators/wells/WellGroupControls.cpp | 10 +++--- 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index 5c384ad4953..530b881bb2f 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -1336,7 +1336,8 @@ updateAndCommunicateGroupData(const int reportStepIdx, const Scalar efficiencyFactor = well->wellEcl().getEfficiencyFactor() * ws.efficiency_scaling_factor; // Translate injector type from control to Phase. - std::optional> group_target; + using GroupTarget = typename SingleWellState::GroupTarget; + std::optional group_target; if (well->isProducer()) { group_target = group_state_helper.getWellGroupTargetProducer( well->name(), @@ -1971,7 +1972,7 @@ updateNONEProductionGroups(DeferredLogger& deferred_logger) if (ws.producer && ws.production_cmode == WellProducerCMode::GRUP) { const auto& group_target = ws.group_target; if (group_target.has_value()) { - targeted_production_groups.insert(group_target->first); + targeted_production_groups.insert(group_target->group_name); } } } diff --git a/opm/simulators/wells/GroupStateHelper.cpp b/opm/simulators/wells/GroupStateHelper.cpp index 48e0fddf033..242622bbca4 100644 --- a/opm/simulators/wells/GroupStateHelper.cpp +++ b/opm/simulators/wells/GroupStateHelper.cpp @@ -198,7 +198,8 @@ GroupStateHelper::checkGroupConstraintsInj(const std::strin if (this->schedule_.hasWell(name) && this->wellState().well(name).group_target) { // for wells we already have computed the target Scalar scale = 1.0; - const Scalar group_target_rate_available = (*this->wellState().well(name).group_target).second; + const auto& group_target = this->wellState().well(name).group_target; + const Scalar group_target_rate_available = group_target->target_value; const Scalar current_well_rate_available = tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. if (current_well_rate_available > 1e-12) { @@ -371,7 +372,8 @@ GroupStateHelper::checkGroupConstraintsProd(const std::stri // Switch sign since 'rates' are negative for producers. const Scalar current_well_rate_available = -tcalc.calcModeRateFromRates(rates); - const Scalar group_target_rate_available = (*this->wellState().well(name).group_target).second; + const auto& group_target = this->wellState().well(name).group_target; + const Scalar group_target_rate_available = group_target->target_value; Scalar scale = 1.0; if (current_well_rate_available > 1e-12) { scale = group_target_rate_available / current_well_rate_available; @@ -483,7 +485,7 @@ GroupStateHelper::getProductionGroupRateVector(const std::s } template -std::optional> +std::optional::GroupTarget> GroupStateHelper::getWellGroupTargetInjector(const std::string& name, const std::string& parent, const Group& group, @@ -613,11 +615,11 @@ GroupStateHelper::getWellGroupTargetInjector(const std::str } } // Avoid negative target rates coming from too large local reductions. - return std::make_pair(group.name(), std::max(Scalar(0.0), target / efficiency_factor)); + return GroupTarget{group.name(), std::max(Scalar(0.0), target / efficiency_factor)}; } template -std::optional> +std::optional::GroupTarget> GroupStateHelper::getWellGroupTargetProducer(const std::string& name, const std::string& parent, const Group& group, @@ -741,7 +743,7 @@ GroupStateHelper::getWellGroupTargetProducer(const std::str } } // Avoid negative target rates coming from too large local reductions. - return std::make_pair(group.name(), std::max(Scalar(0.0), target / efficiency_factor)); + return GroupTarget{group.name(), std::max(Scalar(0.0), target / efficiency_factor)}; } template diff --git a/opm/simulators/wells/GroupStateHelper.hpp b/opm/simulators/wells/GroupStateHelper.hpp index e5a0990fec9..961f3335bf5 100644 --- a/opm/simulators/wells/GroupStateHelper.hpp +++ b/opm/simulators/wells/GroupStateHelper.hpp @@ -142,22 +142,24 @@ class GroupStateHelper GuideRate::RateVector getProductionGroupRateVector(const std::string& group_name) const; - std::optional> getWellGroupTargetInjector(const std::string& name, - const std::string& parent, - const Group& group, - const Scalar* rates, - const Phase injection_phase, - const Scalar efficiency_factor, - const std::vector& resv_coeff, - DeferredLogger& deferred_logger) const; - - std::optional> getWellGroupTargetProducer(const std::string& name, - const std::string& parent, - const Group& group, - const Scalar* rates, - const Scalar efficiency_factor, - const std::vector& resv_coeff, - DeferredLogger& deferred_logger) const; + using GroupTarget = typename SingleWellState::GroupTarget; + + std::optional getWellGroupTargetInjector(const std::string& name, + const std::string& parent, + const Group& group, + const Scalar* rates, + const Phase injection_phase, + const Scalar efficiency_factor, + const std::vector& resv_coeff, + DeferredLogger& deferred_logger) const; + + std::optional getWellGroupTargetProducer(const std::string& name, + const std::string& parent, + const Group& group, + const Scalar* rates, + const Scalar efficiency_factor, + const std::vector& resv_coeff, + DeferredLogger& deferred_logger) const; GuideRate::RateVector getWellRateVector(const std::string& name) const; diff --git a/opm/simulators/wells/SingleWellState.hpp b/opm/simulators/wells/SingleWellState.hpp index bb3332c6b03..980a7350ca8 100644 --- a/opm/simulators/wells/SingleWellState.hpp +++ b/opm/simulators/wells/SingleWellState.hpp @@ -118,6 +118,21 @@ class SingleWellState { vaporized_water = 3 }; + struct GroupTarget { + std::string group_name; + Scalar target_value; + + bool operator==(const GroupTarget& other) const { + return group_name == other.group_name && target_value == other.target_value; + } + + template + void serializeOp(Serializer& serializer) { + serializer(group_name); + serializer(target_value); + } + }; + std::vector well_potentials; std::vector productivity_index; std::vector implicit_ipr_a; @@ -127,7 +142,7 @@ class SingleWellState { std::vector prev_surface_rates; PerfData perf_data; bool trivial_group_target; - std::optional> group_target; + std::optional group_target; SegmentState segments; Events events; WellInjectorCMode injection_cmode{WellInjectorCMode::CMODE_UNDEFINED}; diff --git a/opm/simulators/wells/WellGroupControls.cpp b/opm/simulators/wells/WellGroupControls.cpp index b3098cb0277..21a1352083e 100644 --- a/opm/simulators/wells/WellGroupControls.cpp +++ b/opm/simulators/wells/WellGroupControls.cpp @@ -126,7 +126,7 @@ getGroupInjectionControl(const Group& group, const auto target_rate = well_state.well(well_.indexOfWell()).group_target; if (target_rate) { - control_eq = injection_rate - target_rate->second; + control_eq = injection_rate - target_rate->target_value; } else { const auto& controls = well.injectionControls(summaryState); control_eq = bhp - controls.bhp_limit; @@ -197,7 +197,7 @@ getGroupInjectionTargetRate(const Group& group, return std::nullopt; } - return well_state.well(well_.indexOfWell()).group_target->second; + return well_state.well(well_.indexOfWell()).group_target->target_value; } template @@ -275,7 +275,7 @@ getGroupProductionControl(const Group& group, const auto target_rate = well_state.well(well_.indexOfWell()).group_target; if (target_rate) { const auto current_rate = -tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. - control_eq = current_rate - target_rate->second; + control_eq = current_rate - target_rate->target_value; } else { const auto& controls = well.productionControls(summaryState); control_eq = bhp - controls.bhp_limit; @@ -342,7 +342,7 @@ ::TargetCalculator tcalc(currentGroupControl, if (!target_rate) { return 1.0; } - if (target_rate->second == 0.0) { + if (target_rate->target_value == 0.0) { return 0.0; } const auto& ws = well_state.well(well_.indexOfWell()); @@ -350,7 +350,7 @@ ::TargetCalculator tcalc(currentGroupControl, const auto current_rate = -tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. Scalar scale = 1.0; if (current_rate > 1e-14) - scale = target_rate->second / current_rate; + scale = target_rate->target_value / current_rate; return scale; } From 12fe9b076972edda21d7401b60adf33c96c70a81 Mon Sep 17 00:00:00 2001 From: Kai Bao Date: Sat, 15 Nov 2025 23:07:22 +0100 Subject: [PATCH 4/5] using comm_.sum() to do the parallel communication for the groups that contributes to rate target for wells. --- .../wells/BlackoilWellModelGeneric.cpp | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index 530b881bb2f..0aa23125e18 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -1960,56 +1960,48 @@ updateNONEProductionGroups(DeferredLogger& deferred_logger) } const auto& well_state = this->wellState(); - // numbers of the group production controls - // one group can have multiple controls, including NONE - const std::size_t size_pc = prod_group_controls.size(); - // Collect groups that currently provide production targets to any well on this rank - std::unordered_set targeted_production_groups; // TODO: better name - targeted_production_groups.reserve(size_pc); - - for (const auto& wname : well_state.wells()) { - const auto& ws = well_state.well(wname); + // numbers of the group production controls, including NONE mode + const std::size_t num_gpc = prod_group_controls.size(); + // collect groups that currently provide production targets to any well on this rank + std::unordered_set targeted_production_groups; + targeted_production_groups.reserve(num_gpc); + + for (std::size_t w = 0; w < well_state.size(); ++w) { + const auto& ws = well_state.well(w); if (ws.producer && ws.production_cmode == WellProducerCMode::GRUP) { const auto& group_target = ws.group_target; if (group_target.has_value()) { targeted_production_groups.insert(group_target->group_name); + } else { + const std::string msg = fmt::format("Well {} is on GRUP control but has no group target assigned.", ws.name); + deferred_logger.debug(msg); } } } // parallel communication to synchronize production groups used on all processes + // all the group names in prod_group_controls std::vector gnames; - gnames.reserve(size_pc); - for (const auto& gprod : prod_group_controls) { - gnames.push_back(gprod.first); - } + gnames.reserve(num_gpc); + // the group control is enforcing constraints for at least one well on this rank + // then it will be globally communicated across all the processes + std::vector production_control_used; + production_control_used.reserve(num_gpc); - std::vector local_used(size_pc, 0); - for (std::size_t i = 0; i < size_pc; ++i) { - if (targeted_production_groups.find(gnames[i]) != targeted_production_groups.end()) { - local_used[i] = 1; - } + for (const auto& kv : prod_group_controls) { + const auto& name = kv.first; + gnames.emplace_back(name); + const bool is_used = targeted_production_groups.find(name) != targeted_production_groups.end(); + production_control_used.emplace_back(is_used ? 1 : 0); } - std::vector global_used(size_pc, 0); - - // with the following code, we only need to communicate once - if (comm_.size() > 1 && size_pc > 0) { - std::vector gathered(comm_.size() * size_pc, 0); - comm_.allgather(local_used.data(), static_cast(size_pc), gathered.data()); - - for (int r = 0; r < comm_.size(); ++r) { - const std::size_t base = static_cast(r) * size_pc; - for (std::size_t i = 0; i < size_pc; ++i) { - global_used[i] = global_used[i] || gathered[base + i]; - } - } - } else { - global_used = local_used; + // parallel communication to synchronize production groups used on all processes + if (comm_.size() > 1 && num_gpc > 0) { + comm_.sum(production_control_used.data(), static_cast(num_gpc)); } - for (std::size_t i = 0; i < size_pc; ++i) { - if (global_used[i] > 0) { + for (std::size_t i = 0; i < num_gpc; ++i) { + if (production_control_used[i] > 0) { continue; } const auto& gname = gnames[i]; From ff3eba3763871decdab49ed3bbe97a6f859787f8 Mon Sep 17 00:00:00 2001 From: Kai Bao Date: Wed, 17 Dec 2025 13:07:57 +0100 Subject: [PATCH 5/5] not setting gas lift optimization group to be NONE trying to fix the test_glift1 regression. --- opm/simulators/wells/BlackoilWellModelGeneric.cpp | 9 ++++++++- opm/simulators/wells/BlackoilWellModelGeneric.hpp | 2 +- opm/simulators/wells/BlackoilWellModel_impl.hpp | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index 0aa23125e18..4b802c4bc31 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -1951,7 +1951,7 @@ operator==(const BlackoilWellModelGeneric& rhs) const template void BlackoilWellModelGeneric:: -updateNONEProductionGroups(DeferredLogger& deferred_logger) +updateNONEProductionGroups(const GasLiftOpt& glo, DeferredLogger& deferred_logger) { auto& group_state = this->groupState(); const auto& prod_group_controls = group_state.get_production_controls(); @@ -2006,6 +2006,13 @@ updateNONEProductionGroups(DeferredLogger& deferred_logger) } const auto& gname = gnames[i]; if (group_state.production_control(gname) != Group::ProductionCMode::NONE) { + // If the production group is specified for gas lift optimization, + // the current gas lift optimization implementation relies on the control + // mode is not NONE or FLD. As a result, we can not set it to NONE here. + // More systematic development might be needed in the future in this area. + if (glo.active() && glo.has_group(gname)) { + continue; + } if (comm_.rank() == 0) { const std::string msg = fmt::format("Production group {} has no constraints active, setting control mode to NONE", gname); deferred_logger.info(msg); diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.hpp b/opm/simulators/wells/BlackoilWellModelGeneric.hpp index 6a69a175d1d..7b30f7d22b7 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.hpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.hpp @@ -508,7 +508,7 @@ class BlackoilWellModelGeneric void assignMassGasRate(data::Wells& wsrpt, const Scalar gasDensity) const; - void updateNONEProductionGroups(DeferredLogger& deferred_logger); + void updateNONEProductionGroups(const GasLiftOpt& glo, DeferredLogger& deferred_logger); Schedule& schedule_; diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 4c80ed8428b..0124dc79365 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -742,7 +742,8 @@ namespace Opm { this->calculateProductivityIndexValues(local_deferredLogger); - this->updateNONEProductionGroups(local_deferredLogger); + const auto& glo = this->schedule().glo(reportStepIdx); + this->updateNONEProductionGroups(glo, local_deferredLogger); this->commitWGState();