2018-01-02 05:43:56 -06:00
|
|
|
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
|
|
// vi: set et ts=4 sw=4 sts=4:
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
Consult the COPYING file in the top-level source directory of this
|
|
|
|
module for the precise wording of the license and the list of
|
|
|
|
copyright holders.
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
*/
|
2018-01-02 05:43:56 -06:00
|
|
|
/**
|
|
|
|
* \file
|
|
|
|
*
|
|
|
|
* \brief Routines that actually solve the ODEs that emerge from the hydrostatic
|
|
|
|
* equilibrium problem
|
|
|
|
*/
|
2018-01-02 05:43:56 -06:00
|
|
|
#ifndef EWOMS_INITSTATEEQUIL_HH
|
|
|
|
#define EWOMS_INITSTATEEQUIL_HH
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
#include "equilibrationhelpers.hh"
|
|
|
|
#include "regionmapping.hh"
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
#include <ewoms/common/propertysystem.hh>
|
|
|
|
|
2018-02-08 05:20:17 -06:00
|
|
|
#include <opm/grid/cpgrid/GridHelpers.hpp>
|
2017-11-16 06:04:26 -06:00
|
|
|
|
2016-10-10 10:23:03 -05:00
|
|
|
#include <opm/parser/eclipse/Units/Units.hpp>
|
2014-06-26 07:46:57 -05:00
|
|
|
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
2016-01-20 07:12:04 -06:00
|
|
|
#include <opm/parser/eclipse/EclipseState/Grid/GridProperty.hpp>
|
2016-02-29 08:41:08 -06:00
|
|
|
#include <opm/parser/eclipse/EclipseState/InitConfig/Equil.hpp>
|
|
|
|
#include <opm/parser/eclipse/EclipseState/InitConfig/InitConfig.hpp>
|
2016-02-26 04:49:40 -06:00
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/TableContainer.hpp>
|
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/TableManager.hpp>
|
2016-01-20 07:12:04 -06:00
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/RsvdTable.hpp>
|
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/RvvdTable.hpp>
|
2018-01-08 07:29:33 -06:00
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/PbvdTable.hpp>
|
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/PdvdTable.hpp>
|
2016-06-15 02:40:38 -05:00
|
|
|
#include <opm/common/OpmLog/OpmLog.hpp>
|
2017-11-20 01:47:41 -06:00
|
|
|
#include <opm/common/data/SimulationDataContainer.hpp>
|
|
|
|
|
2017-11-16 06:04:26 -06:00
|
|
|
#include <opm/material/fluidsystems/BlackOilFluidSystem.hpp>
|
|
|
|
#include <opm/material/fluidstates/SimpleModularFluidState.hpp>
|
|
|
|
#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
|
|
|
|
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
#include <array>
|
|
|
|
#include <cassert>
|
2014-01-17 13:07:51 -06:00
|
|
|
#include <utility>
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
#include <vector>
|
|
|
|
|
2018-06-14 09:06:05 -05:00
|
|
|
BEGIN_PROPERTIES
|
|
|
|
|
2018-01-03 08:17:37 -06:00
|
|
|
NEW_PROP_TAG(Simulator);
|
|
|
|
NEW_PROP_TAG(Grid);
|
|
|
|
NEW_PROP_TAG(FluidSystem);
|
2018-06-14 09:06:05 -05:00
|
|
|
|
|
|
|
END_PROPERTIES
|
|
|
|
|
|
|
|
namespace Ewoms {
|
2018-01-03 08:17:37 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
/**
|
|
|
|
* Types and routines that collectively implement a basic
|
|
|
|
* ECLIPSE-style equilibration-based initialisation scheme.
|
|
|
|
*
|
|
|
|
* This namespace is intentionally nested to avoid name clashes
|
|
|
|
* with other parts of OPM.
|
|
|
|
*/
|
|
|
|
namespace EQUIL {
|
|
|
|
namespace Details {
|
|
|
|
template <class RHS>
|
|
|
|
class RK4IVP {
|
|
|
|
public:
|
2018-01-02 05:43:56 -06:00
|
|
|
RK4IVP(const RHS& f,
|
2018-01-02 05:43:56 -06:00
|
|
|
const std::array<double,2>& span,
|
2018-01-02 05:43:56 -06:00
|
|
|
const double y0,
|
|
|
|
const int N)
|
2018-01-02 05:43:56 -06:00
|
|
|
: N_(N)
|
|
|
|
, span_(span)
|
|
|
|
{
|
2018-01-02 05:43:56 -06:00
|
|
|
const double h = stepsize();
|
2018-01-02 05:43:56 -06:00
|
|
|
const double h2 = h / 2;
|
|
|
|
const double h6 = h / 6;
|
|
|
|
|
|
|
|
y_.reserve(N + 1);
|
|
|
|
f_.reserve(N + 1);
|
|
|
|
|
|
|
|
y_.push_back(y0);
|
|
|
|
f_.push_back(f(span_[0], y0));
|
|
|
|
|
|
|
|
for (int i = 0; i < N; ++i) {
|
2018-01-02 05:43:56 -06:00
|
|
|
const double x = span_[0] + i*h;
|
|
|
|
const double y = y_.back();
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
const double k1 = f_[i];
|
|
|
|
const double k2 = f(x + h2, y + h2*k1);
|
|
|
|
const double k3 = f(x + h2, y + h2*k2);
|
2018-01-02 05:43:56 -06:00
|
|
|
const double k4 = f(x + h, y + h*k3);
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
y_.push_back(y + h6*(k1 + 2*(k2 + k3) + k4));
|
|
|
|
f_.push_back(f(x + h, y_.back()));
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (y_.size() == std::vector<double>::size_type(N + 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
double
|
|
|
|
operator()(const double x) const
|
|
|
|
{
|
|
|
|
// Dense output (O(h**3)) according to Shampine
|
|
|
|
// (Hermite interpolation)
|
|
|
|
const double h = stepsize();
|
2018-01-02 05:43:56 -06:00
|
|
|
int i = (x - span_[0]) / h;
|
2018-01-02 05:43:56 -06:00
|
|
|
const double t = (x - (span_[0] + i*h)) / h;
|
|
|
|
|
|
|
|
// Crude handling of evaluation point outside "span_";
|
|
|
|
if (i < 0) { i = 0; }
|
|
|
|
if (N_ <= i) { i = N_ - 1; }
|
|
|
|
|
|
|
|
const double y0 = y_[i], y1 = y_[i + 1];
|
|
|
|
const double f0 = f_[i], f1 = f_[i + 1];
|
|
|
|
|
|
|
|
double u = (1 - 2*t) * (y1 - y0);
|
|
|
|
u += h * ((t - 1)*f0 + t*f1);
|
|
|
|
u *= t * (t - 1);
|
|
|
|
u += (1 - t)*y0 + t*y1;
|
|
|
|
|
|
|
|
return u;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2018-01-02 05:43:56 -06:00
|
|
|
int N_;
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> span_;
|
|
|
|
std::vector<double> y_;
|
|
|
|
std::vector<double> f_;
|
|
|
|
|
|
|
|
double
|
|
|
|
stepsize() const { return (span_[1] - span_[0]) / N_; }
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace PhasePressODE {
|
|
|
|
template <class FluidSystem>
|
2018-01-02 05:43:56 -06:00
|
|
|
class Water
|
|
|
|
{
|
2018-01-02 05:43:56 -06:00
|
|
|
public:
|
2018-01-02 05:43:56 -06:00
|
|
|
Water(const double temp,
|
|
|
|
const int pvtRegionIdx,
|
|
|
|
const double normGrav)
|
2018-01-02 05:43:56 -06:00
|
|
|
: temp_(temp)
|
|
|
|
, pvtRegionIdx_(pvtRegionIdx)
|
2018-01-02 05:43:56 -06:00
|
|
|
, g_(normGrav)
|
2018-01-02 05:43:56 -06:00
|
|
|
{}
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double
|
|
|
|
operator()(const double /* depth */,
|
|
|
|
const double press) const
|
|
|
|
{
|
|
|
|
return this->density(press) * g_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2018-01-02 05:43:56 -06:00
|
|
|
const double temp_;
|
|
|
|
const int pvtRegionIdx_;
|
|
|
|
const double g_;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double
|
|
|
|
density(const double press) const
|
|
|
|
{
|
|
|
|
double rho = FluidSystem::waterPvt().inverseFormationVolumeFactor(pvtRegionIdx_, temp_, press);
|
|
|
|
rho *= FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, pvtRegionIdx_);
|
|
|
|
return rho;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class FluidSystem, class RS>
|
2018-01-02 05:43:56 -06:00
|
|
|
class Oil
|
|
|
|
{
|
2018-01-02 05:43:56 -06:00
|
|
|
public:
|
2018-01-02 05:43:56 -06:00
|
|
|
Oil(const double temp,
|
|
|
|
const RS& rs,
|
|
|
|
const int pvtRegionIdx,
|
|
|
|
const double normGrav)
|
2018-01-02 05:43:56 -06:00
|
|
|
: temp_(temp)
|
|
|
|
, rs_(rs)
|
|
|
|
, pvtRegionIdx_(pvtRegionIdx)
|
2018-01-02 05:43:56 -06:00
|
|
|
, g_(normGrav)
|
2018-01-02 05:43:56 -06:00
|
|
|
{}
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double
|
|
|
|
operator()(const double depth,
|
|
|
|
const double press) const
|
|
|
|
{
|
|
|
|
return this->density(depth, press) * g_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2018-01-02 05:43:56 -06:00
|
|
|
const double temp_;
|
|
|
|
const RS& rs_;
|
|
|
|
const int pvtRegionIdx_;
|
|
|
|
const double g_;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double
|
|
|
|
density(const double depth,
|
|
|
|
const double press) const
|
|
|
|
{
|
|
|
|
double rs = rs_(depth, press, temp_);
|
|
|
|
double bOil = 0.0;
|
2018-01-02 05:43:56 -06:00
|
|
|
if (!FluidSystem::enableDissolvedGas() || rs >= FluidSystem::oilPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, temp_, press)) {
|
2018-01-02 05:43:56 -06:00
|
|
|
bOil = FluidSystem::oilPvt().saturatedInverseFormationVolumeFactor(pvtRegionIdx_, temp_, press);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
bOil = FluidSystem::oilPvt().inverseFormationVolumeFactor(pvtRegionIdx_, temp_, press, rs);
|
|
|
|
}
|
|
|
|
double rho = bOil * FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx_);
|
|
|
|
if (FluidSystem::enableDissolvedGas()) {
|
|
|
|
rho += rs * bOil * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rho;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class FluidSystem, class RV>
|
2018-01-02 05:43:56 -06:00
|
|
|
class Gas
|
|
|
|
{
|
2018-01-02 05:43:56 -06:00
|
|
|
public:
|
2018-01-02 05:43:56 -06:00
|
|
|
Gas(const double temp,
|
|
|
|
const RV& rv,
|
|
|
|
const int pvtRegionIdx,
|
|
|
|
const double normGrav)
|
2018-01-02 05:43:56 -06:00
|
|
|
: temp_(temp)
|
|
|
|
, rv_(rv)
|
|
|
|
, pvtRegionIdx_(pvtRegionIdx)
|
2018-01-02 05:43:56 -06:00
|
|
|
, g_(normGrav)
|
2018-01-02 05:43:56 -06:00
|
|
|
{}
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double
|
|
|
|
operator()(const double depth,
|
|
|
|
const double press) const
|
|
|
|
{
|
|
|
|
return this->density(depth, press) * g_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2018-01-02 05:43:56 -06:00
|
|
|
const double temp_;
|
|
|
|
const RV& rv_;
|
|
|
|
const int pvtRegionIdx_;
|
|
|
|
const double g_;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double
|
|
|
|
density(const double depth,
|
|
|
|
const double press) const
|
|
|
|
{
|
|
|
|
double rv = rv_(depth, press, temp_);
|
|
|
|
double bGas = 0.0;
|
2018-01-02 05:43:56 -06:00
|
|
|
if (!FluidSystem::enableVaporizedOil() || rv >= FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, temp_, press)) {
|
2018-01-02 05:43:56 -06:00
|
|
|
bGas = FluidSystem::gasPvt().saturatedInverseFormationVolumeFactor(pvtRegionIdx_, temp_, press);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
bGas = FluidSystem::gasPvt().inverseFormationVolumeFactor(pvtRegionIdx_, temp_, press, rv);
|
|
|
|
}
|
|
|
|
double rho = bGas * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
|
|
|
|
if (FluidSystem::enableVaporizedOil()) {
|
|
|
|
rho += rv * bGas * FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx_);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rho;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace PhasePressODE
|
|
|
|
|
|
|
|
|
|
|
|
namespace PhasePressure {
|
|
|
|
template <class Grid,
|
|
|
|
class PressFunction,
|
|
|
|
class CellRange>
|
2018-01-02 05:43:56 -06:00
|
|
|
void assign(const Grid& grid,
|
|
|
|
const std::array<PressFunction, 2>& f ,
|
|
|
|
const double split,
|
|
|
|
const CellRange& cells,
|
|
|
|
std::vector<double>& p)
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
{
|
2014-02-27 03:40:14 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
enum { up = 0, down = 1 };
|
2014-02-27 03:40:14 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector<double>::size_type c = 0;
|
|
|
|
for (typename CellRange::const_iterator
|
|
|
|
ci = cells.begin(), ce = cells.end();
|
|
|
|
ci != ce; ++ci, ++c)
|
|
|
|
{
|
|
|
|
assert (c < p.size());
|
2014-02-27 03:39:18 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
const double z = Opm::UgGridHelpers::cellCenterDepth(grid, *ci);
|
|
|
|
p[c] = (z < split) ? f[up](z) : f[down](z);
|
|
|
|
}
|
|
|
|
}
|
2016-02-29 08:41:08 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
template <class FluidSystem,
|
|
|
|
class Grid,
|
|
|
|
class Region,
|
|
|
|
class CellRange>
|
2018-01-02 05:43:56 -06:00
|
|
|
void water(const Grid& grid,
|
|
|
|
const Region& reg,
|
|
|
|
const std::array<double,2>& span ,
|
|
|
|
const double grav,
|
|
|
|
double& poWoc,
|
|
|
|
const CellRange& cells,
|
|
|
|
std::vector<double>& press)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
using PhasePressODE::Water;
|
|
|
|
typedef Water<FluidSystem> ODE;
|
|
|
|
|
|
|
|
const double T = 273.15 + 20; // standard temperature for now
|
|
|
|
ODE drho(T, reg.pvtIdx() , grav);
|
|
|
|
|
|
|
|
double z0;
|
|
|
|
double p0;
|
|
|
|
if (reg.datum() > reg.zwoc()) {//Datum in water zone
|
|
|
|
z0 = reg.datum();
|
|
|
|
p0 = reg.pressure();
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
z0 = reg.zwoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
p0 = poWoc - reg.pcowWoc(); // Water pressure at contact
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> up = {{ z0, span[0] }};
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> down = {{ z0, span[1] }};
|
|
|
|
|
|
|
|
typedef Details::RK4IVP<ODE> WPress;
|
|
|
|
std::array<WPress,2> wpress = {
|
|
|
|
{
|
|
|
|
WPress(drho, up , p0, 2000)
|
|
|
|
,
|
|
|
|
WPress(drho, down, p0, 2000)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
assign(grid, wpress, z0, cells, press);
|
|
|
|
|
|
|
|
if (reg.datum() > reg.zwoc()) {
|
|
|
|
// Return oil pressure at contact
|
2018-01-02 05:43:56 -06:00
|
|
|
poWoc = wpress[0](reg.zwoc()) + reg.pcowWoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class FluidSystem,
|
|
|
|
class Grid,
|
|
|
|
class Region,
|
|
|
|
class CellRange>
|
2018-01-02 05:43:56 -06:00
|
|
|
void oil(const Grid& grid,
|
|
|
|
const Region& reg,
|
|
|
|
const std::array<double,2>& span ,
|
|
|
|
const double grav,
|
|
|
|
const CellRange& cells,
|
|
|
|
std::vector<double>& press,
|
|
|
|
double& poWoc,
|
|
|
|
double& poGoc)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
using PhasePressODE::Oil;
|
|
|
|
typedef Oil<FluidSystem, typename Region::CalcDissolution> ODE;
|
|
|
|
|
|
|
|
const double T = 273.15 + 20; // standard temperature for now
|
|
|
|
ODE drho(T, reg.dissolutionCalculator(),
|
|
|
|
reg.pvtIdx(), grav);
|
|
|
|
|
|
|
|
double z0;
|
|
|
|
double p0;
|
2018-01-02 05:43:56 -06:00
|
|
|
if (reg.datum() > reg.zwoc()) {//Datum in water zone, poWoc given
|
2018-01-02 05:43:56 -06:00
|
|
|
z0 = reg.zwoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
p0 = poWoc;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else if (reg.datum() < reg.zgoc()) {//Datum in gas zone, poGoc given
|
2018-01-02 05:43:56 -06:00
|
|
|
z0 = reg.zgoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
p0 = poGoc;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else { //Datum in oil zone
|
2018-01-02 05:43:56 -06:00
|
|
|
z0 = reg.datum();
|
|
|
|
p0 = reg.pressure();
|
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> up = {{ z0, span[0] }};
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> down = {{ z0, span[1] }};
|
|
|
|
|
|
|
|
typedef Details::RK4IVP<ODE> OPress;
|
|
|
|
std::array<OPress,2> opress = {
|
|
|
|
{
|
|
|
|
OPress(drho, up , p0, 2000)
|
|
|
|
,
|
|
|
|
OPress(drho, down, p0, 2000)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
assign(grid, opress, z0, cells, press);
|
|
|
|
|
|
|
|
const double woc = reg.zwoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
if (z0 > woc) { poWoc = opress[0](woc); } // WOC above datum
|
|
|
|
else if (z0 < woc) { poWoc = opress[1](woc); } // WOC below datum
|
|
|
|
else { poWoc = p0; } // WOC *at* datum
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
const double goc = reg.zgoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
if (z0 > goc) { poGoc = opress[0](goc); } // GOC above datum
|
|
|
|
else if (z0 < goc) { poGoc = opress[1](goc); } // GOC below datum
|
|
|
|
else { poGoc = p0; } // GOC *at* datum
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class FluidSystem,
|
|
|
|
class Grid,
|
|
|
|
class Region,
|
|
|
|
class CellRange>
|
2018-01-02 05:43:56 -06:00
|
|
|
void gas(const Grid& grid,
|
|
|
|
const Region& reg,
|
|
|
|
const std::array<double,2>& span ,
|
|
|
|
const double grav,
|
|
|
|
double& poGoc,
|
|
|
|
const CellRange& cells,
|
|
|
|
std::vector<double>& press)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
using PhasePressODE::Gas;
|
|
|
|
typedef Gas<FluidSystem, typename Region::CalcEvaporation> ODE;
|
|
|
|
|
|
|
|
const double T = 273.15 + 20; // standard temperature for now
|
|
|
|
ODE drho(T, reg.evaporationCalculator(),
|
|
|
|
reg.pvtIdx(), grav);
|
|
|
|
|
|
|
|
double z0;
|
|
|
|
double p0;
|
|
|
|
if (reg.datum() < reg.zgoc()) {//Datum in gas zone
|
|
|
|
z0 = reg.datum();
|
|
|
|
p0 = reg.pressure();
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
z0 = reg.zgoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
p0 = poGoc + reg.pcgoGoc(); // Gas pressure at contact
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> up = {{ z0, span[0] }};
|
2018-01-02 05:43:56 -06:00
|
|
|
std::array<double,2> down = {{ z0, span[1] }};
|
|
|
|
|
|
|
|
typedef Details::RK4IVP<ODE> GPress;
|
|
|
|
std::array<GPress,2> gpress = {
|
|
|
|
{
|
|
|
|
GPress(drho, up , p0, 2000)
|
|
|
|
,
|
|
|
|
GPress(drho, down, p0, 2000)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
assign(grid, gpress, z0, cells, press);
|
|
|
|
|
|
|
|
if (reg.datum() < reg.zgoc()) {
|
|
|
|
// Return oil pressure at contact
|
2018-01-02 05:43:56 -06:00
|
|
|
poGoc = gpress[1](reg.zgoc()) - reg.pcgoGoc();
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace PhasePressure
|
|
|
|
|
|
|
|
template <class FluidSystem,
|
|
|
|
class Grid,
|
|
|
|
class Region,
|
|
|
|
class CellRange>
|
2018-01-02 05:43:56 -06:00
|
|
|
void equilibrateOWG(const Grid& grid,
|
|
|
|
const Region& reg,
|
|
|
|
const double grav,
|
|
|
|
const std::array<double,2>& span,
|
|
|
|
const CellRange& cells,
|
|
|
|
std::vector< std::vector<double> >& press)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
const bool water = FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx);
|
|
|
|
const bool oil = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
|
|
|
|
const bool gas = FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx);
|
|
|
|
const int oilpos = FluidSystem::oilPhaseIdx;
|
|
|
|
const int waterpos = FluidSystem::waterPhaseIdx;
|
|
|
|
const int gaspos = FluidSystem::gasPhaseIdx;
|
|
|
|
|
|
|
|
if (reg.datum() > reg.zwoc()) { // Datum in water zone
|
2018-01-02 05:43:56 -06:00
|
|
|
double poWoc = -1;
|
|
|
|
double poGoc = -1;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
if (water) {
|
2018-01-02 05:43:56 -06:00
|
|
|
PhasePressure::water<FluidSystem>(grid, reg, span, grav, poWoc,
|
2018-01-02 05:43:56 -06:00
|
|
|
cells, press[waterpos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (oil) {
|
|
|
|
PhasePressure::oil<FluidSystem>(grid, reg, span, grav, cells,
|
2018-01-02 05:43:56 -06:00
|
|
|
press[oilpos], poWoc, poGoc);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (gas) {
|
2018-01-02 05:43:56 -06:00
|
|
|
PhasePressure::gas<FluidSystem>(grid, reg, span, grav, poGoc,
|
2018-01-02 05:43:56 -06:00
|
|
|
cells, press[gaspos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else if (reg.datum() < reg.zgoc()) { // Datum in gas zone
|
2018-01-02 05:43:56 -06:00
|
|
|
double poWoc = -1;
|
|
|
|
double poGoc = -1;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
if (gas) {
|
2018-01-02 05:43:56 -06:00
|
|
|
PhasePressure::gas<FluidSystem>(grid, reg, span, grav, poGoc,
|
2018-01-02 05:43:56 -06:00
|
|
|
cells, press[gaspos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (oil) {
|
|
|
|
PhasePressure::oil<FluidSystem>(grid, reg, span, grav, cells,
|
2018-01-02 05:43:56 -06:00
|
|
|
press[oilpos], poWoc, poGoc);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (water) {
|
2018-01-02 05:43:56 -06:00
|
|
|
PhasePressure::water<FluidSystem>(grid, reg, span, grav, poWoc,
|
2018-01-02 05:43:56 -06:00
|
|
|
cells, press[waterpos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else { // Datum in oil zone
|
2018-01-02 05:43:56 -06:00
|
|
|
double poWoc = -1;
|
|
|
|
double poGoc = -1;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
if (oil) {
|
|
|
|
PhasePressure::oil<FluidSystem>(grid, reg, span, grav, cells,
|
2018-01-02 05:43:56 -06:00
|
|
|
press[oilpos], poWoc, poGoc);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (water) {
|
2018-01-02 05:43:56 -06:00
|
|
|
PhasePressure::water<FluidSystem>(grid, reg, span, grav, poWoc,
|
2018-01-02 05:43:56 -06:00
|
|
|
cells, press[waterpos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (gas) {
|
2018-01-02 05:43:56 -06:00
|
|
|
PhasePressure::gas<FluidSystem>(grid, reg, span, grav, poGoc,
|
2018-01-02 05:43:56 -06:00
|
|
|
cells, press[gaspos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace Details
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute initial phase pressures by means of equilibration.
|
|
|
|
*
|
|
|
|
* This function uses the information contained in an
|
|
|
|
* equilibration record (i.e., depths and pressurs) as well as
|
|
|
|
* a density calculator and related data to vertically
|
|
|
|
* integrate the phase pressure ODE
|
|
|
|
* \f[
|
|
|
|
* \frac{\mathrm{d}p_{\alpha}}{\mathrm{d}z} =
|
|
|
|
* \rho_{\alpha}(z,p_{\alpha})\cdot g
|
|
|
|
* \f]
|
|
|
|
* in which \f$\rho_{\alpha}$ denotes the fluid density of
|
|
|
|
* fluid phase \f$\alpha\f$, \f$p_{\alpha}\f$ is the
|
|
|
|
* corresponding phase pressure, \f$z\f$ is the depth and
|
|
|
|
* \f$g\f$ is the acceleration due to gravity (assumed
|
|
|
|
* directed downwords, in the positive \f$z\f$ direction).
|
|
|
|
*
|
|
|
|
* \tparam Region Type of an equilibration region information
|
|
|
|
* base. Typically an instance of the EquilReg
|
|
|
|
* class template.
|
|
|
|
*
|
|
|
|
* \tparam CellRange Type of cell range that demarcates the
|
|
|
|
* cells pertaining to the current
|
|
|
|
* equilibration region. Must implement
|
|
|
|
* methods begin() and end() to bound the range
|
|
|
|
* as well as provide an inner type,
|
|
|
|
* const_iterator, to traverse the range.
|
|
|
|
*
|
|
|
|
* \param[in] grid Grid.
|
|
|
|
* \param[in] reg Current equilibration region.
|
|
|
|
* \param[in] cells Range that spans the cells of the current
|
|
|
|
* equilibration region.
|
|
|
|
* \param[in] grav Acceleration of gravity.
|
|
|
|
*
|
|
|
|
* \return Phase pressures, one vector for each active phase,
|
|
|
|
* of pressure values in each cell in the current
|
|
|
|
* equilibration region.
|
|
|
|
*/
|
|
|
|
template <class FluidSystem, class Grid, class Region, class CellRange>
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector< std::vector<double>>
|
|
|
|
phasePressures(const Grid& grid,
|
|
|
|
const Region& reg,
|
|
|
|
const CellRange& cells,
|
|
|
|
const double grav = Opm::unit::gravity)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
std::array<double,2> span =
|
2018-01-02 05:43:56 -06:00
|
|
|
{{ std::numeric_limits<double>::max(),
|
2018-01-02 05:43:56 -06:00
|
|
|
-std::numeric_limits<double>::max() }}; // Symm. about 0.
|
|
|
|
|
|
|
|
int ncell = 0;
|
|
|
|
{
|
|
|
|
// This code is only supported in three space dimensions
|
2018-01-02 05:43:56 -06:00
|
|
|
assert (Grid::dimensionworld == 3);
|
2018-01-02 05:43:56 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
const int nd = Grid::dimensionworld;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
// Define vertical span as
|
|
|
|
//
|
|
|
|
// [minimum(node depth(cells)), maximum(node depth(cells))]
|
|
|
|
//
|
|
|
|
// Note: We use a sledgehammer approach--looping all
|
|
|
|
// the nodes of all the faces of all the 'cells'--to
|
|
|
|
// compute those bounds. This necessarily entails
|
|
|
|
// visiting some nodes (and faces) multiple times.
|
|
|
|
//
|
|
|
|
// Note: The implementation of 'RK4IVP<>' implicitly
|
|
|
|
// imposes the requirement that cell centroids are all
|
|
|
|
// within this vertical span. That requirement is not
|
|
|
|
// checked.
|
|
|
|
auto cell2Faces = Opm::UgGridHelpers::cell2Faces(grid);
|
|
|
|
auto faceVertices = Opm::UgGridHelpers::face2Vertices(grid);
|
2014-02-27 03:39:18 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
for (typename CellRange::const_iterator
|
|
|
|
ci = cells.begin(), ce = cells.end();
|
|
|
|
ci != ce; ++ci, ++ncell)
|
|
|
|
{
|
|
|
|
for (auto fi=cell2Faces[*ci].begin(),
|
|
|
|
fe=cell2Faces[*ci].end();
|
|
|
|
fi != fe;
|
|
|
|
++fi)
|
2014-03-28 11:35:43 -05:00
|
|
|
{
|
2018-01-02 05:43:56 -06:00
|
|
|
for (auto i = faceVertices[*fi].begin(), e = faceVertices[*fi].end();
|
|
|
|
i != e; ++i)
|
|
|
|
{
|
|
|
|
const double z = Opm::UgGridHelpers::vertexCoordinates(grid, *i)[nd-1];
|
|
|
|
|
|
|
|
if (z < span[0]) { span[0] = z; }
|
|
|
|
if (z > span[1]) { span[1] = z; }
|
2014-03-28 11:35:43 -05:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
const int np = FluidSystem::numPhases; //reg.phaseUsage().numPhases;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
typedef std::vector<double> pval;
|
|
|
|
std::vector<pval> press(np, pval(ncell, 0.0));
|
|
|
|
|
|
|
|
const double zwoc = reg.zwoc ();
|
|
|
|
const double zgoc = reg.zgoc ();
|
|
|
|
|
|
|
|
// make sure goc and woc is within the span for the phase pressure calculation
|
|
|
|
span[0] = std::min(span[0],zgoc);
|
|
|
|
span[1] = std::max(span[1],zwoc);
|
|
|
|
|
|
|
|
Details::equilibrateOWG<FluidSystem>(grid, reg, grav, span, cells, press);
|
|
|
|
|
|
|
|
return press;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute initial phase saturations by means of equilibration.
|
|
|
|
*
|
|
|
|
* \tparam FluidSystem The FluidSystem from opm-material
|
|
|
|
* Must be initialized before used.
|
|
|
|
*
|
|
|
|
* \tparam Grid Type of the grid
|
|
|
|
*
|
|
|
|
* \tparam Region Type of an equilibration region information
|
|
|
|
* base. Typically an instance of the EquilReg
|
|
|
|
* class template.
|
|
|
|
*
|
|
|
|
* \tparam CellRange Type of cell range that demarcates the
|
|
|
|
* cells pertaining to the current
|
|
|
|
* equilibration region. Must implement
|
|
|
|
* methods begin() and end() to bound the range
|
|
|
|
* as well as provide an inner type,
|
|
|
|
* const_iterator, to traverse the range.
|
|
|
|
*
|
|
|
|
* \tparam MaterialLawManager The MaterialLawManager from opm-material
|
|
|
|
*
|
|
|
|
* \param[in] grid Grid.
|
|
|
|
* \param[in] reg Current equilibration region.
|
|
|
|
* \param[in] cells Range that spans the cells of the current
|
|
|
|
* equilibration region.
|
|
|
|
* \param[in] materialLawManager The MaterialLawManager from opm-material
|
2018-01-02 05:43:56 -06:00
|
|
|
* \param[in] swatInit A vector of initial water saturations.
|
2018-01-02 05:43:56 -06:00
|
|
|
* The capillary pressure is scaled to fit these values
|
2018-01-02 05:43:56 -06:00
|
|
|
* \param[in] phasePressures Phase pressures, one vector for each active phase,
|
2018-01-02 05:43:56 -06:00
|
|
|
* of pressure values in each cell in the current
|
|
|
|
* equilibration region.
|
|
|
|
* \return Phase saturations, one vector for each phase, each containing
|
|
|
|
* one saturation value per cell in the region.
|
|
|
|
*/
|
|
|
|
template <class FluidSystem, class Grid, class Region, class CellRange, class MaterialLawManager>
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector< std::vector<double>>
|
|
|
|
phaseSaturations(const Grid& grid,
|
|
|
|
const Region& reg,
|
|
|
|
const CellRange& cells,
|
2018-01-02 05:43:56 -06:00
|
|
|
MaterialLawManager& materialLawManager,
|
2018-01-02 05:43:56 -06:00
|
|
|
const std::vector<double> swatInit,
|
|
|
|
std::vector< std::vector<double> >& phasePressures)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
if (!FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
|
2018-02-01 07:40:01 -06:00
|
|
|
throw std::runtime_error("Cannot initialise: not handling water-gas cases.");
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector< std::vector<double> > phaseSaturations = phasePressures; // Just to get the right size.
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
// Adjust oil pressure according to gas saturation and cap pressure
|
|
|
|
typedef Opm::SimpleModularFluidState<double,
|
|
|
|
/*numPhases=*/3,
|
|
|
|
/*numComponents=*/3,
|
|
|
|
FluidSystem,
|
|
|
|
/*storePressure=*/false,
|
|
|
|
/*storeTemperature=*/false,
|
|
|
|
/*storeComposition=*/false,
|
|
|
|
/*storeFugacity=*/false,
|
|
|
|
/*storeSaturation=*/true,
|
|
|
|
/*storeDensity=*/false,
|
|
|
|
/*storeViscosity=*/false,
|
2019-01-09 02:34:26 -06:00
|
|
|
/*storeEnthalpy=*/false> MySatOnlyFluidState;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
2019-01-09 02:34:26 -06:00
|
|
|
MySatOnlyFluidState fluidState;
|
2018-01-02 05:43:56 -06:00
|
|
|
typedef typename MaterialLawManager::MaterialLaw MaterialLaw;
|
|
|
|
|
|
|
|
const bool water = FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx);
|
|
|
|
const bool gas = FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx);
|
|
|
|
const int oilpos = FluidSystem::oilPhaseIdx;
|
|
|
|
const int waterpos = FluidSystem::waterPhaseIdx;
|
|
|
|
const int gaspos = FluidSystem::gasPhaseIdx;
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector<double>::size_type localIndex = 0;
|
|
|
|
for (typename CellRange::const_iterator ci = cells.begin(); ci != cells.end(); ++ci, ++localIndex) {
|
2018-01-02 05:43:56 -06:00
|
|
|
const int cell = *ci;
|
|
|
|
const auto& scaledDrainageInfo =
|
|
|
|
materialLawManager.oilWaterScaledEpsInfoDrainage(cell);
|
|
|
|
const auto& matParams = materialLawManager.materialLawParams(cell);
|
|
|
|
|
|
|
|
// Find saturations from pressure differences by
|
|
|
|
// inverting capillary pressure functions.
|
|
|
|
double sw = 0.0;
|
|
|
|
if (water) {
|
|
|
|
if (isConstPc<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager,FluidSystem::waterPhaseIdx, cell)){
|
2018-01-02 05:43:56 -06:00
|
|
|
const double cellDepth = Opm::UgGridHelpers::cellCenterDepth(grid,
|
|
|
|
cell);
|
2018-01-02 05:43:56 -06:00
|
|
|
sw = satFromDepth<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager,cellDepth,reg.zwoc(),waterpos,cell,false);
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[waterpos][localIndex] = sw;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
const double pcov = phasePressures[oilpos][localIndex] - phasePressures[waterpos][localIndex];
|
|
|
|
if (swatInit.empty()) { // Invert Pc to find sw
|
2018-01-02 05:43:56 -06:00
|
|
|
sw = satFromPc<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager, waterpos, cell, pcov);
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[waterpos][localIndex] = sw;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else { // Scale Pc to reflect imposed sw
|
2018-01-02 05:43:56 -06:00
|
|
|
sw = swatInit[cell];
|
2018-01-02 05:43:56 -06:00
|
|
|
sw = materialLawManager.applySwatinit(cell, pcov, sw);
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[waterpos][localIndex] = sw;
|
2014-03-28 11:35:43 -05:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
double sg = 0.0;
|
|
|
|
if (gas) {
|
|
|
|
if (isConstPc<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager,FluidSystem::gasPhaseIdx,cell)){
|
2018-01-02 05:43:56 -06:00
|
|
|
const double cellDepth = Opm::UgGridHelpers::cellCenterDepth(grid,
|
|
|
|
cell);
|
2018-01-02 05:43:56 -06:00
|
|
|
sg = satFromDepth<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager,cellDepth,reg.zgoc(),gaspos,cell,true);
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[gaspos][localIndex] = sg;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
// Note that pcog is defined to be (pg - po), not (po - pg).
|
2018-01-02 05:43:56 -06:00
|
|
|
const double pcog = phasePressures[gaspos][localIndex] - phasePressures[oilpos][localIndex];
|
2018-01-02 05:43:56 -06:00
|
|
|
const double increasing = true; // pcog(sg) expected to be increasing function
|
|
|
|
sg = satFromPc<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager, gaspos, cell, pcog, increasing);
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[gaspos][localIndex] = sg;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (gas && water && (sg + sw > 1.0)) {
|
|
|
|
// Overlapping gas-oil and oil-water transition
|
|
|
|
// zones can lead to unphysical saturations when
|
|
|
|
// treated as above. Must recalculate using gas-water
|
|
|
|
// capillary pressure.
|
2018-01-02 05:43:56 -06:00
|
|
|
const double pcgw = phasePressures[gaspos][localIndex] - phasePressures[waterpos][localIndex];
|
|
|
|
if (! swatInit.empty()) {
|
2018-01-02 05:43:56 -06:00
|
|
|
// Re-scale Pc to reflect imposed sw for vanishing oil phase.
|
|
|
|
// This seems consistent with ecl, and fails to honour
|
2018-01-02 05:43:56 -06:00
|
|
|
// swatInit in case of non-trivial gas-oil cap pressure.
|
2018-01-02 05:43:56 -06:00
|
|
|
sw = materialLawManager.applySwatinit(cell, pcgw, sw);
|
|
|
|
}
|
|
|
|
sw = satFromSumOfPcs<FluidSystem, MaterialLaw, MaterialLawManager>(materialLawManager, waterpos, gaspos, cell, pcgw);
|
|
|
|
sg = 1.0 - sw;
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[waterpos][localIndex] = sw;
|
|
|
|
phaseSaturations[gaspos][localIndex] = sg;
|
2018-01-02 05:43:56 -06:00
|
|
|
if (water) {
|
2018-01-02 05:43:56 -06:00
|
|
|
fluidState.setSaturation(FluidSystem::waterPhaseIdx, sw);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fluidState.setSaturation(FluidSystem::waterPhaseIdx, 0.0);
|
|
|
|
}
|
|
|
|
fluidState.setSaturation(FluidSystem::oilPhaseIdx, 1.0 - sw - sg);
|
|
|
|
fluidState.setSaturation(FluidSystem::gasPhaseIdx, sg);
|
|
|
|
|
|
|
|
double pC[/*numPhases=*/3] = { 0.0, 0.0, 0.0 };
|
|
|
|
MaterialLaw::capillaryPressures(pC, matParams, fluidState);
|
|
|
|
double pcGas = pC[FluidSystem::oilPhaseIdx] + pC[FluidSystem::gasPhaseIdx];
|
2018-01-02 05:43:56 -06:00
|
|
|
phasePressures[oilpos][localIndex] = phasePressures[gaspos][localIndex] - pcGas;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
phaseSaturations[oilpos][localIndex] = 1.0 - sw - sg;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
// Adjust phase pressures for max and min saturation ...
|
2018-01-02 05:43:56 -06:00
|
|
|
double thresholdSat = 1.0e-6;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
double so = 1.0;
|
|
|
|
double pC[FluidSystem::numPhases] = { 0.0, 0.0, 0.0 };
|
|
|
|
if (water) {
|
|
|
|
double swu = scaledDrainageInfo.Swu;
|
|
|
|
fluidState.setSaturation(FluidSystem::waterPhaseIdx, swu);
|
|
|
|
so -= swu;
|
|
|
|
}
|
|
|
|
if (gas) {
|
|
|
|
double sgu = scaledDrainageInfo.Sgu;
|
|
|
|
fluidState.setSaturation(FluidSystem::gasPhaseIdx, sgu);
|
|
|
|
so-= sgu;
|
|
|
|
}
|
|
|
|
fluidState.setSaturation(FluidSystem::oilPhaseIdx, so);
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
if (water && sw > scaledDrainageInfo.Swu-thresholdSat) {
|
2018-01-02 05:43:56 -06:00
|
|
|
fluidState.setSaturation(FluidSystem::waterPhaseIdx, scaledDrainageInfo.Swu);
|
|
|
|
MaterialLaw::capillaryPressures(pC, matParams, fluidState);
|
|
|
|
double pcWat = pC[FluidSystem::oilPhaseIdx] - pC[FluidSystem::waterPhaseIdx];
|
2018-01-02 05:43:56 -06:00
|
|
|
phasePressures[oilpos][localIndex] = phasePressures[waterpos][localIndex] + pcWat;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else if (gas && sg > scaledDrainageInfo.Sgu-thresholdSat) {
|
2018-01-02 05:43:56 -06:00
|
|
|
fluidState.setSaturation(FluidSystem::gasPhaseIdx, scaledDrainageInfo.Sgu);
|
|
|
|
MaterialLaw::capillaryPressures(pC, matParams, fluidState);
|
|
|
|
double pcGas = pC[FluidSystem::oilPhaseIdx] + pC[FluidSystem::gasPhaseIdx];
|
2018-01-02 05:43:56 -06:00
|
|
|
phasePressures[oilpos][localIndex] = phasePressures[gaspos][localIndex] - pcGas;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
if (gas && sg < scaledDrainageInfo.Sgl+thresholdSat) {
|
2018-01-02 05:43:56 -06:00
|
|
|
fluidState.setSaturation(FluidSystem::gasPhaseIdx, scaledDrainageInfo.Sgl);
|
|
|
|
MaterialLaw::capillaryPressures(pC, matParams, fluidState);
|
|
|
|
double pcGas = pC[FluidSystem::oilPhaseIdx] + pC[FluidSystem::gasPhaseIdx];
|
2018-01-02 05:43:56 -06:00
|
|
|
phasePressures[gaspos][localIndex] = phasePressures[oilpos][localIndex] + pcGas;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
if (water && sw < scaledDrainageInfo.Swl+thresholdSat) {
|
2018-01-02 05:43:56 -06:00
|
|
|
fluidState.setSaturation(FluidSystem::waterPhaseIdx, scaledDrainageInfo.Swl);
|
|
|
|
MaterialLaw::capillaryPressures(pC, matParams, fluidState);
|
|
|
|
double pcWat = pC[FluidSystem::oilPhaseIdx] - pC[FluidSystem::waterPhaseIdx];
|
2018-01-02 05:43:56 -06:00
|
|
|
phasePressures[waterpos][localIndex] = phasePressures[oilpos][localIndex] - pcWat;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
return phaseSaturations;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2014-03-28 11:35:43 -05:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
/**
|
|
|
|
* Compute initial Rs values.
|
|
|
|
*
|
|
|
|
* \tparam CellRangeType Type of cell range that demarcates the
|
|
|
|
* cells pertaining to the current
|
|
|
|
* equilibration region. Must implement
|
|
|
|
* methods begin() and end() to bound the range
|
|
|
|
* as well as provide an inner type,
|
|
|
|
* const_iterator, to traverse the range.
|
|
|
|
*
|
|
|
|
* \param[in] grid Grid.
|
|
|
|
* \param[in] cells Range that spans the cells of the current
|
|
|
|
* equilibration region.
|
2018-01-02 05:43:56 -06:00
|
|
|
* \param[in] oilPressure Oil pressure for each cell in range.
|
2018-01-02 05:43:56 -06:00
|
|
|
* \param[in] temperature Temperature for each cell in range.
|
2018-01-02 05:43:56 -06:00
|
|
|
* \param[in] rsFunc Rs as function of pressure and depth.
|
2018-01-02 05:43:56 -06:00
|
|
|
* \return Rs values, one for each cell in the 'cells' range.
|
|
|
|
*/
|
|
|
|
template <class Grid, class CellRangeType>
|
|
|
|
std::vector<double> computeRs(const Grid& grid,
|
|
|
|
const CellRangeType& cells,
|
2018-01-02 05:43:56 -06:00
|
|
|
const std::vector<double> oilPressure,
|
2018-01-02 05:43:56 -06:00
|
|
|
const std::vector<double>& temperature,
|
2018-01-02 05:43:56 -06:00
|
|
|
const Miscibility::RsFunction& rsFunc,
|
|
|
|
const std::vector<double> gasSaturation)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
2018-01-02 05:43:56 -06:00
|
|
|
assert(Grid::dimensionworld == 3);
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector<double> rs(cells.size());
|
|
|
|
int count = 0;
|
|
|
|
for (auto it = cells.begin(); it != cells.end(); ++it, ++count) {
|
|
|
|
const double depth = Opm::UgGridHelpers::cellCenterDepth(grid, *it);
|
2018-01-02 05:43:56 -06:00
|
|
|
rs[count] = rsFunc(depth, oilPressure[count], temperature[count], gasSaturation[count]);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
return rs;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace DeckDependent {
|
2018-01-02 05:43:56 -06:00
|
|
|
inline std::vector<Opm::EquilRecord>
|
2018-01-02 05:43:56 -06:00
|
|
|
getEquil(const Opm::EclipseState& state)
|
|
|
|
{
|
|
|
|
const auto& init = state.getInitConfig();
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
if(!init.hasEquil()) {
|
2018-02-01 07:40:01 -06:00
|
|
|
throw std::domain_error("Deck does not provide equilibration data.");
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
const auto& equil = init.getEquil();
|
|
|
|
return { equil.begin(), equil.end() };
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class Grid>
|
|
|
|
std::vector<int>
|
|
|
|
equilnum(const Opm::EclipseState& eclipseState,
|
2018-01-02 05:43:56 -06:00
|
|
|
const Grid& grid)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
std::vector<int> eqlnum;
|
|
|
|
if (eclipseState.get3DProperties().hasDeckIntGridProperty("EQLNUM")) {
|
2018-01-02 05:43:56 -06:00
|
|
|
const int nc = grid.size(/*codim=*/0);
|
2018-01-02 05:43:56 -06:00
|
|
|
eqlnum.resize(nc);
|
|
|
|
const std::vector<int>& e =
|
|
|
|
eclipseState.get3DProperties().getIntGridProperty("EQLNUM").getData();
|
|
|
|
const int* gc = Opm::UgGridHelpers::globalCell(grid);
|
|
|
|
for (int cell = 0; cell < nc; ++cell) {
|
2018-01-02 05:43:56 -06:00
|
|
|
const int deckPos = (gc == NULL) ? cell : gc[cell];
|
|
|
|
eqlnum[cell] = e[deckPos] - 1;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// No explicit equilibration region.
|
|
|
|
// All cells in region zero.
|
2018-01-02 05:43:56 -06:00
|
|
|
eqlnum.assign(grid.size(/*codim=*/0), 0);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return eqlnum;
|
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
template<class TypeTag>
|
|
|
|
class InitialStateComputer
|
|
|
|
{
|
|
|
|
typedef typename GET_PROP_TYPE(TypeTag, FluidSystem) FluidSystem;
|
|
|
|
typedef typename GET_PROP_TYPE(TypeTag, Simulator) Simulator;
|
|
|
|
typedef typename GET_PROP_TYPE(TypeTag, Grid) Grid;
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
public:
|
2018-01-02 05:43:56 -06:00
|
|
|
template<class MaterialLawManager>
|
2018-01-02 05:43:56 -06:00
|
|
|
InitialStateComputer(MaterialLawManager& materialLawManager,
|
|
|
|
const Opm::EclipseState& eclipseState,
|
2018-01-02 05:43:56 -06:00
|
|
|
const Grid& grid,
|
2018-01-02 05:43:56 -06:00
|
|
|
const double grav = Opm::unit::gravity,
|
2018-01-02 05:43:56 -06:00
|
|
|
const bool applySwatInit = true)
|
2018-01-30 09:15:47 -06:00
|
|
|
: temperature_(grid.size(/*codim=*/0)),
|
|
|
|
pp_(FluidSystem::numPhases,
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector<double>(grid.size(/*codim=*/0))),
|
2018-01-02 05:43:56 -06:00
|
|
|
sat_(FluidSystem::numPhases,
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector<double>(grid.size(/*codim=*/0))),
|
|
|
|
rs_(grid.size(/*codim=*/0)),
|
|
|
|
rv_(grid.size(/*codim=*/0))
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
//Check for presence of kw SWATINIT
|
|
|
|
if (eclipseState.get3DProperties().hasDeckDoubleGridProperty("SWATINIT") && applySwatInit) {
|
2018-01-02 05:43:56 -06:00
|
|
|
const std::vector<double>& swatInitEcl = eclipseState.
|
2018-01-02 05:43:56 -06:00
|
|
|
get3DProperties().getDoubleGridProperty("SWATINIT").getData();
|
2018-01-02 05:43:56 -06:00
|
|
|
const int nc = grid.size(/*codim=*/0);
|
2018-01-02 05:43:56 -06:00
|
|
|
swatInit_.resize(nc);
|
2018-01-02 05:43:56 -06:00
|
|
|
const int* gc = Opm::UgGridHelpers::globalCell(grid);
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
2018-01-02 05:43:56 -06:00
|
|
|
const int deckPos = (gc == NULL) ? c : gc[c];
|
|
|
|
swatInit_[c] = swatInitEcl[deckPos];
|
2014-03-28 11:35:43 -05:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
// Get the equilibration records.
|
|
|
|
const std::vector<Opm::EquilRecord> rec = getEquil(eclipseState);
|
|
|
|
const auto& tables = eclipseState.getTableManager();
|
|
|
|
// Create (inverse) region mapping.
|
|
|
|
const Ewoms::RegionMapping<> eqlmap(equilnum(eclipseState, grid));
|
|
|
|
const int invalidRegion = -1;
|
|
|
|
regionPvtIdx_.resize(rec.size(), invalidRegion);
|
|
|
|
setRegionPvtIdx(grid, eclipseState, eqlmap);
|
2014-02-27 03:39:18 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
// Create Rs functions.
|
2018-01-02 05:43:56 -06:00
|
|
|
rsFunc_.reserve(rec.size());
|
2018-01-02 05:43:56 -06:00
|
|
|
if (FluidSystem::enableDissolvedGas()) {
|
|
|
|
for (size_t i = 0; i < rec.size(); ++i) {
|
2018-01-02 05:43:56 -06:00
|
|
|
if (eqlmap.cells(i).empty()) {
|
2018-01-02 05:43:56 -06:00
|
|
|
rsFunc_.push_back(std::shared_ptr<Miscibility::RsVD<FluidSystem>>());
|
2018-01-02 05:43:56 -06:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const int pvtIdx = regionPvtIdx_[i];
|
|
|
|
if (!rec[i].liveOilInitConstantRs()) {
|
2018-01-08 07:29:33 -06:00
|
|
|
const Opm::TableContainer& rsvdTables = tables.getRsvdTables();
|
|
|
|
const Opm::TableContainer& pbvdTables = tables.getPbvdTables();
|
|
|
|
if (rsvdTables.size() > 0) {
|
|
|
|
|
|
|
|
const Opm::RsvdTable& rsvdTable = rsvdTables.getTable<Opm::RsvdTable>(i);
|
|
|
|
std::vector<double> depthColumn = rsvdTable.getColumn("DEPTH").vectorCopy();
|
|
|
|
std::vector<double> rsColumn = rsvdTable.getColumn("RS").vectorCopy();
|
|
|
|
rsFunc_.push_back(std::make_shared<Miscibility::RsVD<FluidSystem>>(pvtIdx,
|
|
|
|
depthColumn, rsColumn));
|
|
|
|
} else if (pbvdTables.size() > 0) {
|
|
|
|
const Opm::PbvdTable& pbvdTable = pbvdTables.getTable<Opm::PbvdTable>(i);
|
|
|
|
std::vector<double> depthColumn = pbvdTable.getColumn("DEPTH").vectorCopy();
|
|
|
|
std::vector<double> pbubColumn = pbvdTable.getColumn("PBUB").vectorCopy();
|
|
|
|
rsFunc_.push_back(std::make_shared<Miscibility::PBVD<FluidSystem>>(pvtIdx,
|
|
|
|
depthColumn, pbubColumn));
|
|
|
|
|
|
|
|
} else {
|
2018-02-01 07:40:01 -06:00
|
|
|
throw std::runtime_error("Cannot initialise: RSVD or PBVD table not available.");
|
2017-11-20 01:47:41 -06:00
|
|
|
}
|
2018-01-08 07:29:33 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
if (rec[i].gasOilContactDepth() != rec[i].datumDepth()) {
|
2018-02-01 07:40:01 -06:00
|
|
|
throw std::runtime_error("Cannot initialise: when no explicit RSVD table is given, \n"
|
|
|
|
"datum depth must be at the gas-oil-contact. "
|
|
|
|
"In EQUIL region "+std::to_string(i + 1)+" (counting from 1), this does not hold.");
|
2017-11-16 06:04:26 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
const double pContact = rec[i].datumDepthPressure();
|
|
|
|
const double TContact = 273.15 + 20; // standard temperature for now
|
|
|
|
rsFunc_.push_back(std::make_shared<Miscibility::RsSatAtContact<FluidSystem>>(pvtIdx, pContact, TContact));
|
2017-11-16 06:04:26 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
for (size_t i = 0; i < rec.size(); ++i) {
|
2018-01-02 05:43:56 -06:00
|
|
|
rsFunc_.push_back(std::make_shared<Miscibility::NoMixing>());
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
2017-11-16 06:04:26 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
rvFunc_.reserve(rec.size());
|
2018-01-02 05:43:56 -06:00
|
|
|
if (FluidSystem::enableVaporizedOil()) {
|
|
|
|
for (size_t i = 0; i < rec.size(); ++i) {
|
2018-01-02 05:43:56 -06:00
|
|
|
if (eqlmap.cells(i).empty()) {
|
2018-01-02 05:43:56 -06:00
|
|
|
rvFunc_.push_back(std::shared_ptr<Miscibility::RvVD<FluidSystem>>());
|
2018-01-02 05:43:56 -06:00
|
|
|
continue;
|
2014-02-21 01:52:25 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
const int pvtIdx = regionPvtIdx_[i];
|
|
|
|
if (!rec[i].wetGasInitConstantRv()) {
|
2018-01-08 07:29:33 -06:00
|
|
|
const Opm::TableContainer& rvvdTables = tables.getRvvdTables();
|
|
|
|
const Opm::TableContainer& pdvdTables = tables.getPdvdTables();
|
|
|
|
|
|
|
|
if (rvvdTables.size() > 0) {
|
|
|
|
const Opm::RvvdTable& rvvdTable = rvvdTables.getTable<Opm::RvvdTable>(i);
|
|
|
|
std::vector<double> depthColumn = rvvdTable.getColumn("DEPTH").vectorCopy();
|
|
|
|
std::vector<double> rvColumn = rvvdTable.getColumn("RV").vectorCopy();
|
|
|
|
rvFunc_.push_back(std::make_shared<Miscibility::RvVD<FluidSystem>>(pvtIdx,
|
|
|
|
depthColumn, rvColumn));
|
|
|
|
} else if (pdvdTables.size() > 0) {
|
|
|
|
const Opm::PdvdTable& pdvdTable = pdvdTables.getTable<Opm::PdvdTable>(i);
|
|
|
|
std::vector<double> depthColumn = pdvdTable.getColumn("DEPTH").vectorCopy();
|
|
|
|
std::vector<double> pdewColumn = pdvdTable.getColumn("PDEW").vectorCopy();
|
|
|
|
rvFunc_.push_back(std::make_shared<Miscibility::PDVD<FluidSystem>>(pvtIdx,
|
|
|
|
depthColumn, pdewColumn));
|
|
|
|
} else {
|
2018-02-01 07:40:01 -06:00
|
|
|
throw std::runtime_error("Cannot initialise: RVVD or PDCD table not available.");
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
if (rec[i].gasOilContactDepth() != rec[i].datumDepth()) {
|
2018-02-01 07:40:01 -06:00
|
|
|
throw std::runtime_error(
|
2018-01-02 05:43:56 -06:00
|
|
|
"Cannot initialise: when no explicit RVVD table is given, \n"
|
|
|
|
"datum depth must be at the gas-oil-contact. "
|
2018-02-01 07:40:01 -06:00
|
|
|
"In EQUIL region "+std::to_string(i + 1)+" (counting from 1), this does not hold.");
|
2014-02-27 02:31:48 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
const double pContact = rec[i].datumDepthPressure() + rec[i].gasOilContactCapillaryPressure();
|
|
|
|
const double TContact = 273.15 + 20; // standard temperature for now
|
2018-01-02 05:43:56 -06:00
|
|
|
rvFunc_.push_back(std::make_shared<Miscibility::RvSatAtContact<FluidSystem>>(pvtIdx,pContact, TContact));
|
2014-02-27 02:31:48 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
else {
|
2018-01-02 05:43:56 -06:00
|
|
|
for (size_t i = 0; i < rec.size(); ++i) {
|
2018-01-02 05:43:56 -06:00
|
|
|
rvFunc_.push_back(std::make_shared<Miscibility::NoMixing>());
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
2014-02-21 01:52:25 -06:00
|
|
|
|
2018-01-30 09:15:47 -06:00
|
|
|
// extract the initial temperature
|
|
|
|
updateInitialTemperature_(eclipseState);
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
// Compute pressures, saturations, rs and rv factors.
|
2018-01-30 09:15:47 -06:00
|
|
|
calcPressSatRsRv(eclipseState, eqlmap, rec, materialLawManager, grid, grav);
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
// Modify oil pressure in no-oil regions so that the pressures of present phases can
|
|
|
|
// be recovered from the oil pressure and capillary relations.
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef std::vector<double> Vec;
|
|
|
|
typedef std::vector<Vec> PVec; // One per phase.
|
|
|
|
|
2018-01-30 09:15:47 -06:00
|
|
|
const Vec& temperature() const { return temperature_; }
|
2018-01-02 05:43:56 -06:00
|
|
|
const PVec& press() const { return pp_; }
|
|
|
|
const PVec& saturation() const { return sat_; }
|
|
|
|
const Vec& rs() const { return rs_; }
|
|
|
|
const Vec& rv() const { return rv_; }
|
|
|
|
|
|
|
|
private:
|
2018-01-30 09:15:47 -06:00
|
|
|
void updateInitialTemperature_(const Opm::EclipseState& eclState)
|
|
|
|
{
|
|
|
|
// Get the initial temperature data
|
|
|
|
const std::vector<double>& tempiData =
|
|
|
|
eclState.get3DProperties().getDoubleGridProperty("TEMPI").getData();
|
|
|
|
|
|
|
|
temperature_ = tempiData;
|
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
typedef EquilReg EqReg;
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector< std::shared_ptr<Miscibility::RsFunction> > rsFunc_;
|
|
|
|
std::vector< std::shared_ptr<Miscibility::RsFunction> > rvFunc_;
|
2018-01-02 05:43:56 -06:00
|
|
|
std::vector<int> regionPvtIdx_;
|
2018-01-30 09:15:47 -06:00
|
|
|
Vec temperature_;
|
2018-01-02 05:43:56 -06:00
|
|
|
PVec pp_;
|
|
|
|
PVec sat_;
|
|
|
|
Vec rs_;
|
|
|
|
Vec rv_;
|
2018-01-02 05:43:56 -06:00
|
|
|
Vec swatInit_;
|
2018-01-02 05:43:56 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
template<class RMap>
|
|
|
|
void setRegionPvtIdx(const Grid& grid, const Opm::EclipseState& eclState, const RMap& reg)
|
|
|
|
{
|
|
|
|
size_t numCompressed = grid.size(/*codim=*/0);
|
|
|
|
const auto* globalCell = Opm::UgGridHelpers::globalCell(grid);
|
|
|
|
std::vector<int> cellPvtRegionIdx(numCompressed);
|
|
|
|
|
|
|
|
//Get the PVTNUM data
|
|
|
|
const std::vector<int>& pvtnumData = eclState.get3DProperties().getIntGridProperty("PVTNUM").getData();
|
|
|
|
|
|
|
|
// Convert PVTNUM data into an array of indices for compressed cells. Remember
|
|
|
|
// that Eclipse uses Fortran-style indices which start at 1 instead of 0, so we
|
|
|
|
// need to subtract 1.
|
|
|
|
for (size_t cellIdx = 0; cellIdx < numCompressed; ++ cellIdx) {
|
|
|
|
size_t cartesianCellIdx = globalCell[cellIdx];
|
|
|
|
assert(cartesianCellIdx < pvtnumData.size());
|
|
|
|
size_t pvtRegionIdx = pvtnumData[cartesianCellIdx] - 1;
|
|
|
|
cellPvtRegionIdx[cellIdx] = pvtRegionIdx;
|
|
|
|
}
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
for (const auto& r : reg.activeRegions()) {
|
|
|
|
const auto& cells = reg.cells(r);
|
|
|
|
const int cell = *(cells.begin());
|
|
|
|
regionPvtIdx_[r] = cellPvtRegionIdx[cell];
|
|
|
|
}
|
|
|
|
}
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
template <class RMap, class MaterialLawManager>
|
2019-01-09 02:34:26 -06:00
|
|
|
void calcPressSatRsRv(const Opm::EclipseState& eclState OPM_UNUSED,
|
2018-01-30 09:15:47 -06:00
|
|
|
const RMap& reg,
|
2018-01-02 05:43:56 -06:00
|
|
|
const std::vector< Opm::EquilRecord >& rec,
|
|
|
|
MaterialLawManager& materialLawManager,
|
|
|
|
const Grid& grid,
|
|
|
|
const double grav)
|
2018-01-02 05:43:56 -06:00
|
|
|
{
|
|
|
|
for (const auto& r : reg.activeRegions()) {
|
|
|
|
const auto& cells = reg.cells(r);
|
2018-01-02 05:43:56 -06:00
|
|
|
if (cells.empty()) {
|
2018-01-02 05:43:56 -06:00
|
|
|
Opm::OpmLog::warning("Equilibration region " + std::to_string(r + 1)
|
|
|
|
+ " has no active cells");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-01-02 05:43:56 -06:00
|
|
|
const EqReg eqreg(rec[r], rsFunc_[r], rvFunc_[r], regionPvtIdx_[r]);
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
PVec pressures = phasePressures<FluidSystem>(grid, eqreg, cells, grav);
|
2018-01-02 05:43:56 -06:00
|
|
|
const PVec sat = phaseSaturations<FluidSystem>(grid, eqreg, cells, materialLawManager, swatInit_, pressures);
|
2018-01-02 05:43:56 -06:00
|
|
|
|
|
|
|
const int np = FluidSystem::numPhases;
|
|
|
|
for (int p = 0; p < np; ++p) {
|
|
|
|
copyFromRegion(pressures[p], cells, pp_[p]);
|
|
|
|
copyFromRegion(sat[p], cells, sat_[p]);
|
|
|
|
}
|
|
|
|
const bool oil = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
|
|
|
|
const bool gas = FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx);
|
|
|
|
if (oil && gas) {
|
|
|
|
const int oilpos = FluidSystem::oilPhaseIdx;
|
|
|
|
const int gaspos = FluidSystem::gasPhaseIdx;
|
2018-01-30 09:15:47 -06:00
|
|
|
const Vec rsVals = computeRs(grid, cells, pressures[oilpos], temperature_, *(rsFunc_[r]), sat[gaspos]);
|
|
|
|
const Vec rvVals = computeRs(grid, cells, pressures[gaspos], temperature_, *(rvFunc_[r]), sat[oilpos]);
|
2018-01-02 05:43:56 -06:00
|
|
|
copyFromRegion(rsVals, cells, rs_);
|
|
|
|
copyFromRegion(rvVals, cells, rv_);
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class CellRangeType>
|
|
|
|
void copyFromRegion(const Vec& source,
|
|
|
|
const CellRangeType& cells,
|
|
|
|
Vec& destination)
|
|
|
|
{
|
|
|
|
auto s = source.begin();
|
|
|
|
auto c = cells.begin();
|
|
|
|
const auto e = cells.end();
|
|
|
|
for (; c != e; ++c, ++s) {
|
2018-01-02 05:43:56 -06:00
|
|
|
destination[*c] =*s;
|
2018-01-02 05:43:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
} // namespace DeckDependent
|
|
|
|
} // namespace EQUIL
|
|
|
|
} // namespace Opm
|
Add basic equilibration facility
This commit adds a simple facility for calculating initial phase
pressures assuming stationary conditions, a known reference pressure
in the oil zone as well as the depth and capillary pressures at the
water-oil and gas-oil contacts.
Function 'Opm::equil::phasePressures()' uses a simple ODE/IVP-based
approach, solved using the traditional RK4 method with constant step
sizes, to derive the required pressure values. Specifically, we
solve the ODE
dp/dz = rho(z,p) * g
with 'z' represening depth, 'p' being a phase pressure and 'rho' the
associate phase density. Finally, 'g' is the acceleration of
gravity. We assume that we can calculate phase densities, e.g.,
from table look-up. This assumption holds in the case of an ECLIPSE
input deck.
Using RK4 with constant step sizes is a limitation of this
implementation. This, basically, assumes that the phase densities
varies only smoothly with depth and pressure (at reservoir
conditions).
2014-01-14 13:37:28 -06:00
|
|
|
|
|
|
|
#endif // OPM_INITSTATEEQUIL_HEADER_INCLUDED
|