From f6dd4ad9a39eb1651bc643e25591b7ad2ed4e26a Mon Sep 17 00:00:00 2001 From: arturcastiel Date: Tue, 16 Jun 2026 08:37:19 +0000 Subject: [PATCH] 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. --- CMakeLists_files.cmake | 1 + opm/simulators/flow/CollectDataOnIORank.hpp | 5 + .../flow/CollectDataOnIORank_impl.hpp | 92 +++++++++++++++---- tests/test_LgrBlockData.cpp | 91 ++++++++++++++++++ 4 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 tests/test_LgrBlockData.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 2bf9d06d29..8ff3122806 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -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 diff --git a/opm/simulators/flow/CollectDataOnIORank.hpp b/opm/simulators/flow/CollectDataOnIORank.hpp index c980162259..304ecfe330 100644 --- a/opm/simulators/flow/CollectDataOnIORank.hpp +++ b/opm/simulators/flow/CollectDataOnIORank.hpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,9 @@ public: const std::map, double>& globalBlockData() const { return globalBlockData_; } + const std::map, double>& globalLgrBlockData() const + { return globalLgrBlockData_; } + const data::Solution& globalCellData() const { return globalCellData_; } @@ -156,6 +160,7 @@ protected: std::vector globalRanks_; data::Solution globalCellData_; std::map, double> globalBlockData_; + std::map, double> globalLgrBlockData_; data::Wells globalWellData_; data::WellBlockAveragePressures globalWBPData_; data::GroupAndNetworkValues globalGroupAndNetworkData_; diff --git a/opm/simulators/flow/CollectDataOnIORank_impl.hpp b/opm/simulators/flow/CollectDataOnIORank_impl.hpp index 1e61c139ab..e12dc1d82b 100644 --- a/opm/simulators/flow/CollectDataOnIORank_impl.hpp +++ b/opm/simulators/flow/CollectDataOnIORank_impl.hpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include 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; the LGR LB* map by +// std::tuple. 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 +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 +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, 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, double>& localLgrBlockData_; + std::map, double>& globalLgrBlockValues_; + +public: + PackUnPackLgrBlockData(const std::map, double>& localLgrBlockData, + std::map, 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 diff --git a/tests/test_LgrBlockData.cpp b/tests/test_LgrBlockData.cpp new file mode 100644 index 0000000000..59655dfcf5 --- /dev/null +++ b/tests/test_LgrBlockData.cpp @@ -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 . +*/ + +#include + +#define BOOST_TEST_MODULE LgrBlockDataGather + +#include + +#include + +#include +#include +#include + +namespace { + // Key = (summary keyword, LGR level, level-local linearised Cartesian index), + // matching opm-common's Summary::DynamicSimulatorState::LgrBlockValues. + using Key = std::tuple; + using LgrBlockMap = std::map; +} + +// 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()); +}