PVTO: Add Means of Identifying Non-Monotonic Saturated FVFs
This commit introduces a way of diagnosing an uncommon but possible issue in the PVTO table. If the formation volume factor (BO) in the saturated table (record 1 of each subtable) does not increase monotonically as a function of the dissolved gas/oil ratio (RS), then there is a risk that the simulation run will have convergence problems. We add a special purpose member function PvtoTable::nonMonotonicSaturatedFVF which checks for this condition and returns a vector records for which the condition is true. The new member function TableManager::checkPVTOMonotonicity then collates these records into a formatted string which is printed to the console and the .PRT file. Example Diagnostic Output: Warning: Non-Monotonic Oil Formation Volume Factor Detected * PVTO [PVTNUM = 1] Record 9: FVF 1.23 at RS 123 is not greater than FVF 2.34 at RS 120 Record 10: FVF 1.22 at RS 125 is not greater than FVF 1.23 at RS 123 Record 11: FVF 1.21 at RS 125 is not greater than FVF 1.22 at RS 125
This commit is contained in:
parent
b5b1588a17
commit
1311afb261
@ -17,22 +17,34 @@
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef OPM_PARSER_PVTO_TABLE_HPP
|
||||
#define OPM_PARSER_PVTO_TABLE_HPP
|
||||
#define OPM_PARSER_PVTO_TABLE_HPP
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/Tables/PvtxTable.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Opm {
|
||||
|
||||
class DeckKeyword;
|
||||
|
||||
class PvtoTable : public PvtxTable {
|
||||
public:
|
||||
struct FlippedFVF {
|
||||
std::size_t i;
|
||||
std::array<double, std::size_t{2}> Rs;
|
||||
std::array<double, std::size_t{2}> Bo;
|
||||
};
|
||||
|
||||
PvtoTable() = default;
|
||||
PvtoTable(const DeckKeyword& keyword, size_t tableIdx);
|
||||
|
||||
static PvtoTable serializeObject();
|
||||
|
||||
bool operator==(const PvtoTable& data) const;
|
||||
|
||||
std::vector<FlippedFVF> nonMonotonicSaturatedFVF() const;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -491,6 +491,8 @@ namespace Opm {
|
||||
tableVector.emplace_back( tableKeyword , tableIdx );
|
||||
}
|
||||
|
||||
void checkPVTOMonotonicity(const Deck& deck) const;
|
||||
|
||||
std::map<std::string , TableContainer> m_simpleTables;
|
||||
std::vector<PvtgTable> m_pvtgTables;
|
||||
std::vector<PvtgwTable> m_pvtgwTables;
|
||||
|
@ -18,8 +18,13 @@
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include <opm/common/OpmLog/OpmLog.hpp>
|
||||
#include <opm/common/OpmLog/LogUtil.hpp>
|
||||
|
||||
#include <opm/parser/eclipse/Parser/ParserKeywords/A.hpp>
|
||||
@ -120,6 +125,10 @@ namespace Opm {
|
||||
initFullTables(deck, "PVTGWO", m_pvtgwoTables);
|
||||
initFullTables(deck, "PVTO", m_pvtoTables);
|
||||
|
||||
if (deck.hasKeyword<ParserKeywords::PVTO>()) {
|
||||
this->checkPVTOMonotonicity(deck);
|
||||
}
|
||||
|
||||
if( deck.hasKeyword( "PVTW" ) )
|
||||
this->m_pvtwTable = PvtwTable( deck.getKeyword( "PVTW" ) );
|
||||
|
||||
@ -1195,6 +1204,48 @@ namespace Opm {
|
||||
}
|
||||
}
|
||||
|
||||
void TableManager::checkPVTOMonotonicity(const Deck& deck) const
|
||||
{
|
||||
const auto& usys = deck.getActiveUnitSystem();
|
||||
const auto& tables = this->getPvtoTables();
|
||||
|
||||
using M = UnitSystem::measure;
|
||||
|
||||
const auto nDigit = [](const std::size_t n) {
|
||||
return 1 + static_cast<int>(std::floor(std::log10(n)));
|
||||
};
|
||||
|
||||
const auto nDigit_reg = nDigit(tables.size());
|
||||
|
||||
auto tableID = std::size_t{0};
|
||||
std::ostringstream os;
|
||||
for (const auto& pvto : tables) {
|
||||
++tableID; // One-based table ID
|
||||
const auto& flipped = pvto.nonMonotonicSaturatedFVF();
|
||||
if (flipped.empty()) { continue; }
|
||||
|
||||
const auto nDigit_rec = nDigit(flipped.back().i + 1);
|
||||
|
||||
os << " * PVTO [PVTNUM = " << std::setw(nDigit_reg) << tableID << "]\n";
|
||||
for (const auto& f : flipped) {
|
||||
os << " Record " << std::setw(nDigit_rec) << (f.i + 1)
|
||||
<< ": FVF " << std::setprecision(3)
|
||||
<< usys.from_si(M::oil_formation_volume_factor, f.Bo[1])
|
||||
<< " at RS "
|
||||
<< usys.from_si(M::gas_oil_ratio, f.Rs[1])
|
||||
<< " is not greater than FVF "
|
||||
<< usys.from_si(M::oil_formation_volume_factor, f.Bo[0])
|
||||
<< " at RS "
|
||||
<< usys.from_si(M::gas_oil_ratio, f.Rs[0]) << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (os.tellp() != std::streampos{0}) {
|
||||
// There were non-monotonic FVFs in saturated table
|
||||
OpmLog::warning("Non-Monotonic Oil Formation Volume Factor Detected\n" + os.str());
|
||||
}
|
||||
}
|
||||
|
||||
TableManager::SplitSimpleTables TableManager::splitSimpleTable(std::map<std::string,TableContainer>& simpleTables)
|
||||
{
|
||||
SplitSimpleTables result;
|
||||
|
@ -191,6 +191,30 @@ bool PvtoTable::operator==(const PvtoTable& data) const {
|
||||
return static_cast<const PvtxTable&>(*this) == static_cast<const PvtxTable&>(data);
|
||||
}
|
||||
|
||||
std::vector<PvtoTable::FlippedFVF>
|
||||
PvtoTable::nonMonotonicSaturatedFVF() const
|
||||
{
|
||||
auto nonmonoFVF = std::vector<FlippedFVF>{};
|
||||
|
||||
const auto& Rs = this->m_saturatedTable.getColumn("RS");
|
||||
const auto& Bo = this->m_saturatedTable.getColumn("BO");
|
||||
|
||||
const auto nrec = Rs.size();
|
||||
for (auto rec = 1 + 0*nrec; rec < nrec; ++rec) {
|
||||
if (Bo[rec] > Bo[rec - 1]) {
|
||||
// Normal case, Bo increases monotonically.
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& flip = nonmonoFVF.emplace_back();
|
||||
|
||||
flip.i = rec;
|
||||
flip.Rs[0] = Rs[rec - 1]; flip.Rs[1] = Rs[rec];
|
||||
flip.Bo[0] = Bo[rec - 1]; flip.Bo[1] = Bo[rec];
|
||||
}
|
||||
|
||||
return nonmonoFVF;
|
||||
}
|
||||
|
||||
SpecheatTable::SpecheatTable(const DeckItem& item)
|
||||
{
|
||||
@ -791,7 +815,7 @@ const TableColumn& WatvisctTable::getTemperatureColumn() const {
|
||||
|
||||
const TableColumn& WatvisctTable::getWaterViscosityColumn() const {
|
||||
return SimpleTable::getColumn(1);
|
||||
}
|
||||
}
|
||||
|
||||
GasvisctTable::GasvisctTable( const Deck& deck, const DeckItem& deckItem ) {
|
||||
int numComponents = deck.getKeyword<ParserKeywords::COMPS>().getRecord(0).getItem(0).get< int >(0);
|
||||
|
Loading…
Reference in New Issue
Block a user