From b5ded128476e691e4457d678e91b57bff327d6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Tue, 1 Feb 2022 17:43:46 +0100 Subject: [PATCH 1/4] Sort List of Existing Unit Tests In preparation of adding new entries. --- CMakeLists_files.cmake | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index ee5d7af53f..f3e8818920 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -135,30 +135,30 @@ endif() # originally generated with the command: # find tests -name '*.cpp' -a ! -wholename '*/not-unit/*' -printf '\t%p\n' | sort list (APPEND TEST_SOURCE_FILES - tests/test_equil.cc - tests/test_ecl_output.cc + tests/test_ALQState.cpp tests/test_blackoil_amg.cpp tests/test_convergencereport.cpp + tests/test_deferredlogger.cpp + tests/test_ecl_output.cc + tests/test_equil.cc tests/test_flexiblesolver.cpp - tests/test_preconditionerfactory.cpp + tests/test_glift1.cpp tests/test_graphcoloring.cpp - tests/test_vfpproperties.cpp + tests/test_GroupState.cpp + tests/test_invert.cpp + tests/test_keyword_validator.cpp tests/test_milu.cpp tests/test_multmatrixtransposed.cpp - tests/test_wellmodel.cpp - tests/test_deferredlogger.cpp - tests/test_timer.cpp - tests/test_invert.cpp - tests/test_stoppedwells.cpp - tests/test_relpermdiagnostics.cpp tests/test_norne_pvt.cpp + tests/test_parallelwellinfo.cpp + tests/test_preconditionerfactory.cpp + tests/test_relpermdiagnostics.cpp + tests/test_stoppedwells.cpp + tests/test_timer.cpp + tests/test_vfpproperties.cpp + tests/test_wellmodel.cpp tests/test_wellprodindexcalculator.cpp tests/test_wellstate.cpp - tests/test_parallelwellinfo.cpp - tests/test_glift1.cpp - tests/test_keyword_validator.cpp - tests/test_GroupState.cpp - tests/test_ALQState.cpp ) if(MPI_FOUND) From 27564f06105ace938613c94e262e049e81173358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Tue, 1 Feb 2022 18:03:03 +0100 Subject: [PATCH 2/4] Add MPI-Aware Accumulator for Inter-Region Flow Rates This commit introduces a new helper class Opm::EclInterRegFlowMapSingleFIP that wraps an object of type Opm::data::InterRegFlowMap along with the MPI rank's notion of a FIP region array definition (e.g., the local FIPNUM array). The new single-FIP flow map is responsible for accumulating local contributions to the inter-region flows defined by that FIP array. In the case of connections between MPI ranks, the rank that owns the lowest region ID accumulates the associate flow rates. 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. --- CMakeLists_files.cmake | 3 + ebos/eclinterregflows.cc | 109 ++ ebos/eclinterregflows.hh | 168 +++ tests/test_eclinterregflows.cpp | 2359 +++++++++++++++++++++++++++++++ 4 files changed, 2639 insertions(+) create mode 100644 ebos/eclinterregflows.cc create mode 100644 ebos/eclinterregflows.hh create mode 100644 tests/test_eclinterregflows.cpp diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index f3e8818920..d082b95a0a 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -31,6 +31,7 @@ list (APPEND MAIN_SOURCE_FILES ebos/eclgenerictracermodel.cc ebos/eclgenericvanguard.cc ebos/eclgenericwriter.cc + ebos/eclinterregflows.cc ebos/ecltransmissibility.cc opm/core/props/BlackoilPhases.cpp opm/core/props/phaseUsageFromDeck.cpp @@ -140,6 +141,7 @@ list (APPEND TEST_SOURCE_FILES tests/test_convergencereport.cpp tests/test_deferredlogger.cpp tests/test_ecl_output.cc + tests/test_eclinterregflows.cpp tests/test_equil.cc tests/test_flexiblesolver.cpp tests/test_glift1.cpp @@ -229,6 +231,7 @@ list (APPEND TEST_DATA_FILES # originally generated with the command: # find opm -name '*.h*' -a ! -name '*-pch.hpp' -printf '\t%p\n' | sort list (APPEND PUBLIC_HEADER_FILES + ebos/eclinterregflows.hh opm/simulators/flow/countGlobalCells.hpp opm/simulators/flow/BlackoilModelEbos.hpp opm/simulators/flow/BlackoilModelParametersEbos.hpp diff --git a/ebos/eclinterregflows.cc b/ebos/eclinterregflows.cc new file mode 100644 index 0000000000..ffcffae2b9 --- /dev/null +++ b/ebos/eclinterregflows.cc @@ -0,0 +1,109 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2022 Equinor 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 . +*/ + +#include + +#include +#include + +Opm::EclInterRegFlowMapSingleFIP:: +EclInterRegFlowMapSingleFIP(const std::vector& region) + : region_(region.size(), 0) +{ + if (! region.empty()) { + this->maxLocalRegionID_= this->maxGlobalRegionID_ = + *std::max_element(region.begin(), region.end()); + } + + std::transform(region.begin(), region.end(), + this->region_.begin(), + [](const int regID) { return regID - 1; }); +} + +void +Opm::EclInterRegFlowMapSingleFIP:: +addConnection(const Cell& source, + const Cell& destination, + const data::InterRegFlowMap::FlowRates& rates) +{ + if (this->isReadFromStream_) { + throw std::logic_error { + "Cannot add new connection to deserialised object" + }; + } + + if (! (source.isInterior || destination.isInterior)) { + // Connection between two cells not on this process. Unlikely, but + // nothing to do here. + return; + } + + const auto r1 = this->region_[ source.activeIndex ]; + const auto r2 = this->region_[ destination.activeIndex ]; + + if (r1 == r2) { + // Connection is internal to a region. Nothing to do. + 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); + } +} + +void Opm::EclInterRegFlowMapSingleFIP::compress() +{ + this->iregFlow_.compress(this->maxGlobalRegionID_); +} + +void Opm::EclInterRegFlowMapSingleFIP::clear() +{ + this->iregFlow_.clear(); + this->isReadFromStream_ = false; +} + +const Opm::data::InterRegFlowMap& +Opm::EclInterRegFlowMapSingleFIP::getInterRegFlows() const +{ + return this->iregFlow_; +} + +std::size_t Opm::EclInterRegFlowMapSingleFIP::getLocalMaxRegionID() const +{ + return this->maxLocalRegionID_; +} + +bool +Opm::EclInterRegFlowMapSingleFIP:: +assignGlobalMaxRegionID(const std::size_t regID) +{ + if (regID < this->maxLocalRegionID_) { + return false; + } + + this->maxGlobalRegionID_ = regID; + return true; +} diff --git a/ebos/eclinterregflows.hh b/ebos/eclinterregflows.hh new file mode 100644 index 0000000000..d390e2c11e --- /dev/null +++ b/ebos/eclinterregflows.hh @@ -0,0 +1,168 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + Copyright 2022 Equinor 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 . +*/ + +#ifndef ECL_INTERREG_FLOWS_MODULE_HH +#define ECL_INTERREG_FLOWS_MODULE_HH + +#include + +#include +#include +#include +#include +#include + +/// \file +/// +/// MPI-aware facility for converting collection of tuples of region ID +/// pairs and associate flow rates into a sparse (CSR) adjacency matrix +/// representation of a graph. Supports O(nnz) compression. + +namespace Opm { + + /// Form CSR adjacency matrix representation of inter-region flow rate + /// graph provided as a list of connections between regions on local MPI + /// rank. Pertains to a single FIP definition array (e.g., FIPNUM). + class EclInterRegFlowMapSingleFIP + { + public: + /// Minimal characteristics of a cell from a simulation grid. + struct Cell { + /// Cell's active index on local rank. + int activeIndex{-1}; + + /// Whether or not cell is interior to local rank. + bool isInterior{true}; + }; + + /// Constructor + /// + /// \param[in] region Local rank's FIP region definition array. + explicit EclInterRegFlowMapSingleFIP(const std::vector& region); + + /// Add flow rate connection between regions. + /// + /// \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(); + + /// Get read-only access to the underlying CSR representation. + /// + /// Mostly intended for summary output purposes. + const data::InterRegFlowMap& getInterRegFlows() const; + + /// Retrieve maximum FIP region ID on local MPI rank. + std::size_t 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::size_t regID); + + /// 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 + { + // Note: this->region_ is *intentionally* omitted here. + buffer.write(this->maxGlobalRegionID_); + this->iregFlow_.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) + { + auto otherMaxRegionID = 0 * this->maxGlobalRegionID_; + buffer.read(otherMaxRegionID); + this->maxGlobalRegionID_ = std::max(this->maxGlobalRegionID_, otherMaxRegionID); + + this->iregFlow_.read(buffer); + this->isReadFromStream_ = true; + } + + private: + /// Zero-based FIP region IDs on local MPI rank. + std::vector region_{}; + + /// Maximum one-based FIP region ID on local MPI rank. + std::size_t maxLocalRegionID_{0}; + + /// Maximum one-based FIP region ID for this FIP region definition + /// array across all MPI ranks. + std::size_t maxGlobalRegionID_{0}; + + /// Rank-local inter-regional flow map. + data::InterRegFlowMap iregFlow_{}; + + /// Whether or not this object contains contributions deserialised + /// from a stream. For error detection. + bool isReadFromStream_{false}; + }; +} // namespace Opm + +#endif // ECL_INTERREG_FLOWS_MODULE_HH diff --git a/tests/test_eclinterregflows.cpp b/tests/test_eclinterregflows.cpp new file mode 100644 index 0000000000..677fc13df8 --- /dev/null +++ b/tests/test_eclinterregflows.cpp @@ -0,0 +1,2359 @@ +/* + Copyright 2022 Equinor. + + 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 TestEclInterRegFlows + +#ifndef HAVE_MPI +// Suppress GCC diagnostics of the form +// +// warning: "HAVE_MPI" is not defined, evaluates to 0 +// +// when compiling with "-Wundef". +#define HAVE_MPI 0 +#endif + +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +// +-----------> I +// | +// | +-----+-----+ +// | | | | +// | | 0 | 1 | +// | | | | +// | +-----+-----+ +// | | | | +// | | 2 | 3 | +// | | | | +// | +-----+-----+ +// | +// v J + +namespace { + Opm::data::InterRegFlowMap::FlowRates conn_01() + { + using Component = Opm::data::InterRegFlowMap::Component; + + auto rate = Opm::data::InterRegFlowMap::FlowRates{}; + + rate[Component::Oil] = 1.0; + rate[Component::Gas] = 2.0; + rate[Component::Water] = 3.0; + rate[Component::Disgas] = 4.0; + rate[Component::Vapoil] = 5.0; + + return rate; + } + + Opm::data::InterRegFlowMap::FlowRates conn_02() + { + using Component = Opm::data::InterRegFlowMap::Component; + + auto rate = Opm::data::InterRegFlowMap::FlowRates{}; + + rate[Component::Oil] = 0.1; + rate[Component::Gas] = 0.2; + rate[Component::Water] = 0.3; + rate[Component::Disgas] = 0.4; + rate[Component::Vapoil] = 0.5; + + return rate; + } + + Opm::data::InterRegFlowMap::FlowRates conn_13() + { + using Component = Opm::data::InterRegFlowMap::Component; + + auto rate = Opm::data::InterRegFlowMap::FlowRates{}; + + rate[Component::Oil] = -0.2; + rate[Component::Gas] = -0.4; + rate[Component::Water] = -0.6; + rate[Component::Disgas] = -0.8; + rate[Component::Vapoil] = -1.0; + + return rate; + } + + Opm::data::InterRegFlowMap::FlowRates conn_23() + { + using Component = Opm::data::InterRegFlowMap::Component; + + auto rate = Opm::data::InterRegFlowMap::FlowRates{}; + + rate[Component::Oil] = -0.12; + rate[Component::Gas] = -0.24; + rate[Component::Water] = -0.36; + rate[Component::Disgas] = -0.48; + rate[Component::Vapoil] = -1.25; + + return rate; + } + + std::vector all_same_region() + { + return { + 1, 1, + 1, 1, + }; + } + + std::vector left_right_split_region() + { + return { + 1, 2, + 1, 2, + }; + } + + std::vector checker_board_region() + { + return { + 1, 2, + 2, 1, + }; + } + + std::vector all_separate_region() + { + return { + 4, 3, + 2, 1, + }; + } + + namespace TwoProc { + namespace P1 { + bool isInterior(const int cellID) + { + return (cellID % 2) == 1; + } + } // namespace P1 + + namespace P2 { + bool isInterior(const int cellID) + { + return (cellID % 2) == 0; + } + } // namespace P2 + } // namespace TwoProc + + namespace FourProc { + namespace P1 { + bool isInterior(const int cellID) + { + return cellID == 1; + } + } // namespace P1 + + namespace P2 { + bool isInterior(const int cellID) + { + return cellID == 2; + } + } // namespace P2 + + namespace P3 { + bool isInterior(const int cellID) + { + return cellID == 3; + } + } // namespace P3 + + namespace P4 { + bool isInterior(const int cellID) + { + return cellID == 0; + } + } // namespace P4 + } // namespace FourProc +} + +// ===================================================================== + +BOOST_AUTO_TEST_SUITE(Single_FIP_Region) + +BOOST_AUTO_TEST_CASE(AllInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_same_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{1}); + + BOOST_CHECK_MESSAGE(flows.assignGlobalMaxRegionID(5), + "Assigning large global maximum " + "Region ID must succeed"); + + flows.addConnection({ 0, 0, true }, { 1, 1, true }, conn_01()); + flows.addConnection({ 0, 0, true }, { 2, 2, true }, conn_02()); + flows.addConnection({ 1, 1, true }, { 3, 3, true }, conn_13()); + flows.addConnection({ 2, 2, true }, { 3, 3, true }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + for (auto r1 = 0*iregFlows.numRegions(); r1 < iregFlows.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < iregFlows.numRegions(); ++r2) { + auto flow = iregFlows.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } +} + +BOOST_AUTO_TEST_CASE(LeftInternal_RightOther) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_same_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{1}); + + BOOST_CHECK_MESSAGE(flows.assignGlobalMaxRegionID(5), + "Assigning large global maximum " + "Region ID must succeed"); + + flows.addConnection({ 0, 0, true } , { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + for (auto r1 = 0*iregFlows.numRegions(); r1 < iregFlows.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < iregFlows.numRegions(); ++r2) { + auto flow = iregFlows.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } +} + +BOOST_AUTO_TEST_CASE(TopInternal_BottomOther) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_same_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{1}); + + BOOST_CHECK_MESSAGE(flows.assignGlobalMaxRegionID(5), + "Assigning large global maximum " + "Region ID must succeed"); + + flows.addConnection({ 0, 0, true } , { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + for (auto r1 = 0*iregFlows.numRegions(); r1 < iregFlows.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < iregFlows.numRegions(); ++r2) { + auto flow = iregFlows.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() // Single_FIP_Region + +// ===================================================================== + +BOOST_AUTO_TEST_SUITE(Left_Right_Split_Region) + +BOOST_AUTO_TEST_CASE(AllInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ left_right_split_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, true }, { 1, 1, true }, conn_01()); + flows.addConnection({ 0, 0, true }, { 2, 2, true }, conn_02()); + flows.addConnection({ 1, 1, true }, { 3, 3, true }, conn_13()); + flows.addConnection({ 2, 2, true }, { 3, 3, true }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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 = iregFlows.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); + } +} + +BOOST_AUTO_TEST_CASE(LeftInternal_RightOther) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ left_right_split_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, true } , { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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 = iregFlows.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); + } +} + +BOOST_AUTO_TEST_CASE(LeftOther_RightInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ left_right_split_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, false }, { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(! q12.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 2"); + } + + { + const auto q21 = iregFlows.getInterRegFlows(1, 0); + BOOST_REQUIRE_MESSAGE(! q21.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 2"); + } +} + +BOOST_AUTO_TEST_CASE(TopOther_BottomInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ left_right_split_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, true } , { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 3.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 5.0f, 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.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); + } +} + +BOOST_AUTO_TEST_CASE(CheckerBoard_Internal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ left_right_split_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, false }, { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -0.48f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), -1.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.0f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 0.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); + } +} + +BOOST_AUTO_TEST_SUITE_END() // Left_Right_Split_Region + +// ===================================================================== + +BOOST_AUTO_TEST_SUITE(Checker_Board_Region) + +BOOST_AUTO_TEST_CASE(AllInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ checker_board_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, true }, { 1, 1, true }, conn_01()); + flows.addConnection({ 0, 0, true }, { 2, 2, true }, conn_02()); + flows.addConnection({ 1, 1, true }, { 3, 3, true }, conn_13()); + flows.addConnection({ 2, 2, true }, { 3, 3, true }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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); + } +} + +BOOST_AUTO_TEST_CASE(LeftInternal_RightOther) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ checker_board_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, true } , { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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.22f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 2.44f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 3.66f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 4.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 6.75f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.22f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.44f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.66f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.88f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 6.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); + } +} + +BOOST_AUTO_TEST_CASE(LeftOther_RightInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ checker_board_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, false }, { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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.2f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.6f, 7.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.8f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.6f, 7.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.8f, 1.0e-6); + 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); + } +} + +BOOST_AUTO_TEST_CASE(TopOther_BottomInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ checker_board_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, false }, { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.36f, 7.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.48f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.36f, 7.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 0.48f, 1.0e-6); + 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); + } +} + +BOOST_AUTO_TEST_CASE(CheckerBoard_Internal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ checker_board_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, false }, { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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.32f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.64f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.96f, 7.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 1.28f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 2.25f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.32f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.64f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 0.96f, 7.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 1.28f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 2.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); + } +} + +BOOST_AUTO_TEST_CASE(Reverse_CheckerBoard_Internal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ checker_board_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{2}); + + flows.addConnection({ 0, 0, true } , { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 2); + + { + const auto q12 = iregFlows.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.1f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 2.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 3.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 4.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), 5.5f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 1.1f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 2.2f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Positive), 3.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Positive), 4.4f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 5.5f, 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); + } +} + +BOOST_AUTO_TEST_SUITE_END() // Checker_Board_Region + +// ===================================================================== + +BOOST_AUTO_TEST_SUITE(All_Separate_Region) + +BOOST_AUTO_TEST_CASE(AllInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_separate_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{4}); + + BOOST_CHECK_MESSAGE(! flows.assignGlobalMaxRegionID(2), + "Assigning small global maximum " + "Region ID must NOT succeed"); + + flows.addConnection({ 0, 0, true }, { 1, 1, true }, conn_01()); + flows.addConnection({ 0, 0, true }, { 2, 2, true }, conn_02()); + flows.addConnection({ 1, 1, true }, { 3, 3, true }, conn_13()); + flows.addConnection({ 2, 2, true }, { 3, 3, true }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 4); + + { + const auto q12 = iregFlows.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 = iregFlows.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 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.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(LeftInternal_RightOther) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_separate_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{4}); + + flows.addConnection({ 0, 0, true } , { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 4); + + { + const auto q12 = iregFlows.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 = iregFlows.getInterRegFlows(0, 2); + BOOST_CHECK_MESSAGE(! q13.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 3"); + } + + { + const auto q14 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.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); + } + + { + const auto q34 = iregFlows.getInterRegFlows(2, 3); + BOOST_REQUIRE_MESSAGE(q34.has_value(), + "There must be inter-region flows " + "between regions 3 and 4"); + + const auto& [rate, sign] = q34.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.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -2.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -4.0f, 1.0e-5); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), -5.0f, 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), -1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -5.0f, 1.0e-6); + } +} + +BOOST_AUTO_TEST_CASE(LeftOther_RightInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_separate_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{4}); + + flows.addConnection({ 0, 0, false }, { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 4); + + { + const auto q12 = iregFlows.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(! q12.has_value(), + "There must NOT be inter-region flows " + "between regions 1 and 2"); + } + + { + const auto q13 = iregFlows.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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.8f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.4f, 1.0e-6); + 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-6); + 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 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.getInterRegFlows(1, 3); + BOOST_CHECK_MESSAGE(! q24.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 4"); + } + + { + const auto q34 = iregFlows.getInterRegFlows(2, 3); + BOOST_REQUIRE_MESSAGE(! q34.has_value(), + "There must NOT be inter-region flows " + "between regions 3 and 4"); + } +} + +BOOST_AUTO_TEST_CASE(TopOther_BottomInternal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_separate_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{4}); + + flows.addConnection({ 0, 0, false }, { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 4); + + { + const auto q12 = iregFlows.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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.48f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.24f, 1.0e-6); + 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-6); + 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 = iregFlows.getInterRegFlows(0, 2); + BOOST_REQUIRE_MESSAGE(! q13.has_value(), + "There must NOT be inter-region flows " + "between regions 1 and 3"); + } + + { + const auto q14 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.getInterRegFlows(1, 3); + BOOST_REQUIRE_MESSAGE(! q24.has_value(), + "There must NOT be inter-region flows " + "between regions 2 and 4"); + } + + { + const auto q34 = iregFlows.getInterRegFlows(2, 3); + BOOST_REQUIRE_MESSAGE(! q34.has_value(), + "There must NOT be inter-region " + "flows between regions 3 and 4"); + } +} + +BOOST_AUTO_TEST_CASE(Checker_Board_Region) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_separate_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{4}); + + flows.addConnection({ 0, 0, false }, { 1, 1, true } , conn_01()); + flows.addConnection({ 0, 0, false }, { 2, 2, true } , conn_02()); + flows.addConnection({ 1, 1, true } , { 3, 3, false }, conn_13()); + flows.addConnection({ 2, 2, true } , { 3, 3, false }, conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 4); + + { + const auto q12 = iregFlows.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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.24f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.36f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.48f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.24f, 1.0e-6); + 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-6); + 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 = iregFlows.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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), 0.4f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), 0.6f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), 0.8f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.4f, 1.0e-6); + 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-6); + 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 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.getInterRegFlows(1, 3); + BOOST_REQUIRE_MESSAGE(! q24.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 4"); + } + + { + const auto q34 = iregFlows.getInterRegFlows(2, 3); + BOOST_REQUIRE_MESSAGE(! q34.has_value(), + "There must NOT be inter-region " + "flows between regions 3 and 4"); + } +} + +BOOST_AUTO_TEST_CASE(Reverse_CheckerBoard_Internal) +{ + auto flows = Opm::EclInterRegFlowMapSingleFIP{ all_separate_region() }; + BOOST_CHECK_EQUAL(flows.getLocalMaxRegionID(), std::size_t{4}); + + flows.addConnection({ 0, 0, true } , { 1, 1, false }, conn_01()); + flows.addConnection({ 0, 0, true } , { 2, 2, false }, conn_02()); + flows.addConnection({ 1, 1, false }, { 3, 3, true } , conn_13()); + flows.addConnection({ 2, 2, false }, { 3, 3, true } , conn_23()); + + flows.compress(); + + const auto& iregFlows = flows.getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 4); + + { + const auto q12 = iregFlows.getInterRegFlows(0, 1); + BOOST_REQUIRE_MESSAGE(! q12.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 2"); + } + + { + const auto q13 = iregFlows.getInterRegFlows(0, 2); + BOOST_REQUIRE_MESSAGE(! q13.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 3"); + } + + { + const auto q14 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.getInterRegFlows(1, 3); + BOOST_CHECK_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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -0.2f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -0.3f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -0.4f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.0f, 1.0e-6); + 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-6); + 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); + } + + { + const auto q34 = iregFlows.getInterRegFlows(2, 3); + BOOST_REQUIRE_MESSAGE(q34.has_value(), + "There must be inter-region " + "flows between regions 3 and 4"); + + const auto& [rate, sign] = q34.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.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas), -2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water), -3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas), -4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil), -5.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Positive), 0.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Positive), 0.0f, 1.0e-6); + 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-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Positive), 0.0f, 1.0e-6); + + BOOST_CHECK_CLOSE(rate.flow(Component::Oil, Direction::Negative), -1.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Gas, Direction::Negative), -2.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Water, Direction::Negative), -3.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Disgas, Direction::Negative), -4.0f, 1.0e-6); + BOOST_CHECK_CLOSE(rate.flow(Component::Vapoil, Direction::Negative), -5.0f, 1.0e-6); + } +} + +BOOST_AUTO_TEST_SUITE_END() // All_Separate_Region + +// ===================================================================== + +BOOST_AUTO_TEST_SUITE(MultiRank_ReadWriteMerge) + +namespace { + auto makeMessageBuffer() + { + return Dune::Point2PointCommunicator + ::MessageBufferType{}; + } + + template + void addConnections(Predicate&& isInterior, + Opm::EclInterRegFlowMapSingleFIP& 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_SUITE(Two_Processes) + +BOOST_AUTO_TEST_CASE(Empty) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(2, Map { all_same_region() }); + + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{1}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{1}); + + rank[0].assignGlobalMaxRegionID(3); + rank[1].assignGlobalMaxRegionID(5); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + 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& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + for (auto r1 = 0*iregFlows.numRegions(); r1 < iregFlows.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < iregFlows.numRegions(); ++r2) { + auto flow = iregFlows.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } +} + +BOOST_AUTO_TEST_CASE(Single_FIP_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(2, Map { all_same_region() }); + + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{1}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{1}); + + addConnections(TwoProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID(3); + + addConnections(TwoProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + 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& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + for (auto r1 = 0*iregFlows.numRegions(); r1 < iregFlows.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < iregFlows.numRegions(); ++r2) { + auto flow = iregFlows.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } +} + +BOOST_AUTO_TEST_CASE(Left_Right_Split_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(2, Map { left_right_split_region() }); + + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{2}); + + addConnections(TwoProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID(3); + + addConnections(TwoProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + rank[0].compress(); + + const auto& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + { + const auto q12 = iregFlows.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); + } +} + +BOOST_AUTO_TEST_CASE(Checker_Board_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(2, Map { checker_board_region() }); + + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{2}); + + addConnections(TwoProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID(3); + + addConnections(TwoProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + rank[0].compress(); + + const auto& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + { + const auto q12 = iregFlows.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, 5.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, 5.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); + } +} + +BOOST_AUTO_TEST_CASE(All_Separate_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(2, Map { all_separate_region() }); + + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{4}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{4}); + + addConnections(TwoProc::P1::isInterior, rank[0]); + BOOST_CHECK_MESSAGE(! rank[0].assignGlobalMaxRegionID(3), + "Assigning small maximum global region ID must NOT succeed"); + + addConnections(TwoProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[0].read(buffer); + + rank[0].compress(); + + const auto& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + { + const auto q12 = iregFlows.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 = iregFlows.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 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.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() // Two_Processes + +// --------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(Four_Processes) + +BOOST_AUTO_TEST_CASE(Single_FIP_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(4, Map { all_same_region() }); + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{1}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{1}); + BOOST_CHECK_EQUAL(rank[2].getLocalMaxRegionID(), std::size_t{1}); + BOOST_CHECK_EQUAL(rank[3].getLocalMaxRegionID(), std::size_t{1}); + + addConnections(FourProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID(3); + + addConnections(FourProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + addConnections(FourProc::P3::isInterior, rank[2]); + rank[2].assignGlobalMaxRegionID(2); + + addConnections(FourProc::P4::isInterior, rank[3]); + + for (auto& r : rank) { + r.compress(); + } + auto buffer = makeMessageBuffer(); + + rank[1].write(buffer); + rank[3].write(buffer); + rank[2].write(buffer); + rank[0].read(buffer); + rank[0].read(buffer); + rank[0].read(buffer); + + BOOST_CHECK_THROW(rank[0].addConnection({ 0, FourProc::P1::isInterior(0) }, + { 1, FourProc::P1::isInterior(1) }, conn_01()), + std::logic_error); + + rank[0].compress(); + + const auto& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + for (auto r1 = 0*iregFlows.numRegions(); r1 < iregFlows.numRegions(); ++r1) { + for (auto r2 = r1 + 1; r2 < iregFlows.numRegions(); ++r2) { + auto flow = iregFlows.getInterRegFlows(r1, r2); + BOOST_CHECK_MESSAGE(! flow.has_value(), + "There must not be inter-regional flow " + "between regions " << r1 << " and " << r2); + } + } +} + +BOOST_AUTO_TEST_CASE(Left_Right_Split_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(4, Map { left_right_split_region() }); + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[2].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[3].getLocalMaxRegionID(), std::size_t{2}); + + addConnections(FourProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID(3); + + addConnections(FourProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + addConnections(FourProc::P3::isInterior, rank[2]); + rank[2].assignGlobalMaxRegionID(2); + + addConnections(FourProc::P4::isInterior, rank[3]); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[0].write(buffer); + rank[3].write(buffer); + rank[2].write(buffer); + rank[1].read(buffer); + rank[1].read(buffer); + rank[1].read(buffer); + + rank[1].compress(); + + const auto& iregFlows = rank[1].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + { + const auto q12 = iregFlows.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); + } +} + +BOOST_AUTO_TEST_CASE(Checker_Board_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(4, Map { checker_board_region() }); + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[2].getLocalMaxRegionID(), std::size_t{2}); + BOOST_CHECK_EQUAL(rank[3].getLocalMaxRegionID(), std::size_t{2}); + + addConnections(FourProc::P1::isInterior, rank[0]); + rank[0].assignGlobalMaxRegionID(3); + + addConnections(FourProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + addConnections(FourProc::P3::isInterior, rank[2]); + rank[2].assignGlobalMaxRegionID(2); + + addConnections(FourProc::P4::isInterior, rank[3]); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[2].write(buffer); + rank[0].write(buffer); + rank[1].write(buffer); + rank[3].read(buffer); + rank[3].read(buffer); + rank[3].read(buffer); + + rank[3].compress(); + + const auto& iregFlows = rank[3].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + { + const auto q12 = iregFlows.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); + } +} + +BOOST_AUTO_TEST_CASE(All_Separate_Region) +{ + using Map = Opm::EclInterRegFlowMapSingleFIP; + + auto rank = std::vector(4, Map { all_separate_region() }); + BOOST_CHECK_EQUAL(rank[0].getLocalMaxRegionID(), std::size_t{4}); + BOOST_CHECK_EQUAL(rank[1].getLocalMaxRegionID(), std::size_t{4}); + BOOST_CHECK_EQUAL(rank[2].getLocalMaxRegionID(), std::size_t{4}); + BOOST_CHECK_EQUAL(rank[3].getLocalMaxRegionID(), std::size_t{4}); + + addConnections(FourProc::P1::isInterior, rank[0]); + BOOST_CHECK_MESSAGE(! rank[0].assignGlobalMaxRegionID(3), + "Assgning small maximum global region ID must NOT succeed"); + + addConnections(FourProc::P2::isInterior, rank[1]); + rank[1].assignGlobalMaxRegionID(5); + + addConnections(FourProc::P3::isInterior, rank[2]); + BOOST_CHECK_MESSAGE(! rank[2].assignGlobalMaxRegionID(2), + "Assgning small maximum global region ID must NOT succeed"); + + addConnections(FourProc::P4::isInterior, rank[3]); + + for (auto& r : rank) { + r.compress(); + } + + auto buffer = makeMessageBuffer(); + + rank[2].write(buffer); + rank[3].write(buffer); + rank[1].write(buffer); + rank[0].read(buffer); + rank[0].read(buffer); + rank[0].read(buffer); + + rank[0].compress(); + + const auto& iregFlows = rank[0].getInterRegFlows(); + BOOST_CHECK_EQUAL(iregFlows.numRegions(), 5); + + { + const auto q12 = iregFlows.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 = iregFlows.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 = iregFlows.getInterRegFlows(0, 3); + BOOST_CHECK_MESSAGE(! q14.has_value(), + "There must NOT be inter-region " + "flows between regions 1 and 4"); + } + + { + const auto q23 = iregFlows.getInterRegFlows(1, 2); + BOOST_CHECK_MESSAGE(! q23.has_value(), + "There must NOT be inter-region " + "flows between regions 2 and 3"); + } + + { + const auto q24 = iregFlows.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() // Four_Processes + +BOOST_AUTO_TEST_SUITE_END() // MultiRank_ReadWriteMerge 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 3/4] 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 ffcffae2b9..4a3c8373c7 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 d390e2c11e..c5c2606ebe 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 677fc13df8..ccb1d73239 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 From ecec83349b85ff090c5ce4ed7218ed58095ed008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Mon, 14 Feb 2022 12:30:32 +0100 Subject: [PATCH 4/4] Capture Bulk Connection Fluxes to Accumlate Inter-Region Flows This commit adds a new grid traversal that computes fluxes-presently surface level component fluxes-for all bulk connections on the current MPI rank. We aggregate those fluxes, if applicable, into a container for inter-region flows, but this support could be extended to capturing the full 3D vector flow rates for restart output if needed. --- ebos/eclgenericoutputblackoilmodule.cc | 27 +++- ebos/eclgenericoutputblackoilmodule.hh | 5 + ebos/ecloutputblackoilmodule.hh | 190 +++++++++++++++++++++++++ ebos/eclwriter.hh | 44 +++++- 4 files changed, 263 insertions(+), 3 deletions(-) diff --git a/ebos/eclgenericoutputblackoilmodule.cc b/ebos/eclgenericoutputblackoilmodule.cc index 9765a89d98..0aec42c72d 100644 --- a/ebos/eclgenericoutputblackoilmodule.cc +++ b/ebos/eclgenericoutputblackoilmodule.cc @@ -18,6 +18,7 @@ */ #include + #include #include @@ -33,11 +34,16 @@ #include #include -#include +#include +#include #include #include #include #include +#include +#include + +#include namespace { @@ -59,6 +65,24 @@ std::string EclString(Opm::Inplace::Phase phase) { } } + std::size_t numCells(const Opm::EclipseState& eclState) + { + return eclState.fieldProps().get_int("FIPNUM").size(); + } + + std::vector + defineInterRegionFlowArrays(const Opm::EclipseState& eclState, + const Opm::SummaryConfig& summaryConfig) + { + auto regions = std::vector{}; + + const auto& fprops = eclState.fieldProps(); + for (const auto& arrayName : summaryConfig.fip_regions_interreg_flow()) { + regions.push_back({ arrayName, std::cref(fprops.get_int(arrayName)) }); + } + + return regions; + } } namespace Opm { @@ -82,6 +106,7 @@ EclGenericOutputBlackoilModule(const EclipseState& eclState, , schedule_(schedule) , summaryConfig_(summaryConfig) , summaryState_(summaryState) + , interRegionFlows_(numCells(eclState), defineInterRegionFlowArrays(eclState, summaryConfig)) , enableEnergy_(enableEnergy) , enableTemperature_(enableTemperature) , enableSolvent_(enableSolvent) diff --git a/ebos/eclgenericoutputblackoilmodule.hh b/ebos/eclgenericoutputblackoilmodule.hh index 3c17c34982..915a248853 100644 --- a/ebos/eclgenericoutputblackoilmodule.hh +++ b/ebos/eclgenericoutputblackoilmodule.hh @@ -43,6 +43,8 @@ #endif #include +#include + namespace Opm { namespace data { class Solution; } @@ -370,6 +372,9 @@ protected: const Schedule& schedule_; const SummaryConfig& summaryConfig_; const SummaryState& summaryState_; + + EclInterRegFlowMap interRegionFlows_; + bool enableEnergy_; bool enableTemperature_; diff --git a/ebos/ecloutputblackoilmodule.hh b/ebos/ecloutputblackoilmodule.hh index b3fe260959..0306ce9f39 100644 --- a/ebos/ecloutputblackoilmodule.hh +++ b/ebos/ecloutputblackoilmodule.hh @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include @@ -576,6 +578,94 @@ public: } } + /*! + * \brief Capture connection fluxes, particularly to account for inter-region flows. + * + * \tparam ActiveIndex Callable type, typically a lambda, that enables + * retrieving the active index, on the local MPI rank, of a + * particular cell/element. Must support a function call operator of + * the form + \code + int operator()(const Element& elem) const + \endcode + * + * \tparam CartesianIndex Callable type, typically a lambda, that + * enables retrieving the globally unique Cartesian index of a + * particular cell/element given its active index on the local MPI + * rank. Must support a function call operator of the form + \code + int operator()(const int activeIndex) const + \endcode + * + * \param[in] elemCtx Primary lookup structure for per-cell/element + * dynamic information. + * + * \param[in] activeIndex Mapping from cell/elements to linear indices + * on local MPI rank. + * + * \param[in] cartesianIndex Mapping from active index on local MPI rank + * to globally unique Cartesian cell/element index. + */ + template + void processFluxes(const ElementContext& elemCtx, + ActiveIndex&& activeIndex, + CartesianIndex&& cartesianIndex) + { + const auto identifyCell = [&activeIndex, &cartesianIndex](const Element& elem) + -> EclInterRegFlowMap::Cell + { + const auto cellIndex = activeIndex(elem); + + return { + cellIndex, + cartesianIndex(cellIndex), + elem.partitionType() == Dune::InteriorEntity + }; + }; + + const auto timeIdx = 0u; + const auto& stencil = elemCtx.stencil(timeIdx); + const auto numInteriorFaces = elemCtx.numInteriorFaces(timeIdx); + + for (auto scvfIdx = 0*numInteriorFaces; scvfIdx < numInteriorFaces; ++scvfIdx) { + const auto& face = stencil.interiorFace(scvfIdx); + const auto left = identifyCell(stencil.element(face.interiorIndex())); + const auto right = identifyCell(stencil.element(face.exteriorIndex())); + + const auto rates = this-> + getComponentSurfaceRates(elemCtx, face.area(), scvfIdx, timeIdx); + + this->interRegionFlows_.addConnection(left, right, rates); + } + } + + /*! + * \brief Prepare for capturing connection fluxes, particularly to + * account for inter-region flows. + */ + void initializeFluxData() + { + // Inter-region flow rates. Note: ".clear()" prepares to accumulate + // contributions per bulk connection between FIP regions. + this->interRegionFlows_.clear(); + } + + /*! + * \brief Finalize capturing connection fluxes. + */ + void finalizeFluxData() + { + this->interRegionFlows_.compress(); + } + + /*! + * \brief Get read-only access to collection of inter-region flows. + */ + const EclInterRegFlowMap& getInterRegFlows() const + { + return this->interRegionFlows_; + } + template void assignToFluidState(FluidState& fs, unsigned elemIdx) const { @@ -772,6 +862,106 @@ private: } } + /*! + * \brief Compute surface level component flow rates across a single + * intersection. + * + * \param[in] elemCtx Primary lookup structure for per-cell/element + * dynamic information. + * + * \param[in] scvfIdx Linear index of current interior bulk connection. + * + * \param[in] timeIdx Historical time-point at which to evaluate dynamic + * quantities (e.g., reciprocal FVF or dissolved gas concentration). + * Zero for the current time. + * + * \return Surface level component flow rates. + */ + data::InterRegFlowMap::FlowRates + getComponentSurfaceRates(const ElementContext& elemCtx, + const Scalar faceArea, + const std::size_t scvfIdx, + const std::size_t timeIdx) const + { + using Component = data::InterRegFlowMap::Component; + + auto rates = data::InterRegFlowMap::FlowRates{}; + + const auto& extQuant = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + + const auto alpha = getValue(extQuant.extrusionFactor()) * faceArea; + + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + const auto& up = elemCtx + .intensiveQuantities(extQuant.upstreamIndex(oilPhaseIdx), timeIdx); + + using FluidState = std::remove_cv_t>; + + const auto pvtReg = up.pvtRegionIndex(); + + const auto bO = getValue(getInvB_ + (up.fluidState(), oilPhaseIdx, pvtReg)); + + const auto qO = alpha * bO * getValue(extQuant.volumeFlux(oilPhaseIdx)); + + rates[Component::Oil] += qO; + + if (FluidSystem::phaseIsActive(gasPhaseIdx)) { + const auto Rs = getValue( + BlackOil::getRs_ + (up.fluidState(), pvtReg)); + + rates[Component::Gas] += qO * Rs; + rates[Component::Disgas] += qO * Rs; + } + } + + if (FluidSystem::phaseIsActive(gasPhaseIdx)) { + const auto& up = elemCtx + .intensiveQuantities(extQuant.upstreamIndex(gasPhaseIdx), timeIdx); + + using FluidState = std::remove_cv_t>; + + const auto pvtReg = up.pvtRegionIndex(); + + const auto bG = getValue(getInvB_ + (up.fluidState(), gasPhaseIdx, pvtReg)); + + const auto qG = alpha * bG * getValue(extQuant.volumeFlux(gasPhaseIdx)); + + rates[Component::Gas] += qG; + + if (FluidSystem::phaseIsActive(oilPhaseIdx)) { + const auto Rv = getValue( + BlackOil::getRv_ + (up.fluidState(), pvtReg)); + + rates[Component::Oil] += qG * Rv; + rates[Component::Vapoil] += qG * Rv; + } + } + + if (FluidSystem::phaseIsActive(waterPhaseIdx)) { + const auto& up = elemCtx + .intensiveQuantities(extQuant.upstreamIndex(waterPhaseIdx), timeIdx); + + using FluidState = std::remove_cv_t>; + + const auto pvtReg = up.pvtRegionIndex(); + + const auto bW = getValue(getInvB_ + (up.fluidState(), waterPhaseIdx, pvtReg)); + + rates[Component::Water] += + alpha * bW * getValue(extQuant.volumeFlux(waterPhaseIdx)); + } + + return rates; + } + const Simulator& simulator_; }; diff --git a/ebos/eclwriter.hh b/ebos/eclwriter.hh index 043dd58fad..dee9a66aeb 100644 --- a/ebos/eclwriter.hh +++ b/ebos/eclwriter.hh @@ -38,8 +38,11 @@ #include -#include +#include + +#include #include +#include namespace Opm::Properties { @@ -181,11 +184,12 @@ public: const auto localGroupAndNetworkData = simulator_.problem().wellModel() .groupAndNetworkData(reportStepNum); - const auto localAquiferData = simulator_.problem().aquiferModel().aquiferData(); const auto localWellTestState = simulator_.problem().wellModel().wellTestState(); this->prepareLocalCellData(isSubStep, reportStepNum); + this->captureLocalFluxData(); + if (this->collectToIORank_.isParallel()) this->collectToIORank_.collect({}, eclOutputModule_->getBlockData(), @@ -435,6 +439,42 @@ private: OPM_END_PARALLEL_TRY_CATCH("EclWriter::prepareLocalCellData() failed: ", simulator_.vanguard().grid().comm()) } + void captureLocalFluxData() + { + const auto& gridView = this->simulator_.vanguard().gridView(); + const auto timeIdx = 0u; + + auto elemCtx = ElementContext { this->simulator_ }; + + const auto elemMapper = ElementMapper { gridView, Dune::mcmgElementLayout() }; + const auto activeIndex = [&elemMapper](const Element& e) + { + return elemMapper.index(e); + }; + + const auto cartesianIndex = [this](const int elemIndex) + { + return this->cartMapper_.cartesianIndex(elemIndex); + }; + + this->eclOutputModule_->initializeFluxData(); + + OPM_BEGIN_PARALLEL_TRY_CATCH(); + + for (const auto& elem : elements(gridView, Dune::Partitions::interiorBorder)) { + elemCtx.updateStencil(elem); + elemCtx.updateIntensiveQuantities(timeIdx); + elemCtx.updateExtensiveQuantities(timeIdx); + + this->eclOutputModule_->processFluxes(elemCtx, activeIndex, cartesianIndex); + } + + OPM_END_PARALLEL_TRY_CATCH("EclWriter::captureLocalFluxData() failed: ", + this->simulator_.vanguard().grid().comm()) + + this->eclOutputModule_->finalizeFluxData(); + } + Simulator& simulator_; std::unique_ptr> eclOutputModule_; Scalar restartTimeStepSize_;