From 57ca294f58c1d25b86d4a308906a8ec97bda0786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Fri, 30 May 2025 09:44:48 +0200 Subject: [PATCH 1/2] Add Undersaturated State Handling in Table Initialisation This commit adds a procedure for expanding missing undersaturated states in PVTO/PVTG in the low-level Pvt*Table classes on top of which the higher level types are built. This, as an added benefit, directly ensures that we emit fully expanded PVTO/PVTG tables to the .INIT file. To this end, equip the base class PvtxTable with a helper function void PvtxTable::populateMissingUndersaturatedStates() which will invoke the virtual function makeScaledUSatTableCopy(src, dest) in its derived classes--especially PvtoTable and PvtgTable--for each missing undersaturated state. The type-specific procedures for PvtoTable and PvtgTable is defined in such a way that the scaled copies preserve oil compressibility. --- .../eclipse/EclipseState/Tables/PvtgTable.hpp | 25 ++- .../EclipseState/Tables/PvtgwTable.hpp | 25 ++- .../EclipseState/Tables/PvtgwoTable.hpp | 25 ++- .../eclipse/EclipseState/Tables/PvtoTable.hpp | 24 ++- .../EclipseState/Tables/PvtsolTable.hpp | 26 ++- .../eclipse/EclipseState/Tables/PvtxTable.cpp | 50 +++++ .../eclipse/EclipseState/Tables/PvtxTable.hpp | 33 ++++ .../EclipseState/Tables/RwgsaltTable.hpp | 25 ++- .../eclipse/EclipseState/Tables/Tables.cpp | 90 ++++++++- tests/parser/TableManagerTests.cpp | 172 +++++++++++++++--- tests/test_Tables.cpp | 58 +++--- 11 files changed, 461 insertions(+), 92 deletions(-) diff --git a/opm/input/eclipse/EclipseState/Tables/PvtgTable.hpp b/opm/input/eclipse/EclipseState/Tables/PvtgTable.hpp index 447f0ab7239..689441f9df4 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtgTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtgTable.hpp @@ -15,25 +15,38 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . - */ +*/ + #ifndef OPM_PARSER_PVTG_TABLE_HPP -#define OPM_PARSER_PVTG_TABLE_HPP +#define OPM_PARSER_PVTG_TABLE_HPP #include +#include + namespace Opm { class DeckKeyword; - class PvtgTable : public PvtxTable { +} // namespace Opm + +namespace Opm { + + class PvtgTable : public PvtxTable + { public: PvtgTable() = default; - PvtgTable( const DeckKeyword& keyword, size_t tableIdx); + PvtgTable(const DeckKeyword& keyword, std::size_t tableIdx); static PvtgTable serializationTestObject(); bool operator==(const PvtgTable& data) const; + + private: + void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) override; }; -} -#endif +} // namespace Opm + +#endif // OPM_PARSER_PVTG_TABLE_HPP diff --git a/opm/input/eclipse/EclipseState/Tables/PvtgwTable.hpp b/opm/input/eclipse/EclipseState/Tables/PvtgwTable.hpp index a891424dfb8..d1e84b1efe8 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtgwTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtgwTable.hpp @@ -16,25 +16,38 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . - */ +*/ + #ifndef OPM_PARSER_PVTGW_TABLE_HPP -#define OPM_PARSER_PVTGW_TABLE_HPP +#define OPM_PARSER_PVTGW_TABLE_HPP #include +#include + namespace Opm { class DeckKeyword; - class PvtgwTable : public PvtxTable { +} // namespace Opm + +namespace Opm { + + class PvtgwTable : public PvtxTable + { public: PvtgwTable() = default; - PvtgwTable( const DeckKeyword& keyword, size_t tableIdx); + PvtgwTable(const DeckKeyword& keyword, const std::size_t tableIdx); static PvtgwTable serializationTestObject(); bool operator==(const PvtgwTable& data) const; + + private: + void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) override; }; -} -#endif +} // namespace Opm + +#endif // OPM_PARSER_PVTGW_TABLE_HPP diff --git a/opm/input/eclipse/EclipseState/Tables/PvtgwoTable.hpp b/opm/input/eclipse/EclipseState/Tables/PvtgwoTable.hpp index faec0f58f04..a1448857fcd 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtgwoTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtgwoTable.hpp @@ -16,25 +16,38 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . - */ +*/ + #ifndef OPM_PARSER_PVTGWO_TABLE_HPP -#define OPM_PARSER_PVTGWO_TABLE_HPP +#define OPM_PARSER_PVTGWO_TABLE_HPP #include +#include + namespace Opm { class DeckKeyword; - class PvtgwoTable : public PvtxTable { +} // namespaced Opm + +namespace Opm { + + class PvtgwoTable : public PvtxTable + { public: PvtgwoTable() = default; - PvtgwoTable( const DeckKeyword& keyword, size_t tableIdx); + PvtgwoTable(const DeckKeyword& keyword, const std::size_t tableIdx); static PvtgwoTable serializationTestObject(); bool operator==(const PvtgwoTable& data) const; + + private: + void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) override; }; -} -#endif +} // namespace Opm + +#endif // OPM_PARSER_PVTGWO_TABLE_HPP diff --git a/opm/input/eclipse/EclipseState/Tables/PvtoTable.hpp b/opm/input/eclipse/EclipseState/Tables/PvtoTable.hpp index 2c85a69cf33..dff4fa00abf 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtoTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtoTable.hpp @@ -15,7 +15,8 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . - */ +*/ + #ifndef OPM_PARSER_PVTO_TABLE_HPP #define OPM_PARSER_PVTO_TABLE_HPP @@ -29,23 +30,34 @@ namespace Opm { class DeckKeyword; - class PvtoTable : public PvtxTable { +} // namespace Opm + +namespace Opm { + + class PvtoTable : public PvtxTable + { public: - struct FlippedFVF { + struct FlippedFVF + { std::size_t i; std::array Rs; std::array Bo; }; PvtoTable() = default; - PvtoTable(const DeckKeyword& keyword, size_t tableIdx); + PvtoTable(const DeckKeyword& keyword, std::size_t tableIdx); static PvtoTable serializationTestObject(); bool operator==(const PvtoTable& data) const; std::vector nonMonotonicSaturatedFVF() const; + + private: + void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) override; }; -} -#endif +} // namespace Opm + +#endif // OPM_PARSER_PVTO_TABLE_HPP diff --git a/opm/input/eclipse/EclipseState/Tables/PvtsolTable.hpp b/opm/input/eclipse/EclipseState/Tables/PvtsolTable.hpp index a63d816b6c9..264dc7f2a56 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtsolTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtsolTable.hpp @@ -15,23 +15,37 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . - */ +*/ + #ifndef OPM_PARSER_PVTSOL_TABLE_HPP -#define OPM_PARSER_PVTSOL_TABLE_HPP +#define OPM_PARSER_PVTSOL_TABLE_HPP #include +#include + namespace Opm { class DeckKeyword; - class PvtsolTable : public PvtxTable { +} // namespace Opm + +namespace Opm { + + class PvtsolTable : public PvtxTable + { public: PvtsolTable() = default; - PvtsolTable(const DeckKeyword& keyword, size_t tableIdx); + PvtsolTable(const DeckKeyword& keyword, const std::size_t tableIdx); + static PvtsolTable serializationTestObject(); bool operator==(const PvtsolTable& data) const; + + private: + void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) override; }; -} -#endif +} // namespace Opm + +#endif // OPM_PARSER_PVTSOL_TABLE_HPP diff --git a/opm/input/eclipse/EclipseState/Tables/PvtxTable.cpp b/opm/input/eclipse/EclipseState/Tables/PvtxTable.cpp index 57a6cdae852..ed13fbe3e4b 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtxTable.cpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtxTable.cpp @@ -197,6 +197,7 @@ namespace Opm { ranges[*tableIdx].first, ranges[*tableIdx].second); + this->populateMissingUndersaturatedStates(); this->populateSaturatedTable(keyword.name()); } @@ -240,4 +241,53 @@ namespace Opm { } } + void PvtxTable::populateMissingUndersaturatedStates() + { + for (const auto& [src, dest] : this->missingUSatTables()) { + this->makeScaledUSatTableCopy(src, dest); + } + } + + std::vector> + PvtxTable::missingUSatTables() const + { + auto missing = std::vector>{}; + + if (this->m_underSaturatedTables.empty()) { + return missing; + } + + auto src = this->m_underSaturatedTables.size() - 1; + + for (auto destIx = src + 1; destIx > 0; --destIx) { + const auto dest = destIx - 1; + + if (this->m_underSaturatedTables[dest].numRows() > 1) { + // There are undersaturated states in 'dest'. This is the + // new 'src'. + src = dest; + } + else { + // There are no undersaturated states in 'dest'. Schedule + // generation of a scaled copy of 'src's undersaturated + // states in 'dest'. + missing.emplace_back(src, dest); + } + } + + return missing; + } + + void PvtxTable::makeScaledUSatTableCopy([[maybe_unused]] const std::size_t src, + [[maybe_unused]] const std::size_t dest) + { + // Implemented only because we need to be able to create objects of + // type 'PvtxTable' for serialisation purposes. Ideally, this would + // be a pure virtual function. + + throw std::runtime_error { + "Derived type does not implement makeScaledUSatTableCopy()" + }; + } + } // namespace Opm diff --git a/opm/input/eclipse/EclipseState/Tables/PvtxTable.hpp b/opm/input/eclipse/EclipseState/Tables/PvtxTable.hpp index 8b47c361502..c24e6eea90f 100644 --- a/opm/input/eclipse/EclipseState/Tables/PvtxTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/PvtxTable.hpp @@ -297,6 +297,39 @@ namespace Opm { /// \param[in] tableName Name of table/keyword we're internalising. /// Typically \code "PVTO" \endcode or \code "PVTG" \endcode. void populateSaturatedTable(const std::string& tableName); + + /// Fill in any missing under-saturated states. + /// + /// Takes scaled copies of under-saturated curves at higher + /// composition/pressure nodes. Amends m_underSaturatedTables. + void populateMissingUndersaturatedStates(); + + /// Identify missing under-saturated states in + /// m_underSaturatedTables. + /// + /// \return Pairs of source/destination indices. The + /// under-saturated destination entries in m_underSaturatedTables + /// will be scaled copies of the under-saturated source entries in + /// m_underSaturatedTables. + std::vector> + missingUSatTables() const; + + /// Generate scaled copies of under-saturated state curves. + /// + /// Intended to amend a specific entry in m_underSaturatedTables + /// based on source values in another specific entry in + /// m_underSaturatedTables. + /// + /// Virtual function in order to call back into the derived type for + /// type-specific copying. + /// + /// \param[in] src Index of source table with full set of + /// under-saturated states. + /// + /// \param[in] dest Index of destination table with no + /// under-saturated states. + virtual void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest); }; } // namespace Opm diff --git a/opm/input/eclipse/EclipseState/Tables/RwgsaltTable.hpp b/opm/input/eclipse/EclipseState/Tables/RwgsaltTable.hpp index 8acde25a1de..043256767d3 100644 --- a/opm/input/eclipse/EclipseState/Tables/RwgsaltTable.hpp +++ b/opm/input/eclipse/EclipseState/Tables/RwgsaltTable.hpp @@ -16,25 +16,38 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . - */ +*/ + #ifndef OPM_PARSER_RWGSALT_TABLE_HPP -#define OPM_PARSER_RWGSALT_TABLE_HPP +#define OPM_PARSER_RWGSALT_TABLE_HPP #include +#include + namespace Opm { class DeckKeyword; -class RwgsaltTable : public PvtxTable { +} // namespace Opm + +namespace Opm { + +class RwgsaltTable : public PvtxTable +{ public: RwgsaltTable() = default; - RwgsaltTable( const DeckKeyword& keyword, size_t tableIdx); + RwgsaltTable(const DeckKeyword& keyword, std::size_t tableIdx); static RwgsaltTable serializationTestObject(); bool operator==(const RwgsaltTable& data) const; + +private: + void makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) override; }; -} -#endif +} // namespace Opm + +#endif // OPM_PARSER_RWGSALT_TABLE_HPP diff --git a/opm/input/eclipse/EclipseState/Tables/Tables.cpp b/opm/input/eclipse/EclipseState/Tables/Tables.cpp index 74a9df40d50..339750fbc4c 100644 --- a/opm/input/eclipse/EclipseState/Tables/Tables.cpp +++ b/opm/input/eclipse/EclipseState/Tables/Tables.cpp @@ -110,6 +110,7 @@ #include #include +#include #include #include #include @@ -405,6 +406,48 @@ namespace { return this->satTable_.get().getColumn(1)[row]; } + void makeScaledUSatTableCopy(const std::string& tableName, + const Opm::SimpleTable& src, + Opm::SimpleTable& dst) + { + const auto ncol = dst.numColumns(); + + assert (src.numColumns() == ncol); + + auto rows = std::array { + // Current row values and row values being prepared for next + // addRow() call. Role switches between the two, thus enabling + // the "next" row to become the current row in the subsequent + // iteration. This means we compute each row's values exactly + // once. + std::vector(ncol), + std::vector(ncol) + }; + + auto curr = 0*rows.size(); + + for (auto col = 0*ncol; col < ncol; ++col) { + rows[curr][col] = dst.get(col, 0); + } + + for (auto row = 1 + 0*src.numRows(); row < src.numRows(); ++row) { + const auto next = 1 - curr; // => 1, 0, 1, 0, 1, ... + + // Primary variable. Linear. + rows[next][0] = rows[curr][0] + src.get(0, row) - src.get(0, row - 1); + + // FVF, viscosity &c. Scaled copy. + for (auto col = 1 + 0*ncol; col < ncol; ++col) { + const auto scale = rows[curr][col] / src.get(col, row - 1); + rows[next][col] = scale * src.get(col, row); + } + + dst.addRow(rows[next], tableName); + + curr = next; + } + } + } // Anonymous namespace namespace Opm @@ -563,7 +606,18 @@ PvtgTable::operator==(const PvtgTable& data) const return static_cast(*this) == static_cast(data); } -PvtgwTable::PvtgwTable(const DeckKeyword& keyword, size_t tableIdx) +void PvtgTable::makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) +{ + assert (dest < src); + assert (this->m_underSaturatedTables[dest].numRows() == 1); + + ::makeScaledUSatTableCopy("PVTG", + this->m_underSaturatedTables[src], + this->m_underSaturatedTables[dest]); +} + +PvtgwTable::PvtgwTable(const DeckKeyword& keyword, const std::size_t tableIdx) : PvtxTable("P") { m_underSaturatedSchema.addColumn(ColumnSchema("RW", Table::STRICTLY_DECREASING, Table::DEFAULT_NONE)); @@ -593,7 +647,11 @@ PvtgwTable::operator==(const PvtgwTable& data) const return static_cast(*this) == static_cast(data); } -PvtgwoTable::PvtgwoTable(const DeckKeyword& keyword, size_t tableIdx) +void PvtgwTable::makeScaledUSatTableCopy([[maybe_unused]] const std::size_t src, + [[maybe_unused]] const std::size_t dest) +{} + +PvtgwoTable::PvtgwoTable(const DeckKeyword& keyword, const std::size_t tableIdx) : PvtxTable("P") { @@ -626,7 +684,11 @@ PvtgwoTable::operator==(const PvtgwoTable& data) const return static_cast(*this) == static_cast(data); } -PvtoTable::PvtoTable(const DeckKeyword& keyword, size_t tableIdx) +void PvtgwoTable::makeScaledUSatTableCopy([[maybe_unused]] const std::size_t src, + [[maybe_unused]] const std::size_t dest) +{} + +PvtoTable::PvtoTable(const DeckKeyword& keyword, const std::size_t tableIdx) : PvtxTable("RS") { m_underSaturatedSchema.addColumn(ColumnSchema("P", Table::STRICTLY_INCREASING, Table::DEFAULT_NONE)); @@ -683,7 +745,18 @@ PvtoTable::nonMonotonicSaturatedFVF() const return nonmonoFVF; } -PvtsolTable::PvtsolTable(const DeckKeyword& keyword, size_t tableIdx) +void PvtoTable::makeScaledUSatTableCopy(const std::size_t src, + const std::size_t dest) +{ + assert (dest < src); + assert (this->m_underSaturatedTables[dest].numRows() == 1); + + ::makeScaledUSatTableCopy("PVTO", + this->m_underSaturatedTables[src], + this->m_underSaturatedTables[dest]); +} + +PvtsolTable::PvtsolTable(const DeckKeyword& keyword, const std::size_t tableIdx) : PvtxTable("ZCO2") { m_underSaturatedSchema.addColumn(ColumnSchema("P", Table::STRICTLY_INCREASING, Table::DEFAULT_NONE)); @@ -725,10 +798,13 @@ PvtsolTable::operator==(const PvtsolTable& data) const return static_cast(*this) == static_cast(data); } +void PvtsolTable::makeScaledUSatTableCopy([[maybe_unused]] const std::size_t src, + [[maybe_unused]] const std::size_t dest) +{} + RwgsaltTable::RwgsaltTable(const DeckKeyword& keyword, size_t tableIdx) : PvtxTable("P") { - m_underSaturatedSchema.addColumn(ColumnSchema("C_SALT", Table::INCREASING, Table::DEFAULT_NONE)); m_underSaturatedSchema.addColumn(ColumnSchema("RVW", Table::RANDOM, Table::DEFAULT_LINEAR)); @@ -754,6 +830,10 @@ RwgsaltTable::operator==(const RwgsaltTable& data) const return static_cast(*this) == static_cast(data); } +void RwgsaltTable::makeScaledUSatTableCopy([[maybe_unused]] const std::size_t src, + [[maybe_unused]] const std::size_t dest) +{} + SpecheatTable::SpecheatTable(const DeckItem& item, const int tableID) { m_schema.addColumn(ColumnSchema("TEMPERATURE", Table::STRICTLY_INCREASING, Table::DEFAULT_NONE)); diff --git a/tests/parser/TableManagerTests.cpp b/tests/parser/TableManagerTests.cpp index b057aa7f373..d7a23ea952b 100644 --- a/tests/parser/TableManagerTests.cpp +++ b/tests/parser/TableManagerTests.cpp @@ -1134,6 +1134,108 @@ END } } +BOOST_AUTO_TEST_CASE(PvtoTable_Tests_PopulateUSat) { + // PVT tables from opm-tests/model5/include/pvt_live_oil_dgas.ecl . + const auto deck = Opm::Parser{}.parseString(R"(RUNSPEC +OIL +GAS +TABDIMS +1 1 / +PROPS +DENSITY + 924.1 1026.0 1.03446 / +PVTO +-- Table number: 1 + 3.9140 10.000 1.102358 2.8625 / + + 7.0500 15.000 1.112540 2.6589 + 25.000 1.111313 2.7221 + 45.000 1.108952 2.8374 / +/ +END +)"); + + const auto tmgr = Opm::TableManager { deck }; + const auto& pvto = tmgr.getPvtoTables(); + BOOST_REQUIRE_EQUAL(pvto.size(), std::size_t{1}); + + { + const auto& t1 = pvto[0]; + + BOOST_REQUIRE_EQUAL(t1.size(), std::size_t{2}); + + const auto& satTbl = t1.getSaturatedTable(); + { + BOOST_REQUIRE_EQUAL(satTbl.numRows(), std::size_t{2}); + BOOST_REQUIRE_EQUAL(satTbl.numColumns(), std::size_t{4}); + + const auto& rs = satTbl.getColumn(0); + BOOST_CHECK_CLOSE(rs[0], 3.914, 1.0e-8); + BOOST_CHECK_CLOSE(rs[1], 7.05, 1.0e-8); + + const auto& p = satTbl.getColumn(1); + BOOST_CHECK_CLOSE(p[0], 1.0e6, 1.0e-8); + BOOST_CHECK_CLOSE(p[1], 1.5e6, 1.0e-8); + + const auto& B = satTbl.getColumn(2); + BOOST_CHECK_CLOSE(B[0], 1.102358, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.11254, 1.0e-8); + + const auto& mu = satTbl.getColumn(3); + BOOST_CHECK_CLOSE(mu[0], 2.8625e-3, 1.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.6589e-3, 1.0e-8); + } + + { + const auto& u1 = t1.getUnderSaturatedTable(0); + BOOST_REQUIRE_EQUAL(u1.numRows(), std::size_t{3}); + BOOST_REQUIRE_EQUAL(u1.numColumns(), std::size_t{3}); + + const auto& p = u1.getColumn(0); + BOOST_REQUIRE_EQUAL(p.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(p[0], 1.0e6, 1.0e-8); + BOOST_CHECK_CLOSE(p[1], 2.0e6, 1.0e-8); + BOOST_CHECK_CLOSE(p[2], 4.0e6, 1.0e-8); + + const auto& B = u1.getColumn(1); + BOOST_REQUIRE_EQUAL(B.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(B[0], 1.102358, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.101142, 3.0e-5); + BOOST_CHECK_CLOSE(B[2], 1.098803, 2.0e-5); + + const auto& mu = u1.getColumn(2); + BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(mu[0], 2.8625e-3, 1.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.9305e-3, 1.4e-3); + BOOST_CHECK_CLOSE(mu[2], 3.0547e-3, 1.1e-3); + } + + { + const auto& u2 = t1.getUnderSaturatedTable(1); + BOOST_REQUIRE_EQUAL(u2.numRows(), std::size_t{3}); + BOOST_REQUIRE_EQUAL(u2.numColumns(), std::size_t{3}); + + const auto& p = u2.getColumn(0); + BOOST_REQUIRE_EQUAL(p.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(p[0], 1.5e6, 1.0e-8); + BOOST_CHECK_CLOSE(p[1], 2.5e6, 1.0e-8); + BOOST_CHECK_CLOSE(p[2], 4.5e6, 1.0e-8); + + const auto& B = u2.getColumn(1); + BOOST_REQUIRE_EQUAL(B.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(B[0], 1.112540, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.111313, 1.0e-8); + BOOST_CHECK_CLOSE(B[2], 1.108952, 1.0e-8); + + const auto& mu = u2.getColumn(2); + BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(mu[0], 2.6589e-3, 1.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.7221e-3, 1.0e-8); + BOOST_CHECK_CLOSE(mu[2], 2.8374e-3, 1.0e-8); + } + } +} + BOOST_AUTO_TEST_CASE(PvtgTable_Tests) { // PVT tables from opm-tests/norne/INCLUDE/PVT/PVT-WET-GAS.INC . const auto deck = Opm::Parser{}.parseString(R"(RUNSPEC @@ -1170,8 +1272,9 @@ END // bar, and one for p=pLim=3.002 bar. BOOST_REQUIRE_EQUAL(t1.size(), std::size_t{4}); - const auto& satTbl = t1.getSaturatedTable(); { + const auto& satTbl = t1.getSaturatedTable(); + BOOST_REQUIRE_EQUAL(satTbl.numRows(), std::size_t{4}); BOOST_REQUIRE_EQUAL(satTbl.numColumns(), std::size_t{4}); @@ -1203,40 +1306,52 @@ END { const auto& u1 = t1.getUnderSaturatedTable(0); - BOOST_REQUIRE_EQUAL(u1.numRows(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(u1.numRows(), std::size_t{3}); BOOST_REQUIRE_EQUAL(u1.numColumns(), std::size_t{3}); const auto& rv = u1.getColumn(0); - BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{1}); - BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_CHECK_CLOSE(rv[1], 1.916029e-06, 5.0e-7); + BOOST_CHECK_CLOSE(rv[2], -5.639710e-07, 2.0e-6); // <-- Note: Negative const auto& B = u1.getColumn(1); - BOOST_REQUIRE_EQUAL(B.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(B.size(), std::size_t{3}); BOOST_CHECK_CLOSE(B[0], 1.1, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.1, 1.0e-8); + BOOST_CHECK_CLOSE(B[2], 1.1, 1.0e-8); const auto& mu = u1.getColumn(2); constexpr auto cP = prefix::centi*unit::Poise; - BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{3}); BOOST_CHECK_CLOSE(mu[0], 2.63557694e-3*cP, 2.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.63374795e-3*cP, 1.0e-7); + BOOST_CHECK_CLOSE(mu[2], 2.63374795e-3*cP, 1.0e-7); } { const auto& u2 = t1.getUnderSaturatedTable(1); - BOOST_REQUIRE_EQUAL(u2.numRows(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(u2.numRows(), std::size_t{3}); BOOST_REQUIRE_EQUAL(u2.numColumns(), std::size_t{3}); const auto& rv = u2.getColumn(0); - BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{1}); - BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_CHECK_CLOSE(rv[1], 1.916029e-06, 5.0e-7); + BOOST_CHECK_CLOSE(rv[2], -5.639710e-07, 2.0e-6); // <-- Note: Negative const auto& B = u2.getColumn(1); - BOOST_REQUIRE_EQUAL(B.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(B.size(), std::size_t{3}); BOOST_CHECK_CLOSE(B[0], 1.0, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.0, 1.0e-8); + BOOST_CHECK_CLOSE(B[2], 1.0, 1.0e-8); const auto& mu = u2.getColumn(2); constexpr auto cP = prefix::centi*unit::Poise; - BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{3}); BOOST_CHECK_CLOSE(mu[0], 2.63557694e-3*cP, 2.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.63374795e-3*cP, 1.0e-7); + BOOST_CHECK_CLOSE(mu[2], 2.63374795e-3*cP, 1.0e-7); } { @@ -1295,8 +1410,9 @@ END // bar, and one for p=pLim=3.002 bar. BOOST_REQUIRE_EQUAL(t2.size(), std::size_t{4}); - const auto& satTbl = t2.getSaturatedTable(); { + const auto& satTbl = t2.getSaturatedTable(); + BOOST_REQUIRE_EQUAL(satTbl.numRows(), std::size_t{4}); BOOST_REQUIRE_EQUAL(satTbl.numColumns(), std::size_t{4}); @@ -1328,40 +1444,52 @@ END { const auto& u1 = t2.getUnderSaturatedTable(0); - BOOST_REQUIRE_EQUAL(u1.numRows(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(u1.numRows(), std::size_t{3}); BOOST_REQUIRE_EQUAL(u1.numColumns(), std::size_t{3}); const auto& rv = u1.getColumn(0); - BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{1}); - BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_CHECK_CLOSE(rv[1], 1.916029e-06, 5.0e-7); + BOOST_CHECK_CLOSE(rv[2], -5.639710e-07, 2.0e-6); // <-- Note: Negative const auto& B = u1.getColumn(1); - BOOST_REQUIRE_EQUAL(B.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(B.size(), std::size_t{3}); BOOST_CHECK_CLOSE(B[0], 1.1, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.1, 1.0e-8); + BOOST_CHECK_CLOSE(B[2], 1.1, 1.0e-8); const auto& mu = u1.getColumn(2); constexpr auto cP = prefix::centi*unit::Poise; - BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{3}); BOOST_CHECK_CLOSE(mu[0], 2.63557694e-3*cP, 2.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.63374795e-3*cP, 1.0e-7); + BOOST_CHECK_CLOSE(mu[2], 2.63374795e-3*cP, 1.0e-7); } { const auto& u2 = t2.getUnderSaturatedTable(1); - BOOST_REQUIRE_EQUAL(u2.numRows(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(u2.numRows(), std::size_t{3}); BOOST_REQUIRE_EQUAL(u2.numColumns(), std::size_t{3}); const auto& rv = u2.getColumn(0); - BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{1}); - BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_REQUIRE_EQUAL(rv.size(), std::size_t{3}); + BOOST_CHECK_CLOSE(rv[0], 4.406029e-06, 2.0e-7); + BOOST_CHECK_CLOSE(rv[1], 1.916029e-06, 5.0e-7); + BOOST_CHECK_CLOSE(rv[2], -5.639710e-07, 2.0e-6); // <-- Note: Negative const auto& B = u2.getColumn(1); - BOOST_REQUIRE_EQUAL(B.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(B.size(), std::size_t{3}); BOOST_CHECK_CLOSE(B[0], 1.0, 1.0e-8); + BOOST_CHECK_CLOSE(B[1], 1.0, 1.0e-8); + BOOST_CHECK_CLOSE(B[2], 1.0, 1.0e-8); const auto& mu = u2.getColumn(2); constexpr auto cP = prefix::centi*unit::Poise; - BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{1}); + BOOST_REQUIRE_EQUAL(mu.size(), std::size_t{3}); BOOST_CHECK_CLOSE(mu[0], 2.63557694e-3*cP, 2.0e-8); + BOOST_CHECK_CLOSE(mu[1], 2.63374795e-3*cP, 1.0e-7); + BOOST_CHECK_CLOSE(mu[2], 2.63374795e-3*cP, 1.0e-7); } { diff --git a/tests/test_Tables.cpp b/tests/test_Tables.cpp index 06a79e330c6..c55e3f608fa 100644 --- a/tests/test_Tables.cpp +++ b/tests/test_Tables.cpp @@ -2187,12 +2187,12 @@ PVTG // // Table 1 (Pg 1, 1 barsa, padding value) 1.463987928388746e-04, 0.90909090909090906 , 1.061101444733158e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + 6.398792838874646e-06, 0.91258070144826386 , 1.047272673828073e+02, -2.492708826682003e+01, 9.877693503632658e+03, // Table 1 (Pg 2, pLim, padding value) --------------------------------------------------------------------------------------- 1.463987928388746e-04, 1.0 , 1.167211589206474e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + 6.398792838874646e-06, 1.0038387715930901 , 1.151999941210880e+02, -2.741979709350069e+01, 1.086546285399603e+04, // Table 1 (Pg 3) ------------------------------------------------------------------------------------------------------------ @@ -2233,42 +2233,42 @@ PVTG // Table 2 (Pg 1, 1 barsa, padding value) 1.463987928388746e-04, 0.90909090909090906 , 1.061101444733159e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -4.536012071611531e-04, 0.89898989898989889 , 1.148689112733881e+02, 1.683501683501694e+01, -1.459794466678704e+04, // Table 2 (Pg 2, pLim, padding value) --------------------------------------------------------------------------------------- 1.463987928388746e-04, 1.0 , 1.167211589206474e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -4.536012071611253e-04, 0.98888888888888882 , 1.263558024007269e+02, 1.851851851851864e+01, -1.605773913346575e+04, // Table 2 (Pg 3) ------------------------------------------------------------------------------------------------------------ 1.400000000000000e-04, 1.912045889101339e+01, 8.171136278210848e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -4.600000000000000e-04, 1.890800934777990e+01, 8.845615400896962e+02, 3.540825720558042e+02, -1.124131871143523e+05, // Table 2 (Pg 4) ------------------------------------------------------------------------------------------------------------ 1.200000000000000e-04, 7.575757575757576e+01, 3.006253006253006e+03, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -4.800000000000000e-04, 7.491582491582491e+01, 3.254401467029136e+03, 1.402918069584753e+03, -4.135807679602166e+05, // Table 2 (Pg 5) ------------------------------------------------------------------------------------------------------------ 1.500000000000000e-04, 1.140250855188141e+02, 4.057832224868830e+03, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -4.500000000000000e-04, 1.127581401241606e+02, 4.392782350022811e+03, 2.111575657755841e+03, -5.582502085899691e+05, // Table 2 (Pg 6) ------------------------------------------------------------------------------------------------------------ 1.900000000000000e-04, 1.805054151624549e+02, 5.676270917058329e+03, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -4.100000000000000e-04, 1.784997994384276e+02, 6.144813613926002e+03, 3.342692873378847e+03, -7.809044947794550e+05, // Table 2 (Pg 7) ------------------------------------------------------------------------------------------------------------ 2.900000000000000e-04, 2.398081534772182e+02, 6.755159252879387e+03, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -3.100000000000000e-04, 2.371436184385825e+02, 7.312757820735446e+03, 4.440891731059603e+03, -9.293309464267653e+05, // Table 2 (Pg 8) ------------------------------------------------------------------------------------------------------------ 4.900000000000000e-04, 2.801120448179272e+02, 7.145715429028755e+03, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + -1.100000000000000e-04, 2.769996887643947e+02, 7.735552106503829e+03, 5.187260089220918e+03, -9.830611291251233e+05, // Table 2 (Pg 9) ------------------------------------------------------------------------------------------------------------ @@ -2363,24 +2363,24 @@ PVTG // Table 1 (Pg 1, 1 barsa, padding value) 4.406028992878809e-06, 0.90909090909090906 , 3.449305141722682e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + 1.916028992878809e-06, 0.90909090909090906 , 3.451700492515545e+02, 0.0 , -9.619882702262125e+04, + -5.639710071211913e-07, 0.90909090909090906 , 3.451700492515545e+02, 0.0 , 0.0 , -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, // Table 1 (Pg 2, pLim, padding value) --------------------------------------------------------------------------------------- 4.406028992878809e-06, 1.0 , 3.794235655894950e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + 1.916028992878809e-06, 1.0 , 3.796870541767099e+02, 0.0 , -1.058187097248628e+05, + -5.639710071211913e-07, 1.0 , 3.796870541767099e+02, 0.0 , 0.0 , -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, // Table 1 (Pg 3) ------------------------------------------------------------------------------------------------------------ 4.970000000000000e-06, 4.006731308598445e+01, 2.780521380012800e+03, -2.000000000000000e+20, -2.000000000000000e+20, - 2.480000000000000e-06, 4.006731308598445e+01, 2.782452297637809e+03, 0, -7.754689257064208e+05, - 0, 4.006731308598445e+01, 2.782452297637809e+03, 0, 0, + 2.480000000000000e-06, 4.006731308598445e+01, 2.782452297637809e+03, 0.0 , -7.754689257064208e+05, + 0, 4.006731308598445e+01, 2.782452297637809e+03, 0.0 , 0.0 , -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, @@ -2512,20 +2512,20 @@ PVTG // Table 1 (Pg 1, p=1 barsa, padding value) 4.406028992878809e-06, 0.90909090909090906 , 3.449305141722682e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + 1.916028992878809e-06, 0.90909090909090906 , 3.451700492515545e+02, 0.0 , -9.619882702262125e+04, + -5.639710071211913e-07, 0.90909090909090906 , 3.451700492515545e+02, 0.0 , 0.0 , // Table 1 (Pg 2, p=pLim, padding value) ------------------------------------------------------------------------------------- 4.406028992878809e-06, 1.0 , 3.794235655894950e+02, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, - -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, -2.000000000000000e+20, + 1.916028992878809e-06, 1.0 , 3.796870541767099e+02, 0.0 , -1.058187097248628e+05, + -5.639710071211913e-07, 1.0 , 3.796870541767099e+02, 0.0 , 0.0 , // Table 1 (Pg 3) ------------------------------------------------------------------------------------------------------------ 4.970000000000000e-06, 4.006731308598445e+01, 2.780521380012800e+03, -2.000000000000000e+20, -2.000000000000000e+20, - 2.480000000000000e-06, 4.006731308598445e+01, 2.782452297637809e+03, 0, -7.754689257064208e+05, - 0, 4.006731308598445e+01, 2.782452297637809e+03, 0, 0, + 2.480000000000000e-06, 4.006731308598445e+01, 2.782452297637809e+03, 0.0 , -7.754689257064208e+05, + 0, 4.006731308598445e+01, 2.782452297637809e+03, 0.0 , 0.0 , // Table 1 (Pg 4) ------------------------------------------------------------------------------------------------------------ @@ -2957,14 +2957,14 @@ PVTO // // Table 1 (Comp 1) 1.014700000000000e+03, 7.722007722007722e-01, 9.303623761455088e-01, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, + 5.014700000000000e+03, 8.122111749054753e-01, 6.963183654412739e-01, 1.000260067617578e-05, -5.851100267605872e-05, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, // Table 1 (Comp 2) ---------------------------------------------------------------------------------------------------------- 3.014700000000000e+03, 6.389776357827476e-01, 1.075719925560181e+00, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, + 7.014700000000000e+03, 6.720852853051696e-01, 8.051094492255388e-01, 8.276912380605518e-06, -6.765261908366062e-05, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, @@ -2979,7 +2979,7 @@ PVTO // Table 2 (Comp 1) 1.470000000000000e+01, 9.416195856873822e-01, 9.054034477763291e-01, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, + 5.014700000000000e+03, 1.010794932071003 , 6.698355189139958e-01, 1.383506927672406e-05, -4.711358577246664e-05, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, @@ -3282,10 +3282,10 @@ PVTO // Table 1 (Comp 2) ---------------------------------------------------------------------------------------------------------- 7.000000000000000e+01, 8.887150957146157e-01, 8.336914593945738e-01, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, - 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, 2.000000000000000e+20, + 9.500000000000000e+01, 8.927172462556416e-01, 7.975674495270629e-01, 1.600860216410370e-04, -1.444960394700433e-03, + 1.200000000000000e+02, 8.964836919960728e-01, 7.645264301518615e-01, 1.506578296172467e-04, -1.321640775008057e-03, + 1.450000000000000e+02, 9.000314821716637e-01, 7.341801796000192e-01, 1.419116070236371e-04, -1.213850022073691e-03, + 1.700000000000000e+02, 9.033786969516677e-01, 7.062059857345745e-01, 1.338885912001597e-04, -1.118967754617790e-03, // Table 1 (Comp 3) ---------------------------------------------------------------------------------------------------------- From 6a9e19e061644cb19d52e18b4653e4b1cd058eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A5rd=20Skaflestad?= Date: Thu, 3 Apr 2025 16:15:35 +0200 Subject: [PATCH 2/2] Add Low Pressure PVT Table Padding for Live Oil (PVTO) This commit adds a procedure for padding live oil (PVTO) tables at low oil pressures. The primary objective is to avoid generating negative Rs values when extrapolating the RsSat(po) curve linearly to low oil pressures. To this end, compute a limiting pressure based on inverse interpolation at Rs=1.0e-6 (could possibly be made configurable) and pad the table if this limiting pressure exceeds atmospheric pressure. We pad the table with a composition node at Rs=0. This node is then assigned either to atmospheric pressure or the limiting pressure value. Associate Bo and vo values defined by linear extrapolation to these pressure values. We add a second composition node at Rs=1.0e-6 if the limiting pressure exceeds atmospheric pressure. --- .../eclipse/EclipseState/Tables/Tables.cpp | 628 +++++++++++++++--- 1 file changed, 548 insertions(+), 80 deletions(-) diff --git a/opm/input/eclipse/EclipseState/Tables/Tables.cpp b/opm/input/eclipse/EclipseState/Tables/Tables.cpp index 339750fbc4c..ec381c2d04a 100644 --- a/opm/input/eclipse/EclipseState/Tables/Tables.cpp +++ b/opm/input/eclipse/EclipseState/Tables/Tables.cpp @@ -126,6 +126,300 @@ namespace { + /// Low pressure PVT table + /// + /// Expected to be the first few nodes of a (saturated) property table + /// for oil or gas. + struct LowPressTable + { + /// Property sample + using Sample = std::array; + + /// Sampled pressure nodes from PVT table. + Sample pressure{}; + + /// Sampled mixing ratio (Rs or Rv) from PVT table. + Sample mixingRatio{}; + + /// Sampled FVF from PVT table (Bo or Bg). + Sample formationVolumeFactor{}; + + /// Sampled viscosity value from PVT table (vo or vg). + Sample viscosity{}; + }; + + /// Perform linear interpolation in two-point property table. + /// + /// \param[in] xi Abscissas. Frequently pressure values. + /// \param[in] yi Ordinates. Frequently Rs/Rv, or Bo/Bg values. + /// \param[in] x Interpolation point. + /// + /// \return Linearly interpolated/extrapolated property value 'y' at \p + /// x. + double linearInterpolation(const LowPressTable::Sample& xi, + const LowPressTable::Sample& yi, + const double x) + { + const auto t = (x - xi[0]) / (xi[1] - xi[0]); + + return (1.0 - t)*yi[0] + t*yi[1]; + } + + // ------------------------------------------------------------------------- + + /// Limiting pressure and property values for a low pressure gas table. + class LowGasPressureLimit + { + public: + /// Compute limiting pressure and associate property values + /// + /// Computes the pressure at which the formation volume factor Bg is + /// 10 times the minimum Bg value in the input table, or + /// 1.0--whichever is larger. Then extrapolates Rv and vg to this + /// pressure value. Determines that the input table needs padding + /// if the minimum input pressure value is greater than atmospheric + /// pressure and that the FVF extrapolated to atmospheric pressure + /// is less than the Bg value mentioned above. + /// + /// \param[in] lowPressTable Sampled PVT table at low pressures + /// values. Mixing ratio treated as Rv (vaporised oil/gas ratio). + void establishLowerLimit(const LowPressTable& lowPressTable); + + /// Whether or not the input table needs padding at low pressure + /// values. + bool needsPadding() const + { + return this->needsPadding_; + } + + /// Limiting pressure value. + double pressure() const + { + return this->pressure_; + } + + /// Vaporised oil/gas ratio (Rv) at limiting pressure value. + double vaporisedOilRatio() const + { + return this->rv_; + } + + /// Formation volume factor Bg at limiting pressure value. + double formationVolumeFactor() const + { + return this->formationVolumeFactor_; + } + + /// Gas viscosity value at limiting pressure value. + double viscosity() const + { + return this->viscosity_; + } + + private: + /// Limiting pressure value. + double pressure_{0.0}; + + /// Vaporised oil concentration at limiting pressure value. + double rv_{0.0}; + + /// Formation volume factor Bg at limiting pressure value. + double formationVolumeFactor_{0.0}; + + /// Gas viscosity at limiting pressure. + double viscosity_{0.0}; + + /// Whether or not input table needs padding at low pressures. + bool needsPadding_{false}; + }; + + void LowGasPressureLimit::establishLowerLimit(const LowPressTable& lowPressTable) + { + const auto& p = lowPressTable.pressure; + + const auto rv = LowPressTable::Sample { + lowPressTable.mixingRatio[0], + lowPressTable.mixingRatio[1], + }; + + const auto b = LowPressTable::Sample { + 1.0 / lowPressTable.formationVolumeFactor[0], // 1 / B(0) + 1.0 / lowPressTable.formationVolumeFactor[1], // 1 / B(1) + }; + + const auto recipBmu = LowPressTable::Sample { + b[0] / lowPressTable.viscosity[0], // 1 / (B(0) * mu(0)) + b[1] / lowPressTable.viscosity[1], // 1 / (B(1) * mu(1)) + }; + + this->formationVolumeFactor_ = std::max(10.0 / b[0], 1.0); + + this->pressure_ = linearInterpolation + (b, p, 1.0 / this->formationVolumeFactor_); + + this->rv_ = linearInterpolation(p, rv, this->pressure_); + + this->viscosity_ = 1.0 / + (this->formationVolumeFactor_ * + linearInterpolation(p, recipBmu, this->pressure_)); + + // We should pad the table if extrapolating the reciprocal FVF to one + // bar yields a smaller value than (typically) 0.1 * b[0]. + { + const auto p0 = 1.0*Opm::unit::barsa; + + this->needsPadding_ = (p[0] > p0) && + (linearInterpolation(p, b, p0) + < 1.0 / this->formationVolumeFactor_); + } + + if (! this->needsPadding_) { + // We should also pad the table if extrapolating the reciprocal FVF + // yields a negative value at zero pressure. + const auto p0 = 0.0*Opm::unit::barsa; + + this->needsPadding_ = (p[0] > p0) + && (linearInterpolation(p, b, p0) < 0.0); + + if (this->needsPadding_) { + // Set the limit object to contain the extra entry at zero + // pressure we want to insert. + this->formationVolumeFactor_ = + 1.1 * lowPressTable.formationVolumeFactor[0]; + + this->pressure_ = p0; + this->rv_ = rv[0]; + this->viscosity_ = lowPressTable.viscosity[0]; + } + } + } + + // ------------------------------------------------------------------------- + + /// Limiting pressure and property values for a low pressure oil table. + class LowOilPressureLimit + { + public: + /// Compute limiting pressure and associate property values + /// + /// Computes the pressure at which the dissolved gas/oil ratio + /// becomes zero. Then extrapolates Bo and vo to this pressure + /// value. Determines that the input table needs padding if the + /// limiting pressure value is greater than atmospheric pressure. + /// + /// \param[in] lowPressTable Sampled PVT table at low pressures + /// values. Mixing ratio treated as Rs (dissolved gas/oil ratio). + void establishLowerLimit(const LowPressTable& lowPressTable); + + /// Whether or not the input table needs padding at low pressure + /// values. + bool needsPadding() const + { + return this->needsPadding_; + } + + /// Limiting pressure value. + double pressure() const + { + return this->pressure_; + } + + /// Dissolved gas/oil ratio (Rs) at limiting pressure value. + double dissolvedGasRatio() const + { + return this->rs_; + } + + /// Formation volume factor Bo. + /// + /// First element (index zero) is Bo at atmospheric pressure while + /// second element (index one) is Bo at limiting pressure. + const LowPressTable::Sample& formationVolumeFactor() const + { + return this->formationVolumeFactor_; + } + + /// Oil viscosity value at limiting pressure value. + double viscosity() const + { + return this->viscosity_; + } + + private: + /// Limiting pressure value. + double pressure_{0.0}; + + /// Dissolved gas/oil ratio at limiting pressure. + double rs_{0.0}; + + /// Formation volume factor for oil. + LowPressTable::Sample formationVolumeFactor_{}; + + /// Oil viscosity at limiting pressure. + double viscosity_{0.0}; + + /// Whether or not input table needs padding at low pressures. + bool needsPadding_{false}; + }; + + void LowOilPressureLimit::establishLowerLimit(const LowPressTable& lowPressTable) + { + const auto rs = LowPressTable::Sample { + lowPressTable.mixingRatio[0], + lowPressTable.mixingRatio[1], + }; + + if (! (rs[0] > 0.0) || ! (rs[1] > rs[0])) { + // RsSat(p) is constant (= 0). Dead oil case. No need to pad + // table. + this->needsPadding_ = false; + return; + } + + const auto& p = lowPressTable.pressure; + const auto p0 = 1.0*Opm::unit::barsa; + + if (! (linearInterpolation(rs, p, 0.0) < p0)) { + // Extrapolated RsSat(p) curve goes to zero for p < p0 + // (atmospheric pressure). No need to pad table. + this->needsPadding_ = false; + return; + } + + // If we get here, the extrapolated RsSat(p) curve goes to zero for + // pressures greater than atmospheric pressure. Table needs padding + // to ensure that we don't get negative Rs values when extrapolating + // the curve during the run. Flag this situation and compute the + // limiting pressure/Rs/Bo/vo values. + + this->needsPadding_ = true; + + const auto b = LowPressTable::Sample { + 1.0 / lowPressTable.formationVolumeFactor[0], // 1 / B(0) + 1.0 / lowPressTable.formationVolumeFactor[1], // 1 / B(1) + }; + + const auto recipBmu = LowPressTable::Sample { + b[0] / lowPressTable.viscosity[0], // 1 / (B(0) * mu(0)) + b[1] / lowPressTable.viscosity[1], // 1 / (B(1) * mu(1)) + }; + + const auto smallRs = 1.0e-6; + + this->pressure_ = std::max(linearInterpolation(rs, p, smallRs), p0); + this->rs_ = linearInterpolation(p, rs, this->pressure_); + + this->formationVolumeFactor_[0] = 1.0 / linearInterpolation(p, b, p0); + this->formationVolumeFactor_[1] = + 1.0 / linearInterpolation(p, b, this->pressure_); + + this->viscosity_ = 1.0 / + (this->formationVolumeFactor_[1] * + linearInterpolation(p, recipBmu, this->pressure_)); + } + + // ------------------------------------------------------------------------- + /// Simple query interface for extracting tabulated values of saturated /// gas. /// @@ -161,118 +455,168 @@ namespace { virtual double vaporisedOil(const std::size_t row) const = 0; }; + // ------------------------------------------------------------------------- + /// Padding for gas property tables at low pressure - class LowPressureTablePadding + class LowPressureGasTablePadding { public: /// Constructor /// /// \param[in] Tabulated gas properties at saturated conditions - explicit LowPressureTablePadding(const GasPropertyTableInterface& prop); + explicit LowPressureGasTablePadding(const GasPropertyTableInterface& prop); /// Whether or not input table needs padding at low pressures - bool inputNeedsPadding() const { return this->needPadding_; } + bool inputNeedsPadding() const { return this->limit_.needsPadding(); } - /// Low pressure padding rows for input table. Needed only if - /// inputNeedsPadding() returns \c true. + /// Create padding table for saturated gas PVT at low pressures. + /// + /// Should only be called if inputNeedsPadding() returns true. One + /// or two padding rows for the satured table. Last or only row + /// corresponds to limiting pressure. If two rows, then first row + /// corresponds to atmospheric pressure. Opm::SimpleTable padding() const; private: /// Gas pressure values in rows zero and one of input table. - std::array p_{}; + double pMin_{0.0}; - /// Interpolated gas property values at "limiting" pressure - struct { - /// Limiting pressure - double p {0.0}; + /// Limiting gas pressure and associate property values. + LowGasPressureLimit limit_{}; + }; - /// Vaporised oil concentration at limiting pressure - double rv {0.0}; + LowPressureGasTablePadding::LowPressureGasTablePadding(const GasPropertyTableInterface& prop) + : pMin_ { prop.pressure(0) } + { + const auto lowPressTable = LowPressTable { + { this->pMin_, prop.pressure(1) }, // pressure + { prop.vaporisedOil(0), prop.vaporisedOil(1) }, // mixingRatio (== Rv) + { prop.fvf(0), prop.fvf(1) }, // formationVolumeFactor + { prop.viscosity(0), prop.viscosity(1) } // viscosity + }; - /// Formation volume factor at limiting pressure - double fvf {0.0}; + this->limit_.establishLowerLimit(lowPressTable); + } - /// Gas viscosity at limiting pressure - double mu {0.0}; - } limit_{}; + Opm::SimpleTable LowPressureGasTablePadding::padding() const + { + auto padSchema = Opm::TableSchema{}; + padSchema.addColumn(Opm::ColumnSchema("PG" , Opm::Table::STRICTLY_INCREASING, Opm::Table::DEFAULT_NONE)); + padSchema.addColumn(Opm::ColumnSchema("RV" , Opm::Table::RANDOM, Opm::Table::DEFAULT_NONE)); + padSchema.addColumn(Opm::ColumnSchema("BG" , Opm::Table::RANDOM, Opm::Table::DEFAULT_LINEAR)); + padSchema.addColumn(Opm::ColumnSchema("MUG", Opm::Table::RANDOM, Opm::Table::DEFAULT_LINEAR)); - /// Whether or not input table needs padding at low pressure values. - bool needPadding_{false}; - }; + auto padTable = Opm::SimpleTable { std::move(padSchema) }; + + const auto p0 = 1.0*Opm::unit::barsa; + + if (const auto pLimit = this->limit_.pressure(); pLimit < this->pMin_) { + if (p0 < pLimit) { + padTable.addRow({ + p0, + this->limit_.vaporisedOilRatio(), + this->limit_.formationVolumeFactor() * 1.1, + this->limit_.viscosity() + }, "PAD"); + } + + padTable.addRow({ + pLimit, + this->limit_.vaporisedOilRatio(), + this->limit_.formationVolumeFactor(), + this->limit_.viscosity() + }, "PAD"); + } + + return padTable; + } - LowPressureTablePadding::LowPressureTablePadding(const GasPropertyTableInterface& prop) - : p_{ prop.pressure(0), prop.pressure(1) } + // ------------------------------------------------------------------------- + + /// Padding for live oil property tables at low pressure. + class LowPressureOilTablePadding { - auto linInterp = [](const std::array& xi, - const std::array& yi, - const double x) - { - const auto t = (x - xi[0]) / (xi[1] - xi[0]); + public: + /// Constructor + /// + /// \param[in] Tabulated oil properties at saturated conditions + explicit LowPressureOilTablePadding(const Opm::PvtoTable& pvto); - return (1.0 - t)*yi[0] + t*yi[1]; - }; + /// Whether or not input table needs padding at low pressures + bool inputNeedsPadding() const { return this->limit_.needsPadding(); } - const auto rv = std::array { - prop.vaporisedOil(0), - prop.vaporisedOil(1), - }; + /// Create padding table for saturated oil PVT at low pressures. + /// + /// Should only be called if inputNeedsPadding() returns true. One + /// or two padding rows for the satured table. Last or only row + /// corresponds to limiting pressure. If two rows, then first row + /// corresponds to atmospheric pressure. First, or only, row + /// corresponds to an Rs value of zero. + Opm::SimpleTable padding() const; - const auto b = std::array { - 1.0 / prop.fvf(0), // 1 / B(0) - 1.0 / prop.fvf(1), // 1 / B(1) - }; + private: + /// Oil pressure values in rows zero and one of input table. + double pMin_{0.0}; + + /// Limiting oil pressure and associate pressure values. + LowOilPressureLimit limit_{}; + }; + + LowPressureOilTablePadding::LowPressureOilTablePadding(const Opm::PvtoTable& pvto) + : pMin_ { pvto.getSaturatedTable().getColumn(1)[0] } + { + const auto& satTable = pvto.getSaturatedTable(); + + const auto& rs = satTable.getColumn(0); + const auto& Bo = satTable.getColumn(2); + const auto& vo = satTable.getColumn(3); - const auto recipBmu = std::array { - b[0] / prop.viscosity(0), // 1 / (B(0) * mu(0)) - b[1] / prop.viscosity(1), // 1 / (B(1) * mu(1)) + const auto lowPressTable = LowPressTable { + { this->pMin_, satTable.getColumn(1)[1] }, // pressure + { rs[0], rs[1] }, // mixingRatio (== Rs) + { Bo[0], Bo[1] }, // formationVolumeFactor + { vo[0], vo[1] } // viscosity }; - this->limit_.fvf = std::max(10.0 / b[0], 1.0); - this->limit_.p = linInterp(b, this->p_, 1.0 / this->limit_.fvf); - this->limit_.rv = linInterp(this->p_, rv, this->limit_.p); - this->limit_.mu = 1.0 / - (this->limit_.fvf * linInterp(this->p_, recipBmu, this->limit_.p)); - - // We should pad the table if extrapolating b to one bar - // yields a smaller b factor than (typically) 0.1 * b[0]. - const auto p1bar = 1.0 * Opm::unit::barsa; - this->needPadding_ - = (this->p_[0] > p1bar) && (linInterp(this->p_, b, p1bar) < 1.0 / this->limit_.fvf); - - if (!this->needPadding_) { - // We should also pad the table if extrapolating b yields - // negative b factor at zero pressure. - const auto p0bar = 0.0 * Opm::unit::barsa; - this->needPadding_ = (this->p_[0] > p0bar) && (linInterp(this->p_, b, p0bar) < 0.0); - if (this->needPadding_) { - // Set the limit object to contain the extra entry at - // zero pressure we want to insert - this->limit_.fvf = 1.1 * prop.fvf(0); - this->limit_.p = p0bar; - this->limit_.rv = prop.vaporisedOil(0); - this->limit_.mu = prop.viscosity(0); - } - } + this->limit_.establishLowerLimit(lowPressTable); } - Opm::SimpleTable LowPressureTablePadding::padding() const + Opm::SimpleTable LowPressureOilTablePadding::padding() const { auto padSchema = Opm::TableSchema{}; - padSchema.addColumn(Opm::ColumnSchema("PG" , Opm::Table::STRICTLY_INCREASING, Opm::Table::DEFAULT_NONE)); - padSchema.addColumn(Opm::ColumnSchema("RV" , Opm::Table::RANDOM, Opm::Table::DEFAULT_NONE)); - padSchema.addColumn(Opm::ColumnSchema("BG" , Opm::Table::RANDOM, Opm::Table::DEFAULT_LINEAR)); - padSchema.addColumn(Opm::ColumnSchema("MUG", Opm::Table::RANDOM, Opm::Table::DEFAULT_LINEAR)); + padSchema.addColumn(Opm::ColumnSchema("RS", Opm::Table::STRICTLY_INCREASING, Opm::Table::DEFAULT_NONE)); + padSchema.addColumn(Opm::ColumnSchema("P" , Opm::Table::RANDOM, Opm::Table::DEFAULT_NONE)); + padSchema.addColumn(Opm::ColumnSchema("BO", Opm::Table::RANDOM, Opm::Table::DEFAULT_LINEAR)); + padSchema.addColumn(Opm::ColumnSchema("MU", Opm::Table::RANDOM, Opm::Table::DEFAULT_LINEAR)); auto padTable = Opm::SimpleTable { std::move(padSchema) }; - const auto p1bar = 1.0 * Opm::unit::barsa; - - if (this->limit_.p < this->p_[0]) { - if (p1bar < this->limit_.p) { - padTable.addRow({ p1bar, this->limit_.rv, 1.1 * this->limit_.fvf, this->limit_.mu }, "PAD"); + const auto p0 = 1.0*Opm::unit::barsa; + + if (const auto pLimit = this->limit_.pressure(); pLimit < this->pMin_) { + const auto addAtmospheric = p0 < pLimit; + if (addAtmospheric) { + // Atmospheric pressure, zero Rs. + padTable.addRow({ + 0.0, + p0, + this->limit_.formationVolumeFactor()[0], + this->limit_.viscosity() + }, "PAD"); } - padTable.addRow({ this->limit_.p, this->limit_.rv, this->limit_.fvf, this->limit_.mu }, "PAD"); + // Add row for imiting pressure. Rs at limiting pressure if we + // added a row for the atmospheric pressure above (p0 < pLimit). + // Zero Rs otherwise. + const auto rsLimit = ! addAtmospheric + ? 0.0 : this->limit_.dissolvedGasRatio(); + + padTable.addRow({ + rsLimit, + pLimit, + this->limit_.formationVolumeFactor()[1], + this->limit_.viscosity() + }, "PAD"); } return padTable; @@ -406,6 +750,8 @@ namespace { return this->satTable_.get().getColumn(1)[row]; } + // ------------------------------------------------------------------------- + void makeScaledUSatTableCopy(const std::string& tableName, const Opm::SimpleTable& src, Opm::SimpleTable& dst) @@ -570,7 +916,7 @@ PvtgTable::PvtgTable(const DeckKeyword& keyword, size_t tableIdx) return; } - if (const auto tablePadding = LowPressureTablePadding { WetGasTable { *this } }; + if (const auto tablePadding = LowPressureGasTablePadding { WetGasTable { *this } }; tablePadding.inputNeedsPadding()) { // Note: The padded PVTG table holds values for a single table/PVT @@ -688,6 +1034,92 @@ void PvtgwoTable::makeScaledUSatTableCopy([[maybe_unused]] const std::size_t src [[maybe_unused]] const std::size_t dest) {} +namespace { + DeckItem createPvtoItemZero(const DeckKeyword& pvtoTableInput) + { + return pvtoTableInput[0].getItem(0).emptyStructuralCopy(); + } + + DeckItem createPvtoItemOne(const DeckKeyword& pvtoTableInput) + { + return pvtoTableInput[0].getItem(1).emptyStructuralCopy(); + } + + DeckKeyword paddedPVTOTable(const SimpleTable& padding, + const DeckKeyword& pvtoTableInput, + const PvtoTable& pvtoTable) + { + auto paddedTable = pvtoTableInput.emptyStructuralCopy(); + + // Padding + { + const auto nrow = padding.numRows(); + const auto ncol = padding.numColumns(); + + for (auto row = 0*nrow; row < nrow; ++row) { + auto recordItems = std::vector { + createPvtoItemZero(pvtoTableInput), + createPvtoItemOne (pvtoTableInput) + }; + + { + auto& rs = recordItems.front(); + const auto& dim = rs.getActiveDimensions(); + rs.push_back(dim.front().convertSiToRaw(padding.get(0, row))); + } + + { + auto& rest = recordItems.back(); + const auto& dim = rest.getActiveDimensions(); + auto n = 0*dim.size(); + + for (auto col = 1 + 0*ncol; col < ncol; ++col, n = (n + 1) % dim.size()) { + rest.push_back(dim[n].convertSiToRaw(padding.get(col, row))); + } + } + + paddedTable.addRecord(DeckRecord { std::move(recordItems) }); + } + } + + // Original input table + for (auto rsIx = 0*pvtoTable.size(); rsIx < pvtoTable.size(); ++rsIx) { + auto recordItems = std::vector { + createPvtoItemZero(pvtoTableInput), + createPvtoItemOne (pvtoTableInput) + }; + + { + auto& rs = recordItems.front(); + const auto& dim = rs.getActiveDimensions(); + rs.push_back(dim.front().convertSiToRaw(pvtoTable.getArgValue(rsIx))); + } + + { + auto& rest = recordItems.back(); + const auto& dim = rest.getActiveDimensions(); + + const auto& rsTable = pvtoTable.getUnderSaturatedTable(rsIx); + const auto nrow = rsTable.numRows(); + const auto ncol = rsTable.numColumns(); + + for (auto row = 0*nrow; row < nrow; ++row) { + for (auto col = 0*ncol; col < ncol; ++col) { + rest.push_back(dim[col].convertSiToRaw(rsTable.get(col, row))); + } + } + } + + paddedTable.addRecord(DeckRecord { std::move(recordItems) }); + } + + // Resulting padded table holds values for just a single table/PVT + // region, even if pvtoTableInput holds tables for multiple PVT + // regions. + return paddedTable; + } +} // Anonymous namespace + PvtoTable::PvtoTable(const DeckKeyword& keyword, const std::size_t tableIdx) : PvtxTable("RS") { @@ -700,7 +1132,44 @@ PvtoTable::PvtoTable(const DeckKeyword& keyword, const std::size_t tableIdx) m_saturatedSchema.addColumn(ColumnSchema("BO", Table::RANDOM, Table::DEFAULT_LINEAR)); m_saturatedSchema.addColumn(ColumnSchema("MU", Table::RANDOM, Table::DEFAULT_LINEAR)); + // Run full table initialisation first. The downside to this is that we + // will end up throwing away and redoing that work if the table needs + // padding at low pressure values. On the other hand, the full table + // initialisation procedure also checks consistency and replaces any + // defaulted values which means we don't have to have special logic in + // place to handle those complexities if the table does need padding. + PvtxTable::init(keyword, tableIdx); + + if (this->size() <= 1) { + // At most a single Rs node in the input PVTO data. There is not + // enough information to perform table padding, even if it might be + // needed. We might for instance be running in the context of a + // unit test with incomplete data or the input might just be very + // sparse. In any case we can't perform table padding so just + // return here. + return; + } + + if (const auto tablePadding = LowPressureOilTablePadding { *this }; + tablePadding.inputNeedsPadding()) + { + // Note: The padded PVTO table holds values for a single table/PVT + // region, even if 'keyword' holds tables for multiple PVT regions. + // We therefore unconditionally pass '0' as the 'tableIdx' in this + // case. Moreover, PvtxTable::init() expects that both the outer + // column and the array of undersaturated tables are empty, so clear + // those here, once we've used their contents to form the padding + // table. + + const auto paddedTable = + paddedPVTOTable(tablePadding.padding(), keyword, *this); + + this->m_outerColumn = TableColumn { this->m_outerColumnSchema }; + this->m_underSaturatedTables.clear(); + + PvtxTable::init(paddedTable, 0); + } } PvtoTable @@ -1182,7 +1651,7 @@ PvdgTable::PvdgTable(const DeckItem& item, const int tableID) return; } - if (const auto tablePadding = LowPressureTablePadding { DryGasTable { *this } }; + if (const auto tablePadding = LowPressureGasTablePadding { DryGasTable { *this } }; tablePadding.inputNeedsPadding()) { SimpleTable::init(tableName, paddedPVDGTable(tablePadding.padding(), item, *this), tableID); @@ -1216,7 +1685,6 @@ PvdoTable::PvdoTable(const DeckItem& item, const int tableID) SimpleTable::init("PVDO", item, tableID); } - const TableColumn& PvdoTable::getPressureColumn() const {