CollectDataOnIORank: add gather handle for LGR block-summary values

Add PackUnPackLgrBlockData, the parallel-gather handle for the tuple-keyed
per-LGR-cell block-summary map (keyword, level, level-local Cartesian),
mirroring opm-common's Summary::DynamicSimulatorState::LgrBlockValues, plus
the globalLgrBlockData_ accumulator and accessor.

PackUnPackBlockData (global, pair-keyed) and the new handle now share one
std::apply-based pack/unpack body; the global block serialization is
byte-identical. The handle is not yet wired into collect(); covered by a
round-trip + disjoint-merge unit test.
This commit is contained in:
arturcastiel committed 2026-06-16 13:58:24 +00:00
1 parent a80adbf263
commit f6dd4ad9a3
4 files changed
+170 -19

No files matched your search

+1
View File
@@ -496,6 +496,7 @@ list (APPEND TEST_SOURCE_FILES
tests/test_interregflows.cpp
tests/test_invert.cpp
tests/test_keyword_validator.cpp
tests/test_LgrBlockData.cpp
tests/test_linearleastsquares.cpp
tests/test_LogOutputHelper.cpp
tests/test_milu.cpp
@@ -41,6 +41,7 @@
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
@@ -90,6 +91,9 @@ public:
const std::map<std::pair<std::string, int>, double>& globalBlockData() const
{ return globalBlockData_; }
const std::map<std::tuple<std::string, int, int>, double>& globalLgrBlockData() const
{ return globalLgrBlockData_; }
const data::Solution& globalCellData() const
{ return globalCellData_; }
@@ -156,6 +160,7 @@ protected:
std::vector<int> globalRanks_;
data::Solution globalCellData_;
std::map<std::pair<std::string, int>, double> globalBlockData_;
std::map<std::tuple<std::string, int, int>, double> globalLgrBlockData_;
data::Wells globalWellData_;
data::WellBlockAveragePressures globalWBPData_;
data::GroupAndNetworkValues globalGroupAndNetworkData_;
@@ -37,6 +37,8 @@
#include <cassert>
#include <stdexcept>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
namespace {
@@ -461,6 +463,37 @@ public:
{ this->globalGroupAndNetworkData_.read(buffer); }
};
// Shared serialisation for the block-summary gather handles. The global B* map is
// keyed by std::pair<keyword, cartesian-index>; the LGR LB* map by
// std::tuple<keyword, level, level-local-cartesian-index>. std::apply serialises
// either key shape: write the map size, then every key field (pair or tuple) followed
// by the value -- one body, thin per-key callers (mirrors the producer-side
// makeExtractor de-duplication).
template <class KeyedMap, class MessageBufferType>
void packKeyedBlockMap(const KeyedMap& localData, MessageBufferType& buffer)
{
const unsigned int size = localData.size();
buffer.write(size);
for (const auto& [key, value] : localData) {
std::apply([&buffer](const auto&... field) { (buffer.write(field), ...); }, key);
buffer.write(value);
}
}
template <class KeyedMap, class MessageBufferType>
void unpackKeyedBlockMap(KeyedMap& globalData, MessageBufferType& buffer)
{
unsigned int size = 0;
buffer.read(size);
for (unsigned int i = 0; i < size; ++i) {
typename KeyedMap::key_type key{};
std::apply([&buffer](auto&... field) { (buffer.read(field), ...); }, key);
typename KeyedMap::mapped_type value{};
buffer.read(value);
globalData[key] = value;
}
}
class PackUnPackBlockData : public P2PCommunicatorType::DataHandleInterface
{
const std::map<std::pair<std::string, int>, double>& localBlockData_;
@@ -490,32 +523,53 @@ public:
if (link != 0)
throw std::logic_error("link in method pack is not 0 as expected");
// write all block data
unsigned int size = localBlockData_.size();
buffer.write(size);
for (const auto& map : localBlockData_) {
buffer.write(map.first.first);
buffer.write(map.first.second);
buffer.write(map.second);
}
packKeyedBlockMap(localBlockData_, buffer);
}
// unpack all data associated with link
void unpack(int /*link*/, MessageBufferType& buffer)
{
// read all block data
unsigned int size = 0;
buffer.read(size);
for (std::size_t i = 0; i < size; ++i) {
std::string name;
int idx;
double data;
buffer.read(name);
buffer.read(idx);
buffer.read(data);
globalBlockValues_[std::make_pair(name, idx)] = data;
unpackKeyedBlockMap(globalBlockValues_, buffer);
}
};
class PackUnPackLgrBlockData : public P2PCommunicatorType::DataHandleInterface
{
const std::map<std::tuple<std::string, int, int>, double>& localLgrBlockData_;
std::map<std::tuple<std::string, int, int>, double>& globalLgrBlockValues_;
public:
PackUnPackLgrBlockData(const std::map<std::tuple<std::string, int, int>, double>& localLgrBlockData,
std::map<std::tuple<std::string, int, int>, double>& globalLgrBlockValues,
bool isIORank)
: localLgrBlockData_(localLgrBlockData)
, globalLgrBlockValues_(globalLgrBlockValues)
{
if (isIORank) {
MessageBufferType buffer;
pack(0, buffer);
// pass a dummyLink to satisfy virtual class
int dummyLink = -1;
unpack(dummyLink, buffer);
}
}
// pack all data associated with link
void pack(int link, MessageBufferType& buffer)
{
// we should only get one link
if (link != 0)
throw std::logic_error("link in method pack is not 0 as expected");
packKeyedBlockMap(localLgrBlockData_, buffer);
}
// unpack all data associated with link
void unpack(int /*link*/, MessageBufferType& buffer)
{
unpackKeyedBlockMap(globalLgrBlockValues_, buffer);
}
};
class PackUnPackWBPData : public P2PCommunicatorType::DataHandleInterface
+91
View File
@@ -0,0 +1,91 @@
/*
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>
#define BOOST_TEST_MODULE LgrBlockDataGather
#include <boost/test/unit_test.hpp>
#include <opm/simulators/flow/CollectDataOnIORank_impl.hpp>
#include <map>
#include <string>
#include <tuple>
namespace {
// Key = (summary keyword, LGR level, level-local linearised Cartesian index),
// matching opm-common's Summary::DynamicSimulatorState::LgrBlockValues.
using Key = std::tuple<std::string, int, int>;
using LgrBlockMap = std::map<Key, double>;
}
// Opm::PackUnPackLgrBlockData is the gather handle for the tuple-keyed LB* per-LGR-cell
// summary map. Its IO-rank constructor self-packs the local map into a message buffer and
// unpacks it back into the supplied global map -- which is exactly how the IO rank folds in
// each rank's contribution during collect(). Constructing it with isIORank == true therefore
// exercises the pack -> unpack round-trip, and constructing it twice into the same global map
// exercises the disjoint union of two ranks' contributions -- all without needing MPI.
BOOST_AUTO_TEST_CASE(RoundTripPreservesEntries)
{
const LgrBlockMap local = {
{ Key{"LBPR", 1, 0}, 250.0 },
{ Key{"LBPR", 1, 3}, 251.5 },
{ Key{"LBOSAT", 2, 0}, 0.30 },
};
LgrBlockMap gathered;
Opm::PackUnPackLgrBlockData{ local, gathered, /*isIORank=*/true };
BOOST_CHECK(gathered == local);
}
BOOST_AUTO_TEST_CASE(DisjointMergeAccumulatesBothRanks)
{
const LgrBlockMap rank0 = {
{ Key{"LBPR", 1, 0}, 250.0 },
{ Key{"LBPR", 1, 3}, 251.5 },
};
const LgrBlockMap rank1 = {
{ Key{"LBPR", 1, 7}, 252.0 },
{ Key{"LBOSAT", 2, 0}, 0.30 },
};
LgrBlockMap gathered;
Opm::PackUnPackLgrBlockData{ rank0, gathered, /*isIORank=*/true };
Opm::PackUnPackLgrBlockData{ rank1, gathered, /*isIORank=*/true };
LgrBlockMap expected = rank0;
expected.insert(rank1.begin(), rank1.end());
BOOST_CHECK(gathered == expected);
BOOST_CHECK_EQUAL(gathered.size(), 4u);
BOOST_CHECK_EQUAL(gathered.at(Key{"LBPR", 1, 0}), 250.0);
BOOST_CHECK_EQUAL(gathered.at(Key{"LBPR", 1, 7}), 252.0);
BOOST_CHECK_EQUAL(gathered.at(Key{"LBOSAT", 2, 0}), 0.30);
}
BOOST_AUTO_TEST_CASE(EmptyMapGathersNothing)
{
const LgrBlockMap empty;
LgrBlockMap gathered;
Opm::PackUnPackLgrBlockData{ empty, gathered, /*isIORank=*/true };
BOOST_CHECK(gathered.empty());
}