diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 6290f25fa..63286267a 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -338,7 +338,7 @@ namespace Opm { const bool event = report_step_starts_ && events.hasEvent(well->name(), effective_events_mask); if (event) { try { - well->updateWellStateWithTarget(ebosSimulator_, this->wellState(), local_deferredLogger); + well->updateWellStateWithTarget(ebosSimulator_, this->groupState(), this->wellState(), local_deferredLogger); well->calculateExplicitQuantities(ebosSimulator_, this->wellState(), local_deferredLogger); well->solveWellEquation(ebosSimulator_, this->wellState(), this->groupState(), local_deferredLogger); } catch (const std::exception& e) { @@ -1415,7 +1415,7 @@ namespace Opm { auto& events = this->wellState().events(well->indexOfWell()); if (events.hasEvent(WellState::event_mask)) { - well->updateWellStateWithTarget(ebosSimulator_, this->wellState(), deferred_logger); + well->updateWellStateWithTarget(ebosSimulator_, this->groupState(), this->wellState(), deferred_logger); // There is no new well control change input within a report step, // so next time step, the well does not consider to have effective events anymore. events.clearEvent(WellState::event_mask); diff --git a/opm/simulators/wells/MultisegmentWell.hpp b/opm/simulators/wells/MultisegmentWell.hpp index 0f6bd9614..0eade4321 100644 --- a/opm/simulators/wells/MultisegmentWell.hpp +++ b/opm/simulators/wells/MultisegmentWell.hpp @@ -118,6 +118,7 @@ namespace Opm /// updating the well state based the current control mode virtual void updateWellStateWithTarget(const Simulator& ebos_simulator, + const GroupState& group_state, WellState& well_state, DeferredLogger& deferred_logger) const override; diff --git a/opm/simulators/wells/MultisegmentWell_impl.hpp b/opm/simulators/wells/MultisegmentWell_impl.hpp index fab9883f9..64ba9fead 100644 --- a/opm/simulators/wells/MultisegmentWell_impl.hpp +++ b/opm/simulators/wells/MultisegmentWell_impl.hpp @@ -140,10 +140,11 @@ namespace Opm void MultisegmentWell:: updateWellStateWithTarget(const Simulator& ebos_simulator, + const GroupState& group_state, WellState& well_state, DeferredLogger& deferred_logger) const { - Base::updateWellStateWithTarget(ebos_simulator, well_state, deferred_logger); + Base::updateWellStateWithTarget(ebos_simulator, group_state, well_state, deferred_logger); // scale segment rates based on the wellRates // and segment pressure based on bhp this->scaleSegmentRatesWithWellRates(well_state); diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index a1701ff1b..c4c3de4a8 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -194,6 +194,7 @@ public: DeferredLogger& deferred_logger) = 0; virtual void updateWellStateWithTarget(const Simulator& ebos_simulator, + const GroupState& group_state, WellState& well_state, DeferredLogger& deferred_logger) const; diff --git a/opm/simulators/wells/WellInterfaceFluidSystem.cpp b/opm/simulators/wells/WellInterfaceFluidSystem.cpp index 3b84801f4..474cdde96 100644 --- a/opm/simulators/wells/WellInterfaceFluidSystem.cpp +++ b/opm/simulators/wells/WellInterfaceFluidSystem.cpp @@ -25,13 +25,15 @@ #include #include +#include #include #include #include #include #include - +#include +#include #include #include @@ -916,6 +918,189 @@ flowPhaseToEbosPhaseIdx(const int phaseIdx) const return phaseIdx; } +template +std::optional +WellInterfaceFluidSystem:: +getGroupInjectionTargetRate(const Group& group, + const WellState& well_state, + const GroupState& group_state, + const Schedule& schedule, + const SummaryState& summaryState, + const InjectorType& injectorType, + double efficiencyFactor, + DeferredLogger& deferred_logger) const +{ + // Setting some defaults to silence warnings below. + // Will be overwritten in the switch statement. + Phase injectionPhase = Phase::WATER; + switch (injectorType) { + case InjectorType::WATER: + { + injectionPhase = Phase::WATER; + break; + } + case InjectorType::OIL: + { + injectionPhase = Phase::OIL; + break; + } + case InjectorType::GAS: + { + injectionPhase = Phase::GAS; + break; + } + default: + // Should not be here. + assert(false); + } + + auto currentGroupControl = group_state.injection_control(group.name(), injectionPhase); + if (currentGroupControl == Group::InjectionCMode::FLD || + currentGroupControl == Group::InjectionCMode::NONE) { + if (!group.injectionGroupControlAvailable(injectionPhase)) { + // We cannot go any further up the hierarchy. This could + // be the FIELD group, or any group for which this has + // been set in GCONINJE or GCONPROD. If we are here + // anyway, it is likely that the deck set inconsistent + // requirements, such as GRUP control mode on a well with + // no appropriate controls defined on any of its + // containing groups. We will therefore use the wells' bhp + // limit equation as a fallback. + return std::nullopt; + } else { + // Inject share of parents control + const auto& parent = schedule.getGroup( group.parent(), currentStep()); + efficiencyFactor *= group.getGroupEfficiencyFactor(); + return getGroupInjectionTargetRate(parent, well_state, group_state, schedule, summaryState, injectorType, efficiencyFactor, deferred_logger); + } + } + + efficiencyFactor *= group.getGroupEfficiencyFactor(); + const auto pu = phaseUsage(); + + if (!group.isInjectionGroup()) { + return std::nullopt; + } + + // If we are here, we are at the topmost group to be visited in the recursion. + // This is the group containing the control we will check against. + + // Make conversion factors for RESV <-> surface rates. + std::vector resv_coeff(pu.num_phases, 1.0); + rateConverter_.calcCoeff(0, pvtRegionIdx(), resv_coeff); // FIPNUM region 0 here, should use FIPNUM from WELSPECS. + + double sales_target = 0; + if (schedule[currentStep()].gconsale().has(group.name())) { + const auto& gconsale = schedule[currentStep()].gconsale().get(group.name(), summaryState); + sales_target = gconsale.sales_target; + } + WellGroupHelpers::InjectionTargetCalculator tcalc(currentGroupControl, pu, resv_coeff, group.name(), sales_target, group_state, injectionPhase, deferred_logger); + WellGroupHelpers::FractionCalculator fcalc(schedule, well_state, group_state, currentStep(), guideRate(), tcalc.guideTargetMode(), pu, false, injectionPhase); + + auto localFraction = [&](const std::string& child) { + return fcalc.localFraction(child, ""); + }; + + auto localReduction = [&](const std::string& group_name) { + const std::vector& groupTargetReductions = group_state.injection_reduction_rates(group_name); + return tcalc.calcModeRateFromRates(groupTargetReductions); + }; + + const double orig_target = tcalc.groupTarget(group.injectionControls(injectionPhase, summaryState), deferred_logger); + const auto chain = WellGroupHelpers::groupChainTopBot(name(), group.name(), schedule, currentStep()); + // Because 'name' is the last of the elements, and not an ancestor, we subtract one below. + const size_t num_ancestors = chain.size() - 1; + double target = orig_target; + for (size_t ii = 0; ii < num_ancestors; ++ii) { + if ((ii == 0) || guideRate()->has(chain[ii], injectionPhase)) { + // Apply local reductions only at the control level + // (top) and for levels where we have a specified + // group guide rate. + target -= localReduction(chain[ii]); + } + target *= localFraction(chain[ii+1]); + } + // Avoid negative target rates coming from too large local reductions. + return std::max(0.0, target / efficiencyFactor); +} +template +double +WellInterfaceFluidSystem:: +getGroupProductionTargetRate(const Group& group, + const WellState& well_state, + const GroupState& group_state, + const Schedule& schedule, + const SummaryState& summaryState, + double efficiencyFactor) const +{ + const Group::ProductionCMode& currentGroupControl = group_state.production_control(group.name()); + if (currentGroupControl == Group::ProductionCMode::FLD || + currentGroupControl == Group::ProductionCMode::NONE) { + if (!group.productionGroupControlAvailable()) { + return 1.0; + } else { + // Produce share of parents control + const auto& parent = schedule.getGroup(group.parent(), currentStep()); + efficiencyFactor *= group.getGroupEfficiencyFactor(); + return getGroupProductionTargetRate(parent, well_state, group_state, schedule, summaryState, efficiencyFactor); + } + } + + efficiencyFactor *= group.getGroupEfficiencyFactor(); + const auto pu = phaseUsage(); + + if (!group.isProductionGroup()) { + return 1.0; + } + + // If we are here, we are at the topmost group to be visited in the recursion. + // This is the group containing the control we will check against. + + // Make conversion factors for RESV <-> surface rates. + std::vector resv_coeff(phaseUsage().num_phases, 1.0); + rateConverter_.calcCoeff(0, pvtRegionIdx(), resv_coeff); // FIPNUM region 0 here, should use FIPNUM from WELSPECS. + + // gconsale may adjust the grat target. + // the adjusted rates is send to the targetCalculator + double gratTargetFromSales = 0.0; + if (group_state.has_grat_sales_target(group.name())) + gratTargetFromSales = group_state.grat_sales_target(group.name()); + + WellGroupHelpers::TargetCalculator tcalc(currentGroupControl, pu, resv_coeff, gratTargetFromSales); + WellGroupHelpers::FractionCalculator fcalc(schedule, well_state, group_state, currentStep(), guideRate(), tcalc.guideTargetMode(), pu, true, Phase::OIL); + + auto localFraction = [&](const std::string& child) { + return fcalc.localFraction(child, ""); + }; + + auto localReduction = [&](const std::string& group_name) { + const std::vector& groupTargetReductions = group_state.production_reduction_rates(group_name); + return tcalc.calcModeRateFromRates(groupTargetReductions); + }; + + const double orig_target = tcalc.groupTarget(group.productionControls(summaryState)); + const auto chain = WellGroupHelpers::groupChainTopBot(name(), group.name(), schedule, currentStep()); + // Because 'name' is the last of the elements, and not an ancestor, we subtract one below. + const size_t num_ancestors = chain.size() - 1; + double target = orig_target; + for (size_t ii = 0; ii < num_ancestors; ++ii) { + if ((ii == 0) || guideRate()->has(chain[ii])) { + // Apply local reductions only at the control level + // (top) and for levels where we have a specified + // group guide rate. + target -= localReduction(chain[ii]); + } + target *= localFraction(chain[ii+1]); + } + // Avoid negative target rates coming from too large local reductions. + const double target_rate = std::max(0.0, target / efficiencyFactor); + const auto& rates = well_state.wellRates(index_of_well_); + const auto current_rate = -tcalc.calcModeRateFromRates(rates); // Switch sign since 'rates' are negative for producers. + double scale = 1.0; + if (current_rate > 1e-14) + scale = target_rate/current_rate; + return scale; +} template class WellInterfaceFluidSystem>; template class WellInterfaceFluidSystem>; diff --git a/opm/simulators/wells/WellInterfaceFluidSystem.hpp b/opm/simulators/wells/WellInterfaceFluidSystem.hpp index 3baf545ad..ec1ef4e49 100644 --- a/opm/simulators/wells/WellInterfaceFluidSystem.hpp +++ b/opm/simulators/wells/WellInterfaceFluidSystem.hpp @@ -145,6 +145,24 @@ protected: WellTestState& well_test_state, DeferredLogger& deferred_logger) const; + std::optional + getGroupInjectionTargetRate(const Group& group, + const WellState& well_state, + const GroupState& group_state, + const Schedule& schedule, + const SummaryState& summaryState, + const InjectorType& injectorType, + double efficiencyFactor, + DeferredLogger& deferred_logger) const; + + double + getGroupProductionTargetRate(const Group& group, + const WellState& well_state, + const GroupState& group_state, + const Schedule& schedule, + const SummaryState& summaryState, + double efficiencyFactor) const; + // For the conversion between the surface volume rate and reservoir voidage rate const RateConverterType& rateConverter_; diff --git a/opm/simulators/wells/WellInterfaceGeneric.cpp b/opm/simulators/wells/WellInterfaceGeneric.cpp index 592b96c8d..b15001c3e 100644 --- a/opm/simulators/wells/WellInterfaceGeneric.cpp +++ b/opm/simulators/wells/WellInterfaceGeneric.cpp @@ -29,7 +29,6 @@ #include #include #include - #include #include #include diff --git a/opm/simulators/wells/WellInterfaceGeneric.hpp b/opm/simulators/wells/WellInterfaceGeneric.hpp index f034f4843..c22033df9 100644 --- a/opm/simulators/wells/WellInterfaceGeneric.hpp +++ b/opm/simulators/wells/WellInterfaceGeneric.hpp @@ -43,6 +43,9 @@ class SummaryState; class VFPProperties; class WellTestState; class WellState; +class GroupState; +class Group; +class Schedule; class WellInterfaceGeneric { public: @@ -174,6 +177,7 @@ protected: WellTestState& well_test_state, DeferredLogger& deferred_logger) const; + // definition of the struct OperabilityStatus struct OperabilityStatus { bool isOperable() const { diff --git a/opm/simulators/wells/WellInterface_impl.hpp b/opm/simulators/wells/WellInterface_impl.hpp index e1d8e7a09..64d333989 100644 --- a/opm/simulators/wells/WellInterface_impl.hpp +++ b/opm/simulators/wells/WellInterface_impl.hpp @@ -200,7 +200,7 @@ namespace Opm ss << " on rank " << cc.rank(); } deferred_logger.info(ss.str()); - updateWellStateWithTarget(ebos_simulator, well_state, deferred_logger); + updateWellStateWithTarget(ebos_simulator, group_state, well_state, deferred_logger); updatePrimaryVariables(well_state, deferred_logger); } @@ -246,7 +246,7 @@ namespace Opm WellState well_state_copy = well_state; - updateWellStateWithTarget(simulator, well_state_copy, deferred_logger); + updateWellStateWithTarget(simulator, group_state, well_state_copy, deferred_logger); calculateExplicitQuantities(simulator, well_state_copy, deferred_logger); updatePrimaryVariables(well_state_copy, deferred_logger); initPrimaryVariablesEvaluation(); @@ -470,7 +470,7 @@ namespace Opm return; } - updateWellStateWithTarget(ebos_simulator, well_state_copy, deferred_logger); + updateWellStateWithTarget(ebos_simulator, group_state, well_state_copy, deferred_logger); calculateExplicitQuantities(ebos_simulator, well_state_copy, deferred_logger); @@ -567,6 +567,7 @@ namespace Opm void WellInterface:: updateWellStateWithTarget(const Simulator& ebos_simulator, + const GroupState& group_state, WellState& well_state, DeferredLogger& deferred_logger) const { @@ -577,6 +578,7 @@ namespace Opm const auto& pu = this->phaseUsage(); const int np = well_state.numPhases(); const auto& summaryState = ebos_simulator.vanguard().summaryState(); + const auto& schedule = ebos_simulator.vanguard().schedule(); if (this->wellIsStopped()) { for (int p = 0; pcurrentStep()); + const double efficiencyFactor = well.getEfficiencyFactor(); + std::optional target = + this->getGroupInjectionTargetRate(group, + well_state, + group_state, + schedule, + summaryState, + injectorType, + efficiencyFactor, + deferred_logger); + if (target) + well_state.wellRates(well_index)[phasePos] = *target; break; } case Well::InjectorCMode::CMODE_UNDEFINED: @@ -862,7 +877,22 @@ namespace Opm } case Well::ProducerCMode::GRUP: { - //do nothing at the moment + assert(well.isAvailableForGroupControl()); + const auto& group = schedule.getGroup(well.groupName(), this->currentStep()); + const double efficiencyFactor = well.getEfficiencyFactor(); + double scale = this->getGroupProductionTargetRate(group, + well_state, + group_state, + schedule, + summaryState, + efficiencyFactor); + + // we don't want to scale with zero and get zero rates. + if (scale > 0) { + for (int p = 0; p