/* 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 #include namespace { std::function fvf(const Opm::ECLUnits::UnitSystem& usys) { const auto scale = usys.reservoirVolume() / usys.surfaceVolumeLiquid(); return [scale](const double x) -> double { return ImportedOpm::unit::convert::from(x, scale); }; } std::function viscosity(const Opm::ECLUnits::UnitSystem& usys) { const auto scale = usys.viscosity(); return [scale](const double x) -> double { return ImportedOpm::unit::convert::from(x, scale); }; } ::Opm::ECLPVT::ConvertUnits pvcdoUnitConverter(const int usys) { using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI; const auto u = ::Opm::ECLUnits::createUnitSystem(usys); // [ Pref, Bo, Co, mu_o, Cv ] return ::Opm::ECLPVT::ConvertUnits { ToSI::pressure(*u), { fvf(*u), ToSI::compressibility(*u), viscosity(*u), ToSI::compressibility(*u) } }; } ::Opm::ECLPVT::ConvertUnits deadOilUnitConverter(const ::Opm::ECLUnits::UnitSystem& usys) { using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI; // [ Po, 1/B, 1/(B*mu), d(1/B)/dPo, d(1/(B*mu))/dPo ] return ::Opm::ECLPVT::ConvertUnits { ToSI::pressure(usys), { ToSI::recipFvf(usys), ToSI::recipFvfVisc(usys), ToSI::recipFvfDerivPress(usys), ToSI::recipFvfViscDerivPress(usys) } }; } ::Opm::ECLPVT::ConvertUnits deadOilUnitConverter(const int usys) { const auto u = ::Opm::ECLUnits::createUnitSystem(usys); return deadOilUnitConverter(*u); } std::pair< ::Opm::ECLPVT::ConvertUnits::Converter, ::Opm::ECLPVT::ConvertUnits> liveOilUnitConverter(const int usys) { using ToSI = ::Opm::ECLPVT::CreateUnitConverter::ToSI; const auto u = ::Opm::ECLUnits::createUnitSystem(usys); // Key = Rs // Table = [ Po, 1/B, 1/(B*mu), d(1/B)/dPo, d(1/(B*mu))/dPo ] // = dead oil table format. return std::make_pair(ToSI::disGas(*u), deadOilUnitConverter(*u)); } std::vector::size_type const_compr_index() { #if defined(LOGIHEAD_CONSTANT_OILCOMPR_INDEX) return LOGIHEAD_CONSTANT_OILCOMPR_INDEX; #else return (39 - 1); // Reverse engineering... #endif // LOGIHEAD_CONSTANT_OILCOMPR_INDEX } } // --------------------------------------------------------------------- // Enable runtime selection of dead or live oil functions. class PVxOBase { public: virtual ~PVxOBase() {} virtual std::vector formationVolumeFactor(const std::vector& rs, const std::vector& po) const = 0; virtual std::vector viscosity(const std::vector& rs, const std::vector& po) const = 0; virtual std::vector getPvtCurve(const Opm::ECLPVT::RawCurve curve) const = 0; virtual std::unique_ptr clone() const = 0; }; // ===================================================================== class DeadOilConstCompr : public PVxOBase { public: using ElemIt = std::vector::const_iterator; using ConvertUnits = ::Opm::ECLPVT::ConvertUnits; DeadOilConstCompr(ElemIt xBegin, ElemIt xEnd, const ConvertUnits& convert, std::vector& colIt); virtual std::vector formationVolumeFactor(const std::vector& /* rs */, const std::vector& po) const { return this->evaluate(po, [this](const double p) -> double { // 1 / (1 / B) return 1.0 / this->recipFvf(p); }); } virtual std::vector viscosity(const std::vector& /*rs*/, const std::vector& po) const { return this->evaluate(po, [this](const double p) -> double { // (1 / B) / (1 / (B * mu)) return this->recipFvf(p) / this->recipFvfVisc(p); }); } virtual std::vector getPvtCurve(const Opm::ECLPVT::RawCurve /*curve*/) const { return {}; } virtual std::unique_ptr clone() const { return std::unique_ptr(new DeadOilConstCompr(*this)); } double& surfaceMassDensity() { return this->rhoS_; } double surfaceMassDensity() const { return this->rhoS_; } private: double po_ref_ { 1.0 }; double fvf_ { 1.0 }; // B double visc_ { 1.0 }; // mu double Co_ { 1.0 }; double cv_ { 0.0 }; // Cv double rhoS_ { 0.0 }; double recipFvf(const double po) const { const auto x = this->Co_ * (po - this->po_ref_); return this->exp(x) / this->fvf_ ; } double recipFvfVisc(const double po) const { const auto y = (this->Co_ - this->cv_) * (po - this->po_ref_); return this->exp(y) / (this->fvf_ * this->visc_); } double exp(const double x) const { return 1.0 + x*(1.0 + x/2.0); } template std::vector evaluate(const std::vector& po, CalcQuant&& calculate) const { auto q = std::vector{}; q.reserve(po.size()); for (const auto& poi : po) { q.push_back(calculate(poi)); } return q; } }; DeadOilConstCompr::DeadOilConstCompr(ElemIt xBegin, ElemIt xEnd, const ConvertUnits& convert, std::vector& colIt) { // Recall: Table is // // [ Po, Bo, Co, mu_o, Cv ] // // xBegin is Po, colIt is remaining four columns. this->fvf_ = convert.column[0](*colIt[0]); // Bo this->Co_ = convert.column[1](*colIt[1]); // Co this->visc_ = convert.column[2](*colIt[2]); // mu_o this->cv_ = convert.column[3](*colIt[3]); // Cv // Honour requirement that constructor advances column iterators. const auto N = std::distance(xBegin, xEnd); for (auto& it : colIt) { it += N; } if (! (std::abs(*xBegin) < 1.0e20)) { throw std::invalid_argument { "Invalid Input PVCDO Table" }; } this->po_ref_ = convert.indep(*xBegin); ++xBegin; if (std::abs(*xBegin) < 1.0e20) { throw std::invalid_argument { "Probably Invalid Input PVCDO Table" }; } } // ===================================================================== class DeadOil : public PVxOBase { public: using ElemIt = ::Opm::ECLPVT::PVDx::ElemIt; using ConvertUnits = ::Opm::ECLPVT::ConvertUnits; DeadOil(ElemIt xBegin, ElemIt xEnd, const ConvertUnits& convert, std::vector& colIt) : interpolant_(xBegin, xEnd, convert, colIt) {} virtual std::vector formationVolumeFactor(const std::vector& /* rs */, const std::vector& po) const override { return this->interpolant_.formationVolumeFactor(po); } virtual std::vector viscosity(const std::vector& /* rs */, const std::vector& po) const override { return this->interpolant_.viscosity(po); } virtual std::vector getPvtCurve(const Opm::ECLPVT::RawCurve curve) const override { return { this->interpolant_.getPvtCurve(curve) }; } virtual std::unique_ptr clone() const override { return std::unique_ptr(new DeadOil(*this)); } private: ::Opm::ECLPVT::PVDx interpolant_; }; // ===================================================================== class LiveOil : public PVxOBase { public: using Extrap = ::Opm::Interp1D::PiecewisePolynomial:: ExtrapolationPolicy::Linearly; using SubtableInterpolant = ::Opm::Interp1D::PiecewisePolynomial:: Linear; LiveOil(std::vector key, std::vector propInterp) : interp_(std::move(key), std::move(propInterp)) {} virtual std::vector formationVolumeFactor(const std::vector& rs, const std::vector& po) const override { // PKey Inner C0 C1 C2 C3 // Rs Po 1/B 1/(B*mu) d(1/B)/dPo d(1/(B*mu))/dPo // : : : : : const auto key = TableInterpolant::PrimaryKey { rs }; const auto x = TableInterpolant::InnerVariate { po }; return this->interp_.formationVolumeFactor(key, x); } virtual std::vector viscosity(const std::vector& rs, const std::vector& po) const override { // PKey Inner C0 C1 C2 C3 // Rs Po 1/B 1/(B*mu) d(1/B)/dPo d(1/(B*mu))/dPo // : : : : : const auto key = TableInterpolant::PrimaryKey { rs }; const auto x = TableInterpolant::InnerVariate { po }; return this->interp_.viscosity(key, x); } virtual std::vector getPvtCurve(const Opm::ECLPVT::RawCurve curve) const override { return this->interp_.getPvtCurve(curve); } virtual std::unique_ptr clone() const override { return std::unique_ptr(new LiveOil(*this)); } private: using TableInterpolant = ::Opm::ECLPVT::PVTx; TableInterpolant interp_; std::vector repackagePvtCurve(std::vector&& graphs, const Opm::ECLPVT::RawCurve curve) const { if (curve != Opm::ECLPVT::RawCurve::SaturatedState) { // Not saturated state curve. Nothing to do here. return graphs; } // Saturated state curve for live oil. Columns are (Rs, Po). Swap // first and second columns to normalised form: (Po, Rs). for (auto& graph : graphs) { graph.first.swap(graph.second); } return graphs; } }; // ##################################################################### namespace { std::vector> createDeadOil(const ::Opm::ECLPropTableRawData& raw, const bool const_compr, const int usys) { using PVTInterp = std::unique_ptr; using ElmIt = ::Opm::ECLPropTableRawData::ElementIterator; assert ((raw.numPrimary == 1) && "Can't Create Dead Oil Function From Live Oil Table"); if (const_compr) { const auto cvrt = pvcdoUnitConverter(usys); return ::Opm::MakeInterpolants::fromRawData(raw, [&cvrt](ElmIt xBegin, ElmIt xEnd, std::vector& colIt) { return PVTInterp{ new DeadOilConstCompr(xBegin, xEnd, cvrt, colIt) }; }); } const auto cvrt = deadOilUnitConverter(usys); return ::Opm::MakeInterpolants::fromRawData(raw, [&cvrt](ElmIt xBegin, ElmIt xEnd, std::vector& colIt) { return PVTInterp{ new DeadOil(xBegin, xEnd, cvrt, colIt) }; }); } std::vector extractPrimaryKey(const ::Opm::ECLPropTableRawData& raw, const ::Opm::ECLPropTableRawData::SizeType t, const ::Opm::ECLPVT::ConvertUnits::Converter& cvrtKey) { auto key = std::vector{}; key.reserve(raw.numPrimary); for (auto begin = std::begin(raw.primaryKey) + (t + 0)*raw.numPrimary, end = begin + raw.numPrimary; begin != end; ++begin) { if (std::abs(*begin) < 1.0e20) { key.push_back(cvrtKey(*begin)); } } return key; } std::vector> createLiveOil(const ::Opm::ECLPropTableRawData& raw, const int usys) { auto ret = std::vector>{}; ret.reserve(raw.numTables); using Extrap = LiveOil::Extrap; using StI = LiveOil::SubtableInterpolant; using ElemIt = ::Opm::ECLPropTableRawData::ElementIterator; const auto cvrt = liveOilUnitConverter(usys); auto sti = ::Opm::MakeInterpolants::fromRawData(raw, [&cvrt](ElemIt xBegin, ElemIt xEnd, std::vector& colIt) -> StI { try { return StI(Extrap{}, xBegin, xEnd, colIt, cvrt.second.indep, cvrt.second.column); } catch (const std::invalid_argument&) { // No valid nodes. Return invalid. return StI(Extrap{}); } }); for (auto t = 0*raw.numTables; t < raw.numTables; ++t) { auto key = extractPrimaryKey(raw, t, cvrt.first); const auto begin = (t + 0)*raw.numPrimary; const auto end = begin + key.size(); ret.emplace_back(new LiveOil(std::move(key), { std::make_move_iterator(std::begin(sti) + begin), std::make_move_iterator(std::begin(sti) + end) })); } return ret; } std::vector> createPVTFunction(const ::Opm::ECLPropTableRawData& raw, const bool const_compr, const int usys) { if (raw.numPrimary == 0) { // Malformed Gas PVT table. throw std::invalid_argument { "Oil PVT Table Without Primary Lookup Key" }; } if (raw.numCols != 5) { throw std::invalid_argument { "PVT Table for Oil Must Have Five Columns" }; } if (raw.primaryKey.size() != (raw.numPrimary * raw.numTables)) { throw std::invalid_argument { "Size Mismatch in RS Nodes of PVT Table for Oil" }; } if (raw.data.size() != (raw.numPrimary * raw.numRows * raw.numCols * raw.numTables)) { throw std::invalid_argument { "Size Mismatch in Condensed Table Data " "of PVT Table for Oil" }; } if (raw.numPrimary == 1) { return createDeadOil(raw, const_compr, usys); } return createLiveOil(raw, usys); } std::vector> clone(const std::vector>& src) { auto dest = std::vector>{}; dest.reserve(src.size()); for (const auto& p : src) { dest.push_back(p->clone()); } return dest; } } // ##################################################################### // ##################################################################### // ===================================================================== // Class ECLPVT::Oil::Impl // --------------------------------------------------------------------- class Opm::ECLPVT::Oil::Impl { private: using EvalPtr = std::unique_ptr; public: Impl(const ECLPropTableRawData& raw, const int usys, const bool const_compr, std::vector rhoS); Impl(const Impl& rhs); Impl(Impl&& rhs); Impl& operator=(const Impl& rhs); Impl& operator=(Impl&& rhs); using RegIdx = std::vector::size_type; std::vector formationVolumeFactor(const RegIdx region, const std::vector& rs, const std::vector& po) const; std::vector viscosity(const RegIdx region, const std::vector& rs, const std::vector& po) const; double surfaceMassDensity(const RegIdx region) const { this->validateRegIdx(region); return this->rhoS_[region]; } std::vector getPvtCurve(const RegIdx region, const RawCurve curve) const; private: std::vector eval_; std::vector rhoS_; void validateRegIdx(const RegIdx region) const; }; Opm::ECLPVT::Oil::Impl:: Impl(const ECLPropTableRawData& raw, const int usys, const bool const_compr, std::vector rhoS) : eval_(createPVTFunction(raw, const_compr, usys)) , rhoS_(std::move(rhoS)) {} Opm::ECLPVT::Oil::Impl::Impl(const Impl& rhs) : eval_(clone(rhs.eval_)) , rhoS_(rhs.rhoS_) {} Opm::ECLPVT::Oil::Impl::Impl(Impl&& rhs) : eval_(std::move(rhs.eval_)) , rhoS_(std::move(rhs.rhoS_)) {} Opm::ECLPVT::Oil::Impl& Opm::ECLPVT::Oil::Impl::operator=(const Impl& rhs) { this->eval_ = clone(rhs.eval_); this->rhoS_ = rhs.rhoS_; return *this; } Opm::ECLPVT::Oil::Impl& Opm::ECLPVT::Oil::Impl::operator=(Impl&& rhs) { this->eval_ = std::move(rhs.eval_); this->rhoS_ = std::move(rhs.rhoS_); return *this; } std::vector Opm::ECLPVT::Oil::Impl:: formationVolumeFactor(const RegIdx region, const std::vector& rs, const std::vector& po) const { this->validateRegIdx(region); return this->eval_[region]->formationVolumeFactor(rs, po); } std::vector Opm::ECLPVT::Oil::Impl:: viscosity(const RegIdx region, const std::vector& rs, const std::vector& po) const { this->validateRegIdx(region); return this->eval_[region]->viscosity(rs, po); } std::vector Opm::ECLPVT::Oil::Impl:: getPvtCurve(const RegIdx region, const RawCurve curve) const { this->validateRegIdx(region); return this->eval_[region]->getPvtCurve(curve); } void Opm::ECLPVT::Oil::Impl::validateRegIdx(const RegIdx region) const { if (region >= this->eval_.size()) { if (this->eval_.empty()) { throw std::invalid_argument { "No Oil 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::Oil // ---------------------------------------------------------------------- Opm::ECLPVT::Oil::Oil(const ECLPropTableRawData& raw, const int usys, const bool const_compr, std::vector rhoS) : pImpl_(new Impl(raw, usys, const_compr, std::move(rhoS))) {} Opm::ECLPVT::Oil::~Oil() {} Opm::ECLPVT::Oil::Oil(const Oil& rhs) : pImpl_(new Impl(*rhs.pImpl_)) {} Opm::ECLPVT::Oil::Oil(Oil&& rhs) : pImpl_(std::move(rhs.pImpl_)) {} Opm::ECLPVT::Oil& Opm::ECLPVT::Oil::operator=(const Oil& rhs) { this->pImpl_.reset(new Impl(*rhs.pImpl_)); return *this; } Opm::ECLPVT::Oil& Opm::ECLPVT::Oil::operator=(Oil&& rhs) { this->pImpl_ = std::move(rhs.pImpl_); return *this; } std::vector Opm::ECLPVT::Oil:: formationVolumeFactor(const int region, const DissolvedGas& rs, const OilPressure& po) const { return this->pImpl_->formationVolumeFactor(region, rs.data, po.data); } std::vector Opm::ECLPVT::Oil:: viscosity(const int region, const DissolvedGas& rs, const OilPressure& po) const { return this->pImpl_->viscosity(region, rs.data, po.data); } double Opm::ECLPVT::Oil::surfaceMassDensity(const int region) const { return this->pImpl_->surfaceMassDensity(region); } std::vector Opm::ECLPVT::Oil:: getPvtCurve(const RawCurve curve, const int region) const { return this->pImpl_->getPvtCurve(region, curve); } // ===================================================================== std::unique_ptr Opm::ECLPVT::CreateOilPVTInterpolant:: fromECLOutput(const ECLInitFileData& init) { using OPtr = std::unique_ptr; const auto& ih = init.keywordData(INTEHEAD_KW); const auto iphs = static_cast(ih[INTEHEAD_PHASE_INDEX]); if ((iphs & (1u << 0)) == 0) { // Oil is not an active phase (unexpected). // Return sentinel (null) pointer. return OPtr{}; } const auto& lh = init.keywordData(LOGIHEAD_KW); const auto is_const_compr = static_cast(lh[const_compr_index()]); auto raw = ::Opm::ECLPropTableRawData{}; const auto& tabdims = init.keywordData("TABDIMS"); const auto& tab = init.keywordData("TAB"); const auto numRs = tabdims[ TABDIMS_NRPVTO_ITEM ]; // #Rs nodes/full table // Inner dimension (number of rows per sub-table) is number of pressure // nodes for both live oil and dead oil cases. raw.numRows = tabdims[ TABDIMS_NPPVTO_ITEM ]; // #Po nodes/sub-table raw.numCols = 5; // [ Po, 1/B, 1/(B*mu), d(1/B)/dPo, d(1/(B*mu))/dPo ] raw.numTables = tabdims[ TABDIMS_NTPVTO_ITEM ]; // # PVTO tables if (raw.numTables == 0) { return OPtr{}; } if (lh[ LOGIHEAD_RS_INDEX ]) { // Dissolved gas flag set => Live Oil. // // Number of primary keys (outer dimension, number of sub-tables per // PVTO table) is number of composition nodes. raw.numPrimary = numRs; } else { // Dissolved gas flag NOT set => Dead Oil. // // Number of primary keys (outer dimension, number of sub-tables per // PVDO table) is one. raw.numPrimary = 1; } // Extract Primary Key (Rs) { const auto nTabElem = raw.numPrimary * raw.numTables; // Subtract one to account for 1-based indices. const auto start = tabdims[ TABDIMS_JBPVTO_OFFSET_ITEM ] - 1; if (start < 0) { throw std::invalid_argument( "Invalid table offset for TABDIMS_JBPVTO_OFFSET_ITEM"); } raw.primaryKey.assign(&tab[start], &tab[start] + nTabElem); } // 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_IBPVTO_OFFSET_ITEM ] - 1; if (start < 0) { throw std::invalid_argument( "Invalid table offset for TABDIMS_IBPVTO_OFFSET_ITEM"); } raw.data.assign(&tab[start], &tab[start] + nTabElem); } auto rhoS = surfaceMassDensity(init, ECLPhaseIndex::Liquid); return OPtr{ new Oil(raw, ih[ INTEHEAD_UNIT_INDEX ], is_const_compr, std::move(rhoS)) }; }