diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index 577bc2a12ca..4b802c4bc31 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(), @@ -1947,6 +1948,81 @@ operator==(const BlackoilWellModelGeneric& rhs) const && this->gen_gaslift_ == rhs.gen_gaslift_; } +template +void +BlackoilWellModelGeneric:: +updateNONEProductionGroups(const GasLiftOpt& glo, 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, 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(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); + + 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); + } + + // 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 < num_gpc; ++i) { + if (production_control_used[i] > 0) { + continue; + } + 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); + } + 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..7b30f7d22b7 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(const GasLiftOpt& glo, 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..0124dc79365 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -742,6 +742,9 @@ namespace Opm { this->calculateProductivityIndexValues(local_deferredLogger); + const auto& glo = this->schedule().glo(reportStepIdx); + this->updateNONEProductionGroups(glo, 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 cddb2cc15f4..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; + 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; + 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, @@ -612,12 +614,12 @@ 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 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, @@ -630,7 +632,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(); @@ -741,7 +743,7 @@ GroupStateHelper::getWellGroupTargetProducer(const std::str } } // Avoid negative target rates coming from too large local reductions. - return 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 8f3634d7d3e..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 73a12d5901b..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 9ead1b8590d..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; + 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; + 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; + 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 == 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 / current_rate; + scale = target_rate->target_value / current_rate; return scale; }