mirror of
https://github.com/OPM/opm-simulators.git
synced 2026-10-26 07:30:08 -05:00
Send more data from the slave to master
The master needs more data than the currently sent slave group potentials: - surface production rates - reservoir voidage rates - reinjection rates - surface injection rate - reservoir injection rate
This commit is contained in:
1 parent
d5e3ceff0b
commit
35fcc75cfa
32 files changed
+2195
-679
No files matched your search
+19
-9
@@ -1332,17 +1332,27 @@ if(dune-alugrid_FOUND)
|
||||
endif()
|
||||
if(MPI_FOUND)
|
||||
list (APPEND MAIN_SOURCE_FILES
|
||||
opm/simulators/flow/ReservoirCoupling.cpp
|
||||
opm/simulators/flow/ReservoirCouplingMaster.cpp
|
||||
opm/simulators/flow/ReservoirCouplingSlave.cpp
|
||||
opm/simulators/flow/ReservoirCouplingSpawnSlaves.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCoupling.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingMaster.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingMasterReportStep.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingSlave.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingSlaveReportStep.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingSpawnSlaves.cpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingTimeStepper.cpp
|
||||
opm/simulators/wells/rescoup/RescoupReceiveSlaveGroupData.cpp
|
||||
opm/simulators/wells/rescoup/RescoupSendSlaveGroupData.cpp
|
||||
)
|
||||
list (APPEND PUBLIC_HEADER_FILES
|
||||
opm/simulators/flow/ReservoirCoupling.hpp
|
||||
opm/simulators/flow/ReservoirCouplingMpiTraits.hpp
|
||||
opm/simulators/flow/ReservoirCouplingMaster.hpp
|
||||
opm/simulators/flow/ReservoirCouplingSlave.hpp
|
||||
opm/simulators/flow/ReservoirCouplingSpawnSlaves.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCoupling.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingMasterReportStep.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingSlaveReportStep.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingSpawnSlaves.hpp
|
||||
opm/simulators/flow/rescoup/ReservoirCouplingTimeStepper.hpp
|
||||
opm/simulators/wells/rescoup/RescoupReceiveSlaveGroupData.hpp
|
||||
opm/simulators/wells/rescoup/RescoupSendSlaveGroupData.hpp
|
||||
)
|
||||
list (APPEND TEST_SOURCE_FILES
|
||||
tests/rescoup/test_chopstep.cpp
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
#define RESERVOIR_COUPLING_ENABLED
|
||||
#endif
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#endif
|
||||
|
||||
#include <dune/common/parallel/mpihelper.hh>
|
||||
@@ -826,19 +826,19 @@ public:
|
||||
}
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster* reservoirCouplingMaster() const
|
||||
ReservoirCouplingMaster<Scalar>* reservoirCouplingMaster() const
|
||||
{
|
||||
return reservoirCouplingMaster_;
|
||||
}
|
||||
ReservoirCouplingSlave* reservoirCouplingSlave() const
|
||||
ReservoirCouplingSlave<Scalar>* reservoirCouplingSlave() const
|
||||
{
|
||||
return reservoirCouplingSlave_;
|
||||
}
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster *reservoirCouplingMaster)
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster<Scalar> *reservoirCouplingMaster)
|
||||
{
|
||||
this->reservoirCouplingMaster_ = reservoirCouplingMaster;
|
||||
}
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave *reservoirCouplingSlave)
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave<Scalar> *reservoirCouplingSlave)
|
||||
{
|
||||
this->reservoirCouplingSlave_ = reservoirCouplingSlave;
|
||||
}
|
||||
@@ -960,8 +960,8 @@ private:
|
||||
bool verbose_;
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster *reservoirCouplingMaster_ = nullptr;
|
||||
ReservoirCouplingSlave *reservoirCouplingSlave_ = nullptr;
|
||||
ReservoirCouplingMaster<Scalar> *reservoirCouplingMaster_ = nullptr;
|
||||
ReservoirCouplingSlave<Scalar> *reservoirCouplingSlave_ = nullptr;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSpawnSlaves.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/Slaves.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
// NOTE: All slave-master communicators have set a custom error handler, which eventually
|
||||
// will call MPI_Abort() so there is no need to check the return value of any MPI_Recv()
|
||||
// or MPI_Send() calls below.
|
||||
|
||||
ReservoirCouplingMaster::
|
||||
ReservoirCouplingMaster(
|
||||
const Parallel::Communication &comm,
|
||||
const Schedule &schedule,
|
||||
int argc, char **argv
|
||||
) :
|
||||
comm_{comm},
|
||||
schedule_{schedule},
|
||||
argc_{argc},
|
||||
argv_{argv}
|
||||
{
|
||||
this->activation_date_ = this->getMasterActivationDate_();
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
bool
|
||||
ReservoirCouplingMaster::
|
||||
isMasterGroup(const std::string &group_name) const
|
||||
{
|
||||
return this->master_group_slave_names_.find(group_name) !=
|
||||
this->master_group_slave_names_.end();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ReservoirCouplingMaster::
|
||||
getMasterGroupPotIdx(
|
||||
const std::string &slave_name, const std::string &master_group_name) const
|
||||
{
|
||||
return this->master_group_name_order_.at(slave_name).at(master_group_name);
|
||||
}
|
||||
|
||||
const ReservoirCoupling::Potentials&
|
||||
ReservoirCouplingMaster::
|
||||
getSlaveGroupPotentials(const std::string &master_group_name)
|
||||
{
|
||||
auto it = this->master_group_slave_names_.find(master_group_name);
|
||||
if (it != this->master_group_slave_names_.end()) {
|
||||
auto& slave_name = it->second;
|
||||
auto pot_idx = this->getMasterGroupPotIdx(slave_name, master_group_name);
|
||||
return this->slave_group_potentials_[slave_name][pot_idx];
|
||||
}
|
||||
else {
|
||||
OPM_THROW(
|
||||
std::runtime_error,
|
||||
fmt::format(
|
||||
"Master group name {} not found in master-to-slave-group-name mapping",
|
||||
master_group_name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
maybeActivate(int report_step) {
|
||||
if (!this->activated()) {
|
||||
double start_date = this->schedule_.getStartTime();
|
||||
auto current_time = start_date + this->schedule_.seconds(report_step);
|
||||
if (Seconds::compare_gt_or_eq(current_time, this->activation_date_)) {
|
||||
this->activated_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
maybeSpawnSlaveProcesses(int report_step)
|
||||
{
|
||||
if (this->numSlavesStarted() > 0) { // We have already spawned the slave processes
|
||||
return;
|
||||
}
|
||||
const auto& rescoup = this->schedule_[report_step].rescoup();
|
||||
auto slave_count = rescoup.slaveCount();
|
||||
auto master_group_count = rescoup.masterGroupCount();
|
||||
if (slave_count > 0 && master_group_count > 0) {
|
||||
ReservoirCouplingSpawnSlaves spawn_slaves{*this, rescoup};
|
||||
spawn_slaves.spawn();
|
||||
}
|
||||
}
|
||||
|
||||
double
|
||||
ReservoirCouplingMaster::
|
||||
maybeChopSubStep(double suggested_timestep_original, double elapsed_time) const
|
||||
{
|
||||
// Check if the suggested timestep needs to be adjusted based on the slave processes'
|
||||
// next report step, or if the slave process has not started yet: the start of a slave process.
|
||||
// NOTE: getStartTime() returns a std::time_t value, which is typically a long integer. It should
|
||||
// be possible to represent reasonable epoch values within a double. See comment for
|
||||
// getMasterActivationDate_() for more information.
|
||||
double start_date = this->schedule_.getStartTime();
|
||||
double step_start_date{start_date + elapsed_time};
|
||||
double step_end_date{step_start_date + suggested_timestep_original};
|
||||
double suggested_timestep{suggested_timestep_original};
|
||||
auto num_slaves = this->numSlavesStarted();
|
||||
// Determine the minimum step_end_date and the corresponding suggested_timestep such that no
|
||||
// slave process will report or start during the timestep [step_start_date, step_end_date]
|
||||
// where suggested_timestep = step_end_date - step_start_date
|
||||
for (std::size_t i = 0; i < num_slaves; i++) {
|
||||
double slave_start_date = this->slave_start_dates_[i];
|
||||
double slave_activation_date = this->slave_activation_dates_[i];
|
||||
double slave_next_report_date{this->slave_next_report_time_offsets_[i] + slave_start_date};
|
||||
if (Seconds::compare_gt_or_eq(slave_activation_date, step_end_date)) {
|
||||
// The slave process has not activated yet, and will not activate during this timestep
|
||||
continue;
|
||||
}
|
||||
double slave_elapsed_time;
|
||||
if (Seconds::compare_lt_or_eq(slave_activation_date,step_start_date)) {
|
||||
// The slave process has already activated, and will continue during this timestep
|
||||
if (Seconds::compare_gt(slave_next_report_date, step_end_date)) {
|
||||
// The slave process will not report during this timestep
|
||||
continue;
|
||||
}
|
||||
// The slave process will report during this timestep
|
||||
slave_elapsed_time = slave_next_report_date - step_start_date;
|
||||
}
|
||||
else {
|
||||
// The slave process will activate during the timestep, but not at the beginning
|
||||
// Ensure that we synchronize the slave activation report date with the
|
||||
// start of a master timestep by chopping the current master timestep.
|
||||
slave_elapsed_time = slave_activation_date - step_start_date;
|
||||
}
|
||||
suggested_timestep = slave_elapsed_time;
|
||||
step_end_date = step_start_date + suggested_timestep;
|
||||
}
|
||||
return suggested_timestep;
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
maybeReceiveActivationHandshakeFromSlaves(double current_time)
|
||||
{
|
||||
// Initialize on first call
|
||||
if (this->slave_activation_status_.empty()) {
|
||||
this->slave_activation_status_.resize(this->numSlavesStarted(), false);
|
||||
}
|
||||
|
||||
if (this->comm_.rank() == 0) {
|
||||
auto current_date = this->schedule_.getStartTime() + current_time;
|
||||
for (unsigned int i = 0; i < this->numSlavesStarted(); i++) {
|
||||
// Skip if already activated
|
||||
if (this->slaveIsActivated(i)) {
|
||||
continue;
|
||||
}
|
||||
// Check if slave should activate during this timestep
|
||||
double slave_activation_date = this->slave_activation_dates_[i];
|
||||
// NOTE: The master will adjust its time stepping to ensure that its step will always
|
||||
// conincide with the slave report steps (and hence the slave activation date)
|
||||
assert(Seconds::compare_gt_or_eq(slave_activation_date, current_date));
|
||||
if (Seconds::compare_eq(slave_activation_date, current_date)) {
|
||||
// Use non-blocking probe first to check for handshake
|
||||
int flag;
|
||||
MPI_Status status;
|
||||
MPI_Iprobe(0, static_cast<int>(MessageTag::SlaveActivationHandshake),
|
||||
this->master_slave_comm_[i], &flag, &status);
|
||||
|
||||
if (!flag) {
|
||||
// Handshake not received yet, for debugging reasons it can be useful to know which slave
|
||||
// is waiting for the handshake
|
||||
OpmLog::info(fmt::format("Waiting for activation handshake from slave {}",
|
||||
this->slave_names_[i]));
|
||||
}
|
||||
std::uint8_t handshake;
|
||||
auto MPI_UINT8_T_TYPE = Dune::MPITraits<std::uint8_t>::getType();
|
||||
MPI_Recv(&handshake, 1, MPI_UINT8_T_TYPE, 0,
|
||||
static_cast<int>(MessageTag::SlaveActivationHandshake),
|
||||
this->master_slave_comm_[i], MPI_STATUS_IGNORE);
|
||||
this->slave_activation_status_[i] = handshake;
|
||||
OpmLog::info(fmt::format("Received activation handshake from slave {}",
|
||||
this->slave_names_[i]));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// Broadcast status to all ranks
|
||||
comm_.broadcast(
|
||||
this->slave_activation_status_.data(), /*count=*/this->numSlavesStarted(), /*emitter_rank=*/0
|
||||
);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ReservoirCouplingMaster::
|
||||
numSlaveGroups(unsigned int index)
|
||||
{
|
||||
return this->master_group_name_order_[this->slave_names_[index]].size();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
ReservoirCouplingMaster::
|
||||
numSlavesStarted() const
|
||||
{
|
||||
return this->slave_names_.size();
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
sendNextTimeStepToSlaves(double timestep)
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
for (unsigned int i = 0; i < this->master_slave_comm_.size(); i++) {
|
||||
if (!this->slaveIsActivated(i)) {
|
||||
OpmLog::info(fmt::format(
|
||||
"Slave {} has not activated yet, skipping sending next time step to slave",
|
||||
this->slave_names_[i]
|
||||
));
|
||||
continue;
|
||||
}
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
MPI_Send(
|
||||
×tep,
|
||||
/*count=*/1,
|
||||
/*datatype=*/MPI_DOUBLE,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveNextTimeStep),
|
||||
this->getSlaveComm(i)
|
||||
);
|
||||
OpmLog::info(fmt::format(
|
||||
"Sent next time step {} from master process rank 0 to slave process "
|
||||
"rank 0 with name: {}", timestep, this->slave_names_[i])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
receiveNextReportDateFromSlaves()
|
||||
{
|
||||
auto num_slaves = this->numSlavesStarted();
|
||||
if (this->comm_.rank() == 0) {
|
||||
OpmLog::info("Receiving next report dates from slave processes");
|
||||
for (unsigned int i = 0; i < num_slaves; i++) {
|
||||
if (!this->slaveIsActivated(i)) {
|
||||
// Set to zero to indicate that the slave has not activated yet
|
||||
this->slave_next_report_time_offsets_[i] = 0.0;
|
||||
OpmLog::info(fmt::format("Slave {} has not activated yet, setting next report date to 0.0",
|
||||
this->slave_names_[i]));
|
||||
continue;
|
||||
}
|
||||
double slave_next_report_time_offset; // Elapsed time from the beginning of the simulation
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
MPI_Recv(
|
||||
&slave_next_report_time_offset,
|
||||
/*count=*/1,
|
||||
/*datatype=*/MPI_DOUBLE,
|
||||
/*source_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveNextReportDate),
|
||||
this->getSlaveComm(i),
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
this->slave_next_report_time_offsets_[i] = slave_next_report_time_offset;
|
||||
OpmLog::info(
|
||||
fmt::format(
|
||||
"Received simulation slave next report date from slave process with name: {}. "
|
||||
"Next report date: {}", this->slave_names_[i], slave_next_report_time_offset
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
this->comm_.broadcast(
|
||||
this->slave_next_report_time_offsets_.data(), /*count=*/num_slaves, /*emitter_rank=*/0
|
||||
);
|
||||
OpmLog::info("Broadcasted slave next report dates to all ranks");
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
receivePotentialsFromSlaves()
|
||||
{
|
||||
auto num_slaves = this->numSlavesStarted();
|
||||
this->logger_.info("Receiving potentials from slave processes");
|
||||
for (unsigned int i = 0; i < num_slaves; i++) {
|
||||
auto num_slave_groups = this->numSlaveGroups(i);
|
||||
assert( num_slave_groups > 0 );
|
||||
std::vector<Potentials> potentials(num_slave_groups);
|
||||
if (this->comm_.rank() == 0) {
|
||||
if (this->slaveIsActivated(i)) {
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
auto MPI_POTENTIALS_TYPE = Dune::MPITraits<Potentials>::getType();
|
||||
MPI_Recv(
|
||||
potentials.data(),
|
||||
/*count=*/num_slave_groups,
|
||||
/*datatype=*/MPI_POTENTIALS_TYPE,
|
||||
/*source_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::Potentials),
|
||||
this->getSlaveComm(i),
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
this->logger_.info(
|
||||
fmt::format(
|
||||
"Received potentials from slave process with name: {}. "
|
||||
"Number of slave groups: {}", this->slave_names_[i], num_slave_groups
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
this->logger_.info(fmt::format(
|
||||
"Slave {} has not activated yet, skipping receiving potentials from slave",
|
||||
this->slave_names_[i]
|
||||
)
|
||||
);
|
||||
potentials.assign(num_slave_groups, Potentials{}); // Set to zero potentials
|
||||
}
|
||||
}
|
||||
// NOTE: The dune broadcast() below will do something like:
|
||||
// MPI_Bcast(inout,len,MPITraits<Potentials>::getType(),root,communicator)
|
||||
// so it should use the custom Potentials MPI type that we defined in
|
||||
// ReservoirCouplingMpiTraits.hpp
|
||||
this->comm_.broadcast(potentials.data(), /*count=*/num_slave_groups, /*emitter_rank=*/0);
|
||||
this->slave_group_potentials_[this->slave_names_[i]] = potentials;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingMaster::
|
||||
updateMasterGroupNameOrderMap(
|
||||
const std::string& slave_name, const std::map<std::string, std::size_t>& master_group_name_map
|
||||
)
|
||||
{
|
||||
this->master_group_name_order_[slave_name] = master_group_name_map;
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Private methods
|
||||
// ------------------
|
||||
|
||||
double
|
||||
ReservoirCouplingMaster::
|
||||
getMasterActivationDate_() const
|
||||
{
|
||||
// Assume master mode is activated when the first SLAVES keyword is encountered in the schedule
|
||||
// NOTE: getStartTime() returns a std::time_t value, which is typically a long integer representing
|
||||
// the number of seconds since the epoch (1970-01-01 00:00:00 UTC)
|
||||
// The maximum integer that can be represented by a double is 2^53 - 1, which is approximately
|
||||
// 9e15. This corresponds to a date in the year 2.85e8 or 285 million years into the future.
|
||||
// So we should be able to represent reasonable epoch values within a double.
|
||||
double start_date = this->schedule_.getStartTime();
|
||||
for (std::size_t report_step = 0; report_step < this->schedule_.size(); ++report_step) {
|
||||
auto rescoup = this->schedule_[report_step].rescoup();
|
||||
if (rescoup.slaveCount() > 0) {
|
||||
return start_date + this->schedule_.seconds(report_step);
|
||||
}
|
||||
}
|
||||
// NOTE: Consistency between SLAVES and GRUPMAST keywords has already been checked in
|
||||
// init() in SimulatorFullyImplicitBlackoil.hpp
|
||||
OPM_THROW(std::runtime_error, "Reservoir coupling: Failed to find master activation time: "
|
||||
"No SLAVES keyword found in schedule");
|
||||
}
|
||||
|
||||
|
||||
} // namespace Opm
|
||||
@@ -31,8 +31,8 @@
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/Slaves.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/common/Exceptions.hpp>
|
||||
#endif
|
||||
|
||||
@@ -105,6 +105,7 @@ public:
|
||||
using MaterialLawParams = GetPropType<TypeTag, Properties::MaterialLawParams>;
|
||||
using AquiferModel = GetPropType<TypeTag, Properties::AquiferModel>;
|
||||
using Model = GetPropType<TypeTag, Properties::NonlinearSystem>;
|
||||
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
|
||||
|
||||
using TimeStepper = AdaptiveTimeStepping<TypeTag>;
|
||||
using PolymerModule = BlackOilPolymerModule<TypeTag>;
|
||||
@@ -231,7 +232,7 @@ public:
|
||||
auto slave_mode = Parameters::Get<Parameters::Slave>();
|
||||
if (slave_mode) {
|
||||
this->reservoirCouplingSlave_ =
|
||||
std::make_unique<ReservoirCouplingSlave>(
|
||||
std::make_unique<ReservoirCouplingSlave<Scalar>>(
|
||||
FlowGenericVanguard::comm(),
|
||||
this->schedule(), timer
|
||||
);
|
||||
@@ -243,7 +244,7 @@ public:
|
||||
auto master_mode = checkRunningAsReservoirCouplingMaster();
|
||||
if (master_mode) {
|
||||
this->reservoirCouplingMaster_ =
|
||||
std::make_unique<ReservoirCouplingMaster>(
|
||||
std::make_unique<ReservoirCouplingMaster<Scalar>>(
|
||||
FlowGenericVanguard::comm(),
|
||||
this->schedule(),
|
||||
argc, argv
|
||||
@@ -691,8 +692,8 @@ protected:
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
bool slaveMode_{false};
|
||||
std::unique_ptr<ReservoirCouplingMaster> reservoirCouplingMaster_{nullptr};
|
||||
std::unique_ptr<ReservoirCouplingSlave> reservoirCouplingSlave_{nullptr};
|
||||
std::unique_ptr<ReservoirCouplingMaster<Scalar>> reservoirCouplingMaster_{nullptr};
|
||||
std::unique_ptr<ReservoirCouplingSlave<Scalar>> reservoirCouplingSlave_{nullptr};
|
||||
#endif
|
||||
|
||||
SimulatorSerializer serializer_;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
|
||||
+131
-7
@@ -20,6 +20,7 @@
|
||||
#ifndef OPM_RESERVOIR_COUPLING_HPP
|
||||
#define OPM_RESERVOIR_COUPLING_HPP
|
||||
#include <opm/simulators/utils/DeferredLogger.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Group/GuideRate.hpp>
|
||||
|
||||
#include <dune/common/parallel/mpitraits.hh>
|
||||
|
||||
@@ -44,29 +45,152 @@ private:
|
||||
DeferredLogger *deferred_logger_ = nullptr;
|
||||
};
|
||||
|
||||
/// @brief Guard for managing DeferredLogger lifecycle in ReservoirCoupling
|
||||
///
|
||||
/// @details This class ensures that a DeferredLogger pointer is properly set and cleared
|
||||
/// in the ReservoirCoupling logger. It follows the RAII (Resource Acquisition Is Initialization)
|
||||
/// pattern to prevent dangling pointer issues when local DeferredLogger objects go out of scope.
|
||||
///
|
||||
/// - Uses a pointer instead of a reference to enable move semantics
|
||||
/// - Implements move constructor/assignment to allow returning from functions and use in std::optional
|
||||
/// - Deletes copy constructor/assignment to ensure single ownership
|
||||
/// - The moved-from object is "nullified" so only the final owner clears the logger
|
||||
///
|
||||
/// Lifecycle guarantees:
|
||||
/// - Constructor: Sets the DeferredLogger pointer in the ReservoirCoupling logger
|
||||
/// - Destructor: Clears the pointer (only if not moved-from)
|
||||
/// - Move: Transfers ownership; moved-from object becomes inactive (logger_ = nullptr)
|
||||
///
|
||||
/// @param logger The ReservoirCoupling logger (either master or slave)
|
||||
/// @param deferred_logger The DeferredLogger to bind to the ReservoirCoupling logger
|
||||
///
|
||||
/// Example usage:
|
||||
/// @code
|
||||
/// DeferredLogger local_logger;
|
||||
/// {
|
||||
/// ReservoirCoupling::ScopedLoggerGuard guard{rc_logger, &local_logger};
|
||||
/// // logger is active here
|
||||
/// } // guard destructor automatically clears the logger
|
||||
/// @endcode
|
||||
class ScopedLoggerGuard {
|
||||
public:
|
||||
explicit ScopedLoggerGuard(Logger& logger, DeferredLogger* deferred_logger)
|
||||
: logger_(&logger)
|
||||
{
|
||||
logger_->setDeferredLogger(deferred_logger);
|
||||
}
|
||||
|
||||
~ScopedLoggerGuard() {
|
||||
// Only clear if we still own the logger (not moved-from)
|
||||
if (logger_) {
|
||||
logger_->clearDeferredLogger();
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent copying to ensure single ownership
|
||||
ScopedLoggerGuard(const ScopedLoggerGuard&) = delete;
|
||||
ScopedLoggerGuard& operator=(const ScopedLoggerGuard&) = delete;
|
||||
|
||||
// Enable moving - required for returning from functions and std::optional
|
||||
ScopedLoggerGuard(ScopedLoggerGuard&& other) noexcept
|
||||
: logger_(other.logger_)
|
||||
{
|
||||
// Transfer ownership: moved-from object becomes inactive and won't clear the logger
|
||||
other.logger_ = nullptr;
|
||||
}
|
||||
|
||||
ScopedLoggerGuard& operator=(ScopedLoggerGuard&& other) noexcept {
|
||||
if (this != &other) {
|
||||
// Clean up current logger before taking ownership of new one
|
||||
if (logger_) {
|
||||
logger_->clearDeferredLogger();
|
||||
}
|
||||
// Transfer ownership from other
|
||||
logger_ = other.logger_;
|
||||
other.logger_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
// Use pointer instead of reference to enable move semantics
|
||||
// (references cannot be reassigned, which is required for move operations)
|
||||
Logger* logger_;
|
||||
};
|
||||
|
||||
enum class MessageTag : int {
|
||||
MasterGroupInfo,
|
||||
MasterGroupNames,
|
||||
MasterGroupNamesSize,
|
||||
Potentials,
|
||||
PotentialsSize,
|
||||
SlaveSimulationStartDate,
|
||||
MasterStartOfReportStep,
|
||||
SlaveActivationDate,
|
||||
SlaveActivationHandshake,
|
||||
SlaveInjectionData,
|
||||
SlaveProcessTermination,
|
||||
SlaveName,
|
||||
SlaveNameSize,
|
||||
SlaveNextReportDate,
|
||||
SlaveNextTimeStep,
|
||||
SlaveProductionData,
|
||||
SlaveSimulationStartDate,
|
||||
SlaveStartOfReportStep,
|
||||
};
|
||||
|
||||
enum class Phase : std::size_t { Oil = 0, Gas, Water, Count };
|
||||
|
||||
template <class Scalar>
|
||||
struct InjectionRates {
|
||||
InjectionRates() = default;
|
||||
|
||||
std::array<Scalar, static_cast<std::size_t>(Phase::Count)> rate{};
|
||||
[[nodiscard]] Scalar& operator[](Phase p) noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
[[nodiscard]] Scalar operator[](Phase p) const noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
};
|
||||
|
||||
|
||||
// Used to communicate potentials for oil, gas, and water rates between slave and master processes
|
||||
template <class Scalar>
|
||||
struct Potentials {
|
||||
enum class Phase : std::size_t { Oil = 0, Gas, Water, Count };
|
||||
std::array<Scalar, static_cast<std::size_t>(Phase::Count)> rate{};
|
||||
|
||||
std::array<double, static_cast<std::size_t>(Phase::Count)> rate{};
|
||||
[[nodiscard]] Scalar& operator[](Phase p) noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
[[nodiscard]] Scalar operator[](Phase p) const noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
};
|
||||
|
||||
[[nodiscard]] double& operator[](Phase p) noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
[[nodiscard]] double operator[](Phase p) const noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
template <class Scalar>
|
||||
struct ProductionRates {
|
||||
ProductionRates() = default;
|
||||
|
||||
explicit ProductionRates(const GuideRate::RateVector& rate_vector)
|
||||
: rate{static_cast<Scalar>(rate_vector.oil_rat),
|
||||
static_cast<Scalar>(rate_vector.gas_rat),
|
||||
static_cast<Scalar>(rate_vector.wat_rat)}
|
||||
{}
|
||||
|
||||
std::array<Scalar, static_cast<std::size_t>(Phase::Count)> rate{};
|
||||
[[nodiscard]] Scalar& operator[](Phase p) noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
[[nodiscard]] Scalar operator[](Phase p) const noexcept { return rate[static_cast<std::size_t>(p)]; }
|
||||
};
|
||||
|
||||
// Slave group production data sent to the corresponding master group for target calculation.
|
||||
template <class Scalar>
|
||||
struct SlaveGroupProductionData {
|
||||
// Group production potentials are used by the master group for guiderate calculations
|
||||
Potentials<Scalar> potentials;
|
||||
// Production rates are used by the master group in guiderate calculations
|
||||
// when converting the guide rate target to the phase of the master group.
|
||||
ProductionRates<Scalar> surface_rates; // Surface production rates by phase
|
||||
// NOTE: Currently, the master does not need the individual phase reservoir rates,
|
||||
// we only send the total voidage replacement rate.
|
||||
Scalar voidage_rate{0.0}; // Reservoir voidage replacement rate
|
||||
Scalar gas_reinjection_rate{0.0}; // Reinjection (surface) rate for the gas phase
|
||||
};
|
||||
|
||||
// Slave group injection data sent to the corresponding master group for target calculation.
|
||||
template <class Scalar>
|
||||
struct SlaveGroupInjectionData {
|
||||
InjectionRates<Scalar> surface_rates; // Surface injection rates by phase
|
||||
InjectionRates<Scalar> reservoir_rates; // Reservoir injection rates by phase
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
Copyright 2024 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMasterReportStep.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSpawnSlaves.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingTimeStepper.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/Slaves.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
// NOTE: All slave-master communicators have set a custom error handler, which eventually
|
||||
// will call MPI_Abort() so there is no need to check the return value of any MPI_Recv()
|
||||
// or MPI_Send() calls below.
|
||||
|
||||
template <class Scalar>
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
ReservoirCouplingMaster(
|
||||
const Parallel::Communication &comm,
|
||||
const Schedule &schedule,
|
||||
int argc, char **argv
|
||||
) :
|
||||
comm_{comm},
|
||||
schedule_{schedule},
|
||||
argc_{argc},
|
||||
argv_{argv}
|
||||
{
|
||||
this->activation_date_ = this->getMasterActivationDate_();
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
const std::vector<std::string>&
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
getMasterGroupNamesForSlave(std::size_t slave_idx) const
|
||||
{
|
||||
if (slave_idx >= this->slave_idx_to_master_groups_.size()) {
|
||||
OPM_THROW(
|
||||
std::runtime_error,
|
||||
fmt::format(
|
||||
"Slave index {} out of bounds. Valid range is [0, {})",
|
||||
slave_idx, this->slave_idx_to_master_groups_.size()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Direct O(1) vector access for performance-critical usage
|
||||
return this->slave_idx_to_master_groups_[slave_idx];
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
std::size_t
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
getMasterGroupCanonicalIdx(
|
||||
const std::string &slave_name, const std::string &master_group_name) const
|
||||
{
|
||||
// NOTE: The master group name order is the canonical order of the master group names
|
||||
// for a given slave name. This is the order in which the slave will communicate
|
||||
// slave group data for its slave groups.
|
||||
return this->master_group_name_order_.at(slave_name).at(master_group_name);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
initTimeStepping()
|
||||
{
|
||||
assert(!this->time_stepper_);
|
||||
this->time_stepper_ = std::make_unique<ReservoirCouplingTimeStepper<Scalar>>(*this);
|
||||
assert(!this->report_step_data_);
|
||||
this->report_step_data_ = std::make_unique<ReservoirCouplingMasterReportStep<Scalar>>(*this);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
initStartOfReportStep(int report_step_idx)
|
||||
{
|
||||
assert(this->report_step_data_);
|
||||
this->report_step_data_->setReportStepIdx(report_step_idx);
|
||||
this->logger_.info("Initializing start of report step");
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
bool
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
isMasterGroup(const std::string &group_name) const
|
||||
{
|
||||
return this->master_group_slave_names_.find(group_name) !=
|
||||
this->master_group_slave_names_.end();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
maybeActivate(int report_step) {
|
||||
if (!this->activated()) {
|
||||
double start_date = this->schedule_.getStartTime();
|
||||
auto current_time = start_date + this->schedule_.seconds(report_step);
|
||||
if (Seconds::compare_gt_or_eq(current_time, this->activation_date_)) {
|
||||
this->activated_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
double
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
maybeChopSubStep(double suggested_timestep, double current_time) const
|
||||
{
|
||||
assert(this->time_stepper_);
|
||||
return this->time_stepper_->maybeChopSubStep(suggested_timestep, current_time);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
maybeSpawnSlaveProcesses(int report_step)
|
||||
{
|
||||
if (this->numSlavesStarted() > 0) { // We have already spawned the slave processes
|
||||
return;
|
||||
}
|
||||
const auto& rescoup = this->schedule_[report_step].rescoup();
|
||||
auto slave_count = rescoup.slaveCount();
|
||||
auto master_group_count = rescoup.masterGroupCount();
|
||||
if (slave_count > 0 && master_group_count > 0) {
|
||||
ReservoirCouplingSpawnSlaves<Scalar> spawn_slaves{*this, rescoup};
|
||||
spawn_slaves.spawn();
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
maybeReceiveActivationHandshakeFromSlaves(double current_time)
|
||||
{
|
||||
// Initialize on first call
|
||||
if (this->slave_activation_status_.empty()) {
|
||||
this->slave_activation_status_.resize(this->numSlavesStarted(), false);
|
||||
}
|
||||
|
||||
if (this->comm_.rank() == 0) {
|
||||
auto current_date = this->schedule_.getStartTime() + current_time;
|
||||
for (unsigned int i = 0; i < this->numSlavesStarted(); i++) {
|
||||
// Skip if already activated
|
||||
if (this->slaveIsActivated(i)) {
|
||||
continue;
|
||||
}
|
||||
// Check if slave should activate during this timestep
|
||||
double slave_activation_date = this->slave_activation_dates_[i];
|
||||
// NOTE: The master will adjust its time stepping to ensure that its step will always
|
||||
// conincide with the slave report steps (and hence the slave activation date)
|
||||
assert(Seconds::compare_gt_or_eq(slave_activation_date, current_date));
|
||||
if (Seconds::compare_eq(slave_activation_date, current_date)) {
|
||||
// Use non-blocking probe first to check for handshake
|
||||
int flag;
|
||||
MPI_Status status;
|
||||
MPI_Iprobe(0, static_cast<int>(MessageTag::SlaveActivationHandshake),
|
||||
this->master_slave_comm_[i], &flag, &status);
|
||||
|
||||
if (!flag) {
|
||||
// Handshake not received yet, for debugging reasons it can be useful to know which slave
|
||||
// is waiting for the handshake
|
||||
OpmLog::info(fmt::format("Waiting for activation handshake from slave {}",
|
||||
this->slave_names_[i]));
|
||||
}
|
||||
std::uint8_t handshake;
|
||||
auto MPI_UINT8_T_TYPE = Dune::MPITraits<std::uint8_t>::getType();
|
||||
MPI_Recv(&handshake, 1, MPI_UINT8_T_TYPE, 0,
|
||||
static_cast<int>(MessageTag::SlaveActivationHandshake),
|
||||
this->master_slave_comm_[i], MPI_STATUS_IGNORE);
|
||||
this->slave_activation_status_[i] = handshake;
|
||||
OpmLog::info(fmt::format("Received activation handshake from slave {}",
|
||||
this->slave_names_[i]));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// Broadcast status to all ranks
|
||||
comm_.broadcast(
|
||||
this->slave_activation_status_.data(), /*count=*/this->numSlavesStarted(), /*emitter_rank=*/0
|
||||
);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
std::size_t
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
numSlaveGroups(unsigned int index)
|
||||
{
|
||||
return this->master_group_name_order_[this->slave_names_[index]].size();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
std::size_t
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
numSlavesStarted() const
|
||||
{
|
||||
return this->slave_names_.size();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
receiveNextReportDateFromSlaves()
|
||||
{
|
||||
assert(this->time_stepper_);
|
||||
this->time_stepper_->receiveNextReportDateFromSlaves();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
rebuildSlaveIdxToMasterGroupsVector()
|
||||
{
|
||||
// Rebuild the index-based vector from the name-based map
|
||||
// This ensures consistent ordering with slave_names_ vector
|
||||
this->slave_idx_to_master_groups_.clear();
|
||||
this->slave_idx_to_master_groups_.resize(this->slave_names_.size());
|
||||
|
||||
for (std::size_t slave_idx = 0; slave_idx < this->slave_names_.size(); ++slave_idx) {
|
||||
const auto& slave_name = this->slave_names_[slave_idx];
|
||||
auto it = this->slave_name_to_master_groups_map_.find(slave_name);
|
||||
if (it != this->slave_name_to_master_groups_map_.end()) {
|
||||
this->slave_idx_to_master_groups_[slave_idx] = it->second;
|
||||
}
|
||||
// If slave_name not found in map, leave the vector empty for that index
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
receiveInjectionDataFromSlaves()
|
||||
{
|
||||
assert(this->report_step_data_);
|
||||
this->report_step_data_->receiveInjectionDataFromSlaves();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
receiveProductionDataFromSlaves()
|
||||
{
|
||||
assert(this->report_step_data_);
|
||||
this->report_step_data_->receiveProductionDataFromSlaves();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
const ReservoirCoupling::Potentials<Scalar>&
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
getSlaveGroupPotentials(const std::string &master_group_name)
|
||||
{
|
||||
assert(this->report_step_data_);
|
||||
return this->report_step_data_->getSlaveGroupPotentials(master_group_name);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
resizeNextReportDates(int size)
|
||||
{
|
||||
assert(this->time_stepper_);
|
||||
this->time_stepper_->resizeNextReportDates(size);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
setSlaveNextReportTimeOffset(int index, double offset)
|
||||
{
|
||||
assert(this->time_stepper_);
|
||||
this->time_stepper_->setSlaveNextReportTimeOffset(index, offset);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
updateMasterGroupNameOrderMap(
|
||||
const std::string& slave_name, const std::map<std::string, std::size_t>& master_group_name_map
|
||||
)
|
||||
{
|
||||
this->master_group_name_order_[slave_name] = master_group_name_map;
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Private methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
double
|
||||
ReservoirCouplingMaster<Scalar>::
|
||||
getMasterActivationDate_() const
|
||||
{
|
||||
// Assume master mode is activated when the first SLAVES keyword is encountered in the schedule
|
||||
// NOTE: getStartTime() returns a std::time_t value, which is typically a long integer representing
|
||||
// the number of seconds since the epoch (1970-01-01 00:00:00 UTC)
|
||||
// The maximum integer that can be represented by a double is 2^53 - 1, which is approximately
|
||||
// 9e15. This corresponds to a date in the year 2.85e8 or 285 million years into the future.
|
||||
// So we should be able to represent reasonable epoch values within a double.
|
||||
double start_date = this->schedule_.getStartTime();
|
||||
for (std::size_t report_step = 0; report_step < this->schedule_.size(); ++report_step) {
|
||||
auto rescoup = this->schedule_[report_step].rescoup();
|
||||
if (rescoup.slaveCount() > 0) {
|
||||
return start_date + this->schedule_.seconds(report_step);
|
||||
}
|
||||
}
|
||||
// NOTE: Consistency between SLAVES and GRUPMAST keywords has already been checked in
|
||||
// init() in SimulatorFullyImplicitBlackoil.hpp
|
||||
OPM_THROW(std::runtime_error, "Reservoir coupling: Failed to find master activation time: "
|
||||
"No SLAVES keyword found in schedule");
|
||||
}
|
||||
|
||||
|
||||
// Explicit template instantiations
|
||||
template class ReservoirCouplingMaster<double>;
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class ReservoirCouplingMaster<float>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
+66
-19
@@ -21,7 +21,7 @@
|
||||
#define OPM_RESERVOIR_COUPLING_MASTER_HPP
|
||||
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
|
||||
@@ -32,11 +32,19 @@
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingMasterReportStep;
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingTimeStepper;
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingMaster {
|
||||
public:
|
||||
using MessageTag = ReservoirCoupling::MessageTag;
|
||||
using Seconds = ReservoirCoupling::Seconds;
|
||||
using Potentials = ReservoirCoupling::Potentials;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
using SlaveGroupProductionData = ReservoirCoupling::SlaveGroupProductionData<Scalar>;
|
||||
ReservoirCouplingMaster(
|
||||
const Parallel::Communication &comm,
|
||||
const Schedule &schedule,
|
||||
@@ -48,9 +56,6 @@ public:
|
||||
this->master_slave_comm_.push_back(comm);
|
||||
}
|
||||
void addSlaveName(const std::string &name) { this->slave_names_.push_back(name); }
|
||||
void addSlaveNextReportTimeOffset(double offset) {
|
||||
this->slave_next_report_time_offsets_.push_back(offset);
|
||||
}
|
||||
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(); }
|
||||
@@ -59,23 +64,44 @@ public:
|
||||
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 index of the master group potential for a given slave name and master group name.
|
||||
/// The index is used to map the slave group potentials to the master group potentials.
|
||||
ReservoirCoupling::Logger& getLogger() { return this->logger_; }
|
||||
/// @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
|
||||
/// specific slave reservoir identified by its index.
|
||||
///
|
||||
/// @param slave_idx The zero-based index of the slave reservoir (must be < numSlaves())
|
||||
/// @return A const reference to a vector of master group names for the specified slave
|
||||
/// @throws std::runtime_error if slave_idx is out of bounds
|
||||
///
|
||||
/// @note Performance: This method uses O(1) direct vector access when possible,
|
||||
/// falling back to O(log n) map lookup for error handling.
|
||||
/// @see RescoupTargetCalculator::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.
|
||||
/// @param slave_name The name of the slave reservoir.
|
||||
/// @param master_group_name The name of the master group.
|
||||
/// @return The index of the master group potential for the given slave name and master group name.
|
||||
std::size_t getMasterGroupPotIdx(
|
||||
/// @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;
|
||||
std::map<std::string, std::string>& getMasterGroupToSlaveNameMap() {
|
||||
return this->master_group_slave_names_;
|
||||
}
|
||||
double getSlaveActivationDate(int index) const { return this->slave_activation_dates_[index]; }
|
||||
const double *getSlaveActivationDates() const { return this->slave_activation_dates_.data(); }
|
||||
std::map<std::string, std::vector<std::string>> &getSlaveNameToMasterGroupsMap() {
|
||||
return this->slave_name_to_master_groups_map_;
|
||||
}
|
||||
double getSimulationStartDate() const { return this->schedule_.getStartTime(); }
|
||||
MPI_Comm getSlaveComm(int index) const { return this->master_slave_comm_[index]; }
|
||||
const Potentials& getSlaveGroupPotentials(const std::string &master_group_name);
|
||||
const std::string &getSlaveName(int index) const { return this->slave_names_[index]; }
|
||||
double getSlaveStartDate(int index) const { return this->slave_start_dates_[index]; }
|
||||
const double *getSlaveStartDates() { return this->slave_start_dates_.data(); }
|
||||
void initStartOfReportStep(int report_step_idx);
|
||||
void initTimeStepping();
|
||||
bool isMasterGroup(const std::string &group_name) const;
|
||||
void maybeActivate(int report_step);
|
||||
void maybeReceiveActivationHandshakeFromSlaves(double current_time);
|
||||
@@ -83,21 +109,24 @@ public:
|
||||
void maybeSpawnSlaveProcesses(int report_step);
|
||||
std::size_t numSlaveGroups(unsigned int index);
|
||||
std::size_t numSlavesStarted() const;
|
||||
void rebuildSlaveIdxToMasterGroupsVector();
|
||||
void receiveNextReportDateFromSlaves();
|
||||
void receivePotentialsFromSlaves();
|
||||
void receiveProductionDataFromSlaves();
|
||||
void receiveInjectionDataFromSlaves();
|
||||
void resizeNextReportDates(int size);
|
||||
void resizeSlaveActivationDates(int size) { this->slave_activation_dates_.resize(size); }
|
||||
void resizeSlaveStartDates(int size) { this->slave_start_dates_.resize(size); }
|
||||
void resizeNextReportDates(int size) { this->slave_next_report_time_offsets_.resize(size); }
|
||||
void sendNextTimeStepToSlaves(double next_time_step);
|
||||
const Schedule& schedule() const { return this->schedule_; }
|
||||
void sendNextTimeStepToSlaves(double next_time_step) {
|
||||
this->time_stepper_->sendNextTimeStepToSlaves(next_time_step);
|
||||
}
|
||||
void setDeferredLogger(DeferredLogger *deferred_logger) {
|
||||
this->logger_.setDeferredLogger(deferred_logger);
|
||||
}
|
||||
// These are currently only used for unit testing
|
||||
void setSlaveActivationDate(int index, double date) { this->slave_activation_dates_[index] = date; }
|
||||
void setSlaveNextReportTimeOffset(int index, double offset);
|
||||
void setSlaveStartDate(int index, std::time_t date) { this->slave_start_dates_[index] = date; }
|
||||
void setSlaveNextReportTimeOffset(int index, double offset) {
|
||||
this->slave_next_report_time_offsets_[index] = offset;
|
||||
}
|
||||
bool slaveIsActivated(int index) const { return this->slave_activation_status_[index] != 0; }
|
||||
void updateMasterGroupNameOrderMap(
|
||||
const std::string& slave_name, const std::map<std::string, std::size_t>& master_group_map);
|
||||
@@ -123,8 +152,6 @@ private:
|
||||
std::vector<double> slave_start_dates_;
|
||||
// The activation dates are in whole seconds since the epoch.
|
||||
std::vector<double> slave_activation_dates_;
|
||||
// Elapsed time from the beginning of the simulation
|
||||
std::vector<double> slave_next_report_time_offsets_;
|
||||
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.
|
||||
@@ -134,11 +161,31 @@ private:
|
||||
// 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_;
|
||||
// Potentials for oil, gas, and water rates for each slave group
|
||||
std::map<std::string, std::vector<Potentials>> slave_group_potentials_;
|
||||
// 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};
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
// Include the complete definitions after the class declaration to avoid circular dependency
|
||||
// while still providing complete types for unique_ptr destructor instantiation.
|
||||
// These are included here (after namespace closing) so that when the destructor is
|
||||
// instantiated during template instantiation, the complete types are available.
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMasterReportStep.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingTimeStepper.hpp>
|
||||
|
||||
#endif // OPM_RESERVOIR_COUPLING_MASTER_HPP
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
Copyright 2025 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMasterReportStep.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/Slaves.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
|
||||
#include <dune/common/parallel/mpitraits.hh>
|
||||
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
ReservoirCouplingMasterReportStep<Scalar>::
|
||||
ReservoirCouplingMasterReportStep(
|
||||
ReservoirCouplingMaster<Scalar> &master
|
||||
) :
|
||||
master_{master}
|
||||
{
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
const ReservoirCoupling::Potentials<Scalar>&
|
||||
ReservoirCouplingMasterReportStep<Scalar>::
|
||||
getSlaveGroupPotentials(const std::string &master_group_name) const
|
||||
{
|
||||
auto it = this->getMasterGroupToSlaveNameMap().find(master_group_name);
|
||||
if (it != this->getMasterGroupToSlaveNameMap().end()) {
|
||||
auto& slave_name = it->second;
|
||||
auto group_idx = this->getMasterGroupCanonicalIdx(slave_name, master_group_name);
|
||||
return this->slave_group_production_data_.at(slave_name)[group_idx].potentials;
|
||||
}
|
||||
else {
|
||||
OPM_THROW(
|
||||
std::runtime_error,
|
||||
fmt::format(
|
||||
"Master group name {} not found in master-to-slave-group-name mapping",
|
||||
master_group_name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMasterReportStep<Scalar>::
|
||||
receiveInjectionDataFromSlaves()
|
||||
{
|
||||
auto num_slaves = this->numSlaves();
|
||||
this->logger().info("Receiving injection data from slave processes");
|
||||
for (unsigned int i = 0; i < num_slaves; i++) {
|
||||
auto num_slave_groups = this->numSlaveGroups(i);
|
||||
assert( num_slave_groups > 0 );
|
||||
std::vector<SlaveGroupInjectionData> injection_data(num_slave_groups);
|
||||
if (this->comm().rank() == 0) {
|
||||
if (this->slaveIsActivated(i)) {
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
auto MPI_INJECTION_DATA_TYPE = Dune::MPITraits<SlaveGroupInjectionData>::getType();
|
||||
MPI_Recv(
|
||||
injection_data.data(),
|
||||
/*count=*/num_slave_groups,
|
||||
/*datatype=*/MPI_INJECTION_DATA_TYPE,
|
||||
/*source_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveInjectionData),
|
||||
this->getSlaveComm(i),
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
this->logger().info(
|
||||
fmt::format(
|
||||
"Received injection data for {} groups from slave process with name: {}. "
|
||||
"Number of slave groups: {}", num_slave_groups, this->slaveName(i), num_slave_groups
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
this->logger().info(fmt::format(
|
||||
"Slave {} has not activated yet, skipping receiving injection data from slave",
|
||||
this->slaveName(i)
|
||||
)
|
||||
);
|
||||
injection_data.assign(num_slave_groups, SlaveGroupInjectionData{}); // Set to zero injection data
|
||||
}
|
||||
}
|
||||
// NOTE: The dune broadcast() below will do something like:
|
||||
// MPI_Bcast(inout,len,MPITraits<SlaveGroupInjectionData>::getType(),root,communicator)
|
||||
// so it should use the custom SlaveGroupInjectionData MPI type that we defined in
|
||||
// ReservoirCouplingMpiTraits.hpp
|
||||
this->comm().broadcast(injection_data.data(), /*count=*/num_slave_groups, /*emitter_rank=*/0);
|
||||
this->slave_group_injection_data_[this->slaveName(i)] = injection_data;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMasterReportStep<Scalar>::
|
||||
receiveProductionDataFromSlaves()
|
||||
{
|
||||
auto num_slaves = this->numSlaves();
|
||||
this->logger().info("Receiving production data from slave processes");
|
||||
for (unsigned int i = 0; i < num_slaves; i++) {
|
||||
auto num_slave_groups = this->numSlaveGroups(i);
|
||||
assert( num_slave_groups > 0 );
|
||||
std::vector<SlaveGroupProductionData> production_data(num_slave_groups);
|
||||
if (this->comm().rank() == 0) {
|
||||
if (this->slaveIsActivated(i)) {
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
auto MPI_PRODUCTION_DATA_TYPE = Dune::MPITraits<SlaveGroupProductionData>::getType();
|
||||
MPI_Recv(
|
||||
production_data.data(),
|
||||
/*count=*/num_slave_groups,
|
||||
/*datatype=*/MPI_PRODUCTION_DATA_TYPE,
|
||||
/*source_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveProductionData),
|
||||
this->getSlaveComm(i),
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
this->logger().info(
|
||||
fmt::format(
|
||||
"Received production data for {} groups from slave process with name: {}. "
|
||||
"Number of slave groups: {}", num_slave_groups, this->slaveName(i), num_slave_groups
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
this->logger().info(fmt::format(
|
||||
"Slave {} has not activated yet, skipping receiving production data from slave",
|
||||
this->slaveName(i)
|
||||
)
|
||||
);
|
||||
production_data.assign(num_slave_groups, SlaveGroupProductionData{}); // Set to zero production data
|
||||
}
|
||||
}
|
||||
// NOTE: The dune broadcast() below will do something like:
|
||||
// MPI_Bcast(inout,len,MPITraits<SlaveGroupProductionData>::getType(),root,communicator)
|
||||
// so it should use the custom SlaveGroupProductionData MPI type that we defined in
|
||||
// ReservoirCouplingMpiTraits.hpp
|
||||
this->comm().broadcast(production_data.data(), /*count=*/num_slave_groups, /*emitter_rank=*/0);
|
||||
this->slave_group_production_data_[this->slaveName(i)] = production_data;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingMasterReportStep<Scalar>::
|
||||
setReportStepIdx(int report_step_idx)
|
||||
{
|
||||
this->report_step_idx_ = report_step_idx;
|
||||
}
|
||||
|
||||
|
||||
// ------------------
|
||||
// Private methods
|
||||
// ------------------
|
||||
|
||||
|
||||
// Explicit instantiations
|
||||
template class ReservoirCouplingMasterReportStep<double>;
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class ReservoirCouplingMasterReportStep<float>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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_RESERVOIR_COUPLING_MASTER_REPORT_STEP_HPP
|
||||
#define OPM_RESERVOIR_COUPLING_MASTER_REPORT_STEP_HPP
|
||||
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingMasterReportStep {
|
||||
public:
|
||||
using MessageTag = ReservoirCoupling::MessageTag;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
using SlaveGroupProductionData = ReservoirCoupling::SlaveGroupProductionData<Scalar>;
|
||||
using SlaveGroupInjectionData = ReservoirCoupling::SlaveGroupInjectionData<Scalar>;
|
||||
|
||||
ReservoirCouplingMasterReportStep(
|
||||
ReservoirCouplingMaster<Scalar> &master
|
||||
);
|
||||
const Parallel::Communication &comm() const { return this->master_.getComm(); }
|
||||
const std::vector<std::string>& getMasterGroupNamesForSlave(std::size_t slave_idx) const {
|
||||
return this->master_.getMasterGroupNamesForSlave(slave_idx);
|
||||
}
|
||||
const std::map<std::string, std::string>& getMasterGroupToSlaveNameMap() const {
|
||||
return this->master_.getMasterGroupToSlaveNameMap();
|
||||
}
|
||||
std::size_t getMasterGroupCanonicalIdx(
|
||||
const std::string &slave_name, const std::string &master_group_name) const {
|
||||
return this->master_.getMasterGroupCanonicalIdx(slave_name, master_group_name);
|
||||
}
|
||||
MPI_Comm getSlaveComm(int index) const { return this->master_.getSlaveComm(index); }
|
||||
const Potentials& getSlaveGroupPotentials(const std::string &master_group_name) const;
|
||||
void maybeReceiveGroupInfoFromSlaves();
|
||||
std::size_t numSlaveGroups(unsigned int index) const { return this->master_.numSlaveGroups(index); }
|
||||
std::size_t numSlaves() const { return this->master_.numSlavesStarted(); }
|
||||
ReservoirCoupling::Logger& logger() const { return this->master_.getLogger(); }
|
||||
void receiveInjectionDataFromSlaves();
|
||||
void receiveProductionDataFromSlaves();
|
||||
const Schedule &schedule() const { return this->master_.schedule(); }
|
||||
void sendGroupInfoToSlaves(int report_step_idx);
|
||||
void setReportStepIdx(int report_step_idx);
|
||||
bool slaveIsActivated(int index) const { return this->master_.slaveIsActivated(index); }
|
||||
const std::string &slaveName(int index) const { return this->master_.getSlaveName(index); }
|
||||
|
||||
private:
|
||||
void receiveGroupInfoFromSlave_(unsigned int slave_idx);
|
||||
void sendMasterGroupInjectorProducerInfoToSlave_(unsigned int slave_idx, int report_step_idx);
|
||||
|
||||
ReservoirCouplingMaster<Scalar> &master_;
|
||||
// Current report step index
|
||||
int report_step_idx_;
|
||||
// Production data and potentials for each slave group
|
||||
std::map<std::string, std::vector<SlaveGroupProductionData>> slave_group_production_data_;
|
||||
// Injection data for each slave group
|
||||
std::map<std::string, std::vector<SlaveGroupInjectionData>> slave_group_injection_data_;
|
||||
};
|
||||
} // namespace Opm
|
||||
#endif // OPM_RESERVOIR_COUPLING_MASTER_REPORT_STEP_HPP
|
||||
+18
-6
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <dune/common/parallel/mpitraits.hh>
|
||||
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <mutex> // For std::call_once
|
||||
@@ -152,13 +152,25 @@ using StructMPITraits = StructMPITraitsImpl<Struct, Members...>;
|
||||
|
||||
} // namespace Dune::detail
|
||||
|
||||
// Trait for Potentials
|
||||
template<>
|
||||
struct MPITraits<::Opm::ReservoirCoupling::Potentials>
|
||||
// Trait for SlaveGroupProductionData
|
||||
template<class Scalar>
|
||||
struct MPITraits<::Opm::ReservoirCoupling::SlaveGroupProductionData<Scalar>>
|
||||
: detail::StructMPITraits<
|
||||
::Opm::ReservoirCoupling::Potentials,
|
||||
&::Opm::ReservoirCoupling::Potentials::rate> { };
|
||||
::Opm::ReservoirCoupling::SlaveGroupProductionData<Scalar>,
|
||||
&::Opm::ReservoirCoupling::SlaveGroupProductionData<Scalar>::potentials,
|
||||
&::Opm::ReservoirCoupling::SlaveGroupProductionData<Scalar>::surface_rates,
|
||||
&::Opm::ReservoirCoupling::SlaveGroupProductionData<Scalar>::voidage_rate,
|
||||
&::Opm::ReservoirCoupling::SlaveGroupProductionData<Scalar>::gas_reinjection_rate
|
||||
> { };
|
||||
|
||||
// Trait for SlaveGroupInjectionData
|
||||
template<class Scalar>
|
||||
struct MPITraits<::Opm::ReservoirCoupling::SlaveGroupInjectionData<Scalar>>
|
||||
: detail::StructMPITraits<
|
||||
::Opm::ReservoirCoupling::SlaveGroupInjectionData<Scalar>,
|
||||
&::Opm::ReservoirCoupling::SlaveGroupInjectionData<Scalar>::surface_rates,
|
||||
&::Opm::ReservoirCoupling::SlaveGroupInjectionData<Scalar>::reservoir_rates
|
||||
> { };
|
||||
} // namespace Dune
|
||||
|
||||
#endif // OPM_RESERVOIR_COUPLING_MPI_TRAITS_HPP
|
||||
+103
-73
@@ -18,9 +18,10 @@
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlaveReportStep.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
@@ -38,7 +39,8 @@ namespace Opm {
|
||||
// will call MPI_Abort() so there is no need to check the return value of any MPI_Recv()
|
||||
// or MPI_Send() calls below.
|
||||
|
||||
ReservoirCouplingSlave::
|
||||
template <class Scalar>
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
ReservoirCouplingSlave(
|
||||
const Parallel::Communication &comm,
|
||||
const Schedule &schedule,
|
||||
@@ -59,17 +61,35 @@ ReservoirCouplingSlave(
|
||||
ReservoirCoupling::setErrhandler(this->slave_master_comm_, /*is_master=*/false);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
sendAndReceiveInitialData() {
|
||||
this->sendActivationDateToMasterProcess_();
|
||||
this->sendSimulationStartDateToMasterProcess_();
|
||||
this->receiveSlaveNameFromMasterProcess_();
|
||||
this->receiveMasterGroupNamesFromMasterProcess_();
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
initTimeStepping()
|
||||
{
|
||||
assert(!this->report_step_data_);
|
||||
this->report_step_data_ = std::make_unique<ReservoirCouplingSlaveReportStep<Scalar>>(*this);
|
||||
}
|
||||
|
||||
// NOTE: It is not legal for a slave to activate before the master has activated. This problem
|
||||
// will be caught by the master when it receives the slave activation date. See:
|
||||
// ReservoirCouplingSpawnSlaves::receiveActivationDateFromSlaves_()
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
maybeActivate(int report_step) {
|
||||
if (!this->activated()) {
|
||||
auto rescoup = this->schedule_[report_step].rescoup();
|
||||
if (rescoup.grupSlavCount() > 0) {
|
||||
this->activated_ = true;
|
||||
// Send a handshake to the master process to indicate that the slave has activated
|
||||
this->sendActivationHandshakeToMasterProcess_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
double
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
receiveNextTimeStepFromMaster() {
|
||||
double timestep;
|
||||
if (this->comm_.rank() == 0) {
|
||||
@@ -83,18 +103,39 @@ receiveNextTimeStepFromMaster() {
|
||||
this->slave_master_comm_,
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
OpmLog::info(
|
||||
this->logger_.info(
|
||||
fmt::format("Slave rank 0 received next timestep {} from master.", timestep)
|
||||
);
|
||||
}
|
||||
this->comm_.broadcast(×tep, /*count=*/1, /*emitter_rank=*/0);
|
||||
OpmLog::info("Broadcasted slave next time step to all ranks");
|
||||
this->logger_.info("Broadcasted slave next time step to all ranks");
|
||||
return timestep;
|
||||
}
|
||||
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendAndReceiveInitialData() {
|
||||
this->sendActivationDateToMasterProcess_();
|
||||
this->sendSimulationStartDateToMasterProcess_();
|
||||
this->receiveSlaveNameFromMasterProcess_();
|
||||
this->receiveMasterGroupNamesFromMasterProcess_();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendInjectionDataToMaster(
|
||||
const std::vector<ReservoirCoupling::SlaveGroupInjectionData<Scalar>> &injection_data
|
||||
) const
|
||||
{
|
||||
assert(this->report_step_data_);
|
||||
this->report_step_data_->sendInjectionDataToMaster(injection_data);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendNextReportDateToMasterProcess() const
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
@@ -112,43 +153,28 @@ sendNextReportDateToMasterProcess() const
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveNextReportDate),
|
||||
this->slave_master_comm_
|
||||
);
|
||||
OpmLog::info("Sent next report date to master process from rank 0");
|
||||
this->logger_.info("Sent next report date to master process from rank 0");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
sendPotentialsToMaster(const std::vector<Potentials> &potentials) const
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendProductionDataToMaster(
|
||||
const std::vector<ReservoirCoupling::SlaveGroupProductionData<Scalar>> &production_data
|
||||
) const
|
||||
{
|
||||
// NOTE: The master can determine from the ordering of the potentials in the vector
|
||||
// which slave group for a given slave name the given potentials belong to,
|
||||
// so we do not need to send the slave group names also.
|
||||
if (this->comm_.rank() == 0) {
|
||||
//auto num_groups = potentials.size();
|
||||
auto MPI_POTENTIALS_TYPE = Dune::MPITraits<Potentials>::getType();
|
||||
MPI_Send(
|
||||
potentials.data(),
|
||||
/*count=*/potentials.size(),
|
||||
/*datatype=*/MPI_POTENTIALS_TYPE,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::Potentials),
|
||||
this->slave_master_comm_
|
||||
);
|
||||
this->logger_.info(
|
||||
"Sent potentials to master process from rank 0, slave name: " + this->slave_name_
|
||||
);
|
||||
}
|
||||
assert(this->report_step_data_);
|
||||
this->report_step_data_->sendProductionDataToMaster(production_data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------
|
||||
// Private methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
checkGrupSlavGroupNames_()
|
||||
{
|
||||
// Validate that each slave group name has a corresponding master group name
|
||||
@@ -182,8 +208,9 @@ checkGrupSlavGroupNames_()
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
double
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
getGrupSlavActivationDate_() const
|
||||
{
|
||||
double start_date = this->schedule_.getStartTime();
|
||||
@@ -197,24 +224,9 @@ getGrupSlavActivationDate_() const
|
||||
"No GRUPSLAV keyword found in schedule");
|
||||
}
|
||||
|
||||
// NOTE: It is not legal for a slave to activate before the master has activated. This problem
|
||||
// will be caught by the master when it receives the slave activation date. See:
|
||||
// ReservoirCouplingSpawnSlaves::receiveActivationDateFromSlaves_()
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
maybeActivate(int report_step) {
|
||||
if (!this->activated()) {
|
||||
auto rescoup = this->schedule_[report_step].rescoup();
|
||||
if (rescoup.grupSlavCount() > 0) {
|
||||
this->activated_ = true;
|
||||
// Send a handshake to the master process to indicate that the slave has activated
|
||||
this->sendActivationHandshakeToMasterProcess_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
receiveMasterGroupNamesFromMasterProcess_() {
|
||||
std::size_t size;
|
||||
std::vector<char> group_names;
|
||||
@@ -230,7 +242,7 @@ receiveMasterGroupNamesFromMasterProcess_() {
|
||||
this->slave_master_comm_,
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
OpmLog::info("Received master group names size from master process rank 0");
|
||||
this->logger_.info("Received master group names size from master process rank 0");
|
||||
group_names.resize(size);
|
||||
MPI_Recv(
|
||||
group_names.data(),
|
||||
@@ -241,19 +253,20 @@ receiveMasterGroupNamesFromMasterProcess_() {
|
||||
this->slave_master_comm_,
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
OpmLog::info("Received master group names from master process rank 0");
|
||||
this->logger_.info("Received master group names from master process rank 0");
|
||||
}
|
||||
this->comm_.broadcast(&size, /*count=*/1, /*emitter_rank=*/0);
|
||||
if (this->comm_.rank() != 0) {
|
||||
group_names.resize(size);
|
||||
}
|
||||
this->comm_.broadcast(group_names.data(), /*count=*/size, /*emitter_rank=*/0);
|
||||
this->saveMasterGroupNamesAsMap_(group_names);
|
||||
this->saveMasterGroupNamesAsMapAndEstablishOrder_(group_names);
|
||||
this->checkGrupSlavGroupNames_();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
receiveSlaveNameFromMasterProcess_() {
|
||||
std::size_t size;
|
||||
std::string slave_name;
|
||||
@@ -269,7 +282,7 @@ receiveSlaveNameFromMasterProcess_() {
|
||||
this->slave_master_comm_,
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
OpmLog::info("Received slave name size from master process rank 0");
|
||||
this->logger_.info("Received slave name size from master process rank 0");
|
||||
slave_name.resize(size+1); // +1 for the null terminator
|
||||
MPI_Recv(
|
||||
slave_name.data(),
|
||||
@@ -281,23 +294,29 @@ receiveSlaveNameFromMasterProcess_() {
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
slave_name[size] = '\0'; // Add null terminator
|
||||
OpmLog::info("Received slave name from master process rank 0");
|
||||
this->logger_.info("Received slave name from master process rank 0");
|
||||
}
|
||||
this->comm_.broadcast(&size, /*count=*/1, /*emitter_rank=*/0);
|
||||
if (this->comm_.rank() != 0) {
|
||||
slave_name.resize(size+1); // +1 for the null terminator
|
||||
}
|
||||
this->comm_.broadcast(slave_name.data(), /*count=*/size+1, /*emitter_rank=*/0);
|
||||
OpmLog::info(fmt::format("Received slave name: {}", slave_name));
|
||||
this->logger_.info(fmt::format("Received slave name: {}", slave_name));
|
||||
this->slave_name_ = slave_name;
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
saveMasterGroupNamesAsMap_(const std::vector<char>& group_names) {
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
saveMasterGroupNamesAsMapAndEstablishOrder_(const std::vector<char>& group_names) {
|
||||
// Deserialize the group names vector into a map of slavegroup names -> mastergroup names
|
||||
// and establish the order the master process sends us the group data. This will enable the
|
||||
// master to send group data without the need to send the group names themselves.
|
||||
//
|
||||
// Call chain: sendAndReceiveInitialData() -> receiveMasterGroupNamesFromMasterProcess_()
|
||||
auto total_size = group_names.size();
|
||||
std::size_t offset = 0;
|
||||
std::size_t idx = 0;
|
||||
while (offset < total_size) {
|
||||
std::string master_group{group_names.data() + offset};
|
||||
offset += master_group.size() + 1;
|
||||
@@ -305,11 +324,14 @@ saveMasterGroupNamesAsMap_(const std::vector<char>& group_names) {
|
||||
std::string slave_group{group_names.data() + offset};
|
||||
offset += slave_group.size() + 1;
|
||||
this->slave_to_master_group_map_[slave_group] = master_group;
|
||||
this->slave_group_order_[idx] = slave_group;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendActivationDateToMasterProcess_() const
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
@@ -324,12 +346,13 @@ sendActivationDateToMasterProcess_() const
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveActivationDate),
|
||||
this->slave_master_comm_
|
||||
);
|
||||
OpmLog::info("Sent simulation activation date to master process from rank 0");
|
||||
this->logger_.info("Sent simulation activation date to master process from rank 0");
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendActivationHandshakeToMasterProcess_() const
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
@@ -344,13 +367,14 @@ sendActivationHandshakeToMasterProcess_() const
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveActivationHandshake),
|
||||
this->slave_master_comm_
|
||||
);
|
||||
OpmLog::info("Sent simulation activation handshake to master process from rank 0");
|
||||
this->logger_.info("Sent simulation activation handshake to master process from rank 0");
|
||||
}
|
||||
this->comm_.barrier();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlave::
|
||||
ReservoirCouplingSlave<Scalar>::
|
||||
sendSimulationStartDateToMasterProcess_() const
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
@@ -365,8 +389,14 @@ sendSimulationStartDateToMasterProcess_() const
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveSimulationStartDate),
|
||||
this->slave_master_comm_
|
||||
);
|
||||
OpmLog::info("Sent simulation start date to master process from rank 0");
|
||||
this->logger_.info("Sent simulation start date to master process from rank 0");
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template class ReservoirCouplingSlave<double>;
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class ReservoirCouplingSlave<float>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
+34
-4
@@ -20,7 +20,7 @@
|
||||
#ifndef OPM_RESERVOIR_COUPLING_SLAVE_HPP
|
||||
#define OPM_RESERVOIR_COUPLING_SLAVE_HPP
|
||||
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
#include <opm/simulators/timestepping/SimulatorTimer.hpp>
|
||||
@@ -32,10 +32,16 @@
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingSlaveReportStep;
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingSlave {
|
||||
public:
|
||||
using MessageTag = ReservoirCoupling::MessageTag;
|
||||
using Potentials = ReservoirCoupling::Potentials;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
using SlaveGroupInjectionData = ReservoirCoupling::SlaveGroupInjectionData<Scalar>;
|
||||
using SlaveGroupProductionData = ReservoirCoupling::SlaveGroupProductionData<Scalar>;
|
||||
|
||||
ReservoirCouplingSlave(
|
||||
const Parallel::Communication &comm, const Schedule &schedule, const SimulatorTimer &timer
|
||||
@@ -43,23 +49,32 @@ public:
|
||||
bool activated() const { return activated_; }
|
||||
void clearDeferredLogger() { logger_.clearDeferredLogger(); }
|
||||
const Parallel::Communication& getComm() const { return comm_; }
|
||||
ReservoirCoupling::Logger& getLogger() { return this->logger_; }
|
||||
MPI_Comm getMasterComm() const { return slave_master_comm_; }
|
||||
const std::string& getSlaveName() const { return slave_name_; }
|
||||
const std::map<std::string, std::string>& getSlaveToMasterGroupNameMap() const {
|
||||
return slave_to_master_group_map_; }
|
||||
void initTimeStepping();
|
||||
void maybeActivate(int report_step);
|
||||
std::size_t numSlaveGroups() const { return this->slave_group_order_.size(); }
|
||||
double receiveNextTimeStepFromMaster();
|
||||
void sendAndReceiveInitialData();
|
||||
void sendInjectionDataToMaster(const std::vector<SlaveGroupInjectionData> &injection_data) const;
|
||||
void sendNextReportDateToMasterProcess() const;
|
||||
void sendPotentialsToMaster(const std::vector<Potentials> &potentials) const;
|
||||
void sendProductionDataToMaster(const std::vector<SlaveGroupProductionData> &production_data) const;
|
||||
void setDeferredLogger(DeferredLogger *deferred_logger) {
|
||||
this->logger_.setDeferredLogger(deferred_logger);
|
||||
}
|
||||
const std::string& slaveGroupIdxToGroupName(std::size_t group_idx) const {
|
||||
return this->slave_group_order_.at(group_idx);
|
||||
}
|
||||
|
||||
private:
|
||||
void checkGrupSlavGroupNames_();
|
||||
double getGrupSlavActivationDate_() const;
|
||||
void receiveMasterGroupNamesFromMasterProcess_();
|
||||
void receiveSlaveNameFromMasterProcess_();
|
||||
void saveMasterGroupNamesAsMap_(const std::vector<char>& group_names);
|
||||
void saveMasterGroupNamesAsMapAndEstablishOrder_(const std::vector<char>& group_names);
|
||||
void sendActivationDateToMasterProcess_() const;
|
||||
void sendActivationHandshakeToMasterProcess_() const;
|
||||
void sendSimulationStartDateToMasterProcess_() const;
|
||||
@@ -73,7 +88,22 @@ private:
|
||||
bool activated_{false};
|
||||
std::string slave_name_; // This is the slave name as defined in the master process
|
||||
ReservoirCoupling::Logger logger_;
|
||||
// Order of the slave groups. A mapping from slave group index to slave group name.
|
||||
// The indices are determined by the order the master process sends us the group names, see
|
||||
// receiveMasterGroupNamesFromMasterProcess_()
|
||||
// Later, the master process will send us group name indices, and not the group names themselves,
|
||||
// so we use this mapping to recover the slave group names from the indices.
|
||||
std::map<std::size_t, std::string> slave_group_order_;
|
||||
// Stores data that changes for a single report step or for timesteps within a report step.
|
||||
std::unique_ptr<ReservoirCouplingSlaveReportStep<Scalar>> report_step_data_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
// Include the complete definition after the class declaration to avoid circular dependency
|
||||
// while still providing complete types for unique_ptr destructor instantiation.
|
||||
// This is included here (after namespace closing) so that when the destructor is
|
||||
// instantiated during template instantiation, the complete type is available.
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlaveReportStep.hpp>
|
||||
|
||||
#endif // OPM_RESERVOIR_COUPLING_SLAVE_HPP
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright 2025 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlaveReportStep.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/Slaves.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
|
||||
#include <dune/common/parallel/mpitraits.hh>
|
||||
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
ReservoirCouplingSlaveReportStep<Scalar>::
|
||||
ReservoirCouplingSlaveReportStep(
|
||||
ReservoirCouplingSlave<Scalar> &slave
|
||||
) :
|
||||
slave_{slave}
|
||||
{
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlaveReportStep<Scalar>::
|
||||
sendInjectionDataToMaster(
|
||||
const std::vector<ReservoirCoupling::SlaveGroupInjectionData<Scalar>> &injection_data
|
||||
) const
|
||||
{
|
||||
// NOTE: The master can determine from the ordering of the injection data in the vector
|
||||
// which slave group for a given slave name the given injection data belong to,
|
||||
// so we do not need to send the slave group names also.
|
||||
if (this->comm().rank() == 0) {
|
||||
auto num_groups = injection_data.size();
|
||||
auto MPI_INJECTION_DATA_TYPE = Dune::MPITraits<SlaveGroupInjectionData>::getType();
|
||||
MPI_Send(
|
||||
injection_data.data(),
|
||||
/*count=*/num_groups,
|
||||
/*datatype=*/MPI_INJECTION_DATA_TYPE,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveInjectionData),
|
||||
this->getSlaveMasterComm()
|
||||
);
|
||||
this->logger().info(
|
||||
fmt::format("Sent injection data for {} groups to master process from rank 0, slave name: {}",
|
||||
num_groups, this->slaveName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSlaveReportStep<Scalar>::
|
||||
sendProductionDataToMaster(
|
||||
const std::vector<ReservoirCoupling::SlaveGroupProductionData<Scalar>> &production_data
|
||||
) const
|
||||
{
|
||||
// NOTE: The master can determine from the ordering of the production data in the vector
|
||||
// which slave group for a given slave name the given production data belong to,
|
||||
// so we do not need to send the slave group names also.
|
||||
if (this->comm().rank() == 0) {
|
||||
auto num_groups = production_data.size();
|
||||
auto MPI_PRODUCTION_DATA_TYPE = Dune::MPITraits<SlaveGroupProductionData>::getType();
|
||||
MPI_Send(
|
||||
production_data.data(),
|
||||
/*count=*/num_groups,
|
||||
/*datatype=*/MPI_PRODUCTION_DATA_TYPE,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveProductionData),
|
||||
this->getSlaveMasterComm()
|
||||
);
|
||||
this->logger().info(
|
||||
fmt::format("Sent production data for {} groups to master process from rank 0, slave name: {}",
|
||||
num_groups, this->slaveName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit instantiations
|
||||
template class ReservoirCouplingSlaveReportStep<double>;
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class ReservoirCouplingSlaveReportStep<float>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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_RESERVOIR_COUPLING_SLAVE_REPORT_STEP_HPP
|
||||
#define OPM_RESERVOIR_COUPLING_SLAVE_REPORT_STEP_HPP
|
||||
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingSlaveReportStep {
|
||||
public:
|
||||
using MessageTag = ReservoirCoupling::MessageTag;
|
||||
using SlaveGroupProductionData = ReservoirCoupling::SlaveGroupProductionData<Scalar>;
|
||||
using SlaveGroupInjectionData = ReservoirCoupling::SlaveGroupInjectionData<Scalar>;
|
||||
ReservoirCouplingSlaveReportStep(
|
||||
ReservoirCouplingSlave<Scalar> &slave
|
||||
);
|
||||
|
||||
const Parallel::Communication &comm() const { return this->slave_.getComm(); }
|
||||
MPI_Comm getSlaveMasterComm() const { return this->slave_.getMasterComm(); }
|
||||
ReservoirCoupling::Logger& logger() const { return this->slave_.getLogger(); }
|
||||
void sendProductionDataToMaster(const std::vector<SlaveGroupProductionData> &production_data) const;
|
||||
void sendInjectionDataToMaster(const std::vector<SlaveGroupInjectionData> &injection_data) const;
|
||||
const std::string& slaveName() const { return this->slave_.getSlaveName(); }
|
||||
|
||||
private:
|
||||
ReservoirCouplingSlave<Scalar> &slave_;
|
||||
};
|
||||
} // namespace Opm
|
||||
#endif // OPM_RESERVOIR_COUPLING_SLAVE_REPORT_STEP_HPP
|
||||
+72
-37
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <opm/simulators/flow/ReservoirCouplingSpawnSlaves.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSpawnSlaves.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
@@ -47,9 +47,10 @@ namespace Opm {
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
template <class Scalar>
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
ReservoirCouplingSpawnSlaves(
|
||||
ReservoirCouplingMaster &master,
|
||||
ReservoirCouplingMaster<Scalar> &master,
|
||||
const ReservoirCoupling::CouplingInfo &rescoup
|
||||
) :
|
||||
master_{master},
|
||||
@@ -58,8 +59,9 @@ ReservoirCouplingSpawnSlaves(
|
||||
{
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
spawn()
|
||||
{
|
||||
// spawn the slave processes and get the simulation start date from the slaves,
|
||||
@@ -70,8 +72,8 @@ spawn()
|
||||
this->sendSlaveNamesToSlaves_();
|
||||
this->createMasterGroupNameOrder_();
|
||||
this->createMasterGroupToSlaveNameMap_();
|
||||
this->createSlaveNameToMasterGroupsMap_();
|
||||
this->sendMasterGroupNamesToSlaves_();
|
||||
this->prepareTimeStepping_();
|
||||
this->logger_.info("Reservoir coupling slave processes was spawned successfully");
|
||||
}
|
||||
|
||||
@@ -80,8 +82,9 @@ spawn()
|
||||
// Private methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
createMasterGroupNameOrder_()
|
||||
{
|
||||
// When the slaves send master/slave group potentials to us, we need to know
|
||||
@@ -115,8 +118,9 @@ createMasterGroupNameOrder_()
|
||||
this->master_.updateMasterGroupNameOrderMap(slave_name, master_group_map);
|
||||
}
|
||||
}
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
createMasterGroupToSlaveNameMap_()
|
||||
{
|
||||
// When the slaves send master/slave group potentials to us, we need to know
|
||||
@@ -129,7 +133,27 @@ createMasterGroupToSlaveNameMap_()
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<char *> ReservoirCouplingSpawnSlaves::
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
createSlaveNameToMasterGroupsMap_()
|
||||
{
|
||||
// Creates a map from slave name to master groups
|
||||
// This is used by the master to send master group targets to the slaves
|
||||
// NOTE: The order of the master groups in the vector is important,
|
||||
// as the slaves will communicate the indices of the master groups in
|
||||
// this vector instead of the group names.
|
||||
auto& slave_name_to_master_groups = this->master_.getSlaveNameToMasterGroupsMap();
|
||||
const auto& master_groups = this->rescoup_.masterGroups();
|
||||
for (const auto& [group_name, master_group] : master_groups) {
|
||||
slave_name_to_master_groups[master_group.slaveName()].push_back(group_name);
|
||||
}
|
||||
// Also build an index-based vector as an O(1) performance optimization
|
||||
this->master_.rebuildSlaveIdxToMasterGroupsVector();
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
std::vector<char *> ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
getSlaveArgv_(
|
||||
const std::filesystem::path &data_file,
|
||||
const std::string &slave_name,
|
||||
@@ -168,39 +192,39 @@ getSlaveArgv_(
|
||||
return slave_argv;
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
std::pair<std::vector<char>, std::size_t>
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
getMasterGroupNamesForSlave_(const std::string &slave_name) const
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
getMasterGroupNamesForSlave_(std::size_t slave_idx) const
|
||||
{
|
||||
// For the given slave name, get all pairs of master group names and slave group names
|
||||
// Serialize the data such that it can be sent over MPI in one chunk
|
||||
// NOTE: The order of the names in the vector is important, as the slaves will
|
||||
// use this order to establish an index mapping. Later, when sending group targets,
|
||||
// the master will send indices instead of group names for efficiency. See:
|
||||
// - ReservoirCouplingSpawnSlaves::createSlaveNameToMasterGroupsMap_() which creates
|
||||
// the index-based mapping
|
||||
// - ReservoirCouplingSlave::saveMasterGroupNamesAsMap_() which establishes the same mapping
|
||||
// on the slave side
|
||||
// - RescoupTargetCalculator::calculateMasterGroupTargetsAndSendToSlaves() which uses
|
||||
// the index-based mapping to send the group targets to the slaves
|
||||
auto master_groups = this->rescoup_.masterGroups();
|
||||
std::vector<std::string> data;
|
||||
std::vector<std::string> master_group_names;
|
||||
for (const auto& [group_name, master_group] : master_groups) {
|
||||
if (master_group.slaveName() == slave_name) {
|
||||
data.push_back(group_name);
|
||||
data.push_back(master_group.slaveGroupName());
|
||||
}
|
||||
const auto& master_group_names = this->master_.getMasterGroupNamesForSlave(slave_idx);
|
||||
for (std::size_t group_idx = 0; group_idx < master_group_names.size(); ++group_idx) {
|
||||
const auto& group_name = master_group_names[group_idx];
|
||||
data.push_back(group_name);
|
||||
const auto& slave_group_name = master_groups[group_name].slaveGroupName();
|
||||
data.push_back(slave_group_name);
|
||||
}
|
||||
assert(data.size() % 2 == 0);
|
||||
assert(data.size() > 0);
|
||||
return ReservoirCoupling::serializeStrings(data);
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
prepareTimeStepping_()
|
||||
{
|
||||
// Prepare the time stepping for the master process
|
||||
// This is done after the slave processes have been spawned
|
||||
// and the master group names have been sent to the slaves
|
||||
auto num_slaves = this->master_.numSlavesStarted();
|
||||
this->master_.resizeNextReportDates(num_slaves);
|
||||
}
|
||||
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
receiveActivationDateFromSlaves_()
|
||||
{
|
||||
// Currently, we only use the activation date to check that no slave process
|
||||
@@ -241,8 +265,9 @@ receiveActivationDateFromSlaves_()
|
||||
this->logger_.info("Broadcasted slave activation dates to all ranks");
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
receiveSimulationStartDateFromSlaves_()
|
||||
{
|
||||
auto num_slaves = this->master_.numSlavesStarted();
|
||||
@@ -277,15 +302,16 @@ receiveSimulationStartDateFromSlaves_()
|
||||
this->logger_.info("Broadcasted slave start dates to all ranks");
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
sendMasterGroupNamesToSlaves_()
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
auto num_slaves = this->master_.numSlavesStarted();
|
||||
for (unsigned int i = 0; i < num_slaves; i++) {
|
||||
auto slave_name = this->master_.getSlaveName(i);
|
||||
auto [group_names, size] = this->getMasterGroupNamesForSlave_(slave_name);
|
||||
for (unsigned int slave_idx = 0; slave_idx < num_slaves; slave_idx++) {
|
||||
auto slave_name = this->master_.getSlaveName(slave_idx);
|
||||
auto [group_names, size] = this->getMasterGroupNamesForSlave_(slave_idx);
|
||||
static_assert(std::is_same_v<decltype(size), std::size_t>, "size must be of type std::size_t");
|
||||
auto MPI_SIZE_T_TYPE = Dune::MPITraits<std::size_t>::getType();
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
@@ -295,7 +321,7 @@ sendMasterGroupNamesToSlaves_()
|
||||
/*datatype=*/MPI_SIZE_T_TYPE,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::MasterGroupNamesSize),
|
||||
this->master_.getSlaveComm(i)
|
||||
this->master_.getSlaveComm(slave_idx)
|
||||
);
|
||||
MPI_Send(
|
||||
group_names.data(),
|
||||
@@ -303,7 +329,7 @@ sendMasterGroupNamesToSlaves_()
|
||||
/*datatype=*/MPI_CHAR,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::MasterGroupNames),
|
||||
this->master_.getSlaveComm(i)
|
||||
this->master_.getSlaveComm(slave_idx)
|
||||
);
|
||||
this->logger_.info(fmt::format(
|
||||
"Sent master group names to slave process rank 0 with name: {}", slave_name)
|
||||
@@ -312,8 +338,9 @@ sendMasterGroupNamesToSlaves_()
|
||||
}
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
sendSlaveNamesToSlaves_()
|
||||
{
|
||||
if (this->comm_.rank() == 0) {
|
||||
@@ -349,8 +376,9 @@ sendSlaveNamesToSlaves_()
|
||||
|
||||
// NOTE: This functions is executed for all ranks, but only rank 0 will spawn
|
||||
// the slave processes
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingSpawnSlaves::
|
||||
ReservoirCouplingSpawnSlaves<Scalar>::
|
||||
spawnSlaveProcesses_()
|
||||
{
|
||||
char *flow_program_name = this->master_.getArgv(0);
|
||||
@@ -407,4 +435,11 @@ spawnSlaveProcesses_()
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template class ReservoirCouplingSpawnSlaves<double>;
|
||||
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class ReservoirCouplingSpawnSlaves<float>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
+7
-6
@@ -21,8 +21,8 @@
|
||||
#define OPM_RESERVOIR_COUPLING_SPAWN_SLAVES_HPP
|
||||
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
@@ -35,12 +35,13 @@
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingSpawnSlaves {
|
||||
public:
|
||||
using MessageTag = ReservoirCoupling::MessageTag;
|
||||
|
||||
ReservoirCouplingSpawnSlaves(
|
||||
ReservoirCouplingMaster &master,
|
||||
ReservoirCouplingMaster<Scalar> &master,
|
||||
const ReservoirCoupling::CouplingInfo &rescoup
|
||||
);
|
||||
|
||||
@@ -49,20 +50,20 @@ public:
|
||||
private:
|
||||
void createMasterGroupNameOrder_();
|
||||
void createMasterGroupToSlaveNameMap_();
|
||||
void createSlaveNameToMasterGroupsMap_();
|
||||
std::pair<std::vector<char>, std::size_t>
|
||||
getMasterGroupNamesForSlave_(const std::string &slave_name) const;
|
||||
getMasterGroupNamesForSlave_(std::size_t slave_idx) const;
|
||||
std::vector<char *> getSlaveArgv_(
|
||||
const std::filesystem::path &data_file,
|
||||
const std::string &slave_name,
|
||||
std::string &log_filename) const;
|
||||
void prepareTimeStepping_();
|
||||
void receiveActivationDateFromSlaves_();
|
||||
void receiveSimulationStartDateFromSlaves_();
|
||||
void sendMasterGroupNamesToSlaves_();
|
||||
void sendSlaveNamesToSlaves_();
|
||||
void spawnSlaveProcesses_();
|
||||
|
||||
ReservoirCouplingMaster &master_;
|
||||
ReservoirCouplingMaster<Scalar> &master_;
|
||||
const ReservoirCoupling::CouplingInfo &rescoup_;
|
||||
const Parallel::Communication &comm_;
|
||||
ReservoirCoupling::Logger logger_;
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2025 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMpiTraits.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingTimeStepper.hpp>
|
||||
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/MasterGroup.hpp>
|
||||
#include <opm/input/eclipse/Schedule/ResCoup/Slaves.hpp>
|
||||
#include <opm/common/ErrorMacros.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
|
||||
#include <dune/common/parallel/mpitraits.hh>
|
||||
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
ReservoirCouplingTimeStepper<Scalar>::
|
||||
ReservoirCouplingTimeStepper(
|
||||
ReservoirCouplingMaster<Scalar> &master
|
||||
) :
|
||||
master_{master}
|
||||
{
|
||||
this->slave_next_report_time_offsets_.resize(this->numSlaves());
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
template <class Scalar>
|
||||
double
|
||||
ReservoirCouplingTimeStepper<Scalar>::
|
||||
maybeChopSubStep(double suggested_timestep_original, double elapsed_time) const
|
||||
{
|
||||
// Check if the suggested timestep needs to be adjusted based on the slave processes'
|
||||
// next report step, or if the slave process has not started yet: the start of a slave process.
|
||||
// NOTE: getStartTime() returns a std::time_t value, which is typically a long integer. It should
|
||||
// be possible to represent reasonable epoch values within a double. See comment for
|
||||
// getMasterActivationDate_() for more information.
|
||||
double start_date = this->schedule().getStartTime();
|
||||
double step_start_date{start_date + elapsed_time};
|
||||
double step_end_date{step_start_date + suggested_timestep_original};
|
||||
double suggested_timestep{suggested_timestep_original};
|
||||
auto num_slaves = this->numSlaves();
|
||||
// Determine the minimum step_end_date and the corresponding suggested_timestep such that no
|
||||
// slave process will report or start during the timestep [step_start_date, step_end_date]
|
||||
// where suggested_timestep = step_end_date - step_start_date
|
||||
for (std::size_t i = 0; i < num_slaves; i++) {
|
||||
double slave_start_date = this->slaveStartDate(i);
|
||||
double slave_activation_date = this->slaveActivationDate(i);
|
||||
double slave_next_report_date{this->slave_next_report_time_offsets_[i] + slave_start_date};
|
||||
if (Seconds::compare_gt_or_eq(slave_activation_date, step_end_date)) {
|
||||
// The slave process has not activated yet, and will not activate during this timestep
|
||||
continue;
|
||||
}
|
||||
double slave_elapsed_time;
|
||||
if (Seconds::compare_lt_or_eq(slave_activation_date,step_start_date)) {
|
||||
// The slave process has already activated, and will continue during this timestep
|
||||
if (Seconds::compare_gt(slave_next_report_date, step_end_date)) {
|
||||
// The slave process will not report during this timestep
|
||||
continue;
|
||||
}
|
||||
// The slave process will report during this timestep
|
||||
slave_elapsed_time = slave_next_report_date - step_start_date;
|
||||
}
|
||||
else {
|
||||
// The slave process will activate during the timestep, but not at the beginning
|
||||
// Ensure that we synchronize the slave activation report date with the
|
||||
// start of a master timestep by chopping the current master timestep.
|
||||
slave_elapsed_time = slave_activation_date - step_start_date;
|
||||
}
|
||||
suggested_timestep = slave_elapsed_time;
|
||||
step_end_date = step_start_date + suggested_timestep;
|
||||
}
|
||||
return suggested_timestep;
|
||||
}
|
||||
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingTimeStepper<Scalar>::
|
||||
receiveNextReportDateFromSlaves()
|
||||
{
|
||||
auto num_slaves = this->numSlaves();
|
||||
if (this->comm().rank() == 0) {
|
||||
this->logger().info("Receiving next report dates from slave processes");
|
||||
for (unsigned int i = 0; i < num_slaves; i++) {
|
||||
if (!this->slaveIsActivated(i)) {
|
||||
// Set to zero to indicate that the slave has not activated yet
|
||||
this->slave_next_report_time_offsets_[i] = 0.0;
|
||||
this->logger().info(fmt::format("Slave {} has not activated yet, setting next report date to 0.0",
|
||||
this->slaveName(i)));
|
||||
continue;
|
||||
}
|
||||
double slave_next_report_time_offset; // Elapsed time from the beginning of the simulation
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
MPI_Recv(
|
||||
&slave_next_report_time_offset,
|
||||
/*count=*/1,
|
||||
/*datatype=*/MPI_DOUBLE,
|
||||
/*source_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveNextReportDate),
|
||||
this->getSlaveComm(i),
|
||||
MPI_STATUS_IGNORE
|
||||
);
|
||||
this->slave_next_report_time_offsets_[i] = slave_next_report_time_offset;
|
||||
this->logger().info(
|
||||
fmt::format(
|
||||
"Received simulation slave next report date from slave process with name: {}. "
|
||||
"Next report date: {}", this->slaveName(i), slave_next_report_time_offset
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
this->comm().broadcast(
|
||||
this->slave_next_report_time_offsets_.data(), /*count=*/num_slaves, /*emitter_rank=*/0
|
||||
);
|
||||
this->logger().info("Broadcasted slave next report dates to all ranks");
|
||||
}
|
||||
|
||||
template <class Scalar>
|
||||
void
|
||||
ReservoirCouplingTimeStepper<Scalar>::
|
||||
sendNextTimeStepToSlaves(double timestep)
|
||||
{
|
||||
if (this->comm().rank() == 0) {
|
||||
for (unsigned int slave_idx = 0; slave_idx < this->numSlaves(); slave_idx++) {
|
||||
if (!this->slaveIsActivated(slave_idx)) {
|
||||
this->logger().info(fmt::format(
|
||||
"Slave {} has not activated yet, skipping sending next time step to slave",
|
||||
this->slaveName(slave_idx)
|
||||
));
|
||||
continue;
|
||||
}
|
||||
// Then send the next time step
|
||||
// NOTE: See comment about error handling at the top of this file.
|
||||
MPI_Send(
|
||||
×tep,
|
||||
/*count=*/1,
|
||||
/*datatype=*/MPI_DOUBLE,
|
||||
/*dest_rank=*/0,
|
||||
/*tag=*/static_cast<int>(MessageTag::SlaveNextTimeStep),
|
||||
this->getSlaveComm(slave_idx)
|
||||
);
|
||||
this->logger().info(fmt::format(
|
||||
"Sent next time step {} from master process rank 0 to slave process "
|
||||
"rank 0 with name: {}", timestep, this->slaveName(slave_idx))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Private methods
|
||||
// ------------------
|
||||
|
||||
|
||||
// Explicit instantiations
|
||||
template class ReservoirCouplingTimeStepper<double>;
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class ReservoirCouplingTimeStepper<float>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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_RESERVOIR_COUPLING_TIME_STEPPER_HPP
|
||||
#define OPM_RESERVOIR_COUPLING_TIME_STEPPER_HPP
|
||||
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/input/eclipse/Schedule/Schedule.hpp>
|
||||
#include <opm/simulators/utils/ParallelCommunication.hpp>
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template <class Scalar>
|
||||
class ReservoirCouplingTimeStepper {
|
||||
public:
|
||||
using MessageTag = ReservoirCoupling::MessageTag;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
using Seconds = ReservoirCoupling::Seconds;
|
||||
|
||||
ReservoirCouplingTimeStepper(
|
||||
ReservoirCouplingMaster<Scalar> &master
|
||||
);
|
||||
const Parallel::Communication &comm() const { return this->master_.getComm(); }
|
||||
MPI_Comm getSlaveComm(int index) const { return this->master_.getSlaveComm(index); }
|
||||
std::size_t numSlaves() const { return this->master_.numSlavesStarted(); }
|
||||
ReservoirCoupling::Logger& logger() const { return this->master_.getLogger(); }
|
||||
double maybeChopSubStep(double suggested_timestep_original, double elapsed_time) const;
|
||||
void receiveNextReportDateFromSlaves();
|
||||
void resizeNextReportDates(int size) { this->slave_next_report_time_offsets_.resize(size); }
|
||||
const Schedule& schedule() const { return this->master_.schedule(); }
|
||||
bool slaveIsActivated(int index) const { return this->master_.slaveIsActivated(index); }
|
||||
const std::string &slaveName(int index) const { return this->master_.getSlaveName(index); }
|
||||
double slaveStartDate(int index) const { return this->master_.getSlaveStartDate(index); }
|
||||
double slaveActivationDate(int index) const { return this->master_.getSlaveActivationDate(index); }
|
||||
void sendNextTimeStepToSlaves(double timestep);
|
||||
void setSlaveNextReportTimeOffset(int index, double offset) {
|
||||
this->slave_next_report_time_offsets_[index] = offset;
|
||||
}
|
||||
|
||||
private:
|
||||
ReservoirCouplingMaster<Scalar> &master_;
|
||||
// Elapsed time from the beginning of the simulation
|
||||
std::vector<double> slave_next_report_time_offsets_;
|
||||
// Potentials for oil, gas, and water rates for each slave group
|
||||
std::map<std::string, std::vector<Potentials>> slave_group_potentials_;
|
||||
};
|
||||
} // namespace Opm
|
||||
#endif // OPM_RESERVOIR_COUPLING_TIME_STEPPER_HPP
|
||||
@@ -40,12 +40,16 @@ consistentlyFailingWells(const std::vector<StepReport>& sr, bool requireRepeated
|
||||
{
|
||||
// If there are wells that cause repeated failures, we
|
||||
// close them, and restart the un-chopped timestep.
|
||||
std::set<std::string> failing_wells;
|
||||
|
||||
if (sr.empty()) {
|
||||
return failing_wells;
|
||||
}
|
||||
|
||||
std::ostringstream msg;
|
||||
msg << " Excessive chopping detected in report step "
|
||||
<< sr.back().report_step << ", substep " << sr.back().current_step << "\n";
|
||||
|
||||
std::set<std::string> failing_wells;
|
||||
|
||||
// return empty set if no report exists
|
||||
// well failures in assembly is not yet registred
|
||||
if (sr.back().report.empty())
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
#define RESERVOIR_COUPLING_ENABLED
|
||||
#endif
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
@@ -117,8 +117,8 @@ private:
|
||||
double maxTimeStep_() const;
|
||||
SimulatorReport runStepOriginal_();
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster& reservoirCouplingMaster_();
|
||||
ReservoirCouplingSlave& reservoirCouplingSlave_();
|
||||
ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster_();
|
||||
ReservoirCouplingSlave<Scalar>& reservoirCouplingSlave_();
|
||||
SimulatorReport runStepReservoirCouplingMaster_();
|
||||
SimulatorReport runStepReservoirCouplingSlave_();
|
||||
#endif
|
||||
|
||||
@@ -578,7 +578,7 @@ runStepOriginal_()
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
template <class TypeTag>
|
||||
template <class Solver>
|
||||
ReservoirCouplingMaster&
|
||||
ReservoirCouplingMaster<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
|
||||
AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
|
||||
reservoirCouplingMaster_()
|
||||
{
|
||||
@@ -589,7 +589,7 @@ reservoirCouplingMaster_()
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
template <class TypeTag>
|
||||
template <class Solver>
|
||||
ReservoirCouplingSlave&
|
||||
ReservoirCouplingSlave<typename AdaptiveTimeStepping<TypeTag>::Scalar>&
|
||||
AdaptiveTimeStepping<TypeTag>::SubStepper<Solver>::
|
||||
reservoirCouplingSlave_()
|
||||
{
|
||||
@@ -639,18 +639,24 @@ runStepReservoirCouplingMaster_()
|
||||
double current_time{this->simulator_timer_.simulationTimeElapsed()};
|
||||
double step_end_time = current_time + original_time_step;
|
||||
auto current_step_length = original_time_step;
|
||||
auto report_step_idx = this->simulator_timer_.currentStepNum();
|
||||
if (report_step_idx == 0 && iteration == 0) {
|
||||
reservoirCouplingMaster_().initTimeStepping();
|
||||
}
|
||||
SimulatorReport report;
|
||||
// The master needs to know which slaves have activated before it can start the substep loop
|
||||
reservoirCouplingMaster_().maybeReceiveActivationHandshakeFromSlaves(current_time);
|
||||
while (true) {
|
||||
reservoirCouplingMaster_().receiveNextReportDateFromSlaves();
|
||||
if (iteration == 0) {
|
||||
maybeUpdateTuning_(current_time, current_step_length, /*substep=*/0);
|
||||
bool start_of_report_step = (iteration == 0);
|
||||
if (start_of_report_step) {
|
||||
reservoirCouplingMaster_().initStartOfReportStep(report_step_idx);
|
||||
}
|
||||
current_step_length = reservoirCouplingMaster_().maybeChopSubStep(
|
||||
current_step_length, current_time);
|
||||
reservoirCouplingMaster_().sendNextTimeStepToSlaves(current_step_length);
|
||||
if (iteration == 0) {
|
||||
if (start_of_report_step) {
|
||||
maybeUpdateTuning_(current_time, current_step_length, /*substep=*/0);
|
||||
maybeModifySuggestedTimeStepAtBeginningOfReportStep_(current_step_length);
|
||||
}
|
||||
AdaptiveSimulatorTimer substep_timer{
|
||||
@@ -689,10 +695,15 @@ runStepReservoirCouplingSlave_()
|
||||
double current_time{this->simulator_timer_.simulationTimeElapsed()};
|
||||
double step_end_time = current_time + original_time_step;
|
||||
SimulatorReport report;
|
||||
auto report_step_idx = this->simulator_timer_.currentStepNum();
|
||||
if (report_step_idx == 0 && iteration == 0) {
|
||||
reservoirCouplingSlave_().initTimeStepping();
|
||||
}
|
||||
while (true) {
|
||||
bool start_of_report_step = (iteration == 0);
|
||||
reservoirCouplingSlave_().sendNextReportDateToMasterProcess();
|
||||
const auto timestep = reservoirCouplingSlave_().receiveNextTimeStepFromMaster();
|
||||
if (iteration == 0) {
|
||||
if (start_of_report_step) {
|
||||
maybeUpdateTuning_(current_time, original_time_step, /*substep=*/0);
|
||||
maybeModifySuggestedTimeStepAtBeginningOfReportStep_(timestep);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
#define RESERVOIR_COUPLING_ENABLED
|
||||
#endif
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#endif
|
||||
|
||||
#include <dune/common/fmatrix.hh>
|
||||
@@ -81,6 +81,7 @@
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
@@ -376,10 +377,10 @@ template<class Scalar> class WellContributions;
|
||||
{ nldd_ = mod; }
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster& reservoirCouplingMaster() {
|
||||
ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster() {
|
||||
return *(this->simulator_.reservoirCouplingMaster());
|
||||
}
|
||||
ReservoirCouplingSlave& reservoirCouplingSlave() {
|
||||
ReservoirCouplingSlave<Scalar>& reservoirCouplingSlave() {
|
||||
return *(this->simulator_.reservoirCouplingSlave());
|
||||
}
|
||||
bool isReservoirCouplingMaster() const {
|
||||
@@ -388,14 +389,34 @@ template<class Scalar> class WellContributions;
|
||||
bool isReservoirCouplingSlave() const {
|
||||
return this->simulator_.reservoirCouplingSlave() != nullptr;
|
||||
}
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster *master)
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster<Scalar> *master)
|
||||
{
|
||||
this->guide_rate_handler_.setReservoirCouplingMaster(master);
|
||||
this->wgHelper().setReservoirCouplingMaster(master);
|
||||
}
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave *slave)
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave<Scalar> *slave)
|
||||
{
|
||||
this->guide_rate_handler_.setReservoirCouplingSlave(slave);
|
||||
this->wgHelper().setReservoirCouplingSlave(slave);
|
||||
}
|
||||
|
||||
/// \brief Send comprehensive slave group data to master
|
||||
void sendSlaveGroupDataToMaster();
|
||||
|
||||
/// \brief Receive comprehensive slave group data from slaves
|
||||
void receiveSlaveGroupData();
|
||||
|
||||
/// \brief Setup RAII guard for reservoir coupling logger
|
||||
///
|
||||
/// Creates a scoped logger guard that automatically clears the logger
|
||||
/// when it goes out of scope. This eliminates the need for manual cleanup.
|
||||
///
|
||||
/// @param local_logger The local DeferredLogger to bind to the reservoir coupling logger
|
||||
/// @return An optional containing the ScopedLoggerGuard if reservoir coupling is active,
|
||||
/// or std::nullopt if not active
|
||||
std::optional<ReservoirCoupling::ScopedLoggerGuard>
|
||||
setupRescoupScopedLogger(DeferredLogger& local_logger);
|
||||
|
||||
#endif
|
||||
protected:
|
||||
Simulator& simulator_;
|
||||
|
||||
@@ -52,6 +52,11 @@
|
||||
#include <opm/simulators/wells/WellGroupHelper.hpp>
|
||||
#include <opm/simulators/wells/TargetCalculator.hpp>
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
#include <opm/simulators/wells/rescoup/RescoupSendSlaveGroupData.hpp>
|
||||
#include <opm/simulators/wells/rescoup/RescoupReceiveSlaveGroupData.hpp>
|
||||
#endif
|
||||
|
||||
#include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
|
||||
#include <opm/simulators/utils/MPIPacker.hpp>
|
||||
|
||||
@@ -333,6 +338,9 @@ namespace Opm {
|
||||
|
||||
DeferredLogger local_deferredLogger;
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
auto rescoup_logger_guard = this->setupRescoupScopedLogger(local_deferredLogger);
|
||||
#endif
|
||||
this->switched_prod_groups_.clear();
|
||||
this->switched_inj_groups_.clear();
|
||||
|
||||
@@ -375,6 +383,13 @@ namespace Opm {
|
||||
// create the well container
|
||||
createWellContainer(reportStepIdx);
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
// Receive all slave group data early to ensure it's available for any calculations
|
||||
if (this->isReservoirCouplingMaster()) {
|
||||
this->receiveSlaveGroupData();
|
||||
}
|
||||
#endif
|
||||
|
||||
// we need to update the group data after the well is created
|
||||
// to make sure we get the correct mapping.
|
||||
this->updateAndCommunicateGroupData(reportStepIdx,
|
||||
@@ -454,18 +469,13 @@ namespace Opm {
|
||||
local_deferredLogger.warning("WELL_POTENTIAL_CALCULATION_FAILED", msg);
|
||||
}
|
||||
this->guide_rate_handler_.setLogger(&local_deferredLogger);
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
if (this->isReservoirCouplingMaster()) {
|
||||
this->guide_rate_handler_.receiveMasterGroupPotentialsFromSlaves();
|
||||
}
|
||||
#endif
|
||||
//update guide rates
|
||||
this->guide_rate_handler_.updateGuideRates(
|
||||
reportStepIdx, simulationTime, this->wellState(), this->groupState()
|
||||
);
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
if (this->isReservoirCouplingSlave()) {
|
||||
this->guide_rate_handler_.sendSlaveGroupPotentialsToMaster(this->groupState());
|
||||
this->sendSlaveGroupDataToMaster();
|
||||
}
|
||||
#endif
|
||||
std::string exc_msg;
|
||||
@@ -531,6 +541,57 @@ namespace Opm {
|
||||
|
||||
}
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
// Automatically manages the lifecycle of the DeferredLogger pointer
|
||||
// in the reservoir coupling logger. Ensures the logger is properly
|
||||
// cleared when it goes out of scope, preventing dangling pointer issues:
|
||||
//
|
||||
// - The ScopedLoggerGuard constructor sets the logger pointer
|
||||
// - When the guard goes out of scope, the destructor clears the pointer
|
||||
// - Move semantics transfer ownership safely when returning from this function
|
||||
// - The moved-from guard is "nullified" and its destructor does nothing
|
||||
// - Only the final guard in the caller will clear the logger
|
||||
template<typename TypeTag>
|
||||
std::optional<ReservoirCoupling::ScopedLoggerGuard>
|
||||
BlackoilWellModel<TypeTag>::
|
||||
setupRescoupScopedLogger(DeferredLogger& local_logger) {
|
||||
if (this->isReservoirCouplingMaster()) {
|
||||
return ReservoirCoupling::ScopedLoggerGuard{
|
||||
this->reservoirCouplingMaster().getLogger(),
|
||||
&local_logger
|
||||
};
|
||||
} else if (this->isReservoirCouplingSlave()) {
|
||||
return ReservoirCoupling::ScopedLoggerGuard{
|
||||
this->reservoirCouplingSlave().getLogger(),
|
||||
&local_logger
|
||||
};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename TypeTag>
|
||||
void
|
||||
BlackoilWellModel<TypeTag>::
|
||||
receiveSlaveGroupData()
|
||||
{
|
||||
assert(this->isReservoirCouplingMaster());
|
||||
RescoupReceiveSlaveGroupData<Scalar, IndexTraits> slave_group_data_receiver{
|
||||
this->wgHelper(),
|
||||
};
|
||||
slave_group_data_receiver.receiveSlaveGroupData();
|
||||
}
|
||||
|
||||
template<typename TypeTag>
|
||||
void
|
||||
BlackoilWellModel<TypeTag>::
|
||||
sendSlaveGroupDataToMaster()
|
||||
{
|
||||
assert(this->isReservoirCouplingSlave());
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits> slave_group_data_sender{this->wgHelper()};
|
||||
slave_group_data_sender.sendSlaveGroupDataToMaster();
|
||||
}
|
||||
#endif // RESERVOIR_COUPLING_ENABLED
|
||||
|
||||
template<typename TypeTag>
|
||||
void
|
||||
BlackoilWellModel<TypeTag>::wellTesting(const int timeStepIdx,
|
||||
|
||||
@@ -107,65 +107,6 @@ debugDumpGuideRates(const int report_step_idx, const double sim_time)
|
||||
}
|
||||
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
void
|
||||
GuideRateHandler<Scalar, IndexTraits>::
|
||||
receiveMasterGroupPotentialsFromSlaves()
|
||||
{
|
||||
assert(this->isReservoirCouplingMaster());
|
||||
auto& rescoup_master = this->reservoirCouplingMaster();
|
||||
rescoup_master.receivePotentialsFromSlaves();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
void
|
||||
GuideRateHandler<Scalar, IndexTraits>::
|
||||
sendSlaveGroupPotentialsToMaster(const GroupState<Scalar>& group_state)
|
||||
{
|
||||
assert(this->isReservoirCouplingSlave());
|
||||
if (this->comm_.rank() == 0) {
|
||||
// NOTE: Traversing a std::map is guaranteed to iterate the keys in lexical order for
|
||||
// std::string keys, so the master can from this order determine which potentials
|
||||
// correspond to which slave group.
|
||||
auto& rescoup_slave = this->reservoirCouplingSlave();
|
||||
const auto& slave_master_group_map = rescoup_slave.getSlaveToMasterGroupNameMap();
|
||||
std::vector<Potentials> potentials;
|
||||
for (const auto& item : slave_master_group_map) {
|
||||
const auto& slave_group_name = item.first;
|
||||
Potentials pot;
|
||||
// For injection groups, we do not have potentials. In that case,
|
||||
// we will send dummy values (0.0) for the potentials.
|
||||
if (this->guide_rate_.hasPotentials(slave_group_name)) {
|
||||
const auto& gr_pot = group_state.get_production_group_potential(slave_group_name);
|
||||
pot[Potentials::Phase::Oil] = gr_pot.oil_rate;
|
||||
pot[Potentials::Phase::Gas] = gr_pot.gas_rate;
|
||||
pot[Potentials::Phase::Water] = gr_pot.water_rate;
|
||||
}
|
||||
potentials.push_back(pot);
|
||||
}
|
||||
rescoup_slave.sendPotentialsToMaster(potentials);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
void
|
||||
GuideRateHandler<Scalar, IndexTraits>::setLogger(DeferredLogger *deferred_logger)
|
||||
{
|
||||
deferred_logger_ = deferred_logger;
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
if (reservoir_coupling_master_) {
|
||||
reservoir_coupling_master_->setDeferredLogger(deferred_logger);
|
||||
}
|
||||
if (reservoir_coupling_slave_) {
|
||||
reservoir_coupling_slave_->setDeferredLogger(deferred_logger);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
void
|
||||
GuideRateHandler<Scalar, IndexTraits>::
|
||||
@@ -717,9 +658,9 @@ updateProductionGroupPotentialFromSlaveGroup_(const Group& group, std::vector<Sc
|
||||
const auto& pu = this->phaseUsage();
|
||||
// TODO: Here we should check that the master uses the same phases as the
|
||||
// slave.
|
||||
pot[pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx)] = slave_pot[Potentials::Phase::Oil];
|
||||
pot[pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx)] = slave_pot[Potentials::Phase::Gas];
|
||||
pot[pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx)] = slave_pot[Potentials::Phase::Water];
|
||||
pot[pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx)] = slave_pot[ReservoirCoupling::Phase::Oil];
|
||||
pot[pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx)] = slave_pot[ReservoirCoupling::Phase::Gas];
|
||||
pot[pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx)] = slave_pot[ReservoirCoupling::Phase::Water];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
#define RESERVOIR_COUPLING_ENABLED
|
||||
#endif
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#endif
|
||||
#include <opm/input/eclipse/Schedule/Group/GuideRate.hpp>
|
||||
#include <opm/output/data/Groups.hpp>
|
||||
@@ -54,7 +54,7 @@ class GuideRateHandler {
|
||||
public:
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
using Potentials = ReservoirCoupling::Potentials;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
bool isReservoirCouplingMaster() const { return this->parent_.isReservoirCouplingMaster(); }
|
||||
ReservoirCouplingMaster& reservoirCouplingMaster() {
|
||||
ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster() {
|
||||
return this->parent_.reservoirCouplingMaster();
|
||||
}
|
||||
#endif
|
||||
@@ -185,14 +185,13 @@ public:
|
||||
bool isReservoirCouplingSlave() const {
|
||||
return this->reservoir_coupling_slave_ != nullptr;
|
||||
}
|
||||
void receiveMasterGroupPotentialsFromSlaves();
|
||||
ReservoirCouplingMaster& reservoirCouplingMaster() { return *(this->reservoir_coupling_master_); }
|
||||
ReservoirCouplingSlave& reservoirCouplingSlave() { return *(this->reservoir_coupling_slave_); }
|
||||
ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster() { return *(this->reservoir_coupling_master_); }
|
||||
ReservoirCouplingSlave<Scalar>& reservoirCouplingSlave() { return *(this->reservoir_coupling_slave_); }
|
||||
void sendSlaveGroupPotentialsToMaster(const GroupState<Scalar>& group_state);
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster *reservoir_coupling_master) {
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster<Scalar> *reservoir_coupling_master) {
|
||||
this->reservoir_coupling_master_ = reservoir_coupling_master;
|
||||
}
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave *reservoir_coupling_slave) {
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave<Scalar> *reservoir_coupling_slave) {
|
||||
this->reservoir_coupling_slave_ = reservoir_coupling_slave;
|
||||
}
|
||||
#endif
|
||||
@@ -207,7 +206,7 @@ public:
|
||||
*/
|
||||
void debugDumpGuideRates(const int report_step_idx, const double sim_time);
|
||||
const Parallel::Communication& getComm() const { return comm_; }
|
||||
void setLogger(DeferredLogger *deferred_logger);
|
||||
void setLogger(DeferredLogger *deferred_logger) { deferred_logger_ = deferred_logger; }
|
||||
const Schedule& schedule() const { return schedule_; }
|
||||
/**
|
||||
* @brief Updates guide rates for the current simulation step.
|
||||
@@ -232,8 +231,8 @@ private:
|
||||
GuideRate& guide_rate_;
|
||||
DeferredLogger *deferred_logger_ = nullptr;
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster *reservoir_coupling_master_ = nullptr;
|
||||
ReservoirCouplingSlave *reservoir_coupling_slave_ = nullptr;
|
||||
ReservoirCouplingMaster<Scalar> *reservoir_coupling_master_ = nullptr;
|
||||
ReservoirCouplingSlave<Scalar> *reservoir_coupling_slave_ = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
*/
|
||||
#ifndef OPM_WELLGROUPHELPER_HEADER_INCLUDED
|
||||
#define OPM_WELLGROUPHELPER_HEADER_INCLUDED
|
||||
#if HAVE_MPI
|
||||
#define RESERVOIR_COUPLING_ENABLED
|
||||
#endif
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#endif
|
||||
|
||||
#include <opm/common/TimingMacros.hpp>
|
||||
#include <opm/input/eclipse/EclipseState/Grid/FieldPropsManager.hpp>
|
||||
@@ -192,6 +200,37 @@ public:
|
||||
return WellStateGuard(*this, well_state);
|
||||
}
|
||||
|
||||
int reportStepIdx() const
|
||||
{
|
||||
return report_step_;
|
||||
}
|
||||
|
||||
const Schedule& schedule() const
|
||||
{
|
||||
return this->schedule_;
|
||||
}
|
||||
|
||||
const GuideRate& guideRate() const
|
||||
{
|
||||
return this->guide_rate_;
|
||||
}
|
||||
|
||||
const PhaseUsageInfo<IndexTraits>& phaseUsage() const {
|
||||
return this->phase_usage_info_;
|
||||
}
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster<Scalar>& reservoirCouplingMaster()
|
||||
{
|
||||
return *this->reservoir_coupling_master_;
|
||||
}
|
||||
|
||||
ReservoirCouplingSlave<Scalar>& reservoirCouplingSlave()
|
||||
{
|
||||
return *this->reservoir_coupling_slave_;
|
||||
}
|
||||
#endif
|
||||
|
||||
void setCmodeGroup(const Group& group, GroupState<Scalar>& group_state) const;
|
||||
|
||||
template <class AverageRegionalPressureType>
|
||||
@@ -206,6 +245,23 @@ public:
|
||||
report_step_ = report_step;
|
||||
}
|
||||
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
void setReservoirCouplingMaster(ReservoirCouplingMaster<Scalar>* reservoir_coupling_master)
|
||||
{
|
||||
reservoir_coupling_master_ = reservoir_coupling_master;
|
||||
}
|
||||
|
||||
void setReservoirCouplingSlave(ReservoirCouplingSlave<Scalar>* reservoir_coupling_slave)
|
||||
{
|
||||
reservoir_coupling_slave_ = reservoir_coupling_slave;
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
@@ -312,6 +368,10 @@ private:
|
||||
// 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_;
|
||||
#ifdef RESERVOIR_COUPLING_ENABLED
|
||||
ReservoirCouplingMaster<Scalar>* reservoir_coupling_master_{nullptr};
|
||||
ReservoirCouplingSlave<Scalar>* reservoir_coupling_slave_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2025 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <opm/material/fluidsystems/BlackOilDefaultFluidSystemIndices.hpp>
|
||||
#include <opm/simulators/wells/rescoup/RescoupReceiveSlaveGroupData.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Constructor for the RescoupTargetCalculator class
|
||||
// -------------------------------------------------------
|
||||
template <class Scalar, class IndexTraits>
|
||||
RescoupReceiveSlaveGroupData<Scalar, IndexTraits>::
|
||||
RescoupReceiveSlaveGroupData(
|
||||
WellGroupHelperType& wg_helper
|
||||
)
|
||||
: wg_helper_{wg_helper}
|
||||
, reservoir_coupling_master_{wg_helper.reservoirCouplingMaster()}
|
||||
, schedule_{wg_helper.schedule()}
|
||||
, group_state_{wg_helper.groupState()}
|
||||
, phase_usage_{wg_helper.phaseUsage()}
|
||||
, report_step_idx_{wg_helper.reportStepIdx()}
|
||||
{
|
||||
}
|
||||
|
||||
template <class Scalar, class IndexTraits>
|
||||
void
|
||||
RescoupReceiveSlaveGroupData<Scalar, IndexTraits>::
|
||||
receiveSlaveGroupData()
|
||||
{
|
||||
auto& rescoup_master = this->reservoir_coupling_master_;
|
||||
rescoup_master.receiveProductionDataFromSlaves();
|
||||
rescoup_master.receiveInjectionDataFromSlaves();
|
||||
}
|
||||
|
||||
|
||||
template class RescoupReceiveSlaveGroupData<double, BlackOilDefaultFluidSystemIndices>;
|
||||
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class RescoupReceiveSlaveGroupData<float, BlackOilDefaultFluidSystemIndices>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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_RESCOUP_RECEIVE_SLAVE_GROUP_DATA_HPP
|
||||
#define OPM_RESCOUP_RECEIVE_SLAVE_GROUP_DATA_HPP
|
||||
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/wells/GuideRateHandler.hpp>
|
||||
#include <opm/simulators/wells/GroupState.hpp>
|
||||
#include <opm/simulators/wells/WellGroupHelper.hpp>
|
||||
#include <opm/simulators/utils/DeferredLogger.hpp>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template<class Scalar, class IndexTraits>
|
||||
class RescoupReceiveSlaveGroupData {
|
||||
public:
|
||||
using SlaveGroupProductionData = ReservoirCoupling::SlaveGroupProductionData<Scalar>;
|
||||
using SlaveGroupInjectionData = ReservoirCoupling::SlaveGroupInjectionData<Scalar>;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
using ProductionRates = ReservoirCoupling::ProductionRates<Scalar>;
|
||||
using WellGroupHelperType = WellGroupHelper<Scalar, IndexTraits>;
|
||||
|
||||
RescoupReceiveSlaveGroupData(WellGroupHelperType& wg_helper);
|
||||
|
||||
void receiveSlaveGroupData();
|
||||
private:
|
||||
|
||||
WellGroupHelperType& wg_helper_;
|
||||
ReservoirCouplingMaster<Scalar>& reservoir_coupling_master_;
|
||||
const Schedule& schedule_;
|
||||
const GroupState<Scalar>& group_state_;
|
||||
const PhaseUsageInfo<IndexTraits>& phase_usage_;
|
||||
const int report_step_idx_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
#endif // OPM_RESCOUP_RECEIVE_SLAVE_GROUP_DATA_HPP
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
Copyright 2025 Equinor ASA
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <opm/material/fluidsystems/BlackOilDefaultFluidSystemIndices.hpp>
|
||||
#include <opm/simulators/wells/rescoup/RescoupSendSlaveGroupData.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Constructor for the RescoupTargetCalculator class
|
||||
// -------------------------------------------------------
|
||||
template <class Scalar, class IndexTraits>
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
RescoupSendSlaveGroupData(WellGroupHelperType& wg_helper)
|
||||
: wg_helper_{wg_helper}
|
||||
, reservoir_coupling_slave_{wg_helper.reservoirCouplingSlave()}
|
||||
, schedule_{wg_helper.schedule()}
|
||||
, group_state_{wg_helper.groupState()}
|
||||
, phase_usage_{wg_helper.phaseUsage()}
|
||||
, report_step_idx_{wg_helper.reportStepIdx()}
|
||||
{
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// Public methods
|
||||
// ------------------
|
||||
|
||||
// The following data from the slave might be needed by the master process at the beginning of the
|
||||
// timestep depending on the situation:
|
||||
//
|
||||
// 1) *Production group potentials*: If both the master group and slave groups are production groups (and
|
||||
// possibly also injection groups), and the master group has GCONPROD item 10 set to "FORM", "POTN", or "0".
|
||||
// In this case, the master group needs the slave group potentials to calculate the guide rates.
|
||||
// NOTE: This could also apply to a parent of the master (and not just the master group
|
||||
// itself). For example, if a parent group of the master have GCONPROD item 10 set to "FORM", but the
|
||||
// master group itself has no guidrate.
|
||||
//
|
||||
// 2) *Production group surface rates*: If both the master group and slave groups are production
|
||||
// groups (and possibly also injection groups), and the master group has GCONPROD item 10 set to
|
||||
// OIL, GAS, WATER, or LIQ and the phase under group control is different from the phase in GCONPROD
|
||||
// item 10. In this case, the master group needs the slave group production rates to transform guide rate
|
||||
// targets to the phase under group control. NOTE: This should also apply to parents of the master group.
|
||||
//
|
||||
// 3) *Production group surface rate for phase P for reinjection*: If the master group is both an injector
|
||||
// and a producer group, and the slave group is a producer group (and possibly also an injection group), and
|
||||
// the master group has GCONINJE item 3 set to REIN for a phase P (set in GCONINJE item 2). In this case,
|
||||
// the master group needs the slave group reinjection surface rates to calculate the injection target for
|
||||
// phase P. NOTE: The reinjection surface rates are essentially the same as the (surface) production rates,
|
||||
// except that if the phase P is GAS, the reinjection surface rates also includes the gas import and
|
||||
// consumption rates. NOTE: This case could also apply to a parent of the master group. NOTE: If we send
|
||||
// the surface production rates, see (2) above, then we only need to send the reinjection rate for the
|
||||
// gas phase since the other phases are not affected by the gas import and consumption rates and can be
|
||||
// computed from the surface production rates.
|
||||
//
|
||||
// 4) *Production group reservoir voidage replacement rates*:
|
||||
// a) If the master group is both an injector and a producer group, and the slave group is a
|
||||
// producer group (and possibly also an injection group), and the master group has
|
||||
// GCONINJE item 10 set to NETV or VOID. In this case, the master group needs the slave group
|
||||
// reservoir voidage replacement rate to calculate the guide rate. NOTE: Also applies to parents of
|
||||
// the master group.
|
||||
// b) If the master group is both an injector and a producer group, and the slave group is a
|
||||
// producer group (and possibly also an injection group), and the master group has GCONINJE item 3
|
||||
// set to VREP. In this case, the master group needs the slave group reservoir voidage replacement
|
||||
// rate to calculate the group target for the phase under group control. NOTE: Also applies to
|
||||
// parents of the master group.
|
||||
//
|
||||
// 5) If the master group or any of its parents is a pressure maintenance group. It may need
|
||||
// a) slave group reservoir voidage production rate (GPMAINT item 2 = PROD)
|
||||
// b) slave group reservoir injection rates (GPMAINT item 2 = OINJ, WINJ, or GINJ)
|
||||
// c) slave group surface injection reates (GPMAINT item 2 = OINS, WINS, or GINS)
|
||||
//
|
||||
// 6) If the master group or any of its parents is a sales gas control group. It will need to know
|
||||
// a) slave group surface gas production rates
|
||||
// b) slave group surface gas injection rates
|
||||
// in order to calculate the sales gas targets.
|
||||
//
|
||||
// TODO: We could try to send only the necessary data to the master process, but that would require
|
||||
// the master process to first tell the slave group its data requirements at the current
|
||||
// report step. Which would be complicated by having to check for parent group control modes, etc.
|
||||
// Note also that even if the slave group is e.g. a pure injection group, it still might have
|
||||
// production wells, that contribute to production group at a higher level, the same goes for
|
||||
// the master group. So to be safe, we have to send all injection and production data available
|
||||
// i.e. group potentials, surface production rates, reservoir voidage rates, reinjection rate for
|
||||
// the gas phase, and surface and reservoir injection rates.
|
||||
//
|
||||
template <class Scalar, class IndexTraits>
|
||||
void
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
sendSlaveGroupDataToMaster()
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
if (rescoup_slave.getComm().rank() == 0) {
|
||||
this->sendSlaveGroupProductionDataToMaster_();
|
||||
this->sendSlaveGroupInjectionDataToMaster_();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Private methods for class RescoupSendSlaveGroupData
|
||||
// -------------------------------------------------------
|
||||
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
typename RescoupSendSlaveGroupData<Scalar, IndexTraits>::SlaveGroupInjectionData
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
collectSlaveGroupInjectionData_(std::size_t group_idx) const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
const auto& group_name = rescoup_slave.slaveGroupIdxToGroupName(group_idx);
|
||||
const Group& group = this->schedule_.getGroup(group_name, this->report_step_idx_);
|
||||
InjectionRates surface_rates = this->createInjectionRatesFromRateVector_(
|
||||
this->group_state_.injection_surface_rates(group.name())
|
||||
);
|
||||
InjectionRates reservoir_rates = this->createInjectionRatesFromRateVector_(
|
||||
this->group_state_.injection_reservoir_rates(group.name())
|
||||
);
|
||||
return SlaveGroupInjectionData{surface_rates, reservoir_rates};
|
||||
}
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
typename RescoupSendSlaveGroupData<Scalar, IndexTraits>::Potentials
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
collectSlaveGroupPotentials_(std::size_t group_idx) const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
const auto& group_name = rescoup_slave.slaveGroupIdxToGroupName(group_idx);
|
||||
Potentials potentials;
|
||||
const auto& gr_pot = this->group_state_.get_production_group_potential(group_name);
|
||||
potentials[ReservoirCoupling::Phase::Oil] = gr_pot.oil_rate;
|
||||
potentials[ReservoirCoupling::Phase::Gas] = gr_pot.gas_rate;
|
||||
potentials[ReservoirCoupling::Phase::Water] = gr_pot.water_rate;
|
||||
return potentials;
|
||||
}
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
Scalar
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
collectSlaveGroupReinjectionRateForGasPhase_(std::size_t group_idx) const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
const auto& group_name = rescoup_slave.slaveGroupIdxToGroupName(group_idx);
|
||||
const Group& group = this->schedule_.getGroup(group_name, this->report_step_idx_);
|
||||
const int gas_phase_idx = this->phase_usage_.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx);
|
||||
return this->group_state_.injection_rein_rates(group.name())[gas_phase_idx];
|
||||
}
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
typename RescoupSendSlaveGroupData<Scalar, IndexTraits>::ProductionRates
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
collectSlaveGroupSurfaceProductionRates_(std::size_t group_idx) const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
const auto& group_name = rescoup_slave.slaveGroupIdxToGroupName(group_idx);
|
||||
GuideRate::RateVector production_rates = this->wg_helper_.getProductionGroupRateVector(group_name);
|
||||
// NOTE: GuideRate::RateVector is a vector of doubles, so we need to convert it to Scalar
|
||||
// TODO: Fix GuideRate::RateVector to be a vector of Scalars instead of doubles
|
||||
return ProductionRates{production_rates};
|
||||
}
|
||||
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
typename RescoupSendSlaveGroupData<Scalar, IndexTraits>::SlaveGroupProductionData
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
collectSlaveGroupProductionData_(std::size_t group_idx) const
|
||||
{
|
||||
SlaveGroupProductionData production_data;
|
||||
// Potentials are used to calculate guide rates for master production groups
|
||||
production_data.potentials = this->collectSlaveGroupPotentials_(group_idx);
|
||||
// Production rates are used to transform guiderate targets for master production groups
|
||||
// from one phase to another
|
||||
production_data.surface_rates = this->collectSlaveGroupSurfaceProductionRates_(group_idx);
|
||||
production_data.voidage_rate = this->collectSlaveGroupVoidageRate_(group_idx);
|
||||
production_data.gas_reinjection_rate = this->collectSlaveGroupReinjectionRateForGasPhase_(group_idx);
|
||||
return production_data;
|
||||
}
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
Scalar
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
collectSlaveGroupVoidageRate_(std::size_t group_idx) const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
const auto& group_name = rescoup_slave.slaveGroupIdxToGroupName(group_idx);
|
||||
const Group& group = this->schedule_.getGroup(group_name, this->report_step_idx_);
|
||||
Scalar voidage_rate = this->group_state_.injection_vrep_rate(group.name());
|
||||
return voidage_rate;
|
||||
}
|
||||
|
||||
template<typename Scalar, typename IndexTraits>
|
||||
typename RescoupSendSlaveGroupData<Scalar, IndexTraits>::InjectionRates
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
createInjectionRatesFromRateVector_(const std::vector<Scalar>& rate_vector) const
|
||||
{
|
||||
const auto& pu = this->phase_usage_;
|
||||
Scalar oil_rate = 0.0;
|
||||
if (pu.phaseIsActive(IndexTraits::oilPhaseIdx)) {
|
||||
oil_rate = rate_vector[pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx)];
|
||||
}
|
||||
|
||||
Scalar gas_rate = 0.0;
|
||||
if (pu.phaseIsActive(IndexTraits::gasPhaseIdx)) {
|
||||
gas_rate = rate_vector[pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx)];
|
||||
}
|
||||
|
||||
Scalar water_rate = 0.0;
|
||||
if (pu.phaseIsActive(IndexTraits::waterPhaseIdx)) {
|
||||
water_rate = rate_vector[pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx)];
|
||||
}
|
||||
InjectionRates injection_rates;
|
||||
injection_rates[ReservoirCoupling::Phase::Oil] = oil_rate;
|
||||
injection_rates[ReservoirCoupling::Phase::Gas] = gas_rate;
|
||||
injection_rates[ReservoirCoupling::Phase::Water] = water_rate;
|
||||
return injection_rates;
|
||||
}
|
||||
|
||||
template <class Scalar, class IndexTraits>
|
||||
void
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
sendSlaveGroupProductionDataToMaster_() const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
auto num_slave_groups = rescoup_slave.numSlaveGroups();
|
||||
std::vector<SlaveGroupProductionData> production_data;
|
||||
for (std::size_t group_idx = 0; group_idx < num_slave_groups; ++group_idx) {
|
||||
// NOTE: We have to send production data even if the slave group is not a production group,
|
||||
// i.e. if it is a pure injection group or has GroupType NONE
|
||||
production_data.emplace_back(this->collectSlaveGroupProductionData_(group_idx));
|
||||
}
|
||||
rescoup_slave.sendProductionDataToMaster(production_data);
|
||||
}
|
||||
|
||||
template <class Scalar, class IndexTraits>
|
||||
void
|
||||
RescoupSendSlaveGroupData<Scalar, IndexTraits>::
|
||||
sendSlaveGroupInjectionDataToMaster_() const
|
||||
{
|
||||
auto& rescoup_slave = this->reservoir_coupling_slave_;
|
||||
auto num_slave_groups = rescoup_slave.numSlaveGroups();
|
||||
std::vector<SlaveGroupInjectionData> injection_data;
|
||||
for (std::size_t group_idx = 0; group_idx < num_slave_groups; ++group_idx) {
|
||||
// NOTE: We would like to only send injection data only if the master group is an injector,
|
||||
// but we cannot do that because even if the master group is a pure producer, one of its parent
|
||||
// groups could be an injector that needs the injection data.
|
||||
//if (rescoup_slave.masterGroupIsInjector(group_idx)) {//
|
||||
// NOTE: We have to send injection data even if the slave group is not an injection group,
|
||||
// i.e. if it is a pure production group or has GroupType NONE
|
||||
injection_data.emplace_back(this->collectSlaveGroupInjectionData_(group_idx));
|
||||
}
|
||||
rescoup_slave.sendInjectionDataToMaster(injection_data);
|
||||
}
|
||||
|
||||
|
||||
template class RescoupSendSlaveGroupData<double, BlackOilDefaultFluidSystemIndices>;
|
||||
|
||||
#if FLOW_INSTANTIATE_FLOAT
|
||||
template class RescoupSendSlaveGroupData<float, BlackOilDefaultFluidSystemIndices>;
|
||||
#endif
|
||||
|
||||
} // namespace Opm
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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_RESCOUP_SEND_SLAVE_GROUP_DATA_HPP
|
||||
#define OPM_RESCOUP_SEND_SLAVE_GROUP_DATA_HPP
|
||||
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/wells/GuideRateHandler.hpp>
|
||||
#include <opm/simulators/wells/GroupState.hpp>
|
||||
#include <opm/simulators/wells/WellGroupHelper.hpp>
|
||||
#include <opm/simulators/utils/DeferredLogger.hpp>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
template<class Scalar, class IndexTraits>
|
||||
class RescoupSendSlaveGroupData {
|
||||
public:
|
||||
using SlaveGroupProductionData = ReservoirCoupling::SlaveGroupProductionData<Scalar>;
|
||||
using SlaveGroupInjectionData = ReservoirCoupling::SlaveGroupInjectionData<Scalar>;
|
||||
using Potentials = ReservoirCoupling::Potentials<Scalar>;
|
||||
using ProductionRates = ReservoirCoupling::ProductionRates<Scalar>;
|
||||
using InjectionRates = ReservoirCoupling::InjectionRates<Scalar>;
|
||||
using WellGroupHelperType = WellGroupHelper<Scalar, IndexTraits>;
|
||||
|
||||
RescoupSendSlaveGroupData(WellGroupHelperType& wg_helper);
|
||||
|
||||
void sendSlaveGroupDataToMaster();
|
||||
private:
|
||||
|
||||
Potentials collectSlaveGroupPotentials_(std::size_t group_idx) const;
|
||||
SlaveGroupInjectionData collectSlaveGroupInjectionData_(std::size_t group_idx) const;
|
||||
SlaveGroupProductionData collectSlaveGroupProductionData_(std::size_t group_idx) const;
|
||||
Scalar collectSlaveGroupReinjectionRateForGasPhase_(std::size_t group_idx) const;
|
||||
ProductionRates collectSlaveGroupSurfaceProductionRates_(std::size_t group_idx) const;
|
||||
Scalar collectSlaveGroupVoidageRate_(std::size_t group_idx) const;
|
||||
InjectionRates createInjectionRatesFromRateVector_(const std::vector<Scalar>& rate_vector) const;
|
||||
void sendSlaveGroupProductionDataToMaster_() const;
|
||||
void sendSlaveGroupInjectionDataToMaster_() const;
|
||||
|
||||
const WellGroupHelperType& wg_helper_;
|
||||
ReservoirCouplingSlave<Scalar>& reservoir_coupling_slave_;
|
||||
const Schedule& schedule_;
|
||||
const GroupState<Scalar>& group_state_;
|
||||
const PhaseUsageInfo<IndexTraits>& phase_usage_;
|
||||
const int report_step_idx_;
|
||||
};
|
||||
|
||||
} // namespace Opm
|
||||
|
||||
#endif // OPM_RESCOUP_SEND_SLAVE_GROUP_DATA_HPP
|
||||
@@ -35,9 +35,9 @@
|
||||
#include <opm/simulators/flow/FlowGenericVanguard.hpp>
|
||||
#include <opm/simulators/flow/Main.hpp>
|
||||
#include <opm/simulators/flow/TTagFlowProblemTPFA.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCoupling.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingMaster.hpp>
|
||||
#include <opm/simulators/flow/rescoup/ReservoirCouplingSlave.hpp>
|
||||
#include <opm/simulators/utils/DeferredLogger.hpp>
|
||||
#include <opm/simulators/flow/BlackoilModel.hpp>
|
||||
#include <opm/simulators/flow/FlowProblemBlackoil.hpp>
|
||||
@@ -153,6 +153,8 @@ struct SimulatorFixture
|
||||
{
|
||||
rc_master_.addSlaveName("RES-1");
|
||||
rc_master_.addSlaveName("RES-2");
|
||||
rc_master_.initTimeStepping();
|
||||
rc_master_.initStartOfReportStep(0);
|
||||
rc_master_.resizeSlaveActivationDates(2);
|
||||
rc_master_.resizeNextReportDates(2);
|
||||
rc_master_.resizeSlaveStartDates(2);
|
||||
@@ -161,7 +163,7 @@ struct SimulatorFixture
|
||||
}
|
||||
|
||||
void checkEq(double a, double b) const { BOOST_CHECK_CLOSE(a, b, 1e-16); }
|
||||
const Opm::ReservoirCouplingMaster& getRcMaster() const { return rc_master_; }
|
||||
const Opm::ReservoirCouplingMaster<double>& getRcMaster() const { return rc_master_; }
|
||||
double getStartDate() const { return start_date_; }
|
||||
double runChopSubStep(double report_time_step_size) const {
|
||||
return rc_master_.maybeChopSubStep(report_time_step_size, /*elapsed_time=*/0.0);
|
||||
@@ -177,7 +179,7 @@ private:
|
||||
OpmSimulatorTestCase simulator_wrapper_;
|
||||
Simulator *simulator_;
|
||||
Opm::Schedule& schedule_;
|
||||
Opm::ReservoirCouplingMaster rc_master_;
|
||||
Opm::ReservoirCouplingMaster<double> rc_master_;
|
||||
double start_date_;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user