Files
opm-simulators/opm/simulators/wells/GroupTargetCalculator.cpp
T

685 lines
27 KiB
C++

/*
Copyright 2025 Equinor ASA
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/input/eclipse/Schedule/Group/GuideRateModel.hpp>
#include <opm/simulators/wells/GroupTargetCalculator.hpp>
#include <opm/material/fluidsystems/BlackOilDefaultFluidSystemIndices.hpp>
#include <tuple> // for std::tie
#include <fmt/format.h>
namespace Opm {
// ----------------------------------------------------
// Constructor for the GroupTargetCalculator class
// ----------------------------------------------------
template<class Scalar, class IndexTraits>
GroupTargetCalculator<Scalar, IndexTraits>::
GroupTargetCalculator(
const BlackoilWellModelGeneric<Scalar, IndexTraits>& well_model,
const GroupStateHelperType& group_state_helper,
DeferredLogger& deferred_logger
) :
well_model_{well_model},
group_state_helper_{group_state_helper},
well_state_{group_state_helper.wellState()},
group_state_{group_state_helper.groupState()},
schedule_{group_state_helper.schedule()},
summary_state_{group_state_helper.summaryState()},
phase_usage_{group_state_helper.phaseUsage()},
guide_rate_{group_state_helper.guideRate()},
report_step_idx_{group_state_helper.reportStepIdx()},
deferred_logger_{deferred_logger},
resv_coeffs_inj_(group_state_helper.phaseUsage().numPhases, 0.0)
{
std::tie(this->fipnum_, this->pvtreg_) = this->well_model_.getGroupFipnumAndPvtreg();
// Get the reservoir coefficients for injection
// NOTE: The production reservoir coefficients depend on the group production rates, so we
// cannot precompute them here.
this->well_model_.calcInjResvCoeff(this->fipnum_, this->pvtreg_, this->resv_coeffs_inj_);
}
template<class Scalar, class IndexTraits>
std::optional<typename GroupTargetCalculator<Scalar, IndexTraits>::InjectionTargetInfo>
GroupTargetCalculator<Scalar, IndexTraits>::
groupInjectionTarget(const Group& group, ReservoirCoupling::Phase injection_phase)
{
GeneralCalculator general_calculator{*this, group, injection_phase};
auto target_info = general_calculator.calculateGroupTarget();
if (!target_info) {
// No target was calculated, return an empty optional.
return std::nullopt;
}
return std::make_optional<InjectionTargetInfo>(
InjectionTargetInfo{target_info->target, std::get<Group::InjectionCMode>(target_info->cmode)}
);
}
template<class Scalar, class IndexTraits>
std::optional<typename GroupTargetCalculator<Scalar, IndexTraits>::ProductionTargetInfo>
GroupTargetCalculator<Scalar, IndexTraits>::
groupProductionTarget(const Group& group) {
GeneralCalculator general_calculator{*this, group};
auto target_info = general_calculator.calculateGroupTarget();
if (!target_info) {
// No target was calculated, return an empty optional.
return std::nullopt;
}
return std::make_optional<ProductionTargetInfo>(
ProductionTargetInfo{
target_info->target,
std::get<Group::ProductionCMode>(target_info->cmode)
}
);
}
// ----------------------------------------------------
// Constructor for inner class GeneralCalculator
// ----------------------------------------------------
template<class Scalar, class IndexTraits>
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
GeneralCalculator(
GroupTargetCalculator<Scalar, IndexTraits>& parent_calculator,
const Group& original_group,
std::optional<ReservoirCoupling::Phase> injection_phase // Only used for injectors
) :
parent_calculator_{parent_calculator},
original_group_{original_group},
injection_phase_{injection_phase},
resv_coeffs_prod_(this->phaseUsage().numPhases, 0.0)
{
this->wellModel().calcResvCoeff(
this->fipnum(),
this->pvtreg(),
this->groupState().production_rates(original_group.name()),
this->resv_coeffs_prod_
);
}
// -------------------------------------------------------
// Public methods for the GeneralCalculator class
// -------------------------------------------------------
template<class Scalar, class IndexTraits>
std::optional<typename GroupTargetCalculator<Scalar, IndexTraits>::TargetInfo>
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
calculateGroupTarget()
{
// TODO: For now this is adapted to the reservoir coupling implementation where "group" below will
// correspond to a master group. In the future we might want to port the logic to include
// e.g. checkGroupConstraintsProd() and checkGroupConstraintsInj() in GroupStateHelper.cpp.
const auto& group = this->original_group_; // The bottom group we want to calculate the target for.
if (group.is_field() || !this->parentGroupControlAvailable_(group)) {
return this->getTargetNoGuideRate(group);
}
assert(this->parentGroupControlAvailable_(group));
if (!this->hasGuideRate_(group)) {
if (this->hasFldOrNoneControl_(group)) {
// Parent control is available, but no guide rate is defined. This is illegal for a master group
// under FLD or NONE control.
OPM_DEFLOG_THROW(
std::runtime_error,
fmt::format("No guide rate defined for master group {}", group.name()),
this->deferredLogger());
}
// A master group with:
// - individual (not FLD or NONE) control,
// - parent control available,
// - but no guide rate
// this could be considered an error, but we can also fall back to use its own target.
return this->getTargetNoGuideRate(group);
}
const auto efficiency_factor = group.getGroupEfficiencyFactor();
auto target = this->calculateGroupTargetRecursive_(group, efficiency_factor);
if (target) {
// TODO: We could probably switch the group to FLD control mode now. However, this call is coming
// from beginTimeStep() in BlackoilWellModel_impl.hpp, but the regular higher constraints
// checks (for all groups, not just the master group) will first be done during the assemble() step,
// at which point the group should be switched to FLD control mode if necessary.
return target;
}
// If a higher level target was not found or not violated, use the group's own target.
return this->getTargetNoGuideRate(group);
}
template<class Scalar, class IndexTraits>
typename GroupTargetCalculator<Scalar, IndexTraits>::GeneralCalculator::TargetCalculatorType
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
getInjectionTargetCalculator(const Group& group)
{
return InjectionTargetCalculator{
this->groupStateHelper(),
this->resvCoeffsInj(),
group,
this->injectionPhase_(),
this->deferredLogger()
};
}
template<class Scalar, class IndexTraits>
typename GroupTargetCalculator<Scalar, IndexTraits>::GeneralCalculator::TargetCalculatorType
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
getProductionTargetCalculator(const Group& group) const
{
return TargetCalculator{
this->groupStateHelper(),
this->resvCoeffsProd(),
group
};
}
template<class Scalar, class IndexTraits>
typename GroupTargetCalculator<Scalar, IndexTraits>::GeneralCalculator::TargetCalculatorType
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
getTargetCalculator(const Group& group)
{
if (this->targetType() == TargetType::Injection) {
return this->getInjectionTargetCalculator(group);
}
else {
return this->getProductionTargetCalculator(group);
}
}
template<class Scalar, class IndexTraits>
typename GroupTargetCalculator<Scalar, IndexTraits>::TargetInfo
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
getTargetFromCalculator(const TargetCalculatorType& target_calculator, const Group& group)
{
if (this->targetType() == TargetType::Injection) {
const auto& control_mode = this->groupState().injection_control(group.name(), this->injectionPhase_());
return TargetInfo{
std::get<InjectionTargetCalculator>(target_calculator).groupTarget(
this->deferredLogger()
), control_mode
};
}
else {
const auto& control_mode = this->groupState().production_control(group.name());
return TargetInfo{
std::get<TargetCalculator>(target_calculator).groupTarget(
this->deferredLogger()
), control_mode
};
}
}
// -------------------------------------------------------
// Private methods for the GeneralCalculator class
// -------------------------------------------------------
// See comments above RescoupTargetCalculator::calculateMasterGroupTargetsAndSendToSlaves() about how
// the target is calculated.
template<class Scalar, class IndexTraits>
std::optional<typename GroupTargetCalculator<Scalar, IndexTraits>::TargetInfo>
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
calculateGroupTargetRecursive_(const Group& group, const Scalar efficiency_factor)
{
if (this->hasFldOrNoneControl_(group)) {
// NOTE: A pure injection group is assumed to have production control NONE and
// a pure production group is assumed to have injection control NONE, see
// GroupStateHelper.cpp::setCmodeGroup() for details.
if (!this->parentGroupControlAvailable_(group)) {
// If we are here, the group has control mode NONE, and either
// - item 8 of GCONPROD/GCONINJE is "NO" or,
// - group.is_field() is true.
// NOTE: it cannot have control mode FLD, since FLD will ovverride item 8 of
// GCONPROD/GCONINJE, see GroupKeywordHandlers.cpp
// TODO: We could throw an error here. Or try to fall back to use the group's own target.
// Fall back to use the group's own target.
return std::nullopt;
} else {
assert(!group.is_field()); // The field group should not have parent control.
auto new_efficiency_factor = efficiency_factor * group.getGroupEfficiencyFactor();
return this->calculateGroupTargetRecursive_(this->parentGroup(group), new_efficiency_factor);
}
}
if ((this->targetType() == TargetType::Injection && !group.isInjectionGroup()) ||
(this->targetType() == TargetType::Production && !group.isProductionGroup()))
{
// Controlling group that is not of the type we are interested in.
// This should never happen, since any group that is not an injector should have injection
// control NONE. And any group that is not a producer should have production control NONE.
// See GroupStateHelper.cpp::setCmodeGroup() for details.
// .. and therefore should be caught by the hasFldOrNoneControl_() check above.
OPM_DEFLOG_THROW(
std::runtime_error,
fmt::format("Controlling group that is not of the type we are interested in: {}", group.name()),
this->deferredLogger()
);
}
// 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.
const auto& top_group = group;
const auto& bottom_group = this->original_group_;
TopToBottomCalculator top_bottom_calc{*this, top_group, bottom_group, efficiency_factor};
return top_bottom_calc.calculateGroupTarget();
}
template<class Scalar, class IndexTraits>
typename GroupTargetCalculator<Scalar, IndexTraits>::TargetInfo
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
getTargetNoGuideRate(const Group& group)
{
// Efficiency factor is not applied here, since it only applies to child rates, not to the group's
// own target.
// NOTE: If a master group is under individual control and has a RESV rate target, it should be
// transferred into a RESV target for the slave group. But it is not obvious how to transform it
// since the master and slave groups in general have different PVT properties.
// TODO: For now we assume the RESV rate target is in slave reservoir units, and just send it as is
// to the slave group. Alternatively, we could also throw an error here for that case
const auto target_calculator = this->getTargetCalculator(group);
return this->getTargetFromCalculator(target_calculator, group);
}
template<class Scalar, class IndexTraits>
bool
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
hasFldOrNoneControl_(const Group& group)
{
// Check if the group (constrained to production or injection) has control FLD or NONE.
// For example, a pure injection group will have production control NONE.
const auto& name = group.name();
if (this->targetType() == TargetType::Injection) {
return this->groupState().has_field_or_none_control(name, this->injectionPhase_());
}
else {
return this->groupState().has_field_or_none_control(name);
}
}
template<class Scalar, class IndexTraits>
bool
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
parentGroupControlAvailable_(const Group& group)
{
if (this->targetType() == TargetType::Injection) {
return group.injectionGroupControlAvailable(this->injectionPhase_());
} else {
return group.productionGroupControlAvailable();
}
}
template<class Scalar, class IndexTraits>
Phase
GroupTargetCalculator<Scalar, IndexTraits>::
GeneralCalculator::
injectionPhase_()
{
if (this->injection_phase_.has_value()) {
switch (this->injection_phase_.value()) {
case ReservoirCoupling::Phase::Water:
return Phase::WATER;
case ReservoirCoupling::Phase::Oil:
return Phase::OIL;
case ReservoirCoupling::Phase::Gas:
return Phase::GAS;
default:
OPM_DEFLOG_THROW(std::runtime_error, "Invalid injection phase", this->deferredLogger());
return Phase::WATER; // Unreachable, but satisfies compiler
}
}
else {
return Phase::OIL; // Dummy phase, not used for producers.
}
}
// ----------------------------------------------------
// Constructor for inner class TopToBottomCalculator
// ----------------------------------------------------
template<class Scalar, class IndexTraits>
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
TopToBottomCalculator(
GeneralCalculator& parent_calculator,
const Group& top_group,
const Group& bottom_group,
Scalar chain_efficiency_factor
) :
parent_calculator_{parent_calculator},
top_group_{top_group},
bottom_group_{bottom_group},
chain_efficiency_factor_{chain_efficiency_factor}
{
if (this->targetType() == TargetType::Injection) {
this->initForInjector_();
}
else {
this->initForProducer_();
}
}
// -------------------------------------------------------
// Public methods for the TopToBottomCalculator class
// -------------------------------------------------------
template<class Scalar, class IndexTraits>
std::optional<typename GroupTargetCalculator<Scalar, IndexTraits>::TargetInfo>
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
calculateGroupTarget()
{
const auto chain = this->getGroupChainTopBot_();
// NOTE: For reservoir coupling: we are called from beginTimeStep() in BlackoilWellModel_impl.hpp,
// so we do not check for guide rate violations here, compare with
// GroupStateHelper.cpp::checkGroupConstraintsProd() and
// GroupStateHelper.cpp::checkGroupConstraintsInj() since the guide rate will be checked during the
// assemble() step.
const auto local_reduction_level = this->getLocalReductionLevel_(chain);
Scalar top_level_target = this->getTopLevelTarget_();
Scalar bottom_group_current_rate_available = this->getBottomGroupCurrentRateAvailable_();
// Because the bottom group (the original group) is the last of the elements,
// and not an ancestor, we subtract one:
const std::size_t num_ancestors = chain.size() - 1;
Scalar target = top_level_target;
for (std::size_t i = 0; i < num_ancestors; ++i) {
if ((i == 0) || this->hasGuideRate_(chain[i])) {
// Apply local reductions only at the control level
// (top) and for levels where we have a specified
// group guide rate.
if (i <= local_reduction_level) {
target -= this->localReduction_(chain[i]);
}
// Add my reduction back at the level where it is included in the local reduction
if (i == local_reduction_level) {
const Scalar addback_efficiency
= this->computeAddbackEfficiency_(chain, local_reduction_level);
target += bottom_group_current_rate_available * addback_efficiency;
}
}
target *= this->localFraction_(chain[i + 1]);
}
// Avoid negative target rates coming from too large local reductions.
target = std::max(Scalar(0.0), target);
// Divide by the accumulated efficiency factor along the chain from top to bottom, excluding the top group.
// This is the full target rate that should be assigned to the bottom group, to be seen as the unscaled
// target rate for the top group.
const Scalar full_target = target / this->chain_efficiency_factor_;
if (bottom_group_current_rate_available > TARGET_RATE_TOLERANCE) { // > 1e-12
if ((bottom_group_current_rate_available > full_target)) {
// The bottom group is producing too much according to the top level group target,
// so we need to scale down the target rate.
if (this->isProducerAndRESVControl_(this->top_group_)) {
// For RESV mode with reservoir coupling master groups, we scale the slave's
// actual reservoir rate to get a target in slave's reservoir units.
const Scalar slave_resv_rate = this->getSlaveGroupReservoirRate_(this->bottom_group_);
// Scale the slave's reservoir rate by the same factor we would scale surface rates
const Scalar scale = full_target / bottom_group_current_rate_available;
return TargetInfo{scale * slave_resv_rate, this->toplevel_control_mode_};
}
else {
return TargetInfo{full_target, this->toplevel_control_mode_};
}
}
else if (this->hasFLDControl_(this->bottom_group_)) {
// The bottom group is under FLD control, so we return the top level target as is.
return TargetInfo{full_target, this->toplevel_control_mode_};
}
}
// Return the bottom group's target rate as is.
return this->getTargetNoGuideRate_(this->bottom_group_);
}
// -------------------------------------------------------
// Private methods for the TopToBottomCalculator class
// -------------------------------------------------------
template <class Scalar, class IndexTraits>
Scalar
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
computeAddbackEfficiency_(
const std::vector<std::string>& chain,
const std::size_t local_reduction_level) const
{
// Compute partial efficiency factor from local_reduction_level down to the entity.
// Chain is ordered [control_group, ..., local_reduction_level, ..., entity].
// We multiply efficiency factors from index (local_reduction_level + 1) to the entity.
// Note: The entity at chain[num_ancestors] may be a well or a group.
// Both wells (WEFAC) and groups (GEFAC) have efficiency factors that must be included,
// since the local_reduction rates include these efficiency factors.
const std::size_t num_ancestors = chain.size() - 1;
Scalar efficiency = 1.0;
for (std::size_t jj = local_reduction_level + 1; jj <= num_ancestors; ++jj) {
const std::string& name = chain[jj];
if (this->schedule().hasGroup(name, this->reportStepIdx())) {
const auto& grp = this->schedule().getGroup(name, this->reportStepIdx());
efficiency *= grp.getGroupEfficiencyFactor();
} else if (this->schedule().hasWell(name, this->reportStepIdx())) {
const auto& well = this->schedule().getWell(name, this->reportStepIdx());
efficiency *= well.getEfficiencyFactor()
* this->wellState()[name].efficiency_scaling_factor;
}
}
return efficiency;
}
template<class Scalar, class IndexTraits>
Scalar
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
getBottomGroupCurrentRateAvailable_() const
{
const auto& group = this->bottom_group_;
if (this->targetType() == TargetType::Injection) {
return std::get<InjectionTargetCalculator>(this->target_calculator_).calcModeRateFromRates(
this->groupStateHelper().getGroupRatesAvailableForHigherLevelControl(group, /*is_injector=*/true)
);
}
else {
const auto& tcalc = std::get<TargetCalculator>(this->target_calculator_);
return tcalc.calcModeRateFromRates(
this->groupStateHelper().getGroupRatesAvailableForHigherLevelControl(group, /*is_injector=*/false)
);
}
}
template<class Scalar, class IndexTraits>
Scalar
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
getSlaveGroupReservoirRate_(const Group& group)
{
if (!this->groupStateHelper().isReservoirCouplingMasterGroup(group)) {
OPM_DEFLOG_THROW(std::runtime_error, "Group is not a reservoir coupling master group", this->deferredLogger());
}
// Sum reservoir rates for all phases from the slave
Scalar total_resv_rate = 0.0;
const auto& master = this->groupStateHelper().reservoirCouplingMaster();
for (auto phase : {ReservoirCoupling::Phase::Oil, ReservoirCoupling::Phase::Gas, ReservoirCoupling::Phase::Water}) {
total_resv_rate += master.getMasterGroupProductionRate(group.name(), phase, /*res_rates=*/true);
}
return total_resv_rate;
}
template<class Scalar, class IndexTraits>
std::vector<std::string>
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
getGroupChainTopBot_() const
{
return this->groupStateHelper().groupChainTopBot(this->bottom_group_.name(), this->top_group_.name());
}
template<class Scalar, class IndexTraits>
std::size_t
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
getLocalReductionLevel_(const std::vector<std::string>& chain)
{
const std::size_t num_ancestors = chain.size() - 1;
std::size_t local_reduction_level = 0;
for (std::size_t ii = 1; ii < num_ancestors; ++ii) {
const int num_gr_ctrl = this->groupStateHelper().groupControlledWells(
chain[ii],
/*always_included_child=*/"",
/*is_producer=*/this->targetType() == TargetType::Production,
/*injection_phase=*/this->injectionPhase_());
if (this->hasGuideRate_(chain[ii]) && num_gr_ctrl > 0) {
local_reduction_level = ii;
}
}
return local_reduction_level;
}
template<class Scalar, class IndexTraits>
Scalar
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
getTopLevelTarget_()
{
if (this->targetType() == TargetType::Injection) {
return std::get<InjectionTargetCalculator>(this->target_calculator_).groupTarget(
this->deferredLogger()
);
}
else {
return std::get<TargetCalculator>(this->target_calculator_).groupTarget(this->deferredLogger());
}
}
template<class Scalar, class IndexTraits>
bool
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
hasFLDControl_(const Group& group) const
{
if (this->targetType() == TargetType::Injection) {
return this->groupState().injection_control(group.name(), this->injectionPhase_()) == Group::InjectionCMode::FLD;
}
else {
return this->groupState().production_control(group.name()) == Group::ProductionCMode::FLD;
}
}
template<class Scalar, class IndexTraits>
void
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
initForInjector_()
{
auto guide_target_mode =
std::get<InjectionTargetCalculator>(this->target_calculator_).guideTargetMode();
this->fraction_calculator_.emplace(
this->schedule(),
this->groupStateHelper(),
this->summaryState(),
this->reportStepIdx(),
&this->guideRate(),
GuideRateModel::Target{guide_target_mode},
/*is_producer=*/false,
this->injectionPhase_()
);
}
template<class Scalar, class IndexTraits>
void
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
initForProducer_()
{
auto calc = this->getProductionTargetCalculator_(this->top_group_);
this->target_calculator_.template emplace<TargetCalculator>(std::get<TargetCalculator>(calc));
auto guide_target_mode =
std::get<TargetCalculator>(this->target_calculator_).guideTargetMode();
auto dummy_phase = Phase::OIL; // Dummy phase, not used for producers.
this->fraction_calculator_.emplace(
this->schedule(),
this->groupStateHelper(),
this->summaryState(),
this->reportStepIdx(),
&this->guideRate(),
GuideRateModel::Target{guide_target_mode},
/*is_producer=*/true,
dummy_phase
);
}
template<class Scalar, class IndexTraits>
bool
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
isProducerAndRESVControl_(const Group& group) const
{
if (this->targetType() == TargetType::Production) {
return this->groupState().production_control(group.name()) == Group::ProductionCMode::RESV;
}
else {
return false;
}
}
template<class Scalar, class IndexTraits>
Scalar
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
localFraction_(const std::string& group_name)
{
const auto& always_included_child = this->bottom_group_.name();
return this->fraction_calculator_->localFraction(group_name, always_included_child);
}
template<class Scalar, class IndexTraits>
Scalar
GroupTargetCalculator<Scalar, IndexTraits>::
TopToBottomCalculator::
localReduction_(const std::string& group_name)
{
if (this->targetType() == TargetType::Injection) {
const std::vector<Scalar>& group_target_reductions =
this->groupState().injection_reduction_rates(group_name);
return std::get<InjectionTargetCalculator>(this->target_calculator_).calcModeRateFromRates(
group_target_reductions
);
}
else {
const std::vector<Scalar>& group_target_reductions =
this->groupState().production_reduction_rates(group_name);
return std::get<TargetCalculator>(this->target_calculator_).calcModeRateFromRates(
group_target_reductions
);
}
}
template class GroupTargetCalculator<double, BlackOilDefaultFluidSystemIndices>;
#if FLOW_INSTANTIATE_FLOAT
template class GroupTargetCalculator<float, BlackOilDefaultFluidSystemIndices>;
#endif
} // namespace Opm