Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
18 changes: 18 additions & 0 deletions opm/simulators/flow/rescoup/ReservoirCouplingSlave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,24 @@ markSlaveGroupsInSchedule(Schedule& schedule, const int report_step_idx)
this->report_step_data_->markSlaveGroupsInSchedule(schedule, report_step_idx);
}

template <class Scalar>
void
ReservoirCouplingSlave<Scalar>::
setWellsSolvedThisSyncStep(bool value)
{
assert(this->report_step_data_);
this->report_step_data_->setWellsSolvedThisSyncStep(value);
}

template <class Scalar>
bool
ReservoirCouplingSlave<Scalar>::
wellsSolvedThisSyncStep() const
{
assert(this->report_step_data_);
return this->report_step_data_->wellsSolvedThisSyncStep();
}

// ------------------
// Private methods
// ------------------
Expand Down
9 changes: 9 additions & 0 deletions opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ class ReservoirCouplingSlave {
/// @details Delegates to ReservoirCouplingSlaveReportStep.
void markSlaveGroupsInSchedule(Schedule& schedule, int report_step_idx);

/// @brief Record whether the initial well solve for this sync step has run
/// @details Delegates to ReservoirCouplingSlaveReportStep
void setWellsSolvedThisSyncStep(bool value);

const std::string& slaveGroupIdxToGroupName(std::size_t group_idx) const {
return this->slave_group_order_.at(group_idx);
}
Expand All @@ -187,10 +191,15 @@ class ReservoirCouplingSlave {
/// MPI_Comm_disconnect() to complete - it is a collective operation.
void receiveTerminateAndDisconnect();

/// @brief True once the initial well solve for this sync step has run
/// @details Delegates to ReservoirCouplingSlaveReportStep
bool wellsSolvedThisSyncStep() const;

private:
void checkGrupSlavGroupNames_();
std::pair<double, bool> getGrupSlavActivationDateAndCheckHistoryMatchingMode_() const;
bool historyMatchingMode_() const { return this->history_matching_mode_; }

std::size_t numMasterGroups_() const { return this->slave_to_master_group_map_.size(); }
//! \brief Receive the master's go-ahead after reporting our OK status.
//!
Expand Down
15 changes: 15 additions & 0 deletions opm/simulators/flow/rescoup/ReservoirCouplingSlaveReportStep.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,17 @@ class ReservoirCouplingSlaveReportStep {
/// @param cmode Production control mode dictated by the master
void setMasterProductionTarget(const std::string& gname, const Scalar target, const Group::ProductionCMode cmode);

/// @brief Record whether the initial well solve for this sync step has run
/// @param value False before the solve, true after it
void setWellsSolvedThisSyncStep(bool value) { wells_solved_this_sync_step_ = value; }

/// @brief True once the initial well solve for this sync step has run
/// @details Before it, a well that opens in this report step still carries the
/// rates SingleWellState::update_producer_targets() derived from its WCONPROD
/// target, which must not be reported to the master as achieved production.
/// See RescoupSendSlaveGroupData::unsolvedNewWellProductionRates_().
bool wellsSolvedThisSyncStep() const { return wells_solved_this_sync_step_; }


private:
/// @brief Generic helper method for sending data to the master process via MPI
Expand Down Expand Up @@ -301,6 +312,10 @@ class ReservoirCouplingSlaveReportStep {
// Used to control reservoir coupling synchronization of summary data sent from
// the slave to the master process.
bool is_last_substep_of_sync_timestep_{false};
// Flag to track whether the initial well solve of this sync timestep has run.
// Cleared before the pre-solve send of slave group data and set after the solve;
// see wellsSolvedThisSyncStep().
bool wells_solved_this_sync_step_{false};

// Master-imposed targets and corresponding control modes, received from the master
// process at the beginning of each sync timestep. Cleared and repopulated on every
Expand Down
20 changes: 19 additions & 1 deletion opm/simulators/wells/BlackoilWellModelRescoup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ class BlackoilWellModelRescoup {
/// Blocking receive of the per-master-group production targets and
/// injection limits computed by the master. The received values are
/// written into the slave's group state via the receiver helper.
/// Called from the slave's beginTimeStep first-substep handshake.
/// Called from the slave's beginTimeStep first-substep handshake, and
/// once per network iteration from maybeSendSlaveGroupFlowToMaster_(),
/// where the master sends injection targets only.
void receiveGroupConstraintsFromMaster();

/// \brief Receive master-computed network-leaf node pressures and
Expand Down Expand Up @@ -242,6 +244,22 @@ class BlackoilWellModelRescoup {
/// activated slaves and gates the master's own iteration.
bool masterNetworkHasMasterGroupLeavesForSlave_(std::size_t slave_idx) const;

/// \brief Master-side: recompute the master's group state from the slave
/// rates just received and send the resulting injection targets on.
///
/// Called immediately after each non-final receiveSlaveGroupData() inside
/// the network iteration, so that a target derived from slave production
/// (GCONINJE REIN, SALE or VREP) keeps up with the production the slaves
/// report as the network iteration proceeds. Its slave-side counterpart
/// is the receiveGroupConstraintsFromMaster() in
/// BlackoilWellModel::maybeSendSlaveGroupFlowToMaster_().
void refreshAndSendInjectionTargets_();

/// \brief Send injection targets to each activated slave, replacing the
/// ones the slaves are currently holding. Production constraints are
/// not resent. Only called by refreshAndSendInjectionTargets_().
void sendMasterGroupInjectionTargetsToSlaves_();

/// \brief Slave-side: true iff this slave's own deck put the named group
/// into its own surface network as a fixed-pressure node.
///
Expand Down
33 changes: 33 additions & 0 deletions opm/simulators/wells/BlackoilWellModelRescoup_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ maybeExchangeNetworkOuterIterationWithSlaves(bool more_network_update)
if (!is_final) {
// receive slaves' updated network_surface_rates for the next outer iteration.
this->receiveSlaveGroupData();
this->refreshAndSendInjectionTargets_();
}
}
}
Expand Down Expand Up @@ -144,6 +145,7 @@ maybeExchangeNetworkSubIterationWithSlaves()
}
this->sendMasterGroupNodePressuresToSlaves(/*is_final=*/false);
this->receiveSlaveGroupData();
this->refreshAndSendInjectionTargets_();
}

template<typename TypeTag>
Expand Down Expand Up @@ -413,6 +415,37 @@ masterNetworkHasMasterGroupLeavesForSlave_(std::size_t slave_idx) const
return false;
}

template<typename TypeTag>
void
BlackoilWellModelRescoup<TypeTag>::
refreshAndSendInjectionTargets_()
{
// Called right after a receiveSlaveGroupData() inside the network iteration.
// Fold the rates just received into the master's group state -- that is what
// recomputes the reinjection and voidage rates that a GCONINJE REIN, SALE
// or VREP target is built from -- and ship the resulting targets to the
// slaves, replacing the ones they are currently holding. Without this the
// targets stay at the values computed in beginTimeStep(), from slave
// production of the previous sync step.
const int report_step_idx = this->well_model_.simulator().episodeIndex();
this->well_model_.updateAndCommunicateGroupData(
report_step_idx, /*update_wellgrouptarget=*/false);
this->sendMasterGroupInjectionTargetsToSlaves_();
}

template<typename TypeTag>
void
BlackoilWellModelRescoup<TypeTag>::
sendMasterGroupInjectionTargetsToSlaves_()
{
OPM_TIMEFUNCTION();
RescoupConstraintsCalculator<Scalar, IndexTraits> constraints_calculator{
this->well_model_.guideRateHandler(),
this->groupStateHelper()
};
constraints_calculator.recalculateInjectionTargetsAndSendToSlaves();
}

template<typename TypeTag>
bool
BlackoilWellModelRescoup<TypeTag>::
Expand Down
11 changes: 11 additions & 0 deletions opm/simulators/wells/BlackoilWellModel_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ namespace Opm {
#ifdef RESERVOIR_COUPLING_ENABLED
if (this->isReservoirCouplingSlave()) {
if (this->reservoirCouplingSlave().isFirstSubstepOfSyncTimestep()) {
// The wells have not been solved for this sync step yet, so a well that
// opens in this report step still carries its WCONPROD target as its rate.
// See RescoupSendSlaveGroupData::collectSlaveGroupSurfaceProductionRates_().
this->reservoirCouplingSlave().setWellsSolvedThisSyncStep(false);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment. I checked it on our coupled model and the premise does not hold: NEW_WELL is not still set on the later sync steps of a report step. We read the live schedule[report_step].wellgroup_events(), and that flag is cleared after the first time step of the report step — which is also why BlackoilWellModel keeps a separate report_step_start_events_ snapshot for the code that does need it to survive.

Measured on a report step with seven sync steps, probing every call: hasEvent(NEW_WELL) is 1 on the first sync step and 0 on the other six, and the amount actually subtracted is the well's target on the first and exactly 0.0 on each of the others. So the correction fires once per newly opened well and never removes a solved rate.

That said, you are pointing at something real. The correctness of the gate depends on that clearing, and nothing in the code said so. Someone reaching for reportStepStartEvents() — the more obviously correct-looking source, since it is a stable snapshot — would reintroduce exactly the behaviour you describe, because wellsSolvedThisSyncStep() is cleared before the pre-solve send of every sync step. I have written that down at the point where the events are read, in ae617f5 ("Say why the new-well correction reads the live schedule events").

this->rescoupHelper_.sendSlaveGroupDataToMaster();
this->rescoupHelper_.receiveGroupConstraintsFromMaster();
this->rescoupHelper_.receiveCoupledNetworkActiveStatus();
Expand Down Expand Up @@ -538,6 +542,8 @@ namespace Opm {

#ifdef RESERVOIR_COUPLING_ENABLED
if (slave_needs_well_solution) { // isReservoirCouplingSlave()
// The initial well solve above has run, so the well states now hold solved rates.
this->reservoirCouplingSlave().setWellsSolvedThisSyncStep(true);
// Need to update group data based on new well solution.
this->updateAndCommunicateGroupData(reportStepIdx, /*update_wellgrouptarget*/ false);
this->rescoupHelper_.sendSlaveGroupDataToMaster();
Expand Down Expand Up @@ -2362,6 +2368,11 @@ namespace Opm {
if (!is_final) {
this->updateAndCommunicateGroupData(reportStepIdx, /*update_wellgrouptarget=*/false);
this->rescoupHelper_.sendSlaveGroupDataToMaster();
// The master turns the rates just sent into fresh injection targets
// for the groups it controls through a derived GCONINJE mode, and
// sends them straight back. See
// BlackoilWellModelRescoup::refreshAndSendInjectionTargets_().
this->rescoupHelper_.receiveGroupConstraintsFromMaster();
return /*more_network_update=*/true;
}
return /*more_network_update=*/false;
Expand Down
97 changes: 75 additions & 22 deletions opm/simulators/wells/rescoup/RescoupConstraintsCalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,42 @@ calculateMasterGroupConstraintsAndSendToSlaves()
this->group_state_helper_.groupState().communicate_rates(comm);
}

// Recompute the injection targets against the slave rates the master holds now
// and ship them to the slaves, replacing the targets sent earlier in this sync
// step. See the declaration in the header for why this second send exists.
template <class Scalar, class IndexTraits>
void
RescoupConstraintsCalculator<Scalar, IndexTraits>::
recalculateInjectionTargetsAndSendToSlaves()
{
// As in calculateMasterGroupConstraintsAndSendToSlaves(), the body must run
// on every rank of the master communicator: GroupConstraintCalculator relies
// on GroupStateHelper, which performs collective operations. The MPI sends
// themselves are rank-0-only inside the send helpers.
auto& rescoup_master = this->reservoir_coupling_master_;
GroupConstraintCalculator calculator{
this->well_model_,
this->group_state_helper_
};
const auto num_slaves = rescoup_master.numSlaves();
for (std::size_t slave_idx = 0; slave_idx < num_slaves; ++slave_idx) {
if (!rescoup_master.slaveIsActivated(slave_idx)) {
continue;
}
auto injection_targets = this->calculateSlaveGroupInjectionTargets_(slave_idx, calculator);
// An empty production-constraint list tells the slave that no production
// constraints follow; the ones it received earlier this sync step stay in
// force.
this->sendSlaveGroupConstraintsToSlave_(
rescoup_master, slave_idx, injection_targets, /*production_constraints=*/{}
);
}
}

// ----------------------------------------------------------------------
// Private methods alphabetically for class RescoupConstraintsCalculator
// ----------------------------------------------------------------------

template <class Scalar, class IndexTraits>
std::tuple<
std::vector<typename RescoupConstraintsCalculator<Scalar, IndexTraits>::InjectionGroupTarget>,
Expand All @@ -268,34 +304,14 @@ std::tuple<
RescoupConstraintsCalculator<Scalar, IndexTraits>::
calculateSlaveGroupConstraints_(std::size_t slave_idx, GroupConstraintCalculator<Scalar, IndexTraits>& calculator) const
{
std::vector<InjectionGroupTarget> injection_targets;
std::vector<InjectionGroupTarget> injection_targets =
this->calculateSlaveGroupInjectionTargets_(slave_idx, calculator);
std::vector<ProductionGroupConstraints> production_constraints;
auto& rescoup_master = this->reservoir_coupling_master_;
static const std::array<ReservoirCoupling::Phase, 3> phases = {
ReservoirCoupling::Phase::Water, ReservoirCoupling::Phase::Oil, ReservoirCoupling::Phase::Gas
};
const auto& master_groups = rescoup_master.getMasterGroupNamesForSlave(slave_idx);
for (std::size_t group_idx = 0; group_idx < master_groups.size(); ++group_idx) {
const auto& group_name = master_groups[group_idx];
const Group& group = this->schedule_.getGroup(group_name, this->report_step_idx_);
if (group.isInjectionGroup()) {
for (ReservoirCoupling::Phase phase : phases) {
auto target_info = calculator.groupInjectionTarget(group, phase);
if (target_info.has_value()) {
// Always send injection targets as RATE. The numeric value is
// already a surface rate for all modes (RATE, REIN, RESV, VREP),
// and the slave cannot evaluate derived modes (REIN, VREP, RESV)
// because it lacks the master's schedule data (reinj_group,
// voidage_group, GCONSUMP, resv_coeff, etc.).
injection_targets.push_back(
InjectionGroupTarget{
group_idx, target_info->constraint,
Group::InjectionCMode::RATE, phase
}
);
}
}
}
if (group.isProductionGroup()) {
auto constraints = calculator.groupProductionConstraints(group);
if (constraints.has_value()) {
Expand All @@ -317,6 +333,43 @@ calculateSlaveGroupConstraints_(std::size_t slave_idx, GroupConstraintCalculator
return {injection_targets, production_constraints};
}

template <class Scalar, class IndexTraits>
std::vector<typename RescoupConstraintsCalculator<Scalar, IndexTraits>::InjectionGroupTarget>
RescoupConstraintsCalculator<Scalar, IndexTraits>::
calculateSlaveGroupInjectionTargets_(std::size_t slave_idx, GroupConstraintCalculator<Scalar, IndexTraits>& calculator) const
{
std::vector<InjectionGroupTarget> injection_targets;
auto& rescoup_master = this->reservoir_coupling_master_;
static const std::array<ReservoirCoupling::Phase, 3> phases = {
ReservoirCoupling::Phase::Water, ReservoirCoupling::Phase::Oil, ReservoirCoupling::Phase::Gas
};
const auto& master_groups = rescoup_master.getMasterGroupNamesForSlave(slave_idx);
for (std::size_t group_idx = 0; group_idx < master_groups.size(); ++group_idx) {
const auto& group_name = master_groups[group_idx];
const Group& group = this->schedule_.getGroup(group_name, this->report_step_idx_);
if (!group.isInjectionGroup()) {
continue;
}
for (ReservoirCoupling::Phase phase : phases) {
auto target_info = calculator.groupInjectionTarget(group, phase);
if (target_info.has_value()) {
// Always send injection targets as RATE. The numeric value is
// already a surface rate for all modes (RATE, REIN, RESV, VREP),
// and the slave cannot evaluate derived modes (REIN, VREP, RESV)
// because it lacks the master's schedule data (reinj_group,
// voidage_group, GCONSUMP, resv_coeff, etc.).
injection_targets.push_back(
InjectionGroupTarget{
group_idx, target_info->constraint,
Group::InjectionCMode::RATE, phase
}
);
}
}
}
return injection_targets;
}

template <class Scalar, class IndexTraits>
void
RescoupConstraintsCalculator<Scalar, IndexTraits>::
Expand Down
Loading