Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,37 @@ class ReservoirCouplingMaster {
void addSlaveActivationDate(double date) { this->slave_activation_dates_.push_back(date); }
void addSlaveStartDate(std::time_t date) { this->slave_start_dates_.push_back(date); }
void clearDeferredLogger() { logger_.clearDeferredLogger(); }

/// @brief Effective group-controlled-wells (GCW) count for a master group,
/// used by guide-rate distribution independently of the group's production
/// control mode.
/// @details Set by RescoupConstraintsCalculator during master-group constraint
/// calculation. A participating master group (GCONPROD item 8
/// RESPOND_TO_PARENT = YES) defaults to 1 so it is counted in the parent's
/// guide-rate sum even while on individual control; it is set to 0
/// when capped at its slave's potential or when its slave is inactive. See
/// GroupStateHelper::updateGroupControlledWellsRecursive_ for the reader.
/// @return The stored effective GCW, or 1 if the group has no explicit entry
/// (the participating-and-uncapped default).
int effectiveGCW(const std::string& group_name) const {
const auto it = this->effective_gcw_.find(group_name);
return (it == this->effective_gcw_.end()) ? 1 : it->second;
}
void setEffectiveGCW(const std::string& group_name, int value) {
this->effective_gcw_[group_name] = value;
}

/// @brief Clear all effective-GCW entries. Call at the start of each
/// master-group constraint calculation before any entries are set, so stale
/// caps from a previous sync step do not leak in.
void resetEffectiveGCW() { this->effective_gcw_.clear(); }

double getActivationDate() const { return this->activation_date_; }
int getArgc() const { return this->argc_; }
char *getArgv(int index) const { return this->argv_[index]; }
char **getArgv() const { return this->argv_; }
const Parallel::Communication &getComm() const { return this->comm_; }

/// @brief Get the master group names associated with a slave reservoir by index.
///
/// This method retrieves the list of master group names that are associated with a
Expand All @@ -77,6 +103,7 @@ class ReservoirCouplingMaster {
/// falling back to O(log n) map lookup for error handling.
/// @see RescoupConstraintsCalculator::calculateAndSendTargets() for primary usage context
const std::vector<std::string>& getMasterGroupNamesForSlave(std::size_t slave_idx) const;

/// @brief Get the canonical index of the master group for a given slave name and master group name.
/// The index is used to map slave group data sent from the slaves, like potentials to the corresponding
/// master group.
Expand All @@ -85,6 +112,7 @@ class ReservoirCouplingMaster {
/// @return The canonical index of the master group for the given slave name and master group name.
std::size_t getMasterGroupCanonicalIdx(
const std::string &slave_name, const std::string &master_group_name) const;

Scalar getMasterGroupRate(
const std::string &group_name, ReservoirCoupling::Phase phase, ReservoirCoupling::RateKind kind) const;
std::map<std::string, std::string>& getMasterGroupToSlaveNameMap() {
Expand All @@ -106,17 +134,20 @@ class ReservoirCouplingMaster {
void initTimeStepping();
bool isFirstSubstepOfSyncTimestep() const;
bool isMasterGroup(const std::string &group_name) const;

/// @brief Check if the master needs to receive production data from the slaves.
/// @details This flag is used to control reservoir coupling synchronization of
/// summary data sent from the slaves to the master process.
/// The master blocks in timeStepSucceeded() until all slaves have sent
/// their production data.
/// @return true if the master needs to receive production data from the slaves, false if not
bool needsSlaveDataReceive() const;

/// @brief Set whether the master needs to receive production data from the slaves.
/// @details See needsSlaveDataReceive() for details.
/// @param value true if the master needs to receive production data from the slaves, false if not
void setNeedsSlaveDataReceive(bool value);

ReservoirCoupling::Logger& logger() { return this->logger_; }
ReservoirCoupling::Logger& logger() const { return this->logger_; }
void maybeActivate(int report_step);
Expand All @@ -138,6 +169,7 @@ class ReservoirCouplingMaster {
void sendNextTimeStepToSlaves(double next_time_step) {
this->time_stepper_->sendNextTimeStepToSlaves(next_time_step);
}

/// @brief Send a single boolean to a slave telling it whether the master
/// will iterate the cross-rescoup network exchange this sync timestep.
void sendCoupledNetworkActiveStatusToSlave(
Expand All @@ -148,6 +180,7 @@ class ReservoirCouplingMaster {
std::size_t slave_idx,
const std::vector<InjectionGroupTarget>& injection_targets
) const;

/// @brief Send master-computed network-leaf node pressures to a slave.
void sendMasterGroupNodePressuresToSlave(
std::size_t slave_idx,
Expand All @@ -158,6 +191,7 @@ class ReservoirCouplingMaster {
std::size_t num_injection_targets,
std::size_t num_production_constraints
) const;

/// @brief Send the count of master-computed network-leaf node pressures
/// plus the master's `is_final` flag for this sync-step iteration.
void sendNumMasterGroupNodePressuresToSlave(
Expand All @@ -183,6 +217,7 @@ class ReservoirCouplingMaster {
void setSlaveNextReportTimeOffset(int index, double offset);
void setSlaveStartDate(int index, std::time_t date) { this->slave_start_dates_[index] = date; }
bool slaveIsActivated(int index) const { return this->slave_activation_status_[index] != 0; }

/// @brief Whether the master syncs with slaves at slave report-step boundaries
/// (true) or at every master actual time step (false, default CLI flag value).
/// @details Set from the --rescoup-sync-at-report-steps CLI flag in the
Expand Down Expand Up @@ -219,43 +254,60 @@ class ReservoirCouplingMaster {
char **argv_;
// Whether the master process has activated the reservoir coupling
bool activated_{false};

// NOTE: MPI_Comm is just an integer handle, so we can just copy it into the vector
std::vector<MPI_Comm> master_slave_comm_; // MPI communicators for the slave processes
std::vector<std::string> slave_names_;

// The start dates are in whole seconds since the epoch. We use a double to store the value
// since both schedule_.getStartTime() and schedule_.stepLength(report_step) returns
// a double value representing whole seconds.
// However, note that schedule_[report_step].start_time() returns a time_point
// which can include milliseconds. The double values are also convenient when we need to
// to add fractions of seconds for sub steps to the start date.
std::vector<double> slave_start_dates_;

// The activation dates are in whole seconds since the epoch.
std::vector<double> slave_activation_dates_;
double activation_date_{0.0}; // The date when SLAVES is encountered in the schedule

// A mapping from a slave name to the master group name order used when slaves send
// potentials to the master process.
std::map<std::string, std::map<std::string, std::size_t>> master_group_name_order_;

// Effective group-controlled-wells count per master group, used by guide-rate
// distribution independently of the production control mode (see effectiveGCW()).
// Reset and repopulated on each master-group constraint calculation.
std::map<std::string, int> effective_gcw_;

mutable ReservoirCoupling::Logger logger_;

// Whether the slave has activated. Unfortunatley, we cannot use std::vector<bool> since
// it is not supported to get a pointer to the underlying array of bools needed
// with MPI broadcast().
std::vector<std::uint8_t> slave_activation_status_;

// A mapping from master group names to slave names
std::map<std::string, std::string> master_group_slave_names_;

// A mapping from slave names to master group names
// NOTE: The order of the master groups in the vector is important,
// as the slaves will communicate the indices of the master groups in
// the vector instead of the group names themselves.
// NOTE: This map is created by ReservoirCouplingSpawnSlaves.cpp
std::map<std::string, std::vector<std::string>> slave_name_to_master_groups_map_;

// Direct index-based lookup for performance optimization (O(1) instead of O(log n))
// This vector is populated in parallel with slave_name_to_master_groups_map_
// and maintains the same ordering as slave_names_ vector for consistent indexing
std::vector<std::vector<std::string>> slave_idx_to_master_groups_;

// Stores data that changes for a single report step or for timesteps within a report step.
std::unique_ptr<ReservoirCouplingMasterReportStep<Scalar>> report_step_data_{nullptr};

// Handles time stepping for the master and slaves
std::unique_ptr<ReservoirCouplingTimeStepper<Scalar>> time_stepper_{nullptr};

// CLI flag --rescoup-sync-at-report-steps. When true, the master syncs with
// its slaves at report-step boundaries (RSYNC). When false
// (default), it syncs at every master time step (TSYNC).
Expand Down
11 changes: 8 additions & 3 deletions opm/simulators/wells/FractionCalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,14 @@ guideRateSum(const Group& group,
for (const std::string& child_group : group.groups()) {
bool included = (child_group == always_included_child);
if (is_producer_) {
const auto ctrl = this->groupStateHelper().groupState().production_control(child_group);
included |= (ctrl == Group::ProductionCMode::FLD) ||
(ctrl == Group::ProductionCMode::NONE);
included |= this->groupStateHelper().groupState().has_field_or_none_control(child_group);
// A reservoir-coupling master group with GCONPROD item 8 = YES is eligible
// to participate in its parent's guide-rate sum even while on individual
// control. Without this, multi-sibling groups on individual control are
// dropped from each other's denominator, giving each ~the full parent
// share. Eligibility is necessary but not sufficient: the capped-state is
// still enforced by the GCW gate below (effective GCW = 0 -> excluded).
included |= this->groupStateHelper().isMasterGroupEligibleForGuideRate(child_group);
} else {
const auto ctrl = this->groupStateHelper().groupState().injection_control(child_group,
this->injection_phase_);
Expand Down
103 changes: 76 additions & 27 deletions opm/simulators/wells/GroupStateHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,17 @@ GroupStateHelper<Scalar, IndexTraits>::groupControlledWells(const std::string& g
return num_wells;
}

template <typename Scalar, typename IndexTraits>
bool GroupStateHelper<Scalar, IndexTraits>::isMasterGroupEligibleForGuideRate(
const std::string& group_name) const
{
if (!this->rescoup_.isMasterGroup(group_name)) {
return false;
}
const auto& group = this->schedule_.getGroup(group_name, this->report_step_);
return group.productionGroupControlAvailable();
}

template <typename Scalar, typename IndexTraits>
int
GroupStateHelper<Scalar, IndexTraits>::phaseToActivePhaseIdx(const Phase phase) const
Expand Down Expand Up @@ -2245,7 +2256,8 @@ GroupStateHelper<Scalar, IndexTraits>::isInGroupChainTopBot_(const std::string&
// ============================================================================
// Private: Reservoir coupling helpers
// Called from: getInjectionGroupTarget(), getProductionConstraintTarget_(),
// sumWellPhaseRates(), updateNONEProductionGroups()
// sumWellPhaseRates(), updateNONEProductionGroups(),
// updateGroupControlledWellsRecursive_()
// ============================================================================

#ifdef RESERVOIR_COUPLING_ENABLED
Expand Down Expand Up @@ -2377,6 +2389,47 @@ getInjectionFilterFlag_(const std::string& group_name,
}
}

// Called from updateGroupControlledWellsRecursive_().
// - Returns the effective GCW (group controlled wells) for a master group.
template <typename Scalar, typename IndexTraits>
int
GroupStateHelper<Scalar, IndexTraits>::getMasterGroupEffectiveGCW_(const std::string& group_name,
bool is_production_group) const
{
const auto& group = this->schedule_.getGroup(group_name, this->report_step_);
int num_wells = 0;
// NOTE: If "group" is a reservoir coupling master group, it should have no child groups or wells.
// During master group constraint calculation, we will set individual control on master groups
// to exclude them from guide rate distribution, see
// RescoupConstraintsCalculator::calculateMasterGroupConstraintsAndSendToSlaves().
if (is_production_group) {
if (group.productionGroupControlAvailable()) {
// Either [individual control AND GCONPROD item 8 RESPOND_TO_PARENT
// = YES], OR a plain FLD/NONE group: the group participates in the
// parent's guide-rate distribution. Its GCW is decoupled from the
// production control mode and read from the rescoup master, which
// sets it to 1 when participating-and-uncapped and 0 when capped at
// the slave potential or belonging to an inactive slave. See
// RescoupConstraintsCalculator::calculateMasterGroupConstraintsAndSendToSlaves().
num_wells = this->reservoirCouplingMaster().effectiveGCW(group_name);
} else {
if (groupState().has_field_or_none_control(group_name)) {
// A group with GCONPROD item 8 RESPOND_TO_PARENT = NO but still on FLD/NONE control
// is an error, throw an exception.
OPM_DEFLOG_THROW(std::logic_error,
"Group with GCONPROD item 8 RESPOND_TO_PARENT = NO but still on FLD/NONE control",
this->deferredLogger());
}
// Individual control with RESPOND_TO_PARENT = NO: not available
// for higher-level control, so it is excluded from guide-rate distribution (GCW=0).
num_wells = 0;
}
} else {
num_wells = 1; // injection: not yet handled
}
return num_wells;
}

// Called from getEffectiveProductionLimit_().
// - Returns the GRUPSLAV production filter flag for a slave group and control mode.
template <typename Scalar, typename IndexTraits>
Expand Down Expand Up @@ -2659,25 +2712,9 @@ GroupStateHelper<Scalar, IndexTraits>::updateGroupControlledWellsRecursive_(
const Group& group = this->schedule_.getGroup(group_name, this->report_step_);
int num_wells = 0;
if (this->isReservoirCouplingMasterGroup(group)) {
// NOTE: If "group" is a reservoir coupling master group, it should have no child groups or wells.
// During master group constraint calculation, we will set individual control on master groups
// to exclude them from guide rate distribution, see
// RescoupConstraintsCalculator::calculateMasterGroupConstraintsAndSendToSlaves().
// TODO: a master group with individual control and GCONPROD item 8 = "YES"
// (RESPOND_TO_PARENT = YES) has an own rate limit AND is available for
// higher-level group control. The current control mode-driven check
// below assigns GCW=0 for such groups, which excludes them from
// guide-rate distribution and yields a wrong target.
// Proposed fix: effective-GCW accessor on ReservoirCouplingMaster + extension
// to FractionCalculator::guideRateSum. Deferred to a follow-up PR.
if (is_production_group) {
const auto ctrl = this->groupState().production_control(group_name);
const bool individual = (ctrl != Group::ProductionCMode::FLD
&& ctrl != Group::ProductionCMode::NONE);
num_wells = individual ? 0 : 1;
} else {
num_wells = 1; // injection: not yet handled
}
#ifdef RESERVOIR_COUPLING_ENABLED
num_wells = this->getMasterGroupEffectiveGCW_(group_name, is_production_group);
#endif
}
else {
// NOTE: A group with sub groups cannot also have direct wells (one level below) under its control.
Expand All @@ -2688,8 +2725,13 @@ GroupStateHelper<Scalar, IndexTraits>::updateGroupControlledWellsRecursive_(
for (const std::string& child_group : group.groups()) {
bool included = false;
if (is_production_group) {
const auto ctrl = this->groupState().production_control(child_group);
included = (ctrl == Group::ProductionCMode::FLD || ctrl == Group::ProductionCMode::NONE);
included = this->groupState().has_field_or_none_control(child_group);
// A participating reservoir-coupling master group (GCONPROD item 8
// = YES) counts toward its parent's GCW even on individual control,
// so the parent is itself treated as group-controlled rather than individual.
// Without this the parent gets GCW=0 and its aggregate slave rate is added to the field
// reduction. Its effective GCW (1 uncapped, 0 capped) is added by the recursive call below.
included |= this->isMasterGroupEligibleForGuideRate(child_group);
} else {
const auto ctrl = this->groupState().injection_control(child_group, injection_phase);
included = (ctrl == Group::InjectionCMode::FLD || ctrl == Group::InjectionCMode::NONE);
Expand Down Expand Up @@ -2798,11 +2840,18 @@ GroupStateHelper<Scalar, IndexTraits>::updateGroupTargetReductionRecursive_(
}
}
} else {
const Group::ProductionCMode& current_group_control
= this->groupState().production_control(sub_group.name());
const bool individual_control = (current_group_control != Group::ProductionCMode::FLD
&& current_group_control != Group::ProductionCMode::NONE);
// NOTE: A reservoir coupling master group: will have GCW (group controlled wells) set to 1 by convention.
bool individual_control = !this->groupState().has_field_or_none_control(sub_group.name());
// A participating reservoir-coupling master group (GCONPROD item 8
// RESPOND_TO_PARENT = YES) has its target guide-rate distributed by the
// parent, so its rate must NOT also be subtracted from the parent's
// available target. Its control mode is set to individual control for
// slave communication, see RescoupConstraintsCalculator::capAndRedistributeProductionTargets_(),
// which would otherwise flag it as individual and
// double-count it here. Treat it as group-controlled; a capped or inactive participating
// group keeps effective GCW = 0 and is still reduced via the num==0 branch below.
if (this->isMasterGroupEligibleForGuideRate(sub_group.name())) {
individual_control = false;
}
const int num_group_controlled_wells
= this->groupControlledWells(sub_group.name(),
/*always_included_child=*/"",
Expand Down
Loading