diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index ee5d7af53f..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 @@ -135,30 +136,31 @@ 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_eclinterregflows.cpp + 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) @@ -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/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/eclinterregflows.cc b/ebos/eclinterregflows.cc new file mode 100644 index 0000000000..4a3c8373c7 --- /dev/null +++ b/ebos/eclinterregflows.cc @@ -0,0 +1,212 @@ +// -*- 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 || + (source.cartesianIndex > destination.cartesianIndex)) + { + // Connection handled in different call. Don't double-count + // contributions. + 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; + } + + // 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; +} + +// ===================================================================== +// +// 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 new file mode 100644 index 0000000000..c5c2606ebe --- /dev/null +++ b/ebos/eclinterregflows.hh @@ -0,0 +1,362 @@ +// -*- 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}; + + /// Cell's global cell ID. + int cartesianIndex{-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}; + }; + + /// 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/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_; diff --git a/tests/test_eclinterregflows.cpp b/tests/test_eclinterregflows.cpp new file mode 100644 index 0000000000..ccb1d73239 --- /dev/null +++ b/tests/test_eclinterregflows.cpp @@ -0,0 +1,3385 @@ +/* + 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 +#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 + +// ===================================================================== + +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