diff --git a/opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp b/opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp index 222c1050420..b072785df25 100644 --- a/opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp +++ b/opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp @@ -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 @@ -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& 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. @@ -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& getMasterGroupToSlaveNameMap() { @@ -106,6 +134,7 @@ 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. @@ -113,10 +142,12 @@ class ReservoirCouplingMaster { /// 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); @@ -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( @@ -148,6 +180,7 @@ class ReservoirCouplingMaster { std::size_t slave_idx, const std::vector& injection_targets ) const; + /// @brief Send master-computed network-leaf node pressures to a slave. void sendMasterGroupNodePressuresToSlave( std::size_t slave_idx, @@ -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( @@ -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 @@ -219,9 +254,11 @@ 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 master_slave_comm_; // MPI communicators for the slave processes std::vector 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. @@ -229,33 +266,48 @@ class ReservoirCouplingMaster { // 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 slave_start_dates_; + // The activation dates are in whole seconds since the epoch. std::vector 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> 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 effective_gcw_; + mutable ReservoirCoupling::Logger logger_; + // Whether the slave has activated. Unfortunatley, we cannot use std::vector since // it is not supported to get a pointer to the underlying array of bools needed // with MPI broadcast(). std::vector slave_activation_status_; + // A mapping from master group names to slave names std::map 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> 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> slave_idx_to_master_groups_; + // Stores data that changes for a single report step or for timesteps within a report step. std::unique_ptr> report_step_data_{nullptr}; + // Handles time stepping for the master and slaves std::unique_ptr> 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). diff --git a/opm/simulators/wells/FractionCalculator.cpp b/opm/simulators/wells/FractionCalculator.cpp index 9b16f1cc04b..10eb1872b1c 100644 --- a/opm/simulators/wells/FractionCalculator.cpp +++ b/opm/simulators/wells/FractionCalculator.cpp @@ -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_); diff --git a/opm/simulators/wells/GroupStateHelper.cpp b/opm/simulators/wells/GroupStateHelper.cpp index dccb93f0023..a62022580ba 100644 --- a/opm/simulators/wells/GroupStateHelper.cpp +++ b/opm/simulators/wells/GroupStateHelper.cpp @@ -973,6 +973,17 @@ GroupStateHelper::groupControlledWells(const std::string& g return num_wells; } +template +bool GroupStateHelper::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 int GroupStateHelper::phaseToActivePhaseIdx(const Phase phase) const @@ -2245,7 +2256,8 @@ GroupStateHelper::isInGroupChainTopBot_(const std::string& // ============================================================================ // Private: Reservoir coupling helpers // Called from: getInjectionGroupTarget(), getProductionConstraintTarget_(), -// sumWellPhaseRates(), updateNONEProductionGroups() +// sumWellPhaseRates(), updateNONEProductionGroups(), +// updateGroupControlledWellsRecursive_() // ============================================================================ #ifdef RESERVOIR_COUPLING_ENABLED @@ -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 +int +GroupStateHelper::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 @@ -2659,25 +2712,9 @@ GroupStateHelper::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. @@ -2688,8 +2725,13 @@ GroupStateHelper::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); @@ -2798,11 +2840,18 @@ GroupStateHelper::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=*/"", diff --git a/opm/simulators/wells/GroupStateHelper.hpp b/opm/simulators/wells/GroupStateHelper.hpp index 3ca524798b5..37540daf339 100644 --- a/opm/simulators/wells/GroupStateHelper.hpp +++ b/opm/simulators/wells/GroupStateHelper.hpp @@ -308,6 +308,17 @@ class GroupStateHelper return this->guide_rate_; } + /// @brief Whether a reservoir-coupling master group is *eligible* to participate in + /// its parent's guide-rate distribution (GCONPROD item 8 RESPOND_TO_PARENT = YES). + /// @details A necessary but not sufficient condition: a group that is eligible still + /// only contributes to the guide-rate sum when its effective GCW > 0 (see + /// ReservoirCouplingMaster::effectiveGCW() and the GCW gate in + /// FractionCalculator::guideRateSum). A capped or inactive-slave group is eligible + /// here but effGCW=0, so it is excluded and instead contributes its rate as a parent + /// target reduction. Returns false for any non-master group, so non-rescoup runs are + /// unaffected. + bool isMasterGroupEligibleForGuideRate(const std::string& group_name) const; + bool isRank0() const { return this->well_state_->isRank0(); @@ -682,6 +693,27 @@ class GroupStateHelper /// @return Set of group names in the master group hierarchy std::unordered_set collectMasterGroupHierarchy_() const; + /// @brief Effective group-controlled-wells (GCW) count for a reservoir-coupling + /// master group, decoupled from its production control mode. + /// @details Only called for master groups (see updateGroupControlledWellsRecursive_). + /// A master group carries the ORAT marker control mode purely for slave + /// communication, so its real participation in the parent's guide-rate + /// distribution cannot be read off the cmode. Instead: + /// - If the group is available for higher-level control (GCONPROD item 8 + /// RESPOND_TO_PARENT = YES, or a plain FLD group): return the effective GCW + /// maintained by the rescoup master — 1 when participating-and-uncapped, 0 + /// when capped at the slave potential or belonging to an inactive slave + /// (see ReservoirCouplingMaster::effectiveGCW()). + /// - Otherwise (RESPOND_TO_PARENT = NO): the group uses only its own limit and + /// is excluded from guide-rate distribution, so GCW = 0. Being on FLD/NONE + /// control in that case is contradictory and throws. + /// The injection path is not yet handled and returns 1. + /// @param group_name Name of the reservoir-coupling master group. + /// @param is_production_group True for the production GCW, false for injection. + /// @return The effective GCW (0 or 1) for the master group. + int getMasterGroupEffectiveGCW_(const std::string& group_name, + bool is_production_group) const; + /// @brief Get the effective production limit for a group and rate type, /// combining master limit, slave-local target, and GRUPSLAV filter flag. /// diff --git a/opm/simulators/wells/rescoup/RescoupConstraintsCalculator.cpp b/opm/simulators/wells/rescoup/RescoupConstraintsCalculator.cpp index 8e3885a437c..e965916701f 100644 --- a/opm/simulators/wells/rescoup/RescoupConstraintsCalculator.cpp +++ b/opm/simulators/wells/rescoup/RescoupConstraintsCalculator.cpp @@ -67,10 +67,33 @@ RescoupConstraintsCalculator( // the underlying GroupStateHelper / GroupConstraintCalculator); the // slave-facing MPI sends in Phase 3 are rank-0-only inside the send helpers. // -// Pre-phase: restore master-group control modes from the Schedule, switch -// inactive slaves' master groups to individual control so they don't -// consume any share of the parent's target, and recompute GCW + FIELD -// target reductions with the resulting state. +// A note on "effective GCW" for master groups: +// GCW (group controlled wells) gates whether a group participates in its +// parent's guide-rate distribution (GCW>0) or instead has its own rate +// subtracted as a parent target reduction (GCW=0). For ordinary groups GCW +// is derived from the control mode. Master groups, however, carry the individual control mode +// purely as a slave-communication signal (see the Phase 2 finalize), so their cmode cannot +// be used to decide participation. The master therefore maintains a per-master-group "effective GCW" +// (ReservoirCouplingMaster::effectiveGCW), set in this routine and read by +// GroupStateHelper::updateGroupControlledWellsRecursive_. It separates two +// concerns: +// - static eligibility: GCONPROD item 8 RESPOND_TO_PARENT = YES +// (Group::productionGroupControlAvailable) — a deck property; from +// - dynamic weight this sync step: 1 if the group participates and is +// uncapped, 0 if capped at its slave potential (Phase 2) or if its +// slave is inactive (pre-phase). +// An eligible group with effective GCW = 0 is excluded from the guide-rate +// sum and contributes its rate as a parent target reduction instead — which +// is precisely how a capped group's shortfall is redistributed to its +// siblings. This decoupling is needed because the cmode (the natural +// dynamic control channel) is already taken by the slave-communication +// marker for master groups. +// +// Pre-phase: restore master-group control modes from the Schedule, reset the +// effective GCW (participating master groups default to 1), switch inactive +// slaves' master groups to individual control and set their effective GCW +// to 0 so they don't consume any share of the parent's target, then +// recompute GCW + FIELD target reductions with the resulting state. // // Phase 1 (per-slave per-master-group): compute initial guide-rate- // distributed targets and per-rate-type limits via @@ -83,15 +106,19 @@ RescoupConstraintsCalculator( // // Phase 2 (cap-and-redistribute): if any master group's Phase-1 target // exceeds its slave's reported production potential, cap the target at -// that potential, switch the group to individual control, recompute the -// FIELD target reduction (so the capped group's rate is now subtracted), -// and recompute targets for the remaining uncapped groups via -// GroupConstraintCalculator. The shortfall is absorbed by the uncapped -// groups through the standard localReduction / FractionCalculator -// machinery. Finally all master groups are switched to individual -// control so the master completes its own time step assuming slave rates -// remain constant; the controls are restored from Schedule at the start -// of the next sync step. See capAndRedistributeProductionTargets_(). +// that potential and set the group's effective GCW to 0. With effective +// GCW = 0 the group drops out of the parent's guide-rate sum, and on the +// recompute below its (capped) rate is subtracted as a FIELD target +// reduction instead; the remaining uncapped groups then absorb the +// shortfall through the standard localReduction / FractionCalculator +// machinery. (The capped group is also switched to individual control, +// but for master groups the cmode is only a slave-communication marker — +// it is the effective GCW, not the cmode, that drives the guide-rate +// exclusion; see the effective-GCW note above.) Finally all master groups +// are switched to individual control so the master completes its own time +// step assuming slave rates remain constant; the controls and effective +// GCW are reset at the start of the next sync step. See +// capAndRedistributeProductionTargets_(). // // Phase 3: send the resulting (target, cmode, per-rate-type limits) // triples to each activated slave via MPI. Only rank 0 actually sends; @@ -185,6 +212,10 @@ calculateMasterGroupConstraintsAndSendToSlaves() this->group_state_helper_ }; this->restoreMasterGroupControlsFromSchedule_(); + // Reset effective-GCW entries from the previous sync step to GCW=1 before + // excludeInactiveSlaveMasterGroupsFromDistribution_() (and later the + // Phase 2 cap) repopulate the 0-entries for this step. + rescoup_master.resetEffectiveGCW(); this->excludeInactiveSlaveMasterGroupsFromDistribution_(); // Recompute GCW and reduction rates after the control changes above. // The earlier updateAndCommunicateGroupData() in beginTimeStep() may @@ -303,15 +334,6 @@ capAndRedistributeProductionTargets_( // updateGroupTargetReduction includes their rates as reduction for the // parent group. Then recompute targets for uncapped groups — they get // a larger share because the parent's available target increased. - // - // TODO: for master groups under individual control in schedule and GCONPROD - // item 8 set to "YES" (RESPOND_TO_PARENT = YES), the target calculation - // above does not compute a correct target because GCW=0 excludes the group from - // FractionCalculator::guideRateSum (see TODO in GroupStateHelper.cpp - // updateGroupControlledWells()). The slave-potential cap below is still - // correct for such groups once Phase 1 (see - // calculateMasterGroupConstraintsAndSendToSlaves() above) is fixed. - // This is deferred to a follow-up PR. auto& rescoup_master = this->reservoir_coupling_master_; const auto num_slaves = all_production_constraints.size(); @@ -332,6 +354,11 @@ capAndRedistributeProductionTargets_( // includes this group's rate as reduction for the parent. this->group_state_helper_.groupState().production_control( group_name, pc.cmode); + // Drop the capped group from guide-rate distribution to its + // siblings: set effective GCW=0 so it is excluded from + // FractionCalculator::guideRateSum on the recompute below. This + // must happen before updateGCWAndTargetReductions_(). + rescoup_master.setEffectiveGCW(group_name, 0); } } } @@ -431,6 +458,12 @@ excludeInactiveSlaveMasterGroupsFromDistribution_() for (const auto& group_name : master_groups) { this->group_state_helper_.groupState().production_control( group_name, individual_cmode); + // For a participating master group (GCONPROD item 8 = YES) the + // ORAT marker above no longer forces GCW=0 — its GCW now comes + // from effectiveGCW(). Force it to 0 here so an inactive slave's + // groups are excluded from guide-rate distribution regardless of + // item 8. + rescoup_master.setEffectiveGCW(group_name, 0); } } }