Files
opm-simulators/opm/simulators/wells/GroupStateHelper.hpp
T

893 lines
40 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/>.
*/
#ifndef OPM_GROUPSTATEHELPER_HEADER_INCLUDED
#define OPM_GROUPSTATEHELPER_HEADER_INCLUDED
#include <opm/simulators/wells/rescoup/RescoupProxy.hpp>
#include <opm/common/TimingMacros.hpp>
#include <opm/input/eclipse/Schedule/ResCoup/GrupSlav.hpp>
#include <opm/input/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
#include <opm/input/eclipse/Schedule/Group/GPMaint.hpp>
#include <opm/input/eclipse/Schedule/Group/GSatProd.hpp>
#include <opm/input/eclipse/Schedule/Group/GuideRate.hpp>
#include <opm/input/eclipse/Schedule/Schedule.hpp>
#include <opm/input/eclipse/Schedule/ScheduleState.hpp>
#include <opm/input/eclipse/Schedule/SummaryState.hpp>
#include <opm/material/fluidsystems/PhaseUsageInfo.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <opm/simulators/utils/gatherDeferredLogger.hpp>
#include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
#include <opm/simulators/wells/GroupState.hpp>
#include <opm/simulators/wells/VFPProdProperties.hpp>
#include <opm/simulators/wells/WellState.hpp>
#include <opm/simulators/utils/ParallelCommunication.hpp>
#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <vector>
namespace Opm
{
template <typename Scalar, typename IndexTraits>
class GroupStateHelper
{
public:
// RAII guard for temporarily setting wellstate pointer
class WellStateGuard
{
public:
WellStateGuard(GroupStateHelper& groupStateHelper, WellState<Scalar, IndexTraits>& well_state)
: groupStateHelper_ {groupStateHelper}
, previous_state_ptr_ {groupStateHelper_.well_state_}
{
// Set the new state directly
groupStateHelper_.well_state_ = &well_state;
}
~WellStateGuard()
{
// Restore the previous state
groupStateHelper_.well_state_ = previous_state_ptr_;
}
// Delete copy and move operations
WellStateGuard(const WellStateGuard&) = delete;
WellStateGuard& operator=(const WellStateGuard&) = delete;
WellStateGuard(WellStateGuard&&) = delete;
WellStateGuard& operator=(WellStateGuard&&) = delete;
private:
GroupStateHelper& groupStateHelper_;
const WellState<Scalar, IndexTraits>* previous_state_ptr_;
};
// RAII guard for temporarily setting groupstate pointer
class GroupStateGuard
{
public:
GroupStateGuard(GroupStateHelper& group_state_helper, GroupState<Scalar>& group_state)
: group_state_helper_ {group_state_helper}
, previous_state_ptr_ {group_state_helper.group_state_}
{
// Set the new state directly
group_state_helper_.group_state_ = &group_state;
}
~GroupStateGuard()
{
// Restore the previous state
group_state_helper_.group_state_ = previous_state_ptr_;
}
// Delete copy and move operations
GroupStateGuard(const GroupStateGuard&) = delete;
GroupStateGuard& operator=(const GroupStateGuard&) = delete;
GroupStateGuard(GroupStateGuard&&) = delete;
GroupStateGuard& operator=(GroupStateGuard&&) = delete;
private:
GroupStateHelper& group_state_helper_;
GroupState<Scalar>* previous_state_ptr_ {nullptr};
};
/// @brief RAII guard that owns a DeferredLogger and auto-gathers on destruction
///
/// @details This class provides a complete lifecycle for deferred logging in parallel
/// simulations. It owns a DeferredLogger instance and automatically handles:
/// - Pushing the logger onto the stack (saving the previous logger)
/// - On destruction: gathering messages across MPI ranks, logging on rank 0
/// (if terminal_output enabled), and restoring the previous logger
///
/// Usage:
/// auto guard = groupStateHelper.pushLogger();
/// // Use groupStateHelper.deferredLogger() to log messages
/// // On scope exit: gather, log (if terminal), restore previous logger
class ScopedLoggerGuard
{
public:
/// @brief Constructor for scoped logger guard
/// @param helper Reference to the GroupStateHelper
/// @param do_mpi_gather If true, gather messages across MPI ranks on destruction.
/// Set to false when called from contexts where MPI synchronization is not
/// possible (e.g., NLDD domain-local operations).
explicit ScopedLoggerGuard(const GroupStateHelper& helper, bool do_mpi_gather = true)
: helper_(&helper)
, previous_(helper.deferred_logger_)
, do_mpi_gather_(do_mpi_gather)
{
helper_->deferred_logger_ = &logger_;
}
~ScopedLoggerGuard()
{
if (helper_) {
if (do_mpi_gather_) {
// 1. Gather messages across MPI ranks
DeferredLogger global = gatherDeferredLogger(logger_, helper_->comm());
// 2. Log on rank 0 (if terminal_output enabled)
if (helper_->terminalOutput()) {
global.logMessages();
}
} else {
// Just log locally without MPI gather
if (helper_->terminalOutput()) {
logger_.logMessages();
}
}
// 3. Restore previous logger
helper_->deferred_logger_ = previous_;
}
}
// Delete copy operations and move assignment
ScopedLoggerGuard(const ScopedLoggerGuard&) = delete;
ScopedLoggerGuard& operator=(const ScopedLoggerGuard&) = delete;
ScopedLoggerGuard& operator=(ScopedLoggerGuard&&) = delete;
// Move constructor required for pushLogger() return-by-value (must be
// available even if elided by RVO)
ScopedLoggerGuard(ScopedLoggerGuard&& other) noexcept
: helper_(other.helper_)
, logger_(std::move(other.logger_))
, previous_(other.previous_)
, do_mpi_gather_(other.do_mpi_gather_)
{
// Update the helper's pointer to our moved logger
if (helper_) {
helper_->deferred_logger_ = &logger_;
}
other.helper_ = nullptr;
other.previous_ = nullptr;
}
private:
// Pointer (not reference) to allow nulling in move constructor
const GroupStateHelper* helper_{nullptr};
DeferredLogger logger_; // Owned logger
DeferredLogger* previous_{nullptr}; // For restore
bool do_mpi_gather_{true}; // Whether to gather messages across MPI ranks
};
using GroupTarget = typename SingleWellState<Scalar, IndexTraits>::GroupTarget;
GroupStateHelper(WellState<Scalar, IndexTraits>& well_state,
GroupState<Scalar>& group_state,
const Schedule& schedule,
const SummaryState& summary_state,
const GuideRate& guide_rate,
const PhaseUsageInfo<IndexTraits>& phase_usage_info,
const Parallel::Communication& comm,
bool terminal_output);
void accumulateGroupEfficiencyFactor(const Group& group, Scalar& factor) const;
std::pair<bool, Scalar> checkGroupConstraintsInj(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<Scalar>& resv_coeff,
const bool check_guide_rate) const;
std::pair<bool, Scalar> checkGroupConstraintsProd(const std::string& name,
const std::string& parent,
const Group& group,
const Scalar* rates,
const Scalar efficiency_factor,
const std::vector<Scalar>& resv_coeff,
const bool check_guide_rate) const;
std::pair<Group::ProductionCMode, Scalar>
checkGroupProductionConstraints(const Group& group) const;
const Parallel::Communication& comm() const { return this->comm_; }
/// @brief Get the deferred logger
/// @throws std::logic_error if no logger has been set via pushLogger()
DeferredLogger& deferredLogger() const
{
if (this->deferred_logger_ == nullptr) {
throw std::logic_error("DeferredLogger not set. Call pushLogger() first.");
}
return *this->deferred_logger_;
}
std::vector<Scalar> getGroupRatesAvailableForHigherLevelControl(const Group& group, const bool is_injector) const;
Scalar getInjectionGroupTarget(const Group& group,
const Phase& injection_phase,
const std::vector<Scalar>& resv_coeff) const;
/// @brief Get the guide rate target mode for an injection phase
/// @param injection_phase The injection phase (WATER, OIL, or GAS)
/// @return The corresponding GuideRateModel::Target for the injection phase
GuideRateModel::Target getInjectionGuideTargetMode(Phase injection_phase) const;
Scalar getProductionGroupTarget(const Group& group) const;
/// Get the production target for a specific control mode (not necessarily the active one).
Scalar getProductionGroupTargetForMode(const Group& group,
const Group::ProductionCMode cmode) const;
/// @brief Get the guide rate target mode for a production group
/// @param group The production group
/// @return The GuideRateModel::Target based on the group's production control mode
GuideRateModel::Target getProductionGuideTargetMode(const Group& group) const;
GuideRateModel::Target
getProductionGuideTargetModeFromControlMode(const Group::ProductionCMode cmode) const;
std::pair<Scalar, Group::ProductionCMode>
getAutoChokeGroupProductionTargetRate(const Group& bottom_group,
const Group& group,
const std::vector<Scalar>& resv_coeff,
Scalar efficiencyFactor) const;
GuideRate::RateVector getProductionGroupRateVector(const std::string& group_name) const;
std::optional<GroupTarget> 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<Scalar>& resv_coeff) const;
std::optional<GroupTarget> getWellGroupTargetProducer(const std::string& name,
const std::string& parent,
const Group& group,
const Scalar* rates,
const Scalar efficiency_factor,
const std::vector<Scalar>& resv_coeff,
std::optional<Group::ProductionCMode> target_cmode = std::nullopt) const;
GuideRate::RateVector getWellRateVector(const std::string& name) const;
std::vector<std::string> groupChainTopBot(const std::string& bottom, const std::string& top) const;
/// returns the number of wells that are actively under group control for a given group with name given
/// by group_name
int groupControlledWells(const std::string& group_name,
const std::string& always_included_child,
const bool is_production_group,
const Phase injection_phase) const;
GroupState<Scalar>& groupState() const
{
return *this->group_state_;
}
const GuideRate& guideRate() const
{
return this->guide_rate_;
}
bool isRank0() const
{
return this->well_state_->isRank0();
}
bool isReservoirCouplingMaster() const { return rescoup_.isMaster(); }
bool isReservoirCouplingMasterGroup(const Group& group) const { return rescoup_.isMasterGroup(group.name()); }
bool isReservoirCouplingSlave() const { return rescoup_.isSlave(); }
bool isReservoirCouplingSlaveGroup(const Group& group) const { return rescoup_.isSlaveGroup(group.name()); }
constexpr int numPhases() const {
return this->wellState().numPhases();
}
int phaseToActivePhaseIdx(const Phase phase) const;
const PhaseUsageInfo<IndexTraits>& phaseUsage() const {
return this->phase_usage_info_;
}
GroupStateGuard pushGroupState(GroupState<Scalar>& group_state)
{
return GroupStateGuard(*this, group_state);
}
/// @brief Push a new logger onto the stack with auto-cleanup on destruction
///
/// @details Creates a new DeferredLogger and pushes it onto the logger stack.
/// When the returned guard goes out of scope:
/// 1. If do_mpi_gather is true: Messages are gathered across MPI ranks via gatherDeferredLogger()
/// and logged on rank 0 (if terminal_output is enabled)
/// 2. If do_mpi_gather is false: Messages are logged locally without MPI gather
/// 3. The previous logger is restored
///
/// @param do_mpi_gather If true (default), gather messages across MPI ranks on destruction.
/// Set to false when called from contexts where MPI synchronization is not
/// possible (e.g., NLDD domain-local operations called at different times on different ranks).
/// @return RAII guard that owns the logger and handles cleanup
ScopedLoggerGuard pushLogger(bool do_mpi_gather = true) const
{
return ScopedLoggerGuard(*this, do_mpi_gather);
}
WellStateGuard pushWellState(WellState<Scalar, IndexTraits>& well_state)
{
return WellStateGuard(*this, well_state);
}
int reportStepIdx() const
{
return report_step_;
}
const Schedule& schedule() const
{
return this->schedule_;
}
ReservoirCoupling::Proxy<Scalar>& rescoup() {
return rescoup_;
}
const ReservoirCoupling::Proxy<Scalar>& rescoup() const {
return rescoup_;
}
ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster() {
return rescoup_.master();
}
const ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster() const {
return rescoup_.master();
}
ReservoirCouplingSlave<Scalar>& reservoirCouplingSlave() {
return rescoup_.slave();
}
const ReservoirCouplingSlave<Scalar>& reservoirCouplingSlave() const {
return rescoup_.slave();
}
#ifdef RESERVOIR_COUPLING_ENABLED
void setReservoirCouplingMaster(ReservoirCouplingMaster<Scalar>* master) {
rescoup_.setMaster(master);
}
void setReservoirCouplingSlave(ReservoirCouplingSlave<Scalar>* slave) {
rescoup_.setSlave(slave);
}
#endif
void setCmodeGroup(const Group& group);
template <class AverageRegionalPressureType>
void
setRegionAveragePressureCalculator(const Group& group,
const FieldPropsManager& fp,
std::map<std::string, std::unique_ptr<AverageRegionalPressureType>>&
regional_average_pressure_calculator) const;
void setReportStep(int report_step)
{
report_step_ = report_step;
}
const SummaryState& summaryState() const
{
return this->summary_state_;
}
Scalar sumSolventRates(const Group& group, const bool is_injector) const;
Scalar sumWellResRates(const Group& group, const int phase_pos, const bool injector) const;
Scalar sumWellSurfaceRates(const Group& group, const int phase_pos, const bool injector) const;
Scalar sumWellPhaseRates(bool res_rates,
const Group& group,
const int phase_pos,
const bool injector,
const bool network = false) const;
bool terminalOutput() const
{
return this->terminal_output_;
}
template <class RegionalValues>
void updateGpMaintTargetForGroups(const Group& group,
const RegionalValues& regional_values,
const double dt);
/// update the number of wells that are actively under group control for a given group with name given by
/// group_name its main usage is to detect cases where there is no wells under group control
int updateGroupControlledWells(const bool is_production_group,
const Phase injection_phase);
void updateGroupProductionRates(const Group& group);
void updatePreviousGroupProductionRates(const Group& group);
void updateGroupTargetReduction(const Group& group,
const bool is_injector);
void updateNetworkLeafNodeProductionRates();
/// @brief Set production control to NONE for groups not targeting any well.
///
/// For each group in the production controls map, checks whether any open
/// producer well has GRUP control with that group as its target. If not,
/// the group's production control is set to NONE. This is needed to get correct summary
/// output for GMCTP/FMCTP, see https://github.com/OPM/opm-simulators/pull/6596 for more details.
///
/// Exceptions (groups excluded from the NONE reset):
/// - Groups specified for gas lift optimization (GLO)
/// - RC master hierarchy groups (master groups + ancestors up to FIELD),
/// which actively distribute targets to slave groups
void updateNONEProductionGroups();
void updateREINForGroups(const Group& group, bool sum_rank);
void updateReservoirRatesInjectionGroups(const Group& group);
#ifdef RESERVOIR_COUPLING_ENABLED
/// @brief Update the slave's GroupState cmodes from the master's active cmodes.
///
/// For each slave group with a master-imposed target, sets the GroupState
/// production/injection control mode to match the master's cmode. This ensures
/// that all downstream consumers (constraint checks, guide rate fractions,
/// well equation assembly) evaluate the correct rate type.
///
/// The update is skipped when the GRUPSLAV filter flag is SLAV, meaning
/// the slave ignores the master's control for that rate type.
void updateSlaveGroupCmodesFromMaster();
#endif
void updateState(WellState<Scalar, IndexTraits>& well_state, GroupState<Scalar>& group_state);
void updateSurfaceRatesInjectionGroups(const Group& group);
void updateVREPForGroups(const Group& group);
void updateWellRates(const Group& group,
const WellState<Scalar, IndexTraits>& well_state_nupcol,
WellState<Scalar, IndexTraits>& well_state) const;
const WellState<Scalar, IndexTraits>& wellState() const
{
return *this->well_state_;
}
void updateWellRatesFromGroupTargetScale(const Scalar scale,
const Group& group,
bool is_injector,
WellState<Scalar, IndexTraits>& well_state) const;
/// Returns the name of the worst offending well and its fraction (i.e. violated_phase / preferred_phase)
std::pair<std::optional<std::string>, Scalar>
worstOffendingWell(const Group& group,
const Group::ProductionCMode& offended_control) const;
private:
//! \brief Calculate group target by applying local rate adjustments and guide rate fractions through group hierarchy.
//!
//! This method encapsulates the core algorithm for calculating the portion of a top-level
//! group target that should be assigned to a bottom group (well or sub-group). The algorithm:
//! 1. Iterates through the group chain from top to bottom
//! 2. At each level with a guide rate (or at the top level), subtracts local reductions (the amount of rate that is not available for the group to control)
//! 3. At the local_reduction_level, adds back the bottom group's rate (if do_addback is true)
//! 4. Multiplies by the guide rate fraction for each level
//!
//! \tparam ReductionLambda Callable type: (const std::string& group_name) -> Scalar
//! \tparam FractionLambda Callable type: (const std::string& child) -> Scalar
//! \param chain Group chain from control group (top) to bottom group
//! \param orig_target Original target from top-level controlling group
//! \param current_rate_available Current rate of the bottom group (for add-back)
//! \param local_reduction_level Level where local reduction/add-back is applied
//! \param is_production_group True for production, false for injection
//! \param injection_phase Phase for injection groups (ignored for production)
//! \param local_reduction_lambda Functor to get local reduction for a group
//! \param local_fraction_lambda Functor to get local fraction for a child group
//! \param do_addback Whether to perform add-back at local_reduction_level
//! \return Target after applying reductions and fractions (before efficiency factor division)
template<typename ReductionLambda, typename FractionLambda>
Scalar applyReductionsAndFractions_(const std::vector<std::string>& chain,
Scalar orig_target,
Scalar current_rate_available,
std::size_t local_reduction_level,
bool is_production_group,
Phase injection_phase,
ReductionLambda&& local_reduction_lambda,
FractionLambda&& local_fraction_lambda,
bool do_addback) const;
std::pair<Group::ProductionCMode, Scalar>
checkProductionRateConstraint_(const Group& group,
Group::ProductionCMode cmode,
Group::ProductionCMode currentControl,
Scalar target,
Scalar current_rate) const;
/// @brief Collect groups that provide production targets to open wells.
///
/// Scans all wells on this rank: for each open producer with GRUP control,
/// adds its group_target to the set. The result is rank-local and must be
/// synchronized via MPI before use.
///
/// @return Set of group names targeted by wells on this rank
std::unordered_set<std::string> collectTargetedProductionGroups_() const;
//! \brief Compute partial efficiency factor for addback calculation.
//!
//! The addback in constraint checking must use the partial efficiency factor
//! (from local_reduction_level down to the entity), not the accumulated efficiency
//! (from the entity to the control group). This helper multiplies efficiency
//! factors from local_reduction_level+1 to the entity.
//!
//! \param chain The group chain from control group (top) to entity (bottom)
//! \param local_reduction_level The level at which addback is applied
//! \return The partial efficiency factor for addback
Scalar computeAddbackEfficiency_(const std::vector<std::string>& chain,
std::size_t local_reduction_level) const;
std::string controlGroup_(const Group& group) const;
GuideRate::RateVector getGuideRateVector_(const std::vector<Scalar>& rates) const;
Scalar getInjectionGroupTargetForMode_(const Group& group,
const Phase& injection_phase,
const std::vector<Scalar>& resv_coeff,
const Group::InjectionCMode cmode) const;
//! \brief Find the local reduction level in a group chain.
//!
//! The local reduction level is the deepest level in the chain (starting from level 1)
//! where a group has both a guide rate and group-controlled wells (GCW > 0).
//! This level determines where reductions are applied and add-back occurs.
//!
//! \param chain Group chain from control group (top) to bottom group
//! \param is_production_group True for production, false for injection
//! \param injection_phase Phase for injection groups (ignored for production)
//! \return The local reduction level (0 if no intermediate group qualifies)
std::size_t getLocalReductionLevel_(const std::vector<std::string>& chain,
bool is_production_group,
Phase injection_phase) const;
Scalar getProductionConstraintTarget_(const Group& group,
Group::ProductionCMode cmode,
const Group::ProductionControls& controls) const;
Scalar getProductionGroupTargetForMode_(const Group& group, const Group::ProductionCMode cmode) const;
Scalar getSatelliteRate_(const Group& group,
const int phase_pos,
const bool res_rates,
const bool is_injector) const;
/// Check if a production auto choke group is underperforming its target rate.
/// Returns true if the group's current rate is below its allocated target,
/// which means wells should be excluded from the GCW count.
bool isAutoChokeGroupUnderperforming_(const Group& group) const;
/// check if well/group bottom is a sub well/group of the group top
bool isInGroupChainTopBot_(const std::string& bottom, const std::string& top) const;
bool isSatelliteGroup_(const Group& group) const;
Scalar satelliteInjectionRate_(const ScheduleState& sched,
const Group& group,
const int phase_pos,
bool res_rates) const;
Scalar satelliteProductionRate_(const ScheduleState& sched,
const Group& group,
const GSatProd::GSatProdGroupProp::Rate rate_comp,
bool res_rates) const;
std::optional<GSatProd::GSatProdGroupProp::Rate> selectRateComponent_(const int phase_pos) const;
//! \brief Subtract other-phase reservoir injection rates from a base rate.
//!
//! For multi-phase injection groups under RESV or VREP control, the total
//! reservoir volume target must be reduced by the reservoir volumes already
//! injected by other phases, leaving only this phase's share.
//!
//! \param injection_phase The controlled injection phase
//! \param base_reservoir_rate The total reservoir rate target before subtraction
//! \param group_injection_reservoir_rates Per-phase reservoir injection rates
//! \return The base rate minus other phases' reservoir injection contributions
Scalar subtractOtherPhaseResvInjection_(
Phase injection_phase,
Scalar base_reservoir_rate,
const std::vector<Scalar>& group_injection_reservoir_rates) const;
Scalar sumProductionRateForControlMode_(const Group& group, Group::ProductionCMode cmode) const;
int updateGroupControlledWellsRecursive_(const std::string& group_name,
const bool is_production_group,
const Phase injection_phase);
void updateGroupTargetReductionRecursive_(const Group& group,
const bool is_injector,
std::vector<Scalar>& group_target_reduction);
// Validate that GCONINJE top-up phases (RESV/VREP) are consistent
// across the group hierarchy. Called once from setCmodeGroup() for FIELD.
void validateInjectionTopupPhases_(const Group& group) const;
// Recursive helper for validateInjectionTopupPhases_()
void validateInjectionTopupPhasesRecursive_(const Group& group,
std::optional<Phase> inherited_topup_phase,
const std::string& inherited_topup_group) const;
// --- Reservoir coupling private methods ---
#ifdef RESERVOIR_COUPLING_ENABLED
/// @brief Convert active phase index to ReservoirCoupling::Phase enum
/// @param phase_pos Active phase index (0, 1, or 2 in a 3-phase model)
/// @return The corresponding ReservoirCoupling::Phase enum value
/// @note This uses the canonical phase ordering (Oil=0, Gas=1, Water=2)
ReservoirCoupling::Phase activePhaseIdxToRescoupPhase_(int phase_pos) const;
/// @brief Collect all groups in the RC master group hierarchy.
///
/// Starting from each master group (identified via GRUPMAST), walks up
/// via group.parent() to FIELD, adding all groups along the path.
/// These groups actively distribute targets to slave groups and must
/// not have their production control reset to NONE.
///
/// @return Set of group names in the master group hierarchy
std::unordered_set<std::string> collectMasterGroupHierarchy_() const;
/// @brief Get the effective production limit for a group and rate type,
/// combining master limit, slave-local target, and GRUPSLAV filter flag.
///
/// If the master sent a per-rate-type limit for this group and rate type,
/// the filter flag determines which value to use:
/// - MAST: return master limit
/// - BOTH: return min(master limit, slave_local_target)
/// - SLAV: return slave_local_target
///
/// @param gname Slave group name
/// @param rate_type The production rate type to check
/// @param slave_local_target The slave's own target from GCONPROD
/// @return The effective limit to apply
Scalar getEffectiveProductionLimit_(const std::string& gname,
Group::ProductionCMode rate_type,
Scalar slave_local_target) const;
ReservoirCoupling::GrupSlav::FilterFlag getInjectionFilterFlag_(const std::string& group_name,
const Phase injection_phase) const;
ReservoirCoupling::GrupSlav::FilterFlag getProductionFilterFlag_(const std::string& group_name,
const Group::ProductionCMode cmode) const;
Scalar getReservoirCouplingMasterGroupRate_(const Group& group,
const int phase_pos,
const ReservoirCoupling::RateKind kind) const;
#endif // RESERVOIR_COUPLING_ENABLED
const WellState<Scalar, IndexTraits>* well_state_ {nullptr};
GroupState<Scalar>* group_state_ {nullptr};
const Schedule& schedule_;
const SummaryState& summary_state_;
const GuideRate& guide_rate_;
// NOTE: The deferred logger does not change the object "meaningful" state, so it should be ok to
// make it mutable and store a pointer to it here.
mutable DeferredLogger* deferred_logger_ {nullptr};
// NOTE: The phase usage info seems to be read-only throughout the simulation, so it should be safe
// to store a reference to it here.
const PhaseUsageInfo<IndexTraits>& phase_usage_info_;
const Parallel::Communication& comm_;
bool terminal_output_ {false};
int report_step_ {0};
ReservoirCoupling::Proxy<Scalar> rescoup_{};
};
// -----------------------------------------------------------------------------
// Template member function implementations
// -----------------------------------------------------------------------------
// NOTE: This template member function is defined here in the header because the
// AverageRegionalPressureType type parameter depends on derived class types that are
// not available when GroupStateHelper.cpp is compiled. Template functions
// must be visible at their instantiation point.
// See GroupStateHelper.cpp for detailed rationale.
template <typename Scalar, typename IndexTraits>
template <class AverageRegionalPressureType>
void
GroupStateHelper<Scalar, IndexTraits>::setRegionAveragePressureCalculator(
const Group& group,
const FieldPropsManager& fp,
std::map<std::string, std::unique_ptr<AverageRegionalPressureType>>& regional_average_pressure_calculator)
const
{
for (const std::string& groupName : group.groups()) {
this->setRegionAveragePressureCalculator(this->schedule_.getGroup(groupName, this->report_step_),
fp,
regional_average_pressure_calculator);
}
const auto& gpm = group.gpmaint();
if (!gpm)
return;
const auto& reg = gpm->region();
if (!reg)
return;
if (regional_average_pressure_calculator.count(reg->first) == 0) {
const std::string name = (reg->first.rfind("FIP", 0) == 0) ? reg->first : "FIP" + reg->first;
const auto& fipnum = fp.get_int(name);
regional_average_pressure_calculator[reg->first]
= std::make_unique<AverageRegionalPressureType>(fipnum);
}
}
// NOTE: This template member function is defined in the header because the
// RegionalValues type parameter depends on derived class types that are
// not available when GroupStateHelper.cpp is compiled. Template functions
// must be visible at their instantiation point.
// See GroupStateHelper.cpp for detailed rationale.
template <typename Scalar, typename IndexTraits>
template <class RegionalValues>
void
GroupStateHelper<Scalar, IndexTraits>::updateGpMaintTargetForGroups(const Group& group,
const RegionalValues& regional_values,
const double dt)
{
OPM_TIMEFUNCTION();
for (const std::string& group_name : group.groups()) {
const Group& group_tmp = this->schedule_.getGroup(group_name, this->report_step_);
this->updateGpMaintTargetForGroups(group_tmp, regional_values, dt);
}
const auto& gpm = group.gpmaint();
if (!gpm)
return;
const auto& region = gpm->region();
if (!region)
return;
if (this->isReservoirCouplingMasterGroup(group)) {
// GPMAINT is not supported for reservoir coupling master groups since master groups do not have
// subordinate wells in the master reservoir, so the slaves cannot influence the master reservoir's
// average pressure.
// Even specifying GPMAINT on a group superior to the master group might not make sense, since if the
// superior target is distributed down to the master group with guide rate fractions, adjusting
// the master group's target (that is sent to the slave) could only indirectly influence the master
// reservoir's average pressure by affecting the guide rate fractions distributed to actual wells
// in the master reservoir.
OPM_DEFLOG_THROW(
std::runtime_error,
"GPMAINT is not supported for reservoir coupling master groups.",
this->deferredLogger()
);
return;
}
else if (this->isReservoirCouplingSlaveGroup(group)) {
// GPMAINT is not supported for reservoir coupling slave groups since their targets will be overridden
// by the corresponding master group's target anyway.
OPM_DEFLOG_THROW(
std::runtime_error,
"GPMAINT is not supported for reservoir coupling slave groups.",
this->deferredLogger()
);
return;
}
const auto [name, number] = *region;
const Scalar error = gpm->pressure_target() - regional_values.at(name)->pressure(number);
Scalar current_rate = 0.0;
const auto& pu = this->phase_usage_info_;
bool injection = true;
Scalar sign = 1.0;
switch (gpm->flow_target()) {
case GPMaint::FlowTarget::RESV_PROD: {
current_rate = -this->groupState().injection_vrep_rate(group.name());
injection = false;
sign = -1.0;
break;
}
case GPMaint::FlowTarget::RESV_OINJ: {
if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) {
const auto io = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx);
current_rate = this->groupState().injection_reservoir_rates(group.name())[io];
}
break;
}
case GPMaint::FlowTarget::RESV_WINJ: {
if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) {
const auto iw = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx);
current_rate = this->groupState().injection_reservoir_rates(group.name())[iw];
}
break;
}
case GPMaint::FlowTarget::RESV_GINJ: {
if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) {
const auto ig = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx);
current_rate = this->groupState().injection_reservoir_rates(group.name())[ig];
}
break;
}
case GPMaint::FlowTarget::SURF_OINJ: {
if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) {
const auto io = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx);
current_rate = this->groupState().injection_surface_rates(group.name())[io];
}
break;
}
case GPMaint::FlowTarget::SURF_WINJ: {
if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) {
const auto iw = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx);
current_rate = this->groupState().injection_surface_rates(group.name())[iw];
}
break;
}
case GPMaint::FlowTarget::SURF_GINJ: {
if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) {
const auto ig = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx);
current_rate = this->groupState().injection_surface_rates(group.name())[ig];
}
break;
}
default:
throw std::invalid_argument("Invalid Flow target type in GPMAINT");
}
auto& gpmaint_state = this->groupState().gpmaint(group.name());
// we only activate gpmaint if pressure is lower than the target regional pressure for injectors
// (i.e. error > 0) and higher for producers.
bool activate = (injection && error > 0) || (!injection && error < 0);
Scalar rate = 0.0;
if (activate) {
rate = gpm->rate(gpmaint_state, current_rate, error, dt);
} else {
gpm->resetState(gpmaint_state);
}
this->groupState().update_gpmaint_target(group.name(), std::max(Scalar {0.0}, sign * rate));
}
} // namespace Opm
#endif // OPM_GROUPSTATE_HELPER_HEADER_INCLUDED