/* Copyright 2017 Statoil 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 #include #include #include #include #include #include #include namespace { ::Opm::ECLPVT::ConvertUnits waterUnitConverter(const int usys) { using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI; const auto u = ::Opm::ECLUnits::createUnitSystem(usys); // [ Pref, 1/Bw, Cw, 1/(Bw*mu_w), Cw - Cv ] return ::Opm::ECLPVT::ConvertUnits { ToSI::pressure(*u), { ToSI::recipFvf(*u), ToSI::compressibility(*u), ToSI::recipFvfVisc(*u), ToSI::compressibility(*u) } }; } } // Anonymous class PVTCurves { public: using ElemIt = std::vector::const_iterator; using ConvertUnits = ::Opm::ECLPVT::ConvertUnits; PVTCurves(ElemIt xBegin, ElemIt xEnd, const ConvertUnits& convert, std::vector& colIt); std::vector formationVolumeFactor(const std::vector& pw) const { return this->evaluate(pw, [this](const double p) -> double { // 1 / (1 / B) return 1.0 / this->recipFvf(p); }); } std::vector viscosity(const std::vector& pw) const { return this->evaluate(pw, [this](const double p) -> double { // (1 / B) / (1 / (B * mu)) return this->recipFvf(p) / this->recipFvfVisc(p); }); } double& surfaceMassDensity() { return this->rhoS_; } double surfaceMassDensity() const { return this->rhoS_; } private: double pw_ref_ { 1.0 }; double recipFvf_ { 1.0 }; // 1 / B double recipFvfVisc_ { 1.0 }; // 1 / (B*mu) double Cw_ { 1.0 }; double diffCwCv_ { 0.0 }; // Cw - Cv double rhoS_ { 0.0 }; double recipFvf(const double pw) const { const auto x = this->Cw_ * (pw - this->pw_ref_); return this->recipFvf_ * this->exp(x); } double recipFvfVisc(const double pw) const { const auto y = this->diffCwCv_ * (pw - this->pw_ref_); return this->recipFvfVisc_ * this->exp(y); } double exp(const double x) const { return 1.0 + x*(1.0 + x/2.0); } template std::vector evaluate(const std::vector& pw, CalcQuant&& calculate) const { auto q = std::vector{}; q.reserve(pw.size()); for (const auto& pwi : pw) { q.push_back(calculate(pwi)); } return q; } }; PVTCurves::PVTCurves(ElemIt xBegin, ElemIt xEnd, const ConvertUnits& convert, std::vector& colIt) { assert ((std::distance(xBegin, xEnd) == 1) && "Logic Error in Defining PVTW Input Ranges"); #ifdef NDEBUG // Suppress "unusued variable" in release mode. static_cast(xEnd); #endif // NDEBUG // Recall: Table is // // [ Pw, 1/Bw, Cw, 1/(Bw*mu_w), Cw - Cv ] // // xBegin is Pw, colIt is remaining four columns. this->recipFvf_ = convert.column[0](*colIt[0]); // 1/Bw this->Cw_ = convert.column[1](*colIt[1]); // Cw this->recipFvfVisc_ = convert.column[2](*colIt[2]); // 1/(Bw*mu_w) this->diffCwCv_ = convert.column[3](*colIt[3]); // Cw - Cv // Honour requirement that constructor advances column iterators. for (auto& it : colIt) { ++it; } if (! (std::abs(*xBegin) < 1.0e20)) { throw std::invalid_argument { "Invalid Input PVTW Table" }; } this->pw_ref_ = convert.indep(*xBegin); } // ##################################################################### // ##################################################################### // ===================================================================== // Class ECLPVT::Water::Impl // --------------------------------------------------------------------- class Opm::ECLPVT::Water::Impl { public: Impl(const ECLPropTableRawData& raw, const int usys, const std::vector& rhoS); using RegIdx = std::vector::size_type; std::vector formationVolumeFactor(const RegIdx region, const std::vector& pw) const { this->validateRegIdx(region); return this->eval_[region].formationVolumeFactor(pw); } std::vector viscosity(const RegIdx region, const std::vector& pw) const { this->validateRegIdx(region); return this->eval_[region].viscosity(pw); } double surfaceMassDensity(const RegIdx region) const { this->validateRegIdx(region); return this->eval_[region].surfaceMassDensity(); } private: std::vector eval_; void validateRegIdx(const RegIdx region) const; }; Opm::ECLPVT::Water::Impl::Impl(const ECLPropTableRawData& raw, const int usys, const std::vector& rhoS) { using ElemIt = PVTCurves::ElemIt; const auto cvrt = waterUnitConverter(usys); this->eval_ = MakeInterpolants::fromRawData(raw, [&cvrt](ElemIt xBegin, ElemIt xEnd, std::vector& colIt) -> PVTCurves { return PVTCurves(xBegin, xEnd, cvrt, colIt); }); assert (rhoS.size() == this->eval_.size()); for (auto n = this->eval_.size(), i = 0*n; i < n; ++i) { this->eval_[i].surfaceMassDensity() = rhoS[i]; } } void Opm::ECLPVT::Water::Impl::validateRegIdx(const RegIdx region) const { if (region >= this->eval_.size()) { if (this->eval_.empty()) { throw std::invalid_argument { "No Water PVT Interpolant Available in Region " + std::to_string(region + 1) }; } throw std::invalid_argument { "Region Index " + std::to_string(region) + " Outside Valid Range (0 .. " + std::to_string(this->eval_.size() - 1) + ')' }; } } // ===================================================================== // Class ECLPVT::Water // --------------------------------------------------------------------- Opm::ECLPVT::Water::Water(const ECLPropTableRawData& raw, const int usys, const std::vector& rhoS) : pImpl_(new Impl(raw, usys, rhoS)) {} Opm::ECLPVT::Water::~Water() {} Opm::ECLPVT::Water::Water(const Water& rhs) : pImpl_(new Impl(*rhs.pImpl_)) {} Opm::ECLPVT::Water::Water(Water&& rhs) : pImpl_(std::move(rhs.pImpl_)) {} Opm::ECLPVT::Water& Opm::ECLPVT::Water::operator=(const Water& rhs) { this->pImpl_.reset(new Impl(*rhs.pImpl_)); return *this; } Opm::ECLPVT::Water& Opm::ECLPVT::Water::operator=(Water&& rhs) { this->pImpl_ = std::move(rhs.pImpl_); return *this; } std::vector Opm::ECLPVT::Water::formationVolumeFactor(const int region, const WaterPressure& pw) const { return this->pImpl_->formationVolumeFactor(region, pw.data); } std::vector Opm::ECLPVT::Water::viscosity(const int region, const WaterPressure& pw) const { return this->pImpl_->viscosity(region, pw.data); } double Opm::ECLPVT::Water::surfaceMassDensity(const int region) const { return this->pImpl_->surfaceMassDensity(region); } // ===================================================================== std::unique_ptr Opm::ECLPVT::CreateWaterPVTInterpolant:: fromECLOutput(const ECLInitFileData& init) { using WPtr = ::std::unique_ptr; const auto& ih = init.keywordData(INTEHEAD_KW); const auto iphs = static_cast(ih[INTEHEAD_PHASE_INDEX]); if ((iphs & (1u << 1)) == 0) { // Water is not an active phase. // Return sentinel (null) pointer. return WPtr{}; } auto raw = ::Opm::ECLPropTableRawData{}; const auto& tabdims = init.keywordData("TABDIMS"); const auto& tab = init.keywordData("TAB"); raw.numPrimary = 1; // Single record per region raw.numRows = 1; // Single record per region raw.numCols = 5; // [ Pw, 1/B, Cw, 1/(B*mu), Cw - Cv ] raw.numTables = tabdims[ TABDIMS_NTPVTW_ITEM ]; // # PVTW tables if (raw.numTables == 0) { return WPtr{}; } // Extract Full Table { const auto nTabElem = raw.numPrimary * raw.numRows * raw.numCols * raw.numTables; // Subtract one to account for 1-based indices. const auto start = tabdims[ TABDIMS_IBPVTW_OFFSET_ITEM ] - 1; if (start < 0) { throw std::invalid_argument( "Invalid table offset for TABDIMS_IBPVTW_OFFSET_ITEM"); } raw.data.assign(&tab[start], &tab[start] + nTabElem); } const auto rhoS = surfaceMassDensity(init, ECLPhaseIndex::Aqua); return WPtr{ new Water(raw, ih[ INTEHEAD_UNIT_INDEX ], rhoS) }; }