From 1e121799c8a70366435a5be595ec1a7172acde87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Thu, 3 Feb 2022 14:01:41 +0100 Subject: [PATCH] Add Multi-Array Aware Accumulator for Inter-Region Flow Rates This commit introduces a new helper class Opm::EclInterRegFlowMapSingleFIP that wraps a vector of Opm::EclInterRegFlowMapSingleFIP along with the associate array names (e.g., "FIPNUM" and any additional "FIP*" arrays). We implement the same operations as the *SingleFIP type and simply loop over the internal accumulators to affect the operations. Add unit tests to exercise the new class, including simulating multiple MPI ranks that are communicated to a single I/O rank for summary output purposes. --- ebos/eclinterregflows.cc | 125 +++- ebos/eclinterregflows.hh | 194 ++++++ tests/test_eclinterregflows.cpp | 1026 +++++++++++++++++++++++++++++++ 3 files changed, 1334 insertions(+), 11 deletions(-) diff --git a/ebos/eclinterregflows.cc b/ebos/eclinterregflows.cc index ffcffae2b..4a3c8373c 100644 --- a/ebos/eclinterregflows.cc +++ b/ebos/eclinterregflows.cc @@ -50,9 +50,11 @@ addConnection(const Cell& source, }; } - if (! (source.isInterior || destination.isInterior)) { - // Connection between two cells not on this process. Unlikely, but - // nothing to do here. + if (! source.isInterior || + (source.cartesianIndex > destination.cartesianIndex)) + { + // Connection handled in different call. Don't double-count + // contributions. return; } @@ -64,14 +66,9 @@ addConnection(const Cell& source, return; } - if ((source.isInterior && destination.isInterior) || - (source.isInterior && (r1 < r2)) || - (destination.isInterior && (r2 < r1))) - { - // Inter-region connection internal to an MPI rank or this rank owns - // the flow rate across this connection. - this->iregFlow_.addConnection(r1, r2, rates); - } + // Inter-region connection internal to an MPI rank or this rank owns + // the flow rate across this connection. + this->iregFlow_.addConnection(r1, r2, rates); } void Opm::EclInterRegFlowMapSingleFIP::compress() @@ -107,3 +104,109 @@ assignGlobalMaxRegionID(const std::size_t regID) this->maxGlobalRegionID_ = regID; return true; } + +// ===================================================================== +// +// Implementation of EclInterRegFlowMap (wrapper for multiple arrays) +// +// ===================================================================== + +Opm::EclInterRegFlowMap:: +EclInterRegFlowMap(const std::size_t numCells, + const std::vector& regions) +{ + this->regionMaps_.reserve(regions.size()); + this->names_.reserve(regions.size()); + + this->numCells_ = numCells; + + for (const auto& region : regions) { + this->regionMaps_.emplace_back(region.definition); + this->names_.push_back(region.name); + } +} + +void +Opm::EclInterRegFlowMap:: +addConnection(const Cell& source, + const Cell& destination, + const data::InterRegFlowMap::FlowRates& rates) +{ + for (auto& regionMap : this->regionMaps_) { + regionMap.addConnection(source, destination, rates); + } +} + +void Opm::EclInterRegFlowMap::compress() +{ + for (auto& regionMap : this->regionMaps_) { + regionMap.compress(); + } +} + +void Opm::EclInterRegFlowMap::clear() +{ + for (auto& regionMap : this->regionMaps_) { + regionMap.clear(); + } + + this->readIsConsistent_ = true; +} + +const std::vector& +Opm::EclInterRegFlowMap::names() const +{ + return this->names_; +} + +std::vector +Opm::EclInterRegFlowMap::getInterRegFlows() const +{ + auto maps = std::vector{}; + maps.reserve(this->regionMaps_.size()); + + for (auto& regionMap : this->regionMaps_) { + maps.push_back(regionMap.getInterRegFlows()); + } + + return maps; +} + +std::vector +Opm::EclInterRegFlowMap::getLocalMaxRegionID() const +{ + auto maxLocalRegionID = std::vector{}; + maxLocalRegionID.reserve(this->regionMaps_.size()); + + for (auto& regionMap : this->regionMaps_) { + maxLocalRegionID.push_back(regionMap.getLocalMaxRegionID()); + } + + return maxLocalRegionID; +} + +bool +Opm::EclInterRegFlowMap:: +assignGlobalMaxRegionID(const std::vector& regID) +{ + if (regID.size() != this->regionMaps_.size()) { + return false; + } + + auto assignmentOK = true; + + const auto numReg = regID.size(); + for (auto region = 0*numReg; region < numReg; ++region) { + const auto ok = this->regionMaps_[region] + .assignGlobalMaxRegionID(regID[region]); + + assignmentOK = assignmentOK && ok; + } + + return assignmentOK; +} + +bool Opm::EclInterRegFlowMap::readIsConsistent() const +{ + return this->readIsConsistent_; +} diff --git a/ebos/eclinterregflows.hh b/ebos/eclinterregflows.hh index d390e2c11..c5c2606eb 100644 --- a/ebos/eclinterregflows.hh +++ b/ebos/eclinterregflows.hh @@ -49,6 +49,9 @@ namespace Opm { /// Cell's active index on local rank. int activeIndex{-1}; + /// Cell's global cell ID. + int cartesianIndex{-1}; + /// Whether or not cell is interior to local rank. bool isInterior{true}; }; @@ -163,6 +166,197 @@ namespace Opm { /// from a stream. For error detection. bool isReadFromStream_{false}; }; + + /// Inter-region flow accumulation maps for all region definition arrays + class EclInterRegFlowMap + { + public: + /// Minimal representation of a single named region defintion. + /// + /// Name is typically "FIPNUM" or any additional "FIP*" array names. + struct SingleRegion { + /// Region definition array name + std::string name; + + /// Region definition array. + std::reference_wrapper> definition; + }; + + /// Characteristics of a cell from a simulation grid. + using Cell = EclInterRegFlowMapSingleFIP::Cell; + + /// Default constructor. + EclInterRegFlowMap() = default; + + /// Constructor. + /// + /// \param[in] numCells Number of cells on local MPI rank, including + /// overlap cells if applicable. + /// + /// \param[in] regions All applicable region definition arrays. + explicit EclInterRegFlowMap(const std::size_t numCells, + const std::vector& regions); + + EclInterRegFlowMap(const EclInterRegFlowMap& rhs) = default; + EclInterRegFlowMap(EclInterRegFlowMap&& rhs) noexcept = default; + + EclInterRegFlowMap& operator=(const EclInterRegFlowMap& rhs) = default; + EclInterRegFlowMap& operator=(EclInterRegFlowMap&& rhs) noexcept = default; + + /// Add flow rate connection between regions for all region + /// definitions. + /// + /// \param[in] source Cell from which the flow nominally originates. + /// + /// \param[in] destination Cell into which flow nominally goes. + /// + /// \param[in] rates Flow rates associated to single connection. + /// + /// If both cells are in the same region, or if neither cell is + /// interior to this MPI rank, then this function does nothing. If + /// one cell is interior to this MPI rank and the other isn't, then + /// this function will include the flow rate contribution if and + /// only if the cell with the smallest associate region ID is + /// interior to this MPI rank. + void addConnection(const Cell& source, + const Cell& destination, + const data::InterRegFlowMap::FlowRates& rates); + + /// Form CSR adjacency matrix representation of input graph from + /// connections established in previous calls to addConnection(). + /// + /// Number of rows in the CSR representation is the maximum FIP + /// region ID. + void compress(); + + /// Clear all internal buffers, but preserve allocated capacity. + void clear(); + + /// Names of all applicable region definition arrays. + /// + /// Mostly intended for summary output purposes. + const std::vector& names() const; + + /// Get read-only access to the underlying CSR representation. + /// + /// Mostly intended for summary output purposes. + std::vector getInterRegFlows() const; + + /// Retrieve maximum FIP region ID on local MPI rank. + std::vector getLocalMaxRegionID() const; + + /// Assign maximum FIP region ID across all MPI ranks. + /// + /// Fails if global maximum is smaller than local maximum region ID. + /// + /// \param[in] regID Global maximum FIP region ID for this FIP + /// definition array across all MPI ranks. + /// + /// \return Whether or not assignment succeeded. + bool assignGlobalMaxRegionID(const std::vector& regID); + + /// Whether or not previous read() operation succeeded. + bool readIsConsistent() const; + + /// Serialise internal representation to MPI message buffer + /// + /// \tparam MessageBufferType Linear MPI message buffer. API should + /// be similar to Dune::MessageBufferIF + /// + /// \param[in,out] buffer Linear MPI message buffer instance. + /// Function appends a partially linearised representation of + /// \code *this \endcode to the buffer contents. + template + void write(MessageBufferType& buffer) const + { + buffer.write(this->names_.size()); + for (const auto& name : this->names_) { + buffer.write(name); + } + + for (const auto& map : this->regionMaps_) { + map.write(buffer); + } + } + + /// Reconstitute internal object representation from MPI message + /// buffer + /// + /// This object (\code *this \endcode) is not usable in subsequent + /// calls to \code addConnection() \endcode following a call to + /// member function \code read() \endcode. + /// + /// \tparam MessageBufferType Linear MPI message buffer. API should + /// be similar to Dune::MessageBufferIF + /// + /// \param[in,out] buffer Linear MPI message buffer instance. + /// Function reads a partially linearised representation of \code + /// *this \endcode from the buffer contents and advances the + /// buffer's read position. + template + void read(MessageBufferType& buffer) + { + const auto names = this->readNames(buffer); + + if (names == this->names_) { + // Input stream holds the maps in expected order (common + // case). Read the maps and merge with internal values. + for (auto& map : this->regionMaps_) { + map.read(buffer); + } + } + else { + // Input stream does not hold the maps in expected order (or + // different number of maps). Unexpected. Read the values + // from the input stream, but do not merge with internal + // values. + auto map = EclInterRegFlowMapSingleFIP { + std::vector(this->numCells_, 1) + }; + + const auto numMaps = names.size(); + for (auto n = 0*numMaps; n < numMaps; ++n) { + map.read(buffer); + } + + this->readIsConsistent_ = false; + } + } + + private: + /// Inter-region flow accumulators. One accumulator map for each + /// region definition array. + std::vector regionMaps_{}; + + /// Names of region definition arrays. Typically "FIPNUM" and other + /// "FIPXYZ" array names. + std::vector names_; + + /// Number of cells, including overlap, reachable from this MPI + /// rank. + std::size_t numCells_{0}; + + /// Wheter or not read() successfully updated this object from an + /// input stream. + bool readIsConsistent_{true}; + + /// Retrieve array names from an input stream. + /// + /// Needed for consistency checks. + template + std::vector readNames(MessageBufferType& buffer) const + { + auto numNames = 0 * this->names_.size(); + buffer.read(numNames); + + auto names = std::vector(numNames); + for (auto name = 0*numNames; name < numNames; ++name) { + buffer.read(names[name]); + } + + return names; + } + }; } // namespace Opm #endif // ECL_INTERREG_FLOWS_MODULE_HH diff --git a/tests/test_eclinterregflows.cpp b/tests/test_eclinterregflows.cpp index 677fc13df..ccb1d7323 100644 --- a/tests/test_eclinterregflows.cpp +++ b/tests/test_eclinterregflows.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -2357,3 +2358,1028 @@ BOOST_AUTO_TEST_CASE(All_Separate_Region) BOOST_AUTO_TEST_SUITE_END() // Four_Processes BOOST_AUTO_TEST_SUITE_END() // MultiRank_ReadWriteMerge + +// ===================================================================== + +BOOST_AUTO_TEST_SUITE(MultiArray_Wrapper) + +namespace { + auto makeMessageBuffer() + { + return Dune::Point2PointCommunicator + ::MessageBufferType{}; + } + + template + void addConnections(Predicate&& isInterior, + Opm::EclInterRegFlowMap& rank) + { + rank.addConnection({ 0, 0, isInterior(0) }, { 1, 1, isInterior(1) }, conn_01()); + rank.addConnection({ 0, 0, isInterior(0) }, { 2, 2, isInterior(2) }, conn_02()); + rank.addConnection({ 1, 1, isInterior(1) }, { 3, 3, isInterior(3) }, conn_13()); + rank.addConnection({ 2, 2, isInterior(2) }, { 3, 3, isInterior(3) }, conn_23()); + } +} + +BOOST_AUTO_TEST_CASE(Single_Process) +{ + const auto fipnum = all_same_region(); + const auto fipspl = left_right_split_region(); + const auto fipchk = checker_board_region(); + const auto fipsep = all_separate_region(); + + auto flows = Opm::EclInterRegFlowMap { + fipnum.size(), + { + { "FIPNUM", std::cref(fipnum) }, + { "FIPSPL", std::cref(fipspl) }, + { "FIPCHK", std::cref(fipchk) }, + { "FIPSEP", std::cref(fipsep) }, + } + }; + + { + const auto names = flows.names(); + const auto expect = std::vector { + "FIPNUM", "FIPSPL", "FIPCHK", "FIPSEP", + }; + + BOOST_CHECK_MESSAGE(names == expect, + "Region array names don't match expected"); + } + + flows.addConnection({ 0, true }, { 1, true }, conn_01()); + flows.addConnection({ 0, true }, { 2, true }, conn_02()); + flows.addConnection({ 1, true }, { 3, true }, conn_13()); + flows.addConnection({ 2, true }, { 3, true }, conn_23()); + + { + const auto maxLocalRegID = flows.getLocalMaxRegionID(); + const auto expect = std::vector { + 1, 2, 2, 4, + }; + + BOOST_CHECK_MESSAGE(maxLocalRegID == expect, + "Maximum local region IDs must match expected"); + + BOOST_CHECK_MESSAGE(! flows.assignGlobalMaxRegionID({ 5, 1, 4, 5, }), + "Assigning small global maximum " + "region IDs must NOT succeed"); + + BOOST_CHECK_MESSAGE(flows.assignGlobalMaxRegionID({ 5, 4, 4, 5, }), + "Assigning global maximum region IDs must succeed"); + } + + flows.compress(); + + const auto iregFlows = flows.getInterRegFlows(); + BOOST_REQUIRE_EQUAL(iregFlows.size(), std::size_t{4}); + + // FIPNUM + { + const auto& map = iregFlows[0]; + BOOST_CHECK_EQUAL(map.numRegions(), 5); + + for (auto r1 = 0*map.numRegions(); r1 < map.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < map.numRegions(); ++r2) { + auto flow = map.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } + } + + // FIPSPL + { + const auto& map = iregFlows[1]; + BOOST_CHECK_EQUAL(map.numRegions(), 4); + + { + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 1.76f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 2.64f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 3.52f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 3.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.12f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -1.25f, 1.0e-6); + } + + { + const auto q21 = map.getInterRegFlows(1, 0); + BOOST_REQUIRE_MESSAGE(q21.has_value(), + "There must be inter-region flows " + "between regions 2 and 1"); + + const auto& [rate, sign] = q21.value(); + + BOOST_CHECK_EQUAL(sign, -1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 1.76f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 2.64f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 3.52f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 3.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.12f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -1.25f, 1.0e-6); + } + } + + // FIPCHK + { + const auto& map = iregFlows[2]; + BOOST_CHECK_EQUAL(map.numRegions(), 4); + + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 1.42f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 2.84f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 4.26f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 5.68f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 7.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.42f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.84f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 4.26f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 5.68f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 7.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + // FIPSEP + { + const auto& map = iregFlows[3]; + BOOST_CHECK_EQUAL(map.numRegions(), 5); + + { + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.12f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.24f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.48f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.12f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.24f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.48f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + { + const auto q13 = map.getInterRegFlows(0, 2); + BOOST_REQUIRE_MESSAGE(q13.has_value(), + "There must be inter-region flows " + "between regions 1 and 3"); + + const auto& [rate, sign] = q13.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.8f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 1.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.8f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 1.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + { + const auto q14 = map.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = map.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = map.getInterRegFlows(1, 3); + BOOST_REQUIRE_MESSAGE(q24.has_value(), + "There must be inter-region flows " + "between regions 2 and 4"); + + const auto& [rate, sign] = q24.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), -0.1f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), -0.5f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 0.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.1f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.2f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -0.5f, 1.0e-6); + } + } +} + +BOOST_AUTO_TEST_CASE(Two_Processes) +{ + using Map = Opm::EclInterRegFlowMap; + + const auto fipnum = all_same_region(); + const auto fipspl = left_right_split_region(); + const auto fipchk = checker_board_region(); + const auto fipsep = all_separate_region(); + + auto rank = std::vector(2, Map { + fipnum.size(), { + { "FIPNUM", std::cref(fipnum) }, + { "FIPSPL", std::cref(fipspl) }, + { "FIPCHK", std::cref(fipchk) }, + { "FIPSEP", std::cref(fipsep) }, + }}); + + { + const auto expect = std::vector{ 1, 2, 2, 4 }; + + BOOST_CHECK_MESSAGE(rank[0].getLocalMaxRegionID() == expect, + "Local maximum region IDs must match expected on rank 0"); + + BOOST_CHECK_MESSAGE(rank[1].getLocalMaxRegionID() == expect, + "Local maximum region IDs must match expected on rank 1"); + } + + addConnections(TwoProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID({3, 2, 2, 4}); + + addConnections(TwoProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID({5, 2, 2, 4}); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + BOOST_CHECK_MESSAGE(rank[0].readIsConsistent(), + "Consistent write() must yield consistent read()"); + + BOOST_CHECK_THROW(rank[0].addConnection({ 0, TwoProc::P1::isInterior(0) }, + { 1, TwoProc::P1::isInterior(1) }, conn_01()), + std::logic_error); + + rank[0].compress(); + + { + const auto names = rank[0].names(); + const auto expect = std::vector { + "FIPNUM", "FIPSPL", "FIPCHK", "FIPSEP", + }; + + BOOST_CHECK_MESSAGE(names == expect, + "Region array names don't match expected"); + } + + const auto iregFlows = rank[0].getInterRegFlows(); + BOOST_REQUIRE_EQUAL(iregFlows.size(), std::size_t{4}); + + // FIPNUM + { + const auto& map = iregFlows[0]; + BOOST_CHECK_EQUAL(map.numRegions(), 5); + + for (auto r1 = 0*map.numRegions(); r1 < map.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < map.numRegions(); ++r2) { + auto flow = map.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } + } + + // FIPSPL + { + const auto& map = iregFlows[1]; + BOOST_CHECK_EQUAL(map.numRegions(), 2); + + { + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 1.76f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 2.64f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 3.52f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 3.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.12f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -1.25f, 1.0e-6); + } + + { + const auto q21 = map.getInterRegFlows(1, 0); + BOOST_REQUIRE_MESSAGE(q21.has_value(), + "There must be inter-region flows " + "between regions 2 and 1"); + + const auto& [rate, sign] = q21.value(); + + BOOST_CHECK_EQUAL(sign, -1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 1.76f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 2.64f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 3.52f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 3.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.12f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -1.25f, 1.0e-6); + } + } + + // FIPCHK + { + const auto& map = iregFlows[2]; + BOOST_CHECK_EQUAL(map.numRegions(), 2); + + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 1.42f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 2.84f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 4.26f, 2.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 5.68f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 7.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.42f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.84f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 4.26f, 2.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 5.68f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 7.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + // FIPSEP + { + const auto& map = iregFlows[3]; + BOOST_CHECK_EQUAL(map.numRegions(), 4); + + { + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.12f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.24f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.48f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.12f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.24f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.48f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + { + const auto q13 = map.getInterRegFlows(0, 2); + BOOST_REQUIRE_MESSAGE(q13.has_value(), + "There must be inter-region flows " + "between regions 1 and 3"); + + const auto& [rate, sign] = q13.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.8f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 1.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.8f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 1.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + { + const auto q14 = map.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = map.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = map.getInterRegFlows(1, 3); + BOOST_REQUIRE_MESSAGE(q24.has_value(), + "There must be inter-region flows " + "between regions 2 and 4"); + + const auto& [rate, sign] = q24.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), -0.1f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), -0.5f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 0.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.1f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.2f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -0.5f, 1.0e-6); + } + } +} + +BOOST_AUTO_TEST_CASE(Two_Processes_Inconsistent_ReadWrite) +{ + using Map = Opm::EclInterRegFlowMap; + + const auto fipnum = all_same_region(); + const auto fipspl = left_right_split_region(); + const auto fipchk = checker_board_region(); + const auto fipsep = all_separate_region(); + + auto rank = std::vector{ + Map { + fipnum.size(), { + { "FIPNUM", std::cref(fipnum) }, + { "FIPSPL", std::cref(fipspl) }, + { "FIPCHK", std::cref(fipchk) }, + { "FIPSEP", std::cref(fipsep) }, + } + }, + + Map { + fipnum.size(), { + { "FIPSPL", std::cref(fipspl) }, + { "FIPSEP", std::cref(fipsep) }, + } + }, + }; + + addConnections(TwoProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID({3, 2, 2, 4}); + + addConnections(TwoProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID({2, 4}); + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + BOOST_CHECK_MESSAGE(! rank[0].readIsConsistent(), + "Inconsistent write() must yield inconsistent read()"); +} + +BOOST_AUTO_TEST_CASE(Four_Processes) +{ + using Map = Opm::EclInterRegFlowMap; + + const auto fipnum = all_same_region(); + const auto fipspl = left_right_split_region(); + const auto fipchk = checker_board_region(); + const auto fipsep = all_separate_region(); + + auto rank = std::vector(4, Map { + fipnum.size(), { + { "FIPNUM", std::cref(fipnum) }, + { "FIPSPL", std::cref(fipspl) }, + { "FIPCHK", std::cref(fipchk) }, + { "FIPSEP", std::cref(fipsep) }, + }}); + { + const auto expect = std::vector{ 1, 2, 2, 4 }; + + BOOST_CHECK_MESSAGE(rank[0].getLocalMaxRegionID() == expect, + "Local maximum region IDs must match expected on rank 0"); + + BOOST_CHECK_MESSAGE(rank[1].getLocalMaxRegionID() == expect, + "Local maximum region IDs must match expected on rank 1"); + } + + addConnections(FourProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID({3, 2, 2, 4}); + + addConnections(FourProc::P2::isInterior, rank[1]); + addConnections(FourProc::P3::isInterior, rank[2]); + + addConnections(FourProc::P4::isInterior, rank[3]); + rank[3].assignGlobalMaxRegionID({5, 2, 2, 4}); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].write(buffer); + rank[3].write(buffer); + rank[2].read(buffer); + rank[2].read(buffer); + rank[2].read(buffer); + + BOOST_CHECK_MESSAGE(rank[2].readIsConsistent(), + "Consistent write() must yield consistent read()"); + + BOOST_CHECK_THROW(rank[2].addConnection({ 0, TwoProc::P1::isInterior(0) }, + { 1, TwoProc::P1::isInterior(1) }, conn_01()), + std::logic_error); + + rank[2].compress(); + + { + const auto names = rank[0].names(); + const auto expect = std::vector { + "FIPNUM", "FIPSPL", "FIPCHK", "FIPSEP", + }; + + BOOST_CHECK_MESSAGE(names == expect, + "Region array names don't match expected"); + } + + const auto iregFlows = rank[2].getInterRegFlows(); + BOOST_REQUIRE_EQUAL(iregFlows.size(), std::size_t{4}); + + // FIPNUM + { + const auto& map = iregFlows[0]; + BOOST_CHECK_EQUAL(map.numRegions(), 5); + + for (auto r1 = 0*map.numRegions(); r1 < map.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < map.numRegions(); ++r2) { + auto flow = map.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } + } + + // FIPSPL + { + const auto& map = iregFlows[1]; + BOOST_CHECK_EQUAL(map.numRegions(), 2); + + { + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 1.76f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 2.64f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 3.52f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 3.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.12f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -1.25f, 1.0e-6); + } + + { + const auto q21 = map.getInterRegFlows(1, 0); + BOOST_REQUIRE_MESSAGE(q21.has_value(), + "There must be inter-region flows " + "between regions 2 and 1"); + + const auto& [rate, sign] = q21.value(); + + BOOST_CHECK_EQUAL(sign, -1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 1.76f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 2.64f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 3.52f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 3.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.12f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -1.25f, 1.0e-6); + } + } + + // FIPCHK + { + const auto& map = iregFlows[2]; + BOOST_CHECK_EQUAL(map.numRegions(), 2); + + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 1.42f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 2.84f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 4.26f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 5.68f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 7.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.42f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.84f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 4.26f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 5.68f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 7.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + // FIPSEP + { + const auto& map = iregFlows[3]; + BOOST_CHECK_EQUAL(map.numRegions(), 4); + + { + const auto q12 = map.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(q12.has_value(), + "There must be inter-region flows " + "between regions 1 and 2"); + + const auto& [rate, sign] = q12.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.12f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.24f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.48f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.12f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.24f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.48f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + { + const auto q13 = map.getInterRegFlows(0, 2); + BOOST_REQUIRE_MESSAGE(q13.has_value(), + "There must be inter-region flows " + "between regions 1 and 3"); + + const auto& [rate, sign] = q13.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), 0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.8f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 1.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.8f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 1.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), 0.0f, 1.0e-6); + } + + { + const auto q14 = map.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = map.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = map.getInterRegFlows(1, 3); + BOOST_REQUIRE_MESSAGE(q24.has_value(), + "There must be inter-region flows " + "between regions 2 and 4"); + + const auto& [rate, sign] = q24.value(); + + BOOST_CHECK_EQUAL(sign, 1.0); + + using FlowView = std::remove_cv_t>; + + using Component = FlowView::Component; + using Direction = FlowView::Direction; + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil), -0.1f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -0.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -0.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), -0.5f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 0.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -0.1f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -0.2f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -0.5f, 1.0e-6); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() // MultiArray_Wrapper