Added a parallel aware logger for switching wells.

It will collect all the switches. Afterwards they are collect on
the root process and logged there.

This commit includes a small test program.
This commit is contained in:
Markus Blatt
2016-09-23 11:50:11 +02:00
parent 02e8211067
commit 07318edfa1
11 changed files with 431 additions and 17 deletions

View File

@@ -66,6 +66,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/polymer/fullyimplicit/PolymerPropsAd.cpp
opm/simulators/SimulatorCompressibleTwophase.cpp
opm/simulators/SimulatorIncompTwophase.cpp
opm/simulators/WellSwitchingLogger.cpp
)
@@ -87,6 +88,7 @@ list (APPEND TEST_SOURCE_FILES
tests/test_solventprops_ad.cpp
tests/test_multisegmentwells.cpp
# tests/test_thresholdpressure.cpp
tests/test_wellswitchlogger.cpp
)
list (APPEND TEST_DATA_FILES
@@ -240,5 +242,6 @@ list (APPEND PUBLIC_HEADER_FILES
opm/polymer/fullyimplicit/WellStateFullyImplicitBlackoilPolymer.hpp
opm/simulators/SimulatorCompressibleTwophase.hpp
opm/simulators/SimulatorIncompTwophase.hpp
opm/simulators/WellSwitchingLogger.hpp
)

View File

@@ -150,12 +150,19 @@ namespace Opm
asImpl().setupOutputWriter();
asImpl().setupLinearSolver();
asImpl().createSimulator();
// Run.
return asImpl().runSimulator();
}
catch (const std::exception &e) {
std::cerr << "Program threw an exception: " << e.what() << "\n";
std::ostringstream message;
message << "Program threw an exception: " << e.what();
if( output_cout_ )
{
OpmLog::error(message.str());
}
return EXIT_FAILURE;
}
@@ -389,7 +396,6 @@ namespace Opm
logFileStream << baseName << ".PRT";
debugFileStream << "." << baseName << ".DEBUG";
}
if ( must_distribute_ && mpi_rank_ != 0 )
{
// Added rank to log file for non-zero ranks.
// This prevents message loss.

View File

@@ -142,7 +142,7 @@ namespace Opm {
MultisegmentWells::
MultisegmentWells(const Wells* wells_arg,
const std::vector< const Well* >& wells_ecl,
const int time_step)
const int time_step, const Communication& comm)
: wells_multisegment_( createMSWellVector(wells_arg, wells_ecl, time_step) )
, wops_ms_(wells_multisegment_)
, num_phases_(wells_arg ? wells_arg->number_of_phases : 0)
@@ -158,6 +158,7 @@ namespace Opm {
, segment_comp_surf_volume_current_(num_phases_, ADB::null())
, segment_mass_flow_rates_(ADB::null())
, segment_viscosities_(ADB::null())
, comm_(comm)
{
const int nw = wells_multisegment_.size();
int nperf_total = 0;

View File

@@ -22,6 +22,8 @@
#ifndef OPM_MULTISEGMENTWELLS_HEADER_INCLUDED
#define OPM_MULTISEGMENTWELLS_HEADER_INCLUDED
#include <dune/common/parallel/mpihelper.hh>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <Eigen/Eigen>
#include <Eigen/Sparse>
@@ -41,6 +43,7 @@
#include <opm/autodiff/WellMultiSegment.hpp>
#include <opm/autodiff/WellDensitySegmented.hpp>
#include <opm/simulators/WellSwitchingLogger.hpp>
@@ -78,13 +81,17 @@ namespace Opm {
Eigen::Dynamic,
Eigen::Dynamic,
Eigen::RowMajor>;
using Communication =
Dune::CollectiveCommunication<typename Dune::MPIHelper
::MPICommunicator>;
// --------- Public methods ---------
// TODO: using a vector of WellMultiSegmentConstPtr for now
// TODO: it should use const Wells or something else later.
MultisegmentWells(const Wells* wells_arg,
const std::vector< const Well* >& wells_ecl,
const int time_step);
const int time_step,
const Communication& comm=Communication());
std::vector<WellMultiSegmentConstPtr> createMSWellVector(const Wells* wells_arg,
const std::vector< const Well* >& wells_ecl,
@@ -305,6 +312,7 @@ namespace Opm {
Vector well_perforation_densities_;
Vector well_perforation_pressure_diffs_;
Communication comm_;
};
} // namespace Opm

View File

@@ -824,6 +824,8 @@ namespace Opm
MultisegmentWells::
updateWellControls(WellState& xw) const
{
wellhelpers::WellSwitchingLogger logger(comm_);
if( msWells().empty() ) return ;
std::string modestring[4] = { "BHP", "THP", "RESERVOIR_RATE", "SURFACE_RATE" };
@@ -860,9 +862,9 @@ namespace Opm
if (ctrl_index != nwc) {
// Constraint number ctrl_index was broken, switch to it.
// Each well is only active on one process. Therefore we always print the sitch info.
std::cout << "Switching control mode for well " << msWells()[w]->name()
<< " from " << modestring[well_controls_iget_type(wc, current)]
<< " to " << modestring[well_controls_iget_type(wc, ctrl_index)] << std::endl;
logger.wellSwitched(msWells()[w]->name(),
well_controls_iget_type(wc, current),
well_controls_iget_type(wc, ctrl_index));
xw.currentControls()[w] = ctrl_index;
current = xw.currentControls()[w];
}

View File

@@ -22,6 +22,8 @@
#ifndef OPM_STANDARDWELLS_HEADER_INCLUDED
#define OPM_STANDARDWELLS_HEADER_INCLUDED
#include <dune/common/parallel/mpihelper.hh>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
@@ -39,7 +41,7 @@
#include <opm/autodiff/AutoDiffBlock.hpp>
#include <opm/autodiff/AutoDiffHelpers.hpp>
#include <opm/autodiff/BlackoilPropsAdInterface.hpp>
#include <opm/simulators/WellSwitchingLogger.hpp>
namespace Opm {
@@ -57,6 +59,9 @@ namespace Opm {
// --------- Types ---------
using ADB = AutoDiffBlock<double>;
using Vector = ADB::V;
using Communication =
Dune::CollectiveCommunication<typename Dune::MPIHelper
::MPICommunicator>;
// copied from BlackoilModelBase
// should put to somewhere better
@@ -65,7 +70,8 @@ namespace Opm {
Eigen::Dynamic,
Eigen::RowMajor>;
// --------- Public methods ---------
explicit StandardWells(const Wells* wells_arg);
explicit StandardWells(const Wells* wells_arg,
const Communication& comm=Communication());
void init(const BlackoilPropsAdInterface* fluid_arg,
const std::vector<bool>* active_arg,
@@ -205,6 +211,8 @@ namespace Opm {
bool store_well_perforation_fluxes_;
Vector well_perforation_fluxes_;
Communication comm_;
// protected methods
template <class SolutionState, class WellState>
void computePropertiesForWellConnectionPressures(const SolutionState& state,

View File

@@ -71,7 +71,8 @@ namespace Opm
StandardWells::StandardWells(const Wells* wells_arg)
StandardWells::StandardWells(const Wells* wells_arg,
const Communication& comm)
: wells_active_(wells_arg!=nullptr)
, wells_(wells_arg)
, wops_(wells_arg)
@@ -82,6 +83,7 @@ namespace Opm
, well_perforation_densities_(Vector())
, well_perforation_pressure_diffs_(Vector())
, store_well_perforation_fluxes_(false)
, comm_(comm)
{
}
@@ -706,9 +708,10 @@ namespace Opm
StandardWells::
updateWellControls(WellState& xw) const
{
wellhelpers::WellSwitchingLogger logger(comm_);
if( !localWellsActive() ) return ;
std::string modestring[4] = { "BHP", "THP", "RESERVOIR_RATE", "SURFACE_RATE" };
// Find, for each well, if any constraints are broken. If so,
// switch control to first broken constraint.
const int np = wells().number_of_phases;
@@ -743,11 +746,9 @@ namespace Opm
// Constraint number ctrl_index was broken, switch to it.
// We disregard terminal_ouput here as with it only messages
// for wells on one process will be printed.
std::ostringstream ss;
ss << "Switching control mode for well " << wells().name[w]
<< " from " << modestring[well_controls_iget_type(wc, current)]
<< " to " << modestring[well_controls_iget_type(wc, ctrl_index)] << std::endl;
OpmLog::info(ss.str());
logger.wellSwitched(wells().name[w],
well_controls_iget_type(wc, current),
well_controls_iget_type(wc, ctrl_index));
xw.currentControls()[w] = ctrl_index;
current = xw.currentControls()[w];
}

View File

@@ -35,6 +35,8 @@ namespace Opm {
namespace wellhelpers
{
inline
double rateToCompare(const std::vector<double>& well_phase_flow_rate,
const int well,

View File

@@ -0,0 +1,206 @@
/*
Copyright 2016 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 Statoil AS
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/>.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <opm/simulators/WellSwitchingLogger.hpp>
namespace Opm
{
namespace wellhelpers
{
#if HAVE_MPI
int WellSwitchingLogger::calculateMessageSize(std::vector<int>& well_name_length)
{
// Each process will send a message to the root process with
// the following data:
// total number of switches, for each switch the length of the
// well name, for each switch the well name and the two controls.
well_name_length.reserve(switchMap_.size());
for(const auto& switchEntry : switchMap_)
{
int length = switchEntry.first.size() +1; //we write an additional \0
well_name_length.push_back(length);
}
// compute the message size
int message_size = 0;
int increment = 0;
// number of switches
MPI_Pack_size(1, MPI_INT, MPI_COMM_WORLD, &message_size);
message_size += increment;
// const char* length include delimiter for each switch
MPI_Pack_size(switchMap_.size(), MPI_INT, MPI_COMM_WORLD, &increment);
message_size += increment;
// for each well the name + two controls in one write
auto length = well_name_length.begin();
for(const auto& switchEntry : switchMap_)
{
// well name
MPI_Pack_size(*length, MPI_CHAR, MPI_COMM_WORLD, &increment);
message_size += increment;
// controls
MPI_Pack_size(2, MPI_CHAR, MPI_COMM_WORLD, &increment);
message_size += increment;
++length;
}
return message_size;
}
void WellSwitchingLogger::packData(std::vector<int>& well_name_length,
std::vector<char>& buffer)
{
// Pack the data
// number of switches
int offset = 0;
int no_switches = switchMap_.size();
MPI_Pack(&no_switches, 1, MPI_INT, buffer.data(), buffer.size(),
&offset, MPI_COMM_WORLD);
MPI_Pack(well_name_length.data(), well_name_length.size(),
MPI_INT, buffer.data(), buffer.size(),
&offset, MPI_COMM_WORLD);
for(const auto& switchEntry : switchMap_)
{
// well name
auto& well_name = switchEntry.first;
MPI_Pack(const_cast<char*>(well_name.c_str()), well_name.size()+1,
MPI_CHAR, buffer.data(), buffer.size(),
&offset, MPI_COMM_WORLD);
// controls
MPI_Pack(const_cast<char*>(switchEntry.second.data()), 2 , MPI_CHAR,
buffer.data(), buffer.size(), &offset, MPI_COMM_WORLD);
}
}
void WellSwitchingLogger::unpackDataAndLog(std::vector<char>& recv_buffer,
const std::vector<int>& displ)
{
for(int p=1; p < cc_.size(); ++p)
{
int offset = displ[p];
int no_switches = 0;
MPI_Unpack(recv_buffer.data(), recv_buffer.size(), &offset,
&no_switches, 1, MPI_INT, MPI_COMM_WORLD);
if ( no_switches == 0 )
{
continue;
}
std::vector<int> well_name_length(no_switches);
MPI_Unpack(recv_buffer.data(), recv_buffer.size(), &offset,
well_name_length.data(), well_name_length.size(),
MPI_INT, MPI_COMM_WORLD);
for ( int i = 0; i < no_switches; ++i )
{
char well_name[well_name_length[i]] = {};
MPI_Unpack(recv_buffer.data(), recv_buffer.size(), &offset,
well_name, well_name_length[i], MPI_CHAR,
MPI_COMM_WORLD);
std::array<char,2> fromto{{}};
MPI_Unpack(recv_buffer.data(), recv_buffer.size(), &offset,
fromto.data(), 2, MPI_CHAR, MPI_COMM_WORLD);
logSwitch(well_name, fromto, p);
}
}
}
void WellSwitchingLogger::logSwitch(const char* name, std::array<char,2> fromto,
int rank)
{
std::ostringstream ss;
ss << "Switching control mode for well " << name
<< " from " << modestring[WellControlType(fromto[0])]
<< " to " << modestring[WellControlType(fromto[1])]
<< " on rank " << rank << std::endl;
OpmLog::info(ss.str());
}
#endif
void WellSwitchingLogger::gatherDataAndLog()
{
#if HAVE_MPI
if(cc_.size() == 1)
{
return;
}
std::vector<int> message_sizes;
std::vector<int> well_name_length;
int message_size = calculateMessageSize(well_name_length);
if ( cc_.rank() == 0 ){
for(const auto& entry : switchMap_)
{
logSwitch(entry.first.c_str(), entry.second,0);
}
message_sizes.resize(cc_.size());
}
MPI_Gather(&message_size, 1, MPI_INT, message_sizes.data(),
1, MPI_INT, 0, MPI_COMM_WORLD);
std::vector<char> buffer(message_size);
packData(well_name_length, buffer);
std::vector<int> displ;
if ( cc_.rank() == 0){
// last entry will be total size of
displ.resize(cc_.size() + 1, 0);
std::partial_sum(message_sizes.begin(), message_sizes.end(),
displ.begin()+1);
}
std::vector<char> recv_buffer;
if ( cc_.rank() == 0 ){
recv_buffer.resize(displ[cc_.size()]);
}
MPI_Gatherv(buffer.data(), buffer.size(), MPI_PACKED,
recv_buffer.data(), message_sizes.data(),
displ.data(), MPI_PACKED, 0, MPI_COMM_WORLD);
if ( cc_.rank() == 0 )
{
unpackDataAndLog(recv_buffer, displ);
}
#endif
}
WellSwitchingLogger::~WellSwitchingLogger()
{
gatherDataAndLog();
}
} // end namespace wellhelpers
} // end namespace Opm

View File

@@ -0,0 +1,112 @@
/*
Copyright 2016 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 Statoil AS
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_WELLSWITCHINGLOGGER_HEADER_INCLUDED
#define OPM_WELLSWITCHINGLOGGER_HEADER_INCLUDED
#include <map>
#include <string>
#include <vector>
#include <dune/common/parallel/mpihelper.hh>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/core/well_controls.h>
namespace Opm
{
namespace wellhelpers
{
/// \brief Utility class to handle the log messages about well switching.
///
/// In parallel all the messages will be send to a root processor
/// and logged there.
class WellSwitchingLogger
{
typedef std::map<std::string, std::array<char,2> > SwitchMap;
public:
/// \brief The type of the collective communication used.
typedef Dune::CollectiveCommunication<typename Dune::MPIHelper::MPICommunicator>
Communication;
/// \brief Constructor.
///
/// \param cc The collective communication to use.
explicit WellSwitchingLogger(const Communication& cc=Communication())
: cc_(cc)
{}
/// \brief Log that a well switched.
/// \param name The name of the well.
/// \param from The control of the well before the switch.
/// \param to The control of the well after the switch.
void wellSwitched(std::string name,
WellControlType from,
WellControlType to)
{
if( cc_.size() > 1 )
{
using Pair = typename SwitchMap::value_type;
switchMap_.insert(Pair(name, {char(from), char(to)}));
}
else
{
std::ostringstream ss;
ss << "Switching control mode for well " << name
<< " from " << modestring[from]
<< " to " << modestring[to] << std::endl;
OpmLog::info(ss.str());
}
}
/// \brief Destructor send does the actual logging.
~WellSwitchingLogger();
private:
#if HAVE_MPI
void unpackDataAndLog(std::vector<char>& recv_buffer,
const std::vector<int>& displ);
void packData(std::vector<int>& well_name_length,
std::vector<char>& buffer);
int calculateMessageSize(std::vector<int>& well_name_length);
void logSwitch(const char* name, std::array<char,2> fromto,
int rank);
#endif // HAVE_MPI
void gatherDataAndLog();
/// \brief A map containing the local switches
SwitchMap switchMap_;
/// \brief Collective communication object.
Communication cc_;
/// \brief The strings for printing.
const std::string modestring[4] = { "BHP", "THP", "RESERVOIR_RATE", "SURFACE_RATE" };
};
} // end namespace wellhelpers
} // end namespace Opm
#endif

View File

@@ -0,0 +1,65 @@
#include <config.h>
#include <dune/common/version.hh>
#if HAVE_DYNAMIC_BOOST_TEST
#define BOOST_TEST_DYN_LINK
#endif
#define BOOST_TEST_MODULE DistributedCpGridTests
#define BOOST_TEST_NO_MAIN
#include <boost/test/unit_test.hpp>
#include <opm/simulators/WellSwitchingLogger.hpp>
#if HAVE_MPI
class MPIError {
public:
/** @brief Constructor. */
MPIError(std::string s, int e) : errorstring(s), errorcode(e){}
/** @brief The error string. */
std::string errorstring;
/** @brief The mpi error code. */
int errorcode;
};
void MPI_err_handler(MPI_Comm *, int *err_code, ...){
char *err_string=new char[MPI_MAX_ERROR_STRING];
int err_length;
MPI_Error_string(*err_code, err_string, &err_length);
std::string s(err_string, err_length);
std::cerr << "An MPI Error ocurred:"<<std::endl<<s<<std::endl;
delete[] err_string;
throw MPIError(s, *err_code);
}
#endif
bool
init_unit_test_func()
{
return true;
}
BOOST_AUTO_TEST_CASE(wellswitchlog)
{
auto cc = Dune::MPIHelper::getCollectiveCommunication();
Opm::wellhelpers::WellSwitchingLogger logger(cc);
std::ostringstream name;
name <<"Well on rank "<<cc.rank()<<std::flush;
logger.wellSwitched(name.str(), BHP, THP);
}
int main(int argc, char** argv)
{
Dune::MPIHelper::instance(argc, argv);
#if HAVE_MPI
// register a throwing error handler to allow for
// debugging with "catch throw" in gdb
MPI_Errhandler handler;
MPI_Comm_create_errhandler(MPI_err_handler, &handler);
MPI_Comm_set_errhandler(MPI_COMM_WORLD, handler);
#endif
boost::unit_test::unit_test_main(&init_unit_test_func,
argc, argv);
}