mirror of
https://github.com/OPM/opm-simulators.git
synced 2025-01-16 16:21:55 -06:00
dc8f811cbe
After the restructuring of of the well model, keeping an extra class for the "Dense" model is not needed. The only thing still left in WellStateFullyImplicitBlackoilDense was some solvent related stuff, this PR moves this to WellStateFullyImplicitBlackoil and removes WellStateFullyImplicitBlackoilDense. In addition to a cleaning code this PR fixes missing solvent well output.
1115 lines
46 KiB
C++
1115 lines
46 KiB
C++
/*
|
|
Copyright 2015 IRIS AS
|
|
|
|
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/>.
|
|
*/
|
|
|
|
#ifndef OPM_BLACKOILSOLVENTMODEL_IMPL_HEADER_INCLUDED
|
|
#define OPM_BLACKOILSOLVENTMODEL_IMPL_HEADER_INCLUDED
|
|
|
|
#include <opm/autodiff/BlackoilSolventModel.hpp>
|
|
|
|
#include <opm/autodiff/AutoDiffBlock.hpp>
|
|
#include <opm/autodiff/AutoDiffHelpers.hpp>
|
|
#include <opm/autodiff/GridHelpers.hpp>
|
|
#include <opm/autodiff/BlackoilPropsAdFromDeck.hpp>
|
|
#include <opm/autodiff/GeoProps.hpp>
|
|
#include <opm/autodiff/WellDensitySegmented.hpp>
|
|
|
|
#include <opm/core/grid.h>
|
|
#include <opm/core/linalg/LinearSolverInterface.hpp>
|
|
#include <opm/core/linalg/ParallelIstlInformation.hpp>
|
|
#include <opm/core/props/rock/RockCompressibility.hpp>
|
|
#include <opm/common/ErrorMacros.hpp>
|
|
#include <opm/common/Exceptions.hpp>
|
|
#include <opm/parser/eclipse/Units/Units.hpp>
|
|
#include <opm/core/well_controls.h>
|
|
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
|
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
|
|
namespace Opm {
|
|
|
|
|
|
|
|
namespace detail {
|
|
|
|
template <class PU>
|
|
int solventPos(const PU& pu)
|
|
{
|
|
const int maxnp = Opm::BlackoilPhases::MaxNumPhases;
|
|
int pos = 0;
|
|
for (int phase = 0; phase < maxnp; ++phase) {
|
|
if (pu.phase_used[phase]) {
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
|
|
|
|
template <class Grid>
|
|
BlackoilSolventModel<Grid>::BlackoilSolventModel(const typename Base::ModelParameters& param,
|
|
const Grid& grid,
|
|
const BlackoilPropsAdFromDeck& fluid,
|
|
const DerivedGeology& geo,
|
|
const RockCompressibility* rock_comp_props,
|
|
const SolventPropsAdFromDeck& solvent_props,
|
|
const StandardWellsSolvent& well_model,
|
|
const NewtonIterationBlackoilInterface& linsolver,
|
|
std::shared_ptr< const EclipseState > eclState,
|
|
const bool has_disgas,
|
|
const bool has_vapoil,
|
|
const bool terminal_output,
|
|
const bool has_solvent,
|
|
const bool is_miscible)
|
|
: Base(param, grid, fluid, geo, rock_comp_props, well_model, linsolver,
|
|
eclState, has_disgas, has_vapoil, terminal_output),
|
|
has_solvent_(has_solvent),
|
|
solvent_pos_(detail::solventPos(fluid.phaseUsage())),
|
|
solvent_props_(solvent_props),
|
|
is_miscible_(is_miscible)
|
|
|
|
{
|
|
if (has_solvent_) {
|
|
|
|
// If deck has solvent, residual_ should contain solvent equation.
|
|
sd_.rq.resize(fluid_.numPhases() + 1);
|
|
residual_.material_balance_eq.resize(fluid_.numPhases() + 1, ADB::null());
|
|
Base::material_name_.push_back("Solvent");
|
|
assert(solvent_pos_ == fluid_.numPhases());
|
|
residual_.matbalscale.resize(fluid_.numPhases() + 1, 0.0031); // use the same as gas
|
|
|
|
wellModel().initSolvent(&solvent_props_, solvent_pos_, has_solvent_);
|
|
}
|
|
if (is_miscible_) {
|
|
mu_eff_.resize(fluid_.numPhases() + 1, ADB::null());
|
|
b_eff_.resize(fluid_.numPhases() + 1, ADB::null());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::makeConstantState(SolutionState& state) const
|
|
{
|
|
Base::makeConstantState(state);
|
|
state.solvent_saturation = ADB::constant(state.solvent_saturation.value());
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
std::vector<V>
|
|
BlackoilSolventModel<Grid>::variableStateInitials(const ReservoirState& x,
|
|
const WellState& xw) const
|
|
{
|
|
std::vector<V> vars0 = Base::variableStateInitials(x, xw);
|
|
assert(int(vars0.size()) == fluid_.numPhases() + 2);
|
|
|
|
// Initial solvent concentration.
|
|
if (has_solvent_) {
|
|
const auto& solvent_saturation = x.getCellData( BlackoilState::SSOL );
|
|
const int nc = solvent_saturation.size();
|
|
const V ss = Eigen::Map<const V>(solvent_saturation.data() , nc);
|
|
|
|
// This is some insanely detailed flickety flackety code;
|
|
// Solvent belongs after other reservoir vars but before well vars.
|
|
auto solvent_pos = vars0.begin() + fluid_.numPhases();
|
|
assert (not solvent_saturation.empty());
|
|
assert(solvent_pos == vars0.end() - 2);
|
|
vars0.insert(solvent_pos, ss);
|
|
}
|
|
return vars0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
std::vector<int>
|
|
BlackoilSolventModel<Grid>::variableStateIndices() const
|
|
{
|
|
std::vector<int> ind = Base::variableStateIndices();
|
|
assert(ind.size() == 5);
|
|
if (has_solvent_) {
|
|
ind.resize(6);
|
|
// Solvent belongs after other reservoir vars but before well vars.
|
|
ind[Solvent] = fluid_.numPhases();
|
|
// Solvent is pushing back the well vars.
|
|
++ind[Qs];
|
|
++ind[Bhp];
|
|
}
|
|
return ind;
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
typename BlackoilSolventModel<Grid>::SolutionState
|
|
BlackoilSolventModel<Grid>::variableStateExtractVars(const ReservoirState& x,
|
|
const std::vector<int>& indices,
|
|
std::vector<ADB>& vars) const
|
|
{
|
|
// This is more or less a copy of the base class. Refactoring is needed in the base class
|
|
// to avoid unnecessary copying.
|
|
|
|
//using namespace Opm::AutoDiffGrid;
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
|
const Opm::PhaseUsage pu = fluid_.phaseUsage();
|
|
|
|
SolutionState state(fluid_.numPhases());
|
|
|
|
// Pressure.
|
|
state.pressure = std::move(vars[indices[Pressure]]);
|
|
|
|
// Temperature cannot be a variable at this time (only constant).
|
|
const V temp = Eigen::Map<const V>(& x.temperature()[0], x.temperature().size());
|
|
state.temperature = ADB::constant(temp);
|
|
|
|
// Saturations
|
|
{
|
|
ADB so = ADB::constant(V::Ones(nc, 1));
|
|
|
|
if (active_[ Water ]) {
|
|
state.saturation[pu.phase_pos[ Water ]] = std::move(vars[indices[Sw]]);
|
|
const ADB& sw = state.saturation[pu.phase_pos[ Water ]];
|
|
so -= sw;
|
|
}
|
|
if (has_solvent_) {
|
|
state.solvent_saturation = std::move(vars[indices[Solvent]]);
|
|
so -= state.solvent_saturation;
|
|
}
|
|
|
|
if (active_[ Gas ]) {
|
|
// Define Sg Rs and Rv in terms of xvar.
|
|
// Xvar is only defined if gas phase is active
|
|
const ADB& xvar = vars[indices[Xvar]];
|
|
ADB& sg = state.saturation[ pu.phase_pos[ Gas ] ];
|
|
sg = Base::isSg_*xvar + Base::isRv_*so;
|
|
so -= sg;
|
|
|
|
if (active_[ Oil ]) {
|
|
// RS and RV is only defined if both oil and gas phase are active.
|
|
state.canonical_phase_pressures = computePressures(state.pressure, state.saturation[pu.phase_pos[ Water ]], so, sg, state.solvent_saturation);
|
|
sd_.rsSat = fluidRsSat(state.canonical_phase_pressures[ Oil ], so , cells_);
|
|
if (has_disgas_) {
|
|
state.rs = (1-Base::isRs_)*sd_.rsSat + Base::isRs_*xvar;
|
|
} else {
|
|
state.rs = sd_.rsSat;
|
|
}
|
|
sd_.rvSat = fluidRvSat(state.canonical_phase_pressures[ Gas ], so , cells_);
|
|
if (has_vapoil_) {
|
|
state.rv = (1-Base::isRv_)*sd_.rvSat + Base::isRv_*xvar;
|
|
} else {
|
|
state.rv = sd_.rvSat;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (active_[ Oil ]) {
|
|
// Note that so is never a primary variable.
|
|
state.saturation[pu.phase_pos[ Oil ]] = std::move(so);
|
|
}
|
|
}
|
|
// wells
|
|
wellModel().variableStateExtractWellsVars(indices, vars, state);
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::computeAccum(const SolutionState& state,
|
|
const int aix )
|
|
{
|
|
|
|
if (is_miscible_) {
|
|
computeEffectiveProperties(state);
|
|
}
|
|
Base::computeAccum(state, aix);
|
|
|
|
// Compute accumulation of the solvent
|
|
if (has_solvent_) {
|
|
const ADB& press = state.pressure;
|
|
const ADB& ss = state.solvent_saturation;
|
|
const ADB pv_mult = poroMult(press); // also computed in Base::computeAccum, could be optimized.
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
|
|
const ADB& pg = state.canonical_phase_pressures[pu.phase_pos[Gas]];
|
|
const std::vector<PhasePresence>& cond = phaseCondition();
|
|
sd_.rq[solvent_pos_].b = fluidReciprocFVF(Solvent, pg, state.temperature, state.rs, state.rv,cond);
|
|
sd_.rq[solvent_pos_].accum[aix] = pv_mult * sd_.rq[solvent_pos_].b * ss;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::
|
|
assembleMassBalanceEq(const SolutionState& state)
|
|
{
|
|
|
|
Base::assembleMassBalanceEq(state);
|
|
|
|
if (has_solvent_) {
|
|
residual_.material_balance_eq[ solvent_pos_ ] =
|
|
pvdt_ * (sd_.rq[solvent_pos_].accum[1] - sd_.rq[solvent_pos_].accum[0])
|
|
+ ops_.div*sd_.rq[solvent_pos_].mflux;
|
|
}
|
|
|
|
}
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::updateEquationsScaling()
|
|
{
|
|
Base::updateEquationsScaling();
|
|
assert(MaxNumPhases + 1 == residual_.matbalscale.size());
|
|
if (has_solvent_) {
|
|
const ADB& temp_b = sd_.rq[solvent_pos_].b;
|
|
ADB::V B = 1. / temp_b.value();
|
|
#if HAVE_MPI
|
|
if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) )
|
|
{
|
|
const ParallelISTLInformation& real_info =
|
|
boost::any_cast<const ParallelISTLInformation&>(linsolver_.parallelInformation());
|
|
double B_global_sum = 0;
|
|
real_info.computeReduction(B, Reduction::makeGlobalSumFunctor<double>(), B_global_sum);
|
|
residual_.matbalscale[solvent_pos_] = B_global_sum / Base::global_nc_;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
residual_.matbalscale[solvent_pos_] = B.mean();
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class Grid>
|
|
void BlackoilSolventModel<Grid>::addWellContributionToMassBalanceEq(const std::vector<ADB>& cq_s,
|
|
const SolutionState& state,
|
|
WellState& xw)
|
|
|
|
{
|
|
|
|
// Add well contributions to solvent mass balance equation
|
|
|
|
Base::addWellContributionToMassBalanceEq(cq_s, state, xw);
|
|
|
|
if (has_solvent_) {
|
|
const int nperf = wells().well_connpos[wells().number_of_wells];
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
const ADB zero = ADB::constant(V::Zero(nc));
|
|
const ADB& ss = state.solvent_saturation;
|
|
const ADB& sg = (active_[ Gas ]
|
|
? state.saturation[ pu.phase_pos[ Gas ] ]
|
|
: zero);
|
|
|
|
const std::vector<int> well_cells(wells().well_cells, wells().well_cells + nperf);
|
|
Selector<double> zero_selector(ss.value() + sg.value(), Selector<double>::Zero);
|
|
ADB F_solvent = subset(zero_selector.select(ss, ss / (ss + sg)),well_cells);
|
|
|
|
const int nw = wells().number_of_wells;
|
|
V injectedSolventFraction = Eigen::Map<const V>(&xw.solventFraction()[0], nperf);
|
|
|
|
V isProducer = V::Zero(nperf);
|
|
V ones = V::Constant(nperf,1.0);
|
|
for (int w = 0; w < nw; ++w) {
|
|
if(wells().type[w] == PRODUCER) {
|
|
for (int perf = wells().well_connpos[w]; perf < wells().well_connpos[w+1]; ++perf) {
|
|
isProducer[perf] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
const ADB& rs_perfcells = subset(state.rs, well_cells);
|
|
const ADB& rv_perfcells = subset(state.rv, well_cells);
|
|
int gas_pos = fluid_.phaseUsage().phase_pos[Gas];
|
|
int oil_pos = fluid_.phaseUsage().phase_pos[Oil];
|
|
|
|
// remove contribution from the dissolved gas.
|
|
const ADB cq_s_solvent = (isProducer * F_solvent + (ones - isProducer) * injectedSolventFraction) * (cq_s[gas_pos] - rs_perfcells * cq_s[oil_pos]) / (ones - rs_perfcells * rv_perfcells);
|
|
|
|
// Solvent contribution to the mass balance equation is given as a fraction
|
|
// of the gas contribution.
|
|
residual_.material_balance_eq[solvent_pos_] -= superset(cq_s_solvent, well_cells, nc);
|
|
|
|
// The gas contribution must be reduced accordingly for the total contribution to be
|
|
// the same.
|
|
residual_.material_balance_eq[gas_pos] += superset(cq_s_solvent, well_cells, nc);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::
|
|
updateState(const V& dx,
|
|
ReservoirState& reservoir_state,
|
|
WellState& well_state)
|
|
{
|
|
//TODO:
|
|
// This is basicly a copy of updateState in the Base class
|
|
// The convergence is very sensitive to details in the appelyard process
|
|
// and the hydrocarbonstate detection. Further changes may occur, refactoring
|
|
// to reuse more of the base class is planned when the code mature a bit more.
|
|
using namespace Opm::AutoDiffGrid;
|
|
const int np = fluid_.numPhases();
|
|
const int nc = numCells(grid_);
|
|
const V null;
|
|
assert(null.size() == 0);
|
|
const V zero = V::Zero(nc);
|
|
const V ones = V::Constant(nc,1.0);
|
|
|
|
// Extract parts of dx corresponding to each part.
|
|
const V dp = subset(dx, Span(nc));
|
|
int varstart = nc;
|
|
const V dsw = active_[Water] ? subset(dx, Span(nc, 1, varstart)) : null;
|
|
varstart += dsw.size();
|
|
|
|
const V dxvar = active_[Gas] ? subset(dx, Span(nc, 1, varstart)): null;
|
|
varstart += dxvar.size();
|
|
|
|
const V dss = has_solvent_ ? subset(dx, Span(nc, 1, varstart)) : null;
|
|
varstart += dss.size();
|
|
|
|
// Extract well parts np phase rates + bhp
|
|
const V dwells = subset(dx, Span(wellModel().numWellVars(), 1, varstart));
|
|
varstart += dwells.size();
|
|
|
|
assert(varstart == dx.size());
|
|
|
|
// Pressure update.
|
|
const double dpmaxrel = dpMaxRel();
|
|
const V p_old = Eigen::Map<const V>(&reservoir_state.pressure()[0], nc, 1);
|
|
const V absdpmax = dpmaxrel*p_old.abs();
|
|
const V dp_limited = sign(dp) * dp.abs().min(absdpmax);
|
|
const V p = (p_old - dp_limited).max(zero);
|
|
std::copy(&p[0], &p[0] + nc, reservoir_state.pressure().begin());
|
|
|
|
|
|
// Saturation updates.
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
const DataBlock s_old = Eigen::Map<const DataBlock>(& reservoir_state.saturation()[0], nc, np);
|
|
const double dsmax = dsMax();
|
|
|
|
// initialize with zeros
|
|
// if the phase is active the saturation are overwritten.
|
|
V so;
|
|
V sw = zero;
|
|
V sg = zero;
|
|
V ss = zero;
|
|
|
|
// Appleyard chop process.
|
|
// We chop too large updates in the saturations
|
|
{
|
|
V maxVal = zero;
|
|
V dso = zero;
|
|
if (active_[Water]){
|
|
maxVal = dsw.abs().max(maxVal);
|
|
dso = dso - dsw;
|
|
}
|
|
|
|
V dsg;
|
|
if (active_[Gas]){
|
|
dsg = Base::isSg_ * dxvar - Base::isRv_ * dsw;
|
|
maxVal = dsg.abs().max(maxVal);
|
|
dso = dso - dsg;
|
|
}
|
|
if (has_solvent_){
|
|
maxVal = dss.abs().max(maxVal);
|
|
// solvent is not added note that the so calculated
|
|
// here is overwritten later
|
|
//dso = dso - dss;
|
|
}
|
|
|
|
maxVal = dso.abs().max(maxVal);
|
|
|
|
V step = dsmax/maxVal;
|
|
step = step.min(1.);
|
|
|
|
if (active_[Water]) {
|
|
const int pos = pu.phase_pos[ Water ];
|
|
const V sw_old = s_old.col(pos);
|
|
sw = sw_old - step * dsw;
|
|
}
|
|
|
|
if (active_[Gas]) {
|
|
const int pos = pu.phase_pos[ Gas ];
|
|
const V sg_old = s_old.col(pos);
|
|
sg = sg_old - step * dsg;
|
|
}
|
|
if (has_solvent_) {
|
|
auto& solvent_saturation = reservoir_state.getCellData( reservoir_state.SSOL );
|
|
const V ss_old = Eigen::Map<const V>(&solvent_saturation[0], nc, 1);
|
|
ss = ss_old - step * dss;
|
|
}
|
|
|
|
const int pos = pu.phase_pos[ Oil ];
|
|
const V so_old = s_old.col(pos);
|
|
so = so_old - step * dso;
|
|
}
|
|
|
|
// solvent is not included in the adjustment for negative saturation
|
|
auto ixg = sg < 0;
|
|
for (int c = 0; c < nc; ++c) {
|
|
if (ixg[c]) {
|
|
sw[c] = sw[c] / (1-sg[c]);
|
|
so[c] = so[c] / (1-sg[c]);
|
|
sg[c] = 0;
|
|
}
|
|
}
|
|
|
|
auto ixo = so < 0;
|
|
for (int c = 0; c < nc; ++c) {
|
|
if (ixo[c]) {
|
|
sw[c] = sw[c] / (1-so[c]);
|
|
sg[c] = sg[c] / (1-so[c]);
|
|
so[c] = 0;
|
|
}
|
|
}
|
|
|
|
auto ixw = sw < 0;
|
|
for (int c = 0; c < nc; ++c) {
|
|
if (ixw[c]) {
|
|
so[c] = so[c] / (1-sw[c]);
|
|
sg[c] = sg[c] / (1-sw[c]);
|
|
sw[c] = 0;
|
|
}
|
|
}
|
|
|
|
auto ixs = ss < 0;
|
|
for (int c = 0; c < nc; ++c) {
|
|
if (ixs[c]) {
|
|
ss[c] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// The oil saturation is defined to
|
|
// fill the rest of the pore space.
|
|
// For convergence reasons oil saturations
|
|
// is included in the appelyard chopping
|
|
so = V::Constant(nc,1.0) - sw - sg - ss;
|
|
|
|
// Update rs and rv
|
|
const double drmaxrel = drMaxRel();
|
|
V rs;
|
|
if (has_disgas_) {
|
|
const V rs_old = Eigen::Map<const V>(&reservoir_state.gasoilratio()[0], nc);
|
|
const V drs = Base::isRs_ * dxvar;
|
|
const V drs_limited = sign(drs) * drs.abs().min( (rs_old.abs()*drmaxrel).max( ones*1.0));
|
|
rs = rs_old - drs_limited;
|
|
rs = rs.max(zero);
|
|
}
|
|
V rv;
|
|
if (has_vapoil_) {
|
|
const V rv_old = Eigen::Map<const V>(&reservoir_state.rv()[0], nc);
|
|
const V drv = Base::isRv_ * dxvar;
|
|
const V drv_limited = sign(drv) * drv.abs().min( (rv_old.abs()*drmaxrel).max( ones*1e-3));
|
|
rv = rv_old - drv_limited;
|
|
rv = rv.max(zero);
|
|
}
|
|
|
|
sd_.soMax = fluid_.satOilMax();
|
|
|
|
// Sg is used as primal variable for water only cells.
|
|
const double epsilon = std::sqrt(std::numeric_limits<double>::epsilon());
|
|
auto watOnly = sw > (1 - epsilon);
|
|
|
|
// phase translation sg <-> rs
|
|
std::vector<HydroCarbonState>& hydroCarbonState = reservoir_state.hydroCarbonState();
|
|
std::fill(hydroCarbonState.begin(), hydroCarbonState.end(), HydroCarbonState::GasAndOil);
|
|
|
|
if (has_disgas_) {
|
|
const V rsSat0 = fluidRsSat(p_old, s_old.col(pu.phase_pos[Oil]), cells_);
|
|
const V rsSat = fluidRsSat(p, so, cells_);
|
|
sd_.rsSat = ADB::constant(rsSat);
|
|
// The obvious case
|
|
auto hasGas = (sg > 0 && Base::isRs_ == 0);
|
|
|
|
// Set oil saturated if previous rs is sufficiently large
|
|
const V rs_old = Eigen::Map<const V>(&reservoir_state.gasoilratio()[0], nc);
|
|
auto gasVaporized = ( (rs > rsSat * (1+epsilon) && Base::isRs_ == 1 ) && (rs_old > rsSat0 * (1-epsilon)) );
|
|
auto useSg = watOnly || hasGas || gasVaporized;
|
|
for (int c = 0; c < nc; ++c) {
|
|
if (useSg[c]) {
|
|
rs[c] = rsSat[c];
|
|
if (watOnly[c]) {
|
|
so[c] = 0;
|
|
sg[c] = 0;
|
|
ss[c] = 0;
|
|
rs[c] = 0;
|
|
}
|
|
|
|
} else {
|
|
hydroCarbonState[c] = HydroCarbonState::OilOnly;
|
|
}
|
|
}
|
|
rs = rs.min(rsSat);
|
|
}
|
|
|
|
// phase transitions so <-> rv
|
|
if (has_vapoil_) {
|
|
|
|
// The gas pressure is needed for the rvSat calculations
|
|
const V gaspress_old = computeGasPressure(p_old, s_old.col(Water), s_old.col(Oil), s_old.col(Gas));
|
|
const V gaspress = computeGasPressure(p, sw, so, sg);
|
|
const V rvSat0 = fluidRvSat(gaspress_old, s_old.col(pu.phase_pos[Oil]), cells_);
|
|
const V rvSat = fluidRvSat(gaspress, so, cells_);
|
|
sd_.rvSat = ADB::constant(rvSat);
|
|
|
|
// The obvious case
|
|
auto hasOil = (so > 0 && Base::isRv_ == 0);
|
|
|
|
// Set oil saturated if previous rv is sufficiently large
|
|
const V rv_old = Eigen::Map<const V>(&reservoir_state.rv()[0], nc);
|
|
auto oilCondensed = ( (rv > rvSat * (1+epsilon) && Base::isRv_ == 1) && (rv_old > rvSat0 * (1-epsilon)) );
|
|
auto useSg = watOnly || hasOil || oilCondensed;
|
|
for (int c = 0; c < nc; ++c) {
|
|
if (useSg[c]) {
|
|
rv[c] = rvSat[c];
|
|
if (watOnly[c]) {
|
|
so[c] = 0;
|
|
sg[c] = 0;
|
|
ss[c] = 0;
|
|
rv[c] = 0;
|
|
}
|
|
|
|
} else {
|
|
hydroCarbonState[c] = HydroCarbonState::GasOnly;
|
|
}
|
|
}
|
|
rv = rv.min(rvSat);
|
|
|
|
}
|
|
|
|
// Update the reservoir_state
|
|
if (has_solvent_) {
|
|
auto& solvent_saturation = reservoir_state.getCellData( reservoir_state.SSOL );
|
|
std::copy(&ss[0], &ss[0] + nc, solvent_saturation.begin());
|
|
}
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
reservoir_state.saturation()[c*np + pu.phase_pos[ Water ]] = sw[c];
|
|
}
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
reservoir_state.saturation()[c*np + pu.phase_pos[ Gas ]] = sg[c];
|
|
}
|
|
|
|
if (active_[ Oil ]) {
|
|
const int pos = pu.phase_pos[ Oil ];
|
|
for (int c = 0; c < nc; ++c) {
|
|
reservoir_state.saturation()[c*np + pos] = so[c];
|
|
}
|
|
}
|
|
|
|
// Update the reservoir_state
|
|
if (has_disgas_) {
|
|
std::copy(&rs[0], &rs[0] + nc, reservoir_state.gasoilratio().begin());
|
|
}
|
|
|
|
if (has_vapoil_) {
|
|
std::copy(&rv[0], &rv[0] + nc, reservoir_state.rv().begin());
|
|
}
|
|
|
|
wellModel().updateWellState(dwells, Base::dbhpMaxRel(), well_state);
|
|
|
|
for( auto w = 0; w < wells().number_of_wells; ++w ) {
|
|
if (wells().type[w] == INJECTOR) {
|
|
continue;
|
|
}
|
|
|
|
for (int perf = wells().well_connpos[w]; perf < wells().well_connpos[w+1]; ++perf ) {
|
|
int wc = wells().well_cells[perf];
|
|
if ( (ss[wc] + sg[wc]) > 0) {
|
|
well_state.solventFraction()[perf] = ss[wc] / (ss[wc] + sg[wc]);
|
|
} else {
|
|
well_state.solventFraction()[perf] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Update phase conditions used for property calculations.
|
|
updatePhaseCondFromPrimalVariable(reservoir_state);
|
|
}
|
|
|
|
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::computeMassFlux(const int actph ,
|
|
const V& transi,
|
|
const ADB& kr ,
|
|
const ADB& mu ,
|
|
const ADB& rho ,
|
|
const ADB& phasePressure,
|
|
const SolutionState& state)
|
|
{
|
|
|
|
const int canonicalPhaseIdx = canph_[ actph ];
|
|
// make a copy to make it possible to modify it
|
|
ADB kr_mod = kr;
|
|
if (canonicalPhaseIdx == Gas) {
|
|
if (has_solvent_) {
|
|
const int nc = Opm::UgGridHelpers::numCells(grid_);
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
const ADB zero = ADB::constant(V::Zero(nc));
|
|
const V ones = V::Constant(nc, 1.0);
|
|
|
|
const ADB& ss = state.solvent_saturation;
|
|
const ADB& sg = (active_[ Gas ]
|
|
? state.saturation[ pu.phase_pos[ Gas ] ]
|
|
: zero);
|
|
Selector<double> zero_selector(ss.value() + sg.value(), Selector<double>::Zero);
|
|
const ADB F_solvent = zero_selector.select(zero, ss / (ss + sg));
|
|
|
|
// Compute solvent properties
|
|
const std::vector<PhasePresence>& cond = phaseCondition();
|
|
ADB mu_s = fluidViscosity(Solvent, phasePressure,state.temperature, state.rs, state.rv, cond);
|
|
ADB rho_s = fluidDensity(Solvent,sd_.rq[solvent_pos_].b, state.rs, state.rv);
|
|
|
|
// Compute solvent relperm and mass flux
|
|
ADB krs = solvent_props_.solventRelPermMultiplier(F_solvent, cells_) * kr_mod;
|
|
Base::computeMassFlux(solvent_pos_, transi, krs, mu_s, rho_s, phasePressure, state);
|
|
|
|
// Modify gas relperm
|
|
kr_mod = solvent_props_.gasRelPermMultiplier( (ones - F_solvent) , cells_) * kr_mod;
|
|
|
|
}
|
|
}
|
|
// Compute mobility and flux
|
|
Base::computeMassFlux(actph, transi, kr_mod, mu, rho, phasePressure, state);
|
|
|
|
}
|
|
|
|
template <class Grid>
|
|
ADB
|
|
BlackoilSolventModel<Grid>::fluidViscosity(const int phase,
|
|
const ADB& p ,
|
|
const ADB& temp ,
|
|
const ADB& rs ,
|
|
const ADB& rv ,
|
|
const std::vector<PhasePresence>& cond) const
|
|
{
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
if (phase == Solvent) {
|
|
if (!is_miscible_) {
|
|
return solvent_props_.muSolvent(p, cells_);
|
|
} else {
|
|
return mu_eff_[solvent_pos_];
|
|
}
|
|
|
|
} else {
|
|
if (!is_miscible_) {
|
|
return Base::fluidViscosity(phase, p, temp, rs, rv, cond);
|
|
} else {
|
|
return mu_eff_[pu.phase_pos[ phase ]];
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class Grid>
|
|
ADB
|
|
BlackoilSolventModel<Grid>::fluidReciprocFVF(const int phase,
|
|
const ADB& p ,
|
|
const ADB& temp ,
|
|
const ADB& rs ,
|
|
const ADB& rv ,
|
|
const std::vector<PhasePresence>& cond) const
|
|
{
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
if (phase == Solvent) {
|
|
if (!is_miscible_) {
|
|
return solvent_props_.bSolvent(p, cells_);
|
|
} else {
|
|
return b_eff_[solvent_pos_];
|
|
}
|
|
|
|
} else {
|
|
if (!is_miscible_) {
|
|
return Base::fluidReciprocFVF(phase, p, temp, rs, rv, cond);
|
|
} else {
|
|
return b_eff_[pu.phase_pos[ phase ]];
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class Grid>
|
|
ADB
|
|
BlackoilSolventModel<Grid>::fluidDensity(const int phase,
|
|
const ADB& b,
|
|
const ADB& rs,
|
|
const ADB& rv) const
|
|
{
|
|
if (phase == Solvent && has_solvent_) {
|
|
return solvent_props_.solventSurfaceDensity(cells_) * b;
|
|
} else {
|
|
return Base::fluidDensity(phase, b, rs, rv);
|
|
}
|
|
}
|
|
|
|
template <class Grid>
|
|
std::vector<ADB>
|
|
BlackoilSolventModel<Grid>::computeRelPerm(const SolutionState& state) const
|
|
{
|
|
using namespace Opm::AutoDiffGrid;
|
|
const int nc = numCells(grid_);
|
|
|
|
const ADB zero = ADB::constant(V::Zero(nc));
|
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
const ADB& sw = (active_[ Water ]
|
|
? state.saturation[ pu.phase_pos[ Water ] ]
|
|
: zero);
|
|
|
|
const ADB& so = (active_[ Oil ]
|
|
? state.saturation[ pu.phase_pos[ Oil ] ]
|
|
: zero);
|
|
|
|
const ADB& sg = (active_[ Gas ]
|
|
? state.saturation[ pu.phase_pos[ Gas ] ]
|
|
: zero);
|
|
|
|
if (has_solvent_) {
|
|
const ADB& ss = state.solvent_saturation;
|
|
if (is_miscible_) {
|
|
|
|
assert(active_[ Oil ]);
|
|
assert(active_[ Gas ]);
|
|
|
|
std::vector<ADB> relperm = fluid_.relperm(sw, so, sg+ss, cells_);
|
|
|
|
Selector<double> zero_selector(ss.value() + sg.value(), Selector<double>::Zero);
|
|
ADB F_solvent = zero_selector.select(ss, ss / (ss + sg));
|
|
const ADB& po = state.canonical_phase_pressures[ Oil ];
|
|
const ADB misc = solvent_props_.miscibilityFunction(F_solvent, cells_)
|
|
* solvent_props_.pressureMiscibilityFunction(po, cells_);
|
|
|
|
const ADB sn = ss + so + sg;
|
|
|
|
// adjust endpoints
|
|
const V sgcr = fluid_.scaledCriticalGasSaturations(cells_);
|
|
const V sogcr = fluid_.scaledCriticalOilinGasSaturations(cells_);
|
|
const ADB sorwmis = solvent_props_.miscibleResidualOilSaturationFunction(sw, cells_);
|
|
const ADB sgcwmis = solvent_props_.miscibleCriticalGasSaturationFunction(sw, cells_);
|
|
|
|
const V ones = V::Constant(nc, 1.0);
|
|
ADB sor = misc * sorwmis + (ones - misc) * sogcr;
|
|
ADB sgc = misc * sgcwmis + (ones - misc) * sgcr;
|
|
|
|
ADB ssg = ss + sg - sgc;
|
|
const ADB sn_eff = sn - sor - sgc;
|
|
|
|
// avoid negative values
|
|
Selector<double> negSsg_selector(ssg.value(), Selector<double>::LessZero);
|
|
ssg = negSsg_selector.select(zero, ssg);
|
|
|
|
// avoid negative value and division on zero
|
|
Selector<double> zeroSn_selector(sn_eff.value(), Selector<double>::LessEqualZero);
|
|
const ADB F_totalGas = zeroSn_selector.select( zero, ssg / sn_eff);
|
|
|
|
const ADB mkrgt = solvent_props_.miscibleSolventGasRelPermMultiplier(F_totalGas, cells_) * solvent_props_.misicibleHydrocarbonWaterRelPerm(sn, cells_);
|
|
const ADB mkro = solvent_props_.miscibleOilRelPermMultiplier(ones - F_totalGas, cells_) * solvent_props_.misicibleHydrocarbonWaterRelPerm(sn, cells_);
|
|
|
|
const V eps = V::Constant(nc, 1e-7);
|
|
Selector<double> noOil_selector(so.value()-eps, Selector<double>::LessEqualZero);
|
|
relperm[Gas] = (ones - misc) * relperm[Gas] + misc * mkrgt;
|
|
relperm[Oil] = noOil_selector.select(relperm[Oil], (ones - misc) * relperm[Oil] + misc * mkro);
|
|
return relperm;
|
|
} else {
|
|
return fluid_.relperm(sw, so, sg+ss, cells_);
|
|
}
|
|
} else {
|
|
return fluid_.relperm(sw, so, sg, cells_);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::computeEffectiveProperties(const SolutionState& state)
|
|
{
|
|
// Viscosity
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
const int np = fluid_.numPhases();
|
|
const int nc = Opm::UgGridHelpers::numCells(grid_);
|
|
const ADB zero = ADB::constant(V::Zero(nc));
|
|
|
|
const ADB& pw = state.canonical_phase_pressures[pu.phase_pos[Water]];
|
|
const ADB& po = state.canonical_phase_pressures[pu.phase_pos[Oil]];
|
|
const ADB& pg = state.canonical_phase_pressures[pu.phase_pos[Gas]];
|
|
const std::vector<PhasePresence>& cond = phaseCondition();
|
|
|
|
const ADB mu_w = fluid_.muWat(pw, state.temperature, cells_);
|
|
const ADB mu_o = fluid_.muOil(po, state.temperature, state.rs, cond, cells_);
|
|
const ADB mu_g = fluid_.muGas(pg, state.temperature, state.rv, cond, cells_);
|
|
const ADB mu_s = solvent_props_.muSolvent(pg,cells_);
|
|
std::vector<ADB> viscosity(np + 1, ADB::null());
|
|
viscosity[pu.phase_pos[Oil]] = mu_o;
|
|
viscosity[pu.phase_pos[Gas]] = mu_g;
|
|
viscosity[pu.phase_pos[Water]] = mu_w;
|
|
viscosity[solvent_pos_] = mu_s;
|
|
|
|
// Density
|
|
const ADB bw = fluid_.bWat(pw, state.temperature, cells_);
|
|
const ADB bo = fluid_.bOil(po, state.temperature, state.rs, cond, cells_);
|
|
const ADB bg = fluid_.bGas(pg, state.temperature, state.rv, cond, cells_);
|
|
const ADB bs = solvent_props_.bSolvent(pg, cells_);
|
|
|
|
std::vector<ADB> density(np + 1, ADB::null());
|
|
density[pu.phase_pos[Oil]] = fluidDensity(Oil, bo, state.rs, state.rv);
|
|
density[pu.phase_pos[Gas]] = fluidDensity(Gas, bg, state.rs, state.rv);
|
|
density[pu.phase_pos[Water]] = fluidDensity(Water, bw, state.rs, state.rv);
|
|
density[solvent_pos_] = fluidDensity(Solvent, bs, state.rs, state.rv);
|
|
|
|
// Effective saturations
|
|
const ADB& ss = state.solvent_saturation;
|
|
const ADB& so = state.saturation[ pu.phase_pos[ Oil ] ];
|
|
const ADB& sg = (active_[ Gas ]
|
|
? state.saturation[ pu.phase_pos[ Gas ] ]
|
|
: zero);
|
|
const ADB& sw = (active_[ Water ]
|
|
? state.saturation[ pu.phase_pos[ Water ] ]
|
|
: zero);
|
|
|
|
const ADB sorwmis = solvent_props_.miscibleResidualOilSaturationFunction(sw, cells_);
|
|
const ADB sgcwmis = solvent_props_.miscibleCriticalGasSaturationFunction(sw, cells_);
|
|
|
|
std::vector<ADB> effective_saturations (np + 1, ADB::null());
|
|
effective_saturations[pu.phase_pos[Oil]] = so - sorwmis;
|
|
effective_saturations[pu.phase_pos[Gas]] = sg - sgcwmis;
|
|
effective_saturations[pu.phase_pos[Water]] = sw;
|
|
effective_saturations[solvent_pos_] = ss - sgcwmis;
|
|
|
|
// Compute effective viscosities and densities
|
|
computeToddLongstaffMixing(viscosity, density, effective_saturations, po, pu);
|
|
|
|
// compute the volume factors from the densities
|
|
const ADB b_eff_o = density[pu.phase_pos[ Oil ]] / (fluid_.surfaceDensity(pu.phase_pos[ Oil ], cells_) + fluid_.surfaceDensity(pu.phase_pos[ Gas ], cells_) * state.rs);
|
|
const ADB b_eff_g = density[pu.phase_pos[ Gas ]] / (fluid_.surfaceDensity(pu.phase_pos[ Gas ], cells_) + fluid_.surfaceDensity(pu.phase_pos[ Oil ], cells_) * state.rv);
|
|
const ADB b_eff_s = density[solvent_pos_] / solvent_props_.solventSurfaceDensity(cells_);
|
|
|
|
// account for pressure effects and store the computed volume factors and viscosities
|
|
const V ones = V::Constant(nc, 1.0);
|
|
const ADB pmisc = solvent_props_.pressureMiscibilityFunction(po, cells_);
|
|
|
|
b_eff_[pu.phase_pos[ Oil ]] = pmisc * b_eff_o + (ones - pmisc) * bo;
|
|
b_eff_[pu.phase_pos[ Gas ]] = pmisc * b_eff_g + (ones - pmisc) * bg;
|
|
b_eff_[solvent_pos_] = pmisc * b_eff_s + (ones - pmisc) * bs;
|
|
|
|
// keep the mu*b interpolation
|
|
mu_eff_[pu.phase_pos[ Oil ]] = b_eff_[pu.phase_pos[ Oil ]] / (pmisc * b_eff_o / viscosity[pu.phase_pos[ Oil ]] + (ones - pmisc) * bo / mu_o);
|
|
mu_eff_[pu.phase_pos[ Gas ]] = b_eff_[pu.phase_pos[ Gas ]] / (pmisc * b_eff_g / viscosity[pu.phase_pos[ Gas ]] + (ones - pmisc) * bg / mu_g);
|
|
mu_eff_[solvent_pos_] = b_eff_[solvent_pos_] / (pmisc * b_eff_s / viscosity[solvent_pos_] + (ones - pmisc) * bs / mu_s);
|
|
|
|
// for water the pure values are used
|
|
mu_eff_[pu.phase_pos[ Water ]] = mu_w;
|
|
b_eff_[pu.phase_pos[ Water ]] = bw;
|
|
}
|
|
|
|
template <class Grid>
|
|
void
|
|
BlackoilSolventModel<Grid>::computeToddLongstaffMixing(std::vector<ADB>& viscosity, std::vector<ADB>& density, const std::vector<ADB>& saturations, const ADB po, const Opm::PhaseUsage pu)
|
|
{
|
|
const int nc = cells_.size();
|
|
const V ones = V::Constant(nc, 1.0);
|
|
const ADB zero = ADB::constant(V::Zero(nc));
|
|
|
|
// Calculation of effective saturations
|
|
ADB so_eff = saturations[pu.phase_pos[ Oil ]];
|
|
ADB sg_eff = saturations[pu.phase_pos[ Gas ]];
|
|
ADB ss_eff = saturations[solvent_pos_];
|
|
|
|
// Avoid negative values
|
|
Selector<double> negative_selectorSo(so_eff.value(), Selector<double>::LessZero);
|
|
Selector<double> negative_selectorSg(sg_eff.value(), Selector<double>::LessZero);
|
|
Selector<double> negative_selectorSs(ss_eff.value(), Selector<double>::LessZero);
|
|
so_eff = negative_selectorSo.select(zero, so_eff);
|
|
sg_eff = negative_selectorSg.select(zero, sg_eff);
|
|
ss_eff = negative_selectorSs.select(zero, ss_eff);
|
|
|
|
// Make copy of the pure viscosities
|
|
const ADB mu_o = viscosity[pu.phase_pos[ Oil ]];
|
|
const ADB mu_g = viscosity[pu.phase_pos[ Gas ]];
|
|
const ADB mu_s = viscosity[solvent_pos_];
|
|
|
|
const ADB sn_eff = so_eff + sg_eff + ss_eff;
|
|
const ADB sos_eff = so_eff + ss_eff;
|
|
const ADB ssg_eff = ss_eff + sg_eff;
|
|
|
|
// Avoid division by zero
|
|
Selector<double> zero_selectorSos(sos_eff.value(), Selector<double>::Zero);
|
|
Selector<double> zero_selectorSsg(ssg_eff.value(), Selector<double>::Zero);
|
|
Selector<double> zero_selectorSn(sn_eff.value(), Selector<double>::Zero);
|
|
|
|
const ADB mu_s_pow = pow(mu_s, 0.25);
|
|
const ADB mu_o_pow = pow(mu_o, 0.25);
|
|
const ADB mu_g_pow = pow(mu_g, 0.25);
|
|
|
|
const ADB mu_mos = zero_selectorSos.select(mu_o, mu_o * mu_s / pow( ( (so_eff / sos_eff) * mu_s_pow) + ( (ss_eff / sos_eff) * mu_o_pow) , 4.0));
|
|
const ADB mu_msg = zero_selectorSsg.select(mu_g, mu_g * mu_s / pow( ( (sg_eff / ssg_eff) * mu_s_pow) + ( (ss_eff / ssg_eff) * mu_g_pow) , 4.0));
|
|
const ADB mu_m = zero_selectorSn.select(mu_s, mu_o * mu_s * mu_g / pow( ( (so_eff / sn_eff) * mu_s_pow * mu_g_pow)
|
|
+ ( (ss_eff / sn_eff) * mu_o_pow * mu_g_pow) + ( (sg_eff / sn_eff) * mu_s_pow * mu_o_pow), 4.0));
|
|
// Mixing parameter for viscosity
|
|
// The pressureMixingParameter represent the miscibility of the solvent while the mixingParameterViscosity the effect of the porous media.
|
|
// The pressureMixingParameter is not implemented in ecl100.
|
|
const ADB mix_param_mu = solvent_props_.mixingParameterViscosity(cells_) * solvent_props_.pressureMixingParameter(po, cells_);
|
|
|
|
Selector<double> zero_selectorSs(ss_eff.value(), Selector<double>::Zero);
|
|
// Update viscosities, use pure values if solvent saturation is zero
|
|
viscosity[pu.phase_pos[ Oil ]] = zero_selectorSs.select(mu_o, pow(mu_o,ones - mix_param_mu) * pow(mu_mos, mix_param_mu));
|
|
viscosity[pu.phase_pos[ Gas ]] = zero_selectorSs.select(mu_g, pow(mu_g,ones - mix_param_mu) * pow(mu_msg, mix_param_mu));
|
|
viscosity[solvent_pos_] = zero_selectorSs.select(mu_s, pow(mu_s,ones - mix_param_mu) * pow(mu_m, mix_param_mu));
|
|
|
|
// Density
|
|
ADB& rho_o = density[pu.phase_pos[ Oil ]];
|
|
ADB& rho_g = density[pu.phase_pos[ Gas ]];
|
|
ADB& rho_s = density[solvent_pos_];
|
|
|
|
// mixing parameter for density
|
|
const ADB mix_param_rho = solvent_props_.mixingParameterDensity(cells_) * solvent_props_.pressureMixingParameter(po, cells_);
|
|
|
|
// compute effective viscosities for density calculations. These have to
|
|
// be recomputed as a different mixing parameter may be used.
|
|
const ADB mu_o_eff = pow(mu_o,ones - mix_param_rho) * pow(mu_mos, mix_param_rho);
|
|
const ADB mu_g_eff = pow(mu_g,ones - mix_param_rho) * pow(mu_msg, mix_param_rho);
|
|
const ADB mu_s_eff = pow(mu_s,ones - mix_param_rho) * pow(mu_m, mix_param_rho);
|
|
|
|
const ADB sog_eff = so_eff + sg_eff;
|
|
// Avoid division by zero
|
|
Selector<double> zero_selectorSog_eff(sog_eff.value(), Selector<double>::Zero);
|
|
const ADB sof = zero_selectorSog_eff.select(zero , so_eff / sog_eff);
|
|
const ADB sgf = zero_selectorSog_eff.select(zero , sg_eff / sog_eff);
|
|
|
|
// Effective densities
|
|
const ADB mu_sog_pow = mu_s_pow * ( (sgf * mu_o_pow) + (sof * mu_g_pow) );
|
|
const ADB mu_o_eff_pow = pow(mu_o_eff, 0.25);
|
|
const ADB mu_g_eff_pow = pow(mu_g_eff, 0.25);
|
|
const ADB mu_s_eff_pow = pow(mu_s_eff, 0.25);
|
|
const ADB sfraction_oe = (mu_o_pow * (mu_o_eff_pow - mu_s_pow)) / (mu_o_eff_pow * (mu_o_pow - mu_s_pow));
|
|
const ADB sfraction_ge = (mu_g_pow * (mu_s_pow - mu_g_eff_pow)) / (mu_g_eff_pow * (mu_s_pow - mu_g_pow));
|
|
const ADB sfraction_se = (mu_sog_pow - ( mu_o_pow * mu_g_pow * mu_s_pow / mu_s_eff_pow) ) / ( mu_sog_pow - (mu_o_pow * mu_g_pow));
|
|
const ADB rho_o_eff = (rho_o * sfraction_oe) + (rho_s * (ones - sfraction_oe));
|
|
const ADB rho_g_eff = (rho_g * sfraction_ge) + (rho_s * (ones - sfraction_ge));
|
|
const ADB rho_s_eff = (rho_s * sfraction_se) + (rho_g * sgf * (ones - sfraction_se)) + (rho_o * sof * (ones - sfraction_se));
|
|
|
|
// Avoid division by zero for equal mobilities. For equal mobilities the effecitive density is calculated
|
|
// based on the saturation fraction directly.
|
|
Selector<double> unitGasSolventMobilityRatio_selector(mu_s.value() - mu_g.value(), Selector<double>::Zero);
|
|
Selector<double> unitOilSolventMobilityRatio_selector(mu_s.value() - mu_o.value(), Selector<double>::Zero);
|
|
|
|
// Effective densities when the mobilities are equal
|
|
const ADB rho_m = zero_selectorSn.select(zero, (rho_o * so_eff / sn_eff) + (rho_g * sg_eff / sn_eff) + (rho_s * ss_eff / sn_eff));
|
|
const ADB rho_o_eff_simple = ((ones - mix_param_rho) * rho_o) + (mix_param_rho * rho_m);
|
|
const ADB rho_g_eff_simple = ((ones - mix_param_rho) * rho_g) + (mix_param_rho * rho_m);
|
|
//const ADB rho_s_eff_simple = ((ones - mix_param_rho) * rho_s) + (mix_param_rho * rho_m);
|
|
|
|
// Update densities, use pure values if solvent saturation is zero
|
|
rho_o = zero_selectorSs.select(rho_o, unitOilSolventMobilityRatio_selector.select(rho_o_eff_simple, rho_o_eff) );
|
|
rho_g = zero_selectorSs.select(rho_g, unitGasSolventMobilityRatio_selector.select(rho_g_eff_simple, rho_g_eff) );
|
|
rho_s = zero_selectorSs.select(rho_s, rho_s_eff);
|
|
|
|
|
|
}
|
|
|
|
|
|
template <class Grid>
|
|
std::vector<ADB>
|
|
BlackoilSolventModel<Grid>::
|
|
computePressures(const ADB& po,
|
|
const ADB& sw,
|
|
const ADB& so,
|
|
const ADB& sg,
|
|
const ADB& ss) const
|
|
{
|
|
std::vector<ADB> pressures = Base::computePressures(po, sw, so, sg);
|
|
|
|
if (has_solvent_) {
|
|
// The imiscible capillary pressure is evaluated using the total gas saturation (sg + ss)
|
|
std::vector<ADB> pressures_imisc = Base::computePressures(po, sw, so, sg + ss);
|
|
|
|
// Pressure effects on capillary pressure miscibility
|
|
const ADB pmisc = solvent_props_.pressureMiscibilityFunction(po, cells_);
|
|
// Only the pcog is effected by the miscibility. Since pg = po + pcog, changing pg is eqvivalent
|
|
// to changing the gas pressure directly.
|
|
const int nc = cells_.size();
|
|
const V ones = V::Constant(nc, 1.0);
|
|
pressures[Gas] = ( pmisc * pressures[Gas] + ((ones - pmisc) * pressures_imisc[Gas]));
|
|
}
|
|
return pressures;
|
|
}
|
|
|
|
|
|
|
|
|
|
template <class Grid>
|
|
std::vector<std::vector<double> >
|
|
BlackoilSolventModel<Grid>::
|
|
computeFluidInPlace(const ReservoirState& x,
|
|
const std::vector<int>& fipnum)
|
|
{
|
|
if (has_solvent_ && is_miscible_ && b_eff_[0].size() == 0) {
|
|
// A hack to avoid trouble for initial fluid in place, due to usage of b_eff_.
|
|
WellState xw, xwdummy;
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
xw.init(&wells(), x, xwdummy, pu);
|
|
SolutionState solstate = variableState(x, xw);
|
|
computeEffectiveProperties(solstate);
|
|
}
|
|
return Base::computeFluidInPlace(x, fipnum);
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif // OPM_BLACKOILSOLVENT_IMPL_HEADER_INCLUDED
|