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:
Håkon Hægland committed 2025-11-05 12:38:09 +01:00
1 parent d5e3ceff0b
commit 35fcc75cfa
32 files changed
+2195 -679

No files matched your search

+19 -9
View File
@@ -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
+8 -8
View File
@@ -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(
&timestep,
/*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_;
@@ -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>
@@ -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
@@ -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
@@ -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
@@ -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(&timestep, /*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
@@ -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
@@ -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
@@ -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(
&timestep,
/*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 -6
View File
@@ -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,
+3 -62
View File
@@ -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
+12 -13
View File
@@ -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
};
+60
View File
@@ -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
+7 -5
View File
@@ -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_;
};