diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index fc74dd665..6b4502ad4 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -204,6 +204,7 @@ if(ENABLE_ECL_INPUT) src/opm/input/eclipse/Schedule/Well/PAvg.cpp src/opm/input/eclipse/Schedule/Well/PAvgCalculator.cpp src/opm/input/eclipse/Schedule/Well/PAvgCalculatorCollection.cpp + src/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.cpp src/opm/input/eclipse/Schedule/Well/Well.cpp src/opm/input/eclipse/Schedule/Well/WellConnections.cpp src/opm/input/eclipse/Schedule/Well/WellMatcher.cpp @@ -460,11 +461,12 @@ if(ENABLE_ECL_INPUT) tests/test_RestartFileView.cpp tests/test_EclIO.cpp tests/test_EGrid.cpp + tests/test_EInit.cpp tests/test_ERft.cpp tests/test_ERst.cpp tests/test_ESmry.cpp - tests/test_EInit.cpp tests/test_ExtESmry.cpp + tests/test_PAvgDynamicSourceData.cpp tests/test_Serialization.cpp tests/material/test_co2brinepvt.cpp tests/material/test_eclblackoilfluidsystem.cpp @@ -1212,6 +1214,7 @@ if(ENABLE_ECL_INPUT) opm/input/eclipse/Schedule/Well/PAvg.hpp opm/input/eclipse/Schedule/Well/PAvgCalculator.hpp opm/input/eclipse/Schedule/Well/PAvgCalculatorCollection.hpp + opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.hpp opm/input/eclipse/Schedule/Well/Well.hpp opm/input/eclipse/Schedule/Well/WellEnums.hpp opm/input/eclipse/Schedule/Well/WellInjectionControls.hpp diff --git a/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.hpp b/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.hpp new file mode 100644 index 000000000..481f68c93 --- /dev/null +++ b/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.hpp @@ -0,0 +1,256 @@ +/* + Copyright 2023 Equinor ASA. + + 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 PAVE_DYNAMIC_SOURCE_DATA_HPP +#define PAVE_DYNAMIC_SOURCE_DATA_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Opm { + +/// Dynamic source data for block-average pressure calculations +class PAvgDynamicSourceData +{ +public: + /// Ad hoc implementation of fixed-width span/view of an underlying + /// contiguous range of elements. + /// + /// \tparam T Element type. Const or non-const as needed. Typically \c + /// double or \code const double \endcode. + template + class SourceDataSpan + { + private: + friend class PAvgDynamicSourceData; + + public: + /// Supported items of dynamic data per source location + enum class Item + { + Pressure, //< Dynamic pressure value + MixtureDensity, //< Dynamic mixture density + PoreVol, //< Dynamic pore volume + + // ---------------------------------------- + + Last_Do_Not_Use, //< Simplifies item count + }; + + using ElmT = std::remove_cv_t; + + /// Read-only access to numerical value of specified item + /// + /// \param[in] i Item of dynamic source data. + /// \return Numerical value of specified item. + [[nodiscard]] constexpr ElmT operator[](const Item i) const + { + return this->begin_[this->index(i)]; + } + + /// Assign specified item + /// + /// Availble only if underlying range is non-const. + /// + /// \param[in] i Item of dynamic source data. + /// \param[in] value Numerical value of specified item. + /// \return \code *this \endcode to enable chaining. + template + constexpr std::enable_if_t, Ret> + set(const Item i, const ElmT value) + { + this->begin_[this->index(i)] = value; + return *this; + } + + /// Assign all items + /// + /// Availble only if underlying range is non-const. + /// + /// \param[in] i Item of dynamic source data. + /// \param[in] value Numerical value of specified item. + /// \return \code *this \endcode to enable chaining. + template + constexpr std::enable_if_t, Ret> + operator=(const SourceDataSpan src) + { + std::copy_n(src.begin_, NumItems, this->begin_); + return *this; + } + + private: + /// Number of data items per source location + static constexpr auto NumItems = + static_cast(Item::Last_Do_Not_Use); + + /// Beginning of items associated to a single source location. + T* begin_{nullptr}; + + /// Constructor. + /// + /// \param[in] begin Beginning of items associated to a single + /// source location. + explicit SourceDataSpan(T* begin) + : begin_{begin} + {} + + /// Translate item into linear index + /// + /// \param[in] i Item of dynamic source data + /// \return Linear index into span corresponding to specified item. + constexpr std::size_t index(const Item i) const + { + const auto ix = static_cast(i); + if (ix >= NumItems) { + throw std::invalid_argument { + "Index out of bounds" + }; + } + + return ix; + } + }; + + /// Constructor + /// + /// \param[in] sourceLocations Known locations, typically linearised + /// global call IDs, for which to enable collecting/reporting dynamic + /// source data. + explicit PAvgDynamicSourceData(const std::vector& sourceLocations); + + /// Destructor + /// + /// Marked virtual because this type is intended for inheritance. + virtual ~PAvgDynamicSourceData() {} + + /// Acquire read/write span of data items corresponding to a single + /// source location. + /// + /// Mostly intended for assigning values. + /// + /// \param[in] source Source location. Function will \c throw if \p + /// source is not one of the known locations registered in the object + /// constructor. + /// + /// \return Read/write span of data items. + [[nodiscard]] SourceDataSpan + operator[](const std::size_t source); + + /// Acquire read-only span of data items corresponding to a single + /// source location. + /// + /// Intended for extracting previously assigned data items. + /// + /// \param[in] source Source location. Function will \c throw if \p + /// source is not one of the known locations registered in the object + /// constructor. + /// + /// \return Read-only span of data items. + [[nodiscard]] SourceDataSpan + operator[](const std::size_t source) const; + +protected: + /// Contiguous array of data items for all source locations. + /// + /// Intentionally accessible to derived classes for use in parallel + /// runs. + std::vector src_{}; + + /// Form mutable data span into non-default backing store. + /// + /// Mainly intended for constructing span objects in backing store for + /// local (on-rank) sources in parallel runs. + /// + /// \param[in] ix Logical element index into source term backing store. + /// + /// \param[in,out] src Source term backing store. + /// + /// \return Mutable view into \p src. + [[nodiscard]] SourceDataSpan + sourceTerm(const std::size_t ix, std::vector& src); + + /// Reconstruct Source Data backing storage and internal mapping tables + /// + /// Effectively replaces the original object formed by the constructor. + /// Mainly intended for updating objects as new wells and/or new + /// reservoir connections are introduced. + /// + /// \param[in] sourceLocations Known locations, typically linearised + /// global call IDs, for which to enable collecting/reporting dynamic + /// source data. + void reconstruct(const std::vector& sourceLocations); + + /// Provide number of span items using function syntax + /// + /// Marked 'protected' because derived classes might need this + /// information too. + /// + /// \return Number of span items. + static constexpr std::size_t numSpanItems() noexcept + { + return SourceDataSpan::NumItems; + } + +private: + /// Translate non-contiguous source locations to starting indices in \c + /// src_. + std::unordered_map::size_type> ix_{}; + + /// Form source location to index translation table. + /// + /// \param[in] sourceLocations Known locations, typically linearised + /// global call IDs, for which to enable collecting/reporting dynamic + /// source data. + /// + /// \return Whether or not table formation succeeded. Typical reasons + /// for failure is repeated source locations. + void buildLocationMapping(const std::vector& sourceLocations); + + /// Translate source location to starting index in \c src_. + /// + /// \param[in] source Source location. + /// + /// \return Starting index. Nullopt if no index exists for \p source. + [[nodiscard]] std::optional::size_type> + index(const std::size_t source) const; + + /// Translate element index into storage index. + /// + /// This is a customisation point to simplify usage in parallel contexts. + /// + /// Default implementation, identity mapping. + /// + /// \param[in] elemIndex Source element index. + /// + /// \return Storage (starting) index in \c src_. + [[nodiscard]] virtual std::vector::size_type + storageIndex(std::vector::size_type elemIndex) const + { + return elemIndex; + } +}; + +} // namespace Opm + +#endif // PAVE_DYNAMIC_SOURCE_DATA_HPP diff --git a/src/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.cpp b/src/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.cpp new file mode 100644 index 000000000..7d39893ee --- /dev/null +++ b/src/opm/input/eclipse/Schedule/Well/PAvgDynamicSourceData.cpp @@ -0,0 +1,107 @@ +/* + Copyright 2023 Equinor ASA. + + 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 +#include +#include +#include +#include +#include + +#include + +Opm::PAvgDynamicSourceData::PAvgDynamicSourceData(const std::vector& sourceLocations) + : src_(numSpanItems() * sourceLocations.size(), 0.0) +{ + this->buildLocationMapping(sourceLocations); +} + +Opm::PAvgDynamicSourceData::SourceDataSpan +Opm::PAvgDynamicSourceData::operator[](const std::size_t source) +{ + const auto i = this->index(source); + if (! i.has_value()) { + OPM_THROW_NOLOG(std::invalid_argument, + fmt::format("Dynamic source location " + "'{}' is not registered", source)); + } + + return SourceDataSpan{ &this->src_[*i] }; +} + +Opm::PAvgDynamicSourceData::SourceDataSpan +Opm::PAvgDynamicSourceData::operator[](const std::size_t source) const +{ + const auto i = this->index(source); + if (! i.has_value()) { + OPM_THROW_NOLOG(std::invalid_argument, + fmt::format("Dynamic source location " + "'{}' is not registered", source)); + } + + return SourceDataSpan{ &this->src_[*i] }; +} + +Opm::PAvgDynamicSourceData::SourceDataSpan +Opm::PAvgDynamicSourceData::sourceTerm(const std::size_t ix, std::vector& src) +{ + return SourceDataSpan { &src[ix*numSpanItems() + 0] }; +} + +void +Opm::PAvgDynamicSourceData:: +reconstruct(const std::vector& sourceLocations) +{ + this->src_.assign(numSpanItems() * sourceLocations.size(), 0.0); + + this->buildLocationMapping(sourceLocations); +} + +void +Opm::PAvgDynamicSourceData:: +buildLocationMapping(const std::vector& sourceLocations) +{ + this->ix_.clear(); + + auto ix = std::vector::size_type{0}; + for (const auto& srcLoc : sourceLocations) { + auto elm = this->ix_.emplace(srcLoc, ix++); + if (! elm.second) { + OPM_THROW_NOLOG(std::invalid_argument, + fmt::format("Failed to set up internal mapping table, " + "single location {} entered multiple times.", + srcLoc)); + } + } +} + +std::optional::size_type> +Opm::PAvgDynamicSourceData::index(const std::size_t source) const +{ + auto pos = this->ix_.find(source); + if (pos == this->ix_.end()) { + return {}; + } + + return numSpanItems() * this->storageIndex(pos->second); +} diff --git a/tests/test_PAvgDynamicSourceData.cpp b/tests/test_PAvgDynamicSourceData.cpp new file mode 100644 index 000000000..f5547cd51 --- /dev/null +++ b/tests/test_PAvgDynamicSourceData.cpp @@ -0,0 +1,151 @@ +/* + Copyright (c) 2023 Equinor ASA + + 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 . +*/ + +#define BOOST_TEST_MODULE test_PAvgDynamicSourceData + +#include + +#include + +#include +#include +#include +#include +#include + +// =========================================================================== + +BOOST_AUTO_TEST_SUITE(Dynamic_Source_Data) + +namespace { + std::vector small() + { + return { 1, 2, 3, 5, }; + } + + std::vector repeated() + { + return { 1, 2, 3, 5, 3, }; + } + + Opm::PAvgDynamicSourceData smallResult() + { + using S = decltype(std::declval()[0]); + using I = typename S::Item; + + auto src = Opm::PAvgDynamicSourceData { small() }; + + src[1].set(I::Pressure, 123.4).set(I::MixtureDensity, 121.2).set(I::PoreVol, 543.21); + src[2].set(I::Pressure, 12.34).set(I::MixtureDensity, 12.12).set(I::PoreVol, 54.321); + src[3].set(I::Pressure, 1.234).set(I::MixtureDensity, 1.212).set(I::PoreVol, 5.4321); + src[5].set(I::Pressure, 1234) .set(I::MixtureDensity, 1212.).set(I::PoreVol, 5432.1); + + return src; + } +} // Anonymous namespace + +BOOST_AUTO_TEST_CASE(Mutable) +{ + using S = decltype(std::declval()[0]); + using I = typename S::Item; + + auto src = Opm::PAvgDynamicSourceData { small() }; + + src[1].set(I::Pressure, 123.4).set(I::MixtureDensity, 121.2).set(I::PoreVol, 543.21); + + { + const auto s1 = src[1]; + BOOST_CHECK_CLOSE(s1[I::Pressure], 123.4, 1.0e-10); + BOOST_CHECK_CLOSE(s1[I::MixtureDensity], 121.2, 1.0e-10); + BOOST_CHECK_CLOSE(s1[I::PoreVol], 543.21, 1.0e-10); + } + + { + const auto s2 = src[2]; + BOOST_CHECK_CLOSE(s2[I::Pressure], 0.0, 1.0e-10); + BOOST_CHECK_CLOSE(s2[I::MixtureDensity], 0.0, 1.0e-10); + BOOST_CHECK_CLOSE(s2[I::PoreVol], 0.0, 1.0e-10); + } + + src[2].set(I::Pressure, 123.4).set(I::MixtureDensity, 121.2).set(I::PoreVol, 543.21); + + { + const auto s2 = src[2]; + BOOST_CHECK_CLOSE(s2[I::Pressure], 123.4, 1.0e-10); + BOOST_CHECK_CLOSE(s2[I::MixtureDensity], 121.2, 1.0e-10); + BOOST_CHECK_CLOSE(s2[I::PoreVol], 543.21, 1.0e-10); + } + + BOOST_CHECK_THROW(src[4].set(I::Pressure, 0.5), std::invalid_argument); + + src[5].set(I::Pressure, 123.4).set(I::MixtureDensity, 121.2).set(I::PoreVol, 543.21); + + { + const auto s5 = src[5]; + BOOST_CHECK_CLOSE(s5[I::Pressure], 123.4, 1.0e-10); + BOOST_CHECK_CLOSE(s5[I::MixtureDensity], 121.2, 1.0e-10); + BOOST_CHECK_CLOSE(s5[I::PoreVol], 543.21, 1.0e-10); + } +} + +BOOST_AUTO_TEST_CASE(Immutable) +{ + const auto src = smallResult(); + + using S = decltype(src[0]); + using I = typename S::Item; + + { + const auto s1 = src[1]; + BOOST_CHECK_CLOSE(s1[I::Pressure], 123.4, 1.0e-10); + BOOST_CHECK_CLOSE(s1[I::MixtureDensity], 121.2, 1.0e-10); + BOOST_CHECK_CLOSE(s1[I::PoreVol], 543.21, 1.0e-10); + } + + { + const auto s2 = src[2]; + BOOST_CHECK_CLOSE(s2[I::Pressure], 12.34, 1.0e-10); + BOOST_CHECK_CLOSE(s2[I::MixtureDensity], 12.12, 1.0e-10); + BOOST_CHECK_CLOSE(s2[I::PoreVol], 54.321, 1.0e-10); + } + + { + const auto s3 = src[3]; + BOOST_CHECK_CLOSE(s3[I::Pressure], 1.234, 1.0e-10); + BOOST_CHECK_CLOSE(s3[I::MixtureDensity], 1.212, 1.0e-10); + BOOST_CHECK_CLOSE(s3[I::PoreVol], 5.4321, 1.0e-10); + } + + { + const auto s5 = src[5]; + BOOST_CHECK_CLOSE(s5[I::Pressure], 1234., 1.0e-10); + BOOST_CHECK_CLOSE(s5[I::MixtureDensity], 1212., 1.0e-10); + BOOST_CHECK_CLOSE(s5[I::PoreVol], 5432.1, 1.0e-10); + } + + BOOST_CHECK_THROW(std::ignore = src[1729], std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(Repeated) +{ + BOOST_CHECK_THROW(std::ignore = Opm::PAvgDynamicSourceData{ repeated() }, + std::invalid_argument); +} + +BOOST_AUTO_TEST_SUITE_END() // Dynamic_Source_Data