2013-05-24 10:52:49 +02:00
|
|
|
/*
|
2015-05-24 09:59:40 +02:00
|
|
|
Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
|
2015-05-20 09:26:25 +02:00
|
|
|
Copyright 2014, 2015 Dr. Blatt - HPC-Simulation-Software & Services
|
2015-05-24 09:59:40 +02:00
|
|
|
Copyright 2014, 2015 Statoil ASA.
|
2015-02-03 11:31:57 +01:00
|
|
|
Copyright 2015 NTNU
|
2015-03-31 11:16:44 +02:00
|
|
|
Copyright 2015 IRIS AS
|
2013-05-24 10:52:49 +02: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/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
#ifndef OPM_BLACKOILMODELBASE_IMPL_HEADER_INCLUDED
|
|
|
|
|
#define OPM_BLACKOILMODELBASE_IMPL_HEADER_INCLUDED
|
|
|
|
|
|
|
|
|
|
#include <opm/autodiff/BlackoilModelBase.hpp>
|
2013-05-24 11:14:05 +02:00
|
|
|
|
|
|
|
|
#include <opm/autodiff/AutoDiffBlock.hpp>
|
|
|
|
|
#include <opm/autodiff/AutoDiffHelpers.hpp>
|
2014-02-20 13:15:02 +01:00
|
|
|
#include <opm/autodiff/GridHelpers.hpp>
|
2016-04-11 15:45:03 +02:00
|
|
|
#include <opm/autodiff/WellHelpers.hpp>
|
2013-05-24 11:14:05 +02:00
|
|
|
#include <opm/autodiff/BlackoilPropsAdInterface.hpp>
|
|
|
|
|
#include <opm/autodiff/GeoProps.hpp>
|
2014-03-18 08:48:34 +01:00
|
|
|
#include <opm/autodiff/WellDensitySegmented.hpp>
|
2015-07-07 15:49:10 +02:00
|
|
|
#include <opm/autodiff/VFPProperties.hpp>
|
2015-08-11 09:47:06 +02:00
|
|
|
#include <opm/autodiff/VFPProdProperties.hpp>
|
2015-08-11 16:31:43 +02:00
|
|
|
#include <opm/autodiff/VFPInjProperties.hpp>
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2013-05-26 11:49:44 +02:00
|
|
|
#include <opm/core/grid.h>
|
|
|
|
|
#include <opm/core/linalg/LinearSolverInterface.hpp>
|
2015-01-29 11:32:39 +01:00
|
|
|
#include <opm/core/linalg/ParallelIstlInformation.hpp>
|
2013-06-03 14:14:48 +02:00
|
|
|
#include <opm/core/props/rock/RockCompressibility.hpp>
|
2015-10-08 11:43:36 +02:00
|
|
|
#include <opm/common/ErrorMacros.hpp>
|
|
|
|
|
#include <opm/common/Exceptions.hpp>
|
2016-05-09 13:33:44 +08:00
|
|
|
#include <opm/common/OpmLog/OpmLog.hpp>
|
2014-05-08 11:03:42 +02:00
|
|
|
#include <opm/core/utility/Units.hpp>
|
2014-01-09 09:33:21 +01:00
|
|
|
#include <opm/core/well_controls.h>
|
2014-05-16 18:02:55 +02:00
|
|
|
#include <opm/core/utility/parameters/ParameterGroup.hpp>
|
2015-06-10 09:25:45 +02:00
|
|
|
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
|
2016-02-26 11:56:44 +01:00
|
|
|
#include <opm/parser/eclipse/EclipseState/Tables/TableManager.hpp>
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2016-02-28 11:27:00 +01:00
|
|
|
#include <opm/common/data/SimulationDataContainer.hpp>
|
2013-05-24 22:04:58 +02:00
|
|
|
#include <cassert>
|
2013-05-30 14:43:32 +02:00
|
|
|
#include <cmath>
|
2013-09-03 15:30:00 +02:00
|
|
|
#include <iostream>
|
2013-05-26 11:49:44 +02:00
|
|
|
#include <iomanip>
|
2015-01-21 15:43:18 +01:00
|
|
|
#include <limits>
|
2015-08-07 15:27:35 +02:00
|
|
|
#include <vector>
|
2016-04-21 16:19:45 +02:00
|
|
|
#include <algorithm>
|
2014-01-24 13:35:04 +01:00
|
|
|
//#include <fstream>
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2013-09-19 12:53:28 +02:00
|
|
|
// A debugging utility.
|
2015-05-24 09:59:40 +02:00
|
|
|
#define OPM_AD_DUMP(foo) \
|
2013-06-03 00:15:40 +02:00
|
|
|
do { \
|
|
|
|
|
std::cout << "==========================================\n" \
|
|
|
|
|
<< #foo ":\n" \
|
|
|
|
|
<< collapseJacs(foo) << std::endl; \
|
|
|
|
|
} while (0)
|
2013-05-27 00:24:38 +02:00
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
#define OPM_AD_DUMPVAL(foo) \
|
2014-04-24 18:50:49 +02:00
|
|
|
do { \
|
|
|
|
|
std::cout << "==========================================\n" \
|
|
|
|
|
<< #foo ":\n" \
|
|
|
|
|
<< foo.value() << std::endl; \
|
|
|
|
|
} while (0)
|
|
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
#define OPM_AD_DISKVAL(foo) \
|
2014-04-28 13:19:18 +02:00
|
|
|
do { \
|
|
|
|
|
std::ofstream os(#foo); \
|
|
|
|
|
os.precision(16); \
|
|
|
|
|
os << foo.value() << std::endl; \
|
|
|
|
|
} while (0)
|
2013-09-19 12:53:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Opm {
|
|
|
|
|
|
|
|
|
|
typedef AutoDiffBlock<double> ADB;
|
2013-05-24 11:14:05 +02:00
|
|
|
typedef ADB::V V;
|
|
|
|
|
typedef ADB::M M;
|
|
|
|
|
typedef Eigen::Array<double,
|
|
|
|
|
Eigen::Dynamic,
|
|
|
|
|
Eigen::Dynamic,
|
|
|
|
|
Eigen::RowMajor> DataBlock;
|
|
|
|
|
|
|
|
|
|
|
2015-02-19 09:07:42 +01:00
|
|
|
namespace detail {
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
2015-09-02 13:02:27 +02:00
|
|
|
inline
|
2013-05-24 11:14:05 +02:00
|
|
|
std::vector<int>
|
|
|
|
|
buildAllCells(const int nc)
|
|
|
|
|
{
|
|
|
|
|
std::vector<int> all_cells(nc);
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < nc; ++c) { all_cells[c] = c; }
|
|
|
|
|
|
|
|
|
|
return all_cells;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
template <class PU>
|
|
|
|
|
std::vector<bool>
|
|
|
|
|
activePhases(const PU& pu)
|
|
|
|
|
{
|
|
|
|
|
const int maxnp = Opm::BlackoilPhases::MaxNumPhases;
|
|
|
|
|
std::vector<bool> active(maxnp, false);
|
|
|
|
|
|
|
|
|
|
for (int p = 0; p < pu.MaxNumPhases; ++p) {
|
|
|
|
|
active[ p ] = pu.phase_used[ p ] != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return active;
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
template <class PU>
|
|
|
|
|
std::vector<int>
|
|
|
|
|
active2Canonical(const PU& pu)
|
|
|
|
|
{
|
|
|
|
|
const int maxnp = Opm::BlackoilPhases::MaxNumPhases;
|
|
|
|
|
std::vector<int> act2can(maxnp, -1);
|
|
|
|
|
|
|
|
|
|
for (int phase = 0; phase < maxnp; ++phase) {
|
|
|
|
|
if (pu.phase_used[ phase ]) {
|
|
|
|
|
act2can[ pu.phase_pos[ phase ] ] = phase;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return act2can;
|
|
|
|
|
}
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2016-05-09 14:00:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
inline
|
|
|
|
|
double getGravity(const double* g, const int dim) {
|
|
|
|
|
double grav = 0.0;
|
|
|
|
|
if (g) {
|
|
|
|
|
// Guard against gravity in anything but last dimension.
|
|
|
|
|
for (int dd = 0; dd < dim - 1; ++dd) {
|
|
|
|
|
assert(g[dd] == 0.0);
|
|
|
|
|
}
|
|
|
|
|
grav = g[dim - 1];
|
|
|
|
|
}
|
|
|
|
|
return grav;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-19 09:07:42 +01:00
|
|
|
} // namespace detail
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
|
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
BlackoilModelBase(const ModelParameters& param,
|
|
|
|
|
const Grid& grid ,
|
|
|
|
|
const BlackoilPropsAdInterface& fluid,
|
|
|
|
|
const DerivedGeology& geo ,
|
|
|
|
|
const RockCompressibility* rock_comp_props,
|
2016-05-09 17:44:59 +02:00
|
|
|
const WellModel& well_model,
|
2015-05-24 09:59:40 +02:00
|
|
|
const NewtonIterationBlackoilInterface& linsolver,
|
2015-06-10 09:25:45 +02:00
|
|
|
Opm::EclipseStateConstPtr eclState,
|
2015-05-24 09:59:40 +02:00
|
|
|
const bool has_disgas,
|
|
|
|
|
const bool has_vapoil,
|
|
|
|
|
const bool terminal_output)
|
2013-05-24 11:14:05 +02:00
|
|
|
: grid_ (grid)
|
|
|
|
|
, fluid_ (fluid)
|
|
|
|
|
, geo_ (geo)
|
2013-06-03 14:14:48 +02:00
|
|
|
, rock_comp_props_(rock_comp_props)
|
2016-04-11 09:15:06 +02:00
|
|
|
, vfp_properties_(
|
|
|
|
|
eclState->getTableManager().getVFPInjTables(),
|
|
|
|
|
eclState->getTableManager().getVFPProdTables())
|
2013-05-26 11:49:44 +02:00
|
|
|
, linsolver_ (linsolver)
|
2015-02-19 09:07:42 +01:00
|
|
|
, active_(detail::activePhases(fluid.phaseUsage()))
|
|
|
|
|
, canph_ (detail::active2Canonical(fluid.phaseUsage()))
|
|
|
|
|
, cells_ (detail::buildAllCells(Opm::AutoDiffGrid::numCells(grid)))
|
2015-12-08 10:20:25 +01:00
|
|
|
, ops_ (grid, geo.nnc())
|
2014-05-27 13:36:22 +02:00
|
|
|
, has_disgas_(has_disgas)
|
|
|
|
|
, has_vapoil_(has_vapoil)
|
2014-10-01 13:04:23 +02:00
|
|
|
, param_( param )
|
2014-08-27 10:27:49 +02:00
|
|
|
, use_threshold_pressure_(false)
|
2013-05-24 11:14:05 +02:00
|
|
|
, rq_ (fluid.numPhases())
|
2014-02-20 13:17:18 +01:00
|
|
|
, phaseCondition_(AutoDiffGrid::numCells(grid))
|
2016-05-10 11:40:43 +02:00
|
|
|
, well_model_ (well_model)
|
2015-05-26 14:38:25 +02:00
|
|
|
, isRs_(V::Zero(AutoDiffGrid::numCells(grid)))
|
|
|
|
|
, isRv_(V::Zero(AutoDiffGrid::numCells(grid)))
|
|
|
|
|
, isSg_(V::Zero(AutoDiffGrid::numCells(grid)))
|
2013-05-24 22:16:06 +02:00
|
|
|
, residual_ ( { std::vector<ADB>(fluid.numPhases(), ADB::null()),
|
2013-06-02 08:58:30 +02:00
|
|
|
ADB::null(),
|
2015-08-27 16:58:44 +02:00
|
|
|
ADB::null(),
|
2016-02-29 11:17:54 +01:00
|
|
|
{ 1.1169, 1.0031, 0.0031 }, // the default magic numbers
|
|
|
|
|
false } )
|
2015-03-04 13:42:27 +01:00
|
|
|
, terminal_output_ (terminal_output)
|
2016-04-21 16:19:45 +02:00
|
|
|
, material_name_(0)
|
2015-10-02 13:51:40 +02:00
|
|
|
, current_relaxation_(1.0)
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[Water]) {
|
|
|
|
|
material_name_.push_back("Water");
|
|
|
|
|
}
|
|
|
|
|
if (active_[Oil]) {
|
|
|
|
|
material_name_.push_back("Oil");
|
|
|
|
|
}
|
|
|
|
|
if (active_[Gas]) {
|
|
|
|
|
material_name_.push_back("Gas");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(numMaterials() == std::accumulate(active_.begin(), active_.end(), 0)); // Due to the material_name_ init above.
|
|
|
|
|
|
2016-05-09 14:00:21 +02:00
|
|
|
const double gravity = detail::getGravity(geo_.gravity(), UgGridHelpers::dimensions(grid_));
|
|
|
|
|
const V depth = Opm::AutoDiffGrid::cellCentroidsZToEigen(grid_);
|
|
|
|
|
|
2016-05-10 11:40:43 +02:00
|
|
|
well_model_.init(&fluid_, &active_, &phaseCondition_, &vfp_properties_, gravity, depth);
|
2016-05-02 16:21:14 +02:00
|
|
|
|
2016-05-09 17:44:59 +02:00
|
|
|
// TODO: put this for now to avoid modify the following code.
|
2016-05-11 11:43:48 +02:00
|
|
|
// TODO: this code can be fragile.
|
2016-06-27 13:03:30 +02:00
|
|
|
const Wells* wells_arg = asImpl().well_model_.wellsPointer();
|
2016-05-09 17:44:59 +02:00
|
|
|
|
2015-02-20 11:35:47 +01:00
|
|
|
#if HAVE_MPI
|
2015-09-05 20:17:43 +02:00
|
|
|
if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) )
|
|
|
|
|
{
|
|
|
|
|
const ParallelISTLInformation& info =
|
|
|
|
|
boost::any_cast<const ParallelISTLInformation&>(linsolver_.parallelInformation());
|
|
|
|
|
if ( terminal_output_ ) {
|
2015-03-04 13:42:27 +01:00
|
|
|
// Only rank 0 does print to std::cout if terminal_output is enabled
|
|
|
|
|
terminal_output_ = (info.communicator().rank()==0);
|
|
|
|
|
}
|
2016-04-06 12:52:54 +02:00
|
|
|
int local_number_of_wells = localWellsActive() ? wells().number_of_wells : 0;
|
2015-09-07 09:38:49 +02:00
|
|
|
int global_number_of_wells = info.communicator().sum(local_number_of_wells);
|
2016-04-06 14:36:51 +02:00
|
|
|
const bool wells_active = ( wells_arg && global_number_of_wells > 0 );
|
2016-05-10 11:40:43 +02:00
|
|
|
wellModel().setWellsActive(wells_active);
|
2015-10-27 00:29:22 +01:00
|
|
|
// Compute the global number of cells
|
|
|
|
|
std::vector<int> v( Opm::AutoDiffGrid::numCells(grid_), 1);
|
|
|
|
|
global_nc_ = 0;
|
|
|
|
|
info.computeReduction(v, Opm::Reduction::makeGlobalSumFunctor<int>(), global_nc_);
|
2015-09-05 20:17:43 +02:00
|
|
|
}else
|
2015-10-27 00:29:22 +01:00
|
|
|
#endif
|
2015-09-05 20:17:43 +02:00
|
|
|
{
|
2016-05-10 11:40:43 +02:00
|
|
|
wellModel().setWellsActive( localWellsActive() );
|
2015-10-27 00:29:22 +01:00
|
|
|
global_nc_ = Opm::AutoDiffGrid::numCells(grid_);
|
2015-02-20 11:35:47 +01:00
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2014-08-27 11:30:42 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-08-27 11:30:42 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
prepareStep(const double dt,
|
2016-01-15 11:28:15 +01:00
|
|
|
const ReservoirState& reservoir_state,
|
|
|
|
|
const WellState& /* well_state */)
|
2014-08-27 11:30:42 +02:00
|
|
|
{
|
2015-05-24 09:59:40 +02:00
|
|
|
pvdt_ = geo_.poreVolume() / dt;
|
|
|
|
|
if (active_[Gas]) {
|
|
|
|
|
updatePrimalVariableFromState(reservoir_state);
|
2014-08-27 14:21:34 +02:00
|
|
|
}
|
2014-08-27 11:30:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-10-01 12:54:30 +02:00
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-10-02 13:51:40 +02:00
|
|
|
template <class NonlinearSolverType>
|
|
|
|
|
IterationReport
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-10-02 13:51:40 +02:00
|
|
|
nonlinearIteration(const int iteration,
|
|
|
|
|
const double dt,
|
|
|
|
|
NonlinearSolverType& nonlinear_solver,
|
|
|
|
|
ReservoirState& reservoir_state,
|
|
|
|
|
WellState& well_state)
|
|
|
|
|
{
|
|
|
|
|
if (iteration == 0) {
|
|
|
|
|
// For each iteration we store in a vector the norms of the residual of
|
|
|
|
|
// the mass balance for each active phase, the well flux and the well equations.
|
|
|
|
|
residual_norms_history_.clear();
|
|
|
|
|
current_relaxation_ = 1.0;
|
|
|
|
|
dx_old_ = V::Zero(sizeNonLinear());
|
|
|
|
|
}
|
2016-06-28 14:37:48 +08:00
|
|
|
IterationReport iter_report = asImpl().assemble(reservoir_state, well_state, iteration == 0);
|
2015-11-19 14:39:49 +01:00
|
|
|
residual_norms_history_.push_back(asImpl().computeResidualNorms());
|
|
|
|
|
const bool converged = asImpl().getConvergence(dt, iteration);
|
2015-11-24 15:21:37 +01:00
|
|
|
const bool must_solve = (iteration < nonlinear_solver.minIter()) || (!converged);
|
|
|
|
|
if (must_solve) {
|
2016-02-09 16:39:30 +01:00
|
|
|
// enable single precision for solvers when dt is smaller then 20 days
|
|
|
|
|
residual_.singlePrecision = (unit::convert::to(dt, unit::day) < 20.) ;
|
2016-02-08 17:13:43 +01:00
|
|
|
|
2015-11-19 14:39:49 +01:00
|
|
|
// Compute the nonlinear update.
|
|
|
|
|
V dx = asImpl().solveJacobianSystem();
|
2015-10-02 13:51:40 +02:00
|
|
|
|
2016-01-15 11:30:07 +01:00
|
|
|
if (param_.use_update_stabilization_) {
|
|
|
|
|
// Stabilize the nonlinear update.
|
|
|
|
|
bool isOscillate = false;
|
|
|
|
|
bool isStagnate = false;
|
|
|
|
|
nonlinear_solver.detectOscillations(residual_norms_history_, iteration, isOscillate, isStagnate);
|
|
|
|
|
if (isOscillate) {
|
|
|
|
|
current_relaxation_ -= nonlinear_solver.relaxIncrement();
|
|
|
|
|
current_relaxation_ = std::max(current_relaxation_, nonlinear_solver.relaxMax());
|
|
|
|
|
if (terminalOutputEnabled()) {
|
|
|
|
|
std::string msg = " Oscillating behavior detected: Relaxation set to "
|
|
|
|
|
+ std::to_string(current_relaxation_);
|
|
|
|
|
OpmLog::info(msg);
|
|
|
|
|
}
|
2015-10-02 13:51:40 +02:00
|
|
|
}
|
2016-01-15 11:30:07 +01:00
|
|
|
nonlinear_solver.stabilizeNonlinearUpdate(dx, dx_old_, current_relaxation_);
|
2015-10-02 13:51:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply the update, applying model-dependent
|
|
|
|
|
// limitations and chopping of the update.
|
2015-11-19 14:39:49 +01:00
|
|
|
asImpl().updateState(dx, reservoir_state, well_state);
|
2015-10-02 13:51:40 +02:00
|
|
|
}
|
|
|
|
|
const bool failed = false; // Not needed in this model.
|
2015-11-24 15:21:37 +01:00
|
|
|
const int linear_iters = must_solve ? asImpl().linearIterationsLastSolve() : 0;
|
2016-06-28 14:37:48 +08:00
|
|
|
return IterationReport{ failed, converged, linear_iters , iter_report.well_iterations};
|
2015-10-02 13:51:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-24 09:59:40 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
afterStep(const double /* dt */,
|
|
|
|
|
ReservoirState& /* reservoir_state */,
|
|
|
|
|
WellState& /* well_state */)
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2015-05-24 09:59:40 +02:00
|
|
|
// Does nothing in this model.
|
|
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2014-06-03 13:30:35 +02:00
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-24 09:59:40 +02:00
|
|
|
int
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
sizeNonLinear() const
|
|
|
|
|
{
|
|
|
|
|
return residual_.sizeNonLinear();
|
|
|
|
|
}
|
2014-05-06 15:47:08 +02:00
|
|
|
|
2014-05-23 14:50:49 +02:00
|
|
|
|
2014-05-08 17:19:02 +02:00
|
|
|
|
2014-05-20 11:25:04 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-24 09:59:40 +02:00
|
|
|
int
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
linearIterationsLastSolve() const
|
|
|
|
|
{
|
|
|
|
|
return linsolver_.iterations();
|
|
|
|
|
}
|
2014-05-20 15:00:49 +02:00
|
|
|
|
2014-05-22 21:58:44 +02:00
|
|
|
|
2013-05-30 14:43:32 +02:00
|
|
|
|
2014-10-01 13:50:08 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-24 09:59:40 +02:00
|
|
|
bool
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
terminalOutputEnabled() const
|
|
|
|
|
{
|
|
|
|
|
return terminal_output_;
|
|
|
|
|
}
|
2014-05-20 19:52:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-24 09:59:40 +02:00
|
|
|
int
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-24 09:59:40 +02:00
|
|
|
numPhases() const
|
|
|
|
|
{
|
2015-09-30 14:44:50 +02:00
|
|
|
return fluid_.numPhases();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-09-30 14:44:50 +02:00
|
|
|
int
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-09-30 14:44:50 +02:00
|
|
|
numMaterials() const
|
|
|
|
|
{
|
|
|
|
|
return material_name_.size();
|
2015-05-24 09:59:40 +02:00
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
|
|
|
|
|
|
2014-11-10 09:29:13 +01:00
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-09-30 12:48:16 +02:00
|
|
|
const std::string&
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-09-30 14:44:50 +02:00
|
|
|
materialName(int material_index) const
|
2015-09-30 12:48:16 +02:00
|
|
|
{
|
2015-09-30 14:44:50 +02:00
|
|
|
assert(material_index < numMaterials());
|
|
|
|
|
return material_name_[material_index];
|
2015-09-30 12:48:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-24 09:59:40 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-12-08 10:56:42 +01:00
|
|
|
setThresholdPressures(const std::vector<double>& threshold_pressures)
|
2015-05-24 09:59:40 +02:00
|
|
|
{
|
|
|
|
|
const int num_faces = AutoDiffGrid::numFaces(grid_);
|
2015-12-08 10:56:42 +01:00
|
|
|
const int num_nnc = geo_.nnc().numNNC();
|
|
|
|
|
const int num_connections = num_faces + num_nnc;
|
|
|
|
|
if (int(threshold_pressures.size()) != num_connections) {
|
2015-12-28 09:22:53 +01:00
|
|
|
OPM_THROW(std::runtime_error, "Illegal size of threshold_pressures input ( " << threshold_pressures.size()
|
|
|
|
|
<< " ), must be equal to number of faces + nncs ( " << num_faces << " + " << num_nnc << " ).");
|
2015-05-24 09:59:40 +02:00
|
|
|
}
|
|
|
|
|
use_threshold_pressure_ = true;
|
|
|
|
|
// Map to interior faces.
|
|
|
|
|
const int num_ifaces = ops_.internal_faces.size();
|
2015-12-08 10:56:42 +01:00
|
|
|
threshold_pressures_by_connection_.resize(num_ifaces + num_nnc);
|
2015-05-24 09:59:40 +02:00
|
|
|
for (int ii = 0; ii < num_ifaces; ++ii) {
|
2015-12-08 10:56:42 +01:00
|
|
|
threshold_pressures_by_connection_[ii] = threshold_pressures[ops_.internal_faces[ii]];
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
2015-12-08 10:56:42 +01:00
|
|
|
// Handle NNCs
|
|
|
|
|
// Note: the nnc threshold pressures is appended after the face threshold pressures
|
|
|
|
|
for (int ii = 0; ii < num_nnc; ++ii) {
|
|
|
|
|
threshold_pressures_by_connection_[ii + num_ifaces] = threshold_pressures[ii + num_faces];
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
|
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
ReservoirResidualQuant::ReservoirResidualQuant()
|
2013-05-24 11:14:05 +02:00
|
|
|
: accum(2, ADB::null())
|
|
|
|
|
, mflux( ADB::null())
|
|
|
|
|
, b ( ADB::null())
|
2015-05-26 16:33:00 +02:00
|
|
|
, dh ( ADB::null())
|
2013-05-24 11:14:05 +02:00
|
|
|
, mob ( ADB::null())
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-03-06 13:05:37 +01:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
makeConstantState(SolutionState& state) const
|
2015-03-06 13:05:37 +01:00
|
|
|
{
|
2014-03-25 11:52:13 +01:00
|
|
|
// HACK: throw away the derivatives. this may not be the most
|
|
|
|
|
// performant way to do things, but it will make the state
|
|
|
|
|
// automatically consistent with variableState() (and doing
|
|
|
|
|
// things automatically is all the rage in this module ;)
|
|
|
|
|
state.pressure = ADB::constant(state.pressure.value());
|
2014-11-20 12:31:50 +01:00
|
|
|
state.temperature = ADB::constant(state.temperature.value());
|
2014-03-25 11:52:13 +01:00
|
|
|
state.rs = ADB::constant(state.rs.value());
|
|
|
|
|
state.rv = ADB::constant(state.rv.value());
|
2015-03-06 13:05:37 +01:00
|
|
|
const int num_phases = state.saturation.size();
|
|
|
|
|
for (int phaseIdx = 0; phaseIdx < num_phases; ++ phaseIdx) {
|
2014-03-25 11:52:13 +01:00
|
|
|
state.saturation[phaseIdx] = ADB::constant(state.saturation[phaseIdx].value());
|
2015-03-06 11:43:16 +01:00
|
|
|
}
|
2014-03-25 11:52:13 +01:00
|
|
|
state.qs = ADB::constant(state.qs.value());
|
|
|
|
|
state.bhp = ADB::constant(state.bhp.value());
|
2015-04-30 15:47:09 +02:00
|
|
|
assert(state.canonical_phase_pressures.size() == static_cast<std::size_t>(Opm::BlackoilPhases::MaxNumPhases));
|
2015-03-06 11:43:16 +01:00
|
|
|
for (int canphase = 0; canphase < Opm::BlackoilPhases::MaxNumPhases; ++canphase) {
|
|
|
|
|
ADB& pp = state.canonical_phase_pressures[canphase];
|
|
|
|
|
pp = ADB::constant(pp.value());
|
|
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
|
|
|
|
typename BlackoilModelBase<Grid, WellModel, Implementation>::SolutionState
|
|
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
variableState(const ReservoirState& x,
|
|
|
|
|
const WellState& xw) const
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2015-05-26 11:19:52 +02:00
|
|
|
std::vector<V> vars0 = asImpl().variableStateInitials(x, xw);
|
2015-05-26 11:16:21 +02:00
|
|
|
std::vector<ADB> vars = ADB::variables(vars0);
|
2015-05-26 11:19:52 +02:00
|
|
|
return asImpl().variableStateExtractVars(x, asImpl().variableStateIndices(), vars);
|
2015-05-26 11:16:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-26 11:16:21 +02:00
|
|
|
std::vector<V>
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
variableStateInitials(const ReservoirState& x,
|
|
|
|
|
const WellState& xw) const
|
2015-05-26 11:16:21 +02:00
|
|
|
{
|
|
|
|
|
assert(active_[ Oil ]);
|
|
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
const int np = x.numPhases();
|
|
|
|
|
|
|
|
|
|
std::vector<V> vars0;
|
2014-01-10 14:19:37 +01:00
|
|
|
// p, Sw and Rs, Rv or Sg is used as primary depending on solution conditions
|
2015-06-11 08:03:28 +02:00
|
|
|
// and bhp and Q for the wells
|
2014-01-10 14:19:37 +01:00
|
|
|
vars0.reserve(np + 1);
|
2015-06-16 11:13:42 +02:00
|
|
|
variableReservoirStateInitials(x, vars0);
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().variableWellStateInitials(xw, vars0);
|
2015-06-11 08:03:28 +02:00
|
|
|
return vars0;
|
|
|
|
|
}
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-06-11 08:03:28 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
variableReservoirStateInitials(const ReservoirState& x, std::vector<V>& vars0) const
|
2015-06-11 08:03:28 +02:00
|
|
|
{
|
|
|
|
|
using namespace Opm::AutoDiffGrid;
|
|
|
|
|
const int nc = numCells(grid_);
|
|
|
|
|
const int np = x.numPhases();
|
2013-05-24 23:20:15 +02:00
|
|
|
// Initial pressure.
|
2013-05-24 11:14:05 +02:00
|
|
|
assert (not x.pressure().empty());
|
|
|
|
|
const V p = Eigen::Map<const V>(& x.pressure()[0], nc, 1);
|
|
|
|
|
vars0.push_back(p);
|
|
|
|
|
|
2013-05-24 23:20:15 +02:00
|
|
|
// Initial saturation.
|
2013-05-24 11:14:05 +02:00
|
|
|
assert (not x.saturation().empty());
|
2013-05-24 23:20:15 +02:00
|
|
|
const DataBlock s = Eigen::Map<const DataBlock>(& x.saturation()[0], nc, np);
|
2013-05-24 11:14:05 +02:00
|
|
|
const Opm::PhaseUsage pu = fluid_.phaseUsage();
|
2013-05-27 00:24:38 +02:00
|
|
|
// We do not handle a Water/Gas situation correctly, guard against it.
|
2013-09-03 13:55:36 +02:00
|
|
|
assert (active_[ Oil]);
|
2013-05-24 11:14:05 +02:00
|
|
|
if (active_[ Water ]) {
|
|
|
|
|
const V sw = s.col(pu.phase_pos[ Water ]);
|
|
|
|
|
vars0.push_back(sw);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-26 11:16:21 +02:00
|
|
|
if (active_[ Gas ]) {
|
2014-01-10 14:19:37 +01:00
|
|
|
// define new primary variable xvar depending on solution condition
|
|
|
|
|
V xvar(nc);
|
|
|
|
|
const V sg = s.col(pu.phase_pos[ Gas ]);
|
2013-05-27 11:32:35 +02:00
|
|
|
const V rs = Eigen::Map<const V>(& x.gasoilratio()[0], x.gasoilratio().size());
|
2014-01-10 14:19:37 +01:00
|
|
|
const V rv = Eigen::Map<const V>(& x.rv()[0], x.rv().size());
|
2015-05-26 14:38:25 +02:00
|
|
|
xvar = isRs_*rs + isRv_*rv + isSg_*sg;
|
2014-01-10 14:19:37 +01:00
|
|
|
vars0.push_back(xvar);
|
2013-05-27 10:29:04 +02:00
|
|
|
}
|
2015-06-11 08:03:28 +02:00
|
|
|
}
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-26 11:16:21 +02:00
|
|
|
std::vector<int>
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
variableStateIndices() const
|
2015-05-26 11:16:21 +02:00
|
|
|
{
|
|
|
|
|
assert(active_[Oil]);
|
2015-05-26 11:41:34 +02:00
|
|
|
std::vector<int> indices(5, -1);
|
2015-05-26 11:16:21 +02:00
|
|
|
int next = 0;
|
|
|
|
|
indices[Pressure] = next++;
|
|
|
|
|
if (active_[Water]) {
|
|
|
|
|
indices[Sw] = next++;
|
|
|
|
|
}
|
|
|
|
|
if (active_[Gas]) {
|
|
|
|
|
indices[Xvar] = next++;
|
|
|
|
|
}
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().variableStateWellIndices(indices, next);
|
2015-05-26 11:41:34 +02:00
|
|
|
assert(next == fluid_.numPhases() + 2);
|
2015-05-26 11:16:21 +02:00
|
|
|
return indices;
|
|
|
|
|
}
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
|
|
|
|
typename BlackoilModelBase<Grid, WellModel, Implementation>::SolutionState
|
|
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
variableStateExtractVars(const ReservoirState& x,
|
|
|
|
|
const std::vector<int>& indices,
|
|
|
|
|
std::vector<ADB>& vars) const
|
2015-05-26 11:16:21 +02:00
|
|
|
{
|
|
|
|
|
//using namespace Opm::AutoDiffGrid;
|
|
|
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
|
|
|
|
const Opm::PhaseUsage pu = fluid_.phaseUsage();
|
|
|
|
|
|
|
|
|
|
SolutionState state(fluid_.numPhases());
|
2013-05-24 23:20:15 +02:00
|
|
|
|
|
|
|
|
// Pressure.
|
2015-05-26 11:16:21 +02:00
|
|
|
state.pressure = std::move(vars[indices[Pressure]]);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-05-26 11:16:21 +02:00
|
|
|
// Temperature cannot be a variable at this time (only constant).
|
2014-11-20 12:31:50 +01:00
|
|
|
const V temp = Eigen::Map<const V>(& x.temperature()[0], x.temperature().size());
|
|
|
|
|
state.temperature = ADB::constant(temp);
|
|
|
|
|
|
2014-01-10 14:19:37 +01:00
|
|
|
// Saturations
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2015-03-10 10:34:21 +01:00
|
|
|
ADB so = ADB::constant(V::Ones(nc, 1));
|
2013-05-24 11:14:05 +02:00
|
|
|
|
|
|
|
|
if (active_[ Water ]) {
|
2015-05-26 11:16:21 +02:00
|
|
|
state.saturation[pu.phase_pos[ Water ]] = std::move(vars[indices[Sw]]);
|
2015-03-10 10:34:21 +01:00
|
|
|
const ADB& sw = state.saturation[pu.phase_pos[ Water ]];
|
2014-12-18 12:23:58 +01:00
|
|
|
so -= sw;
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
|
2015-01-07 11:24:11 +01:00
|
|
|
if (active_[ Gas ]) {
|
2014-05-27 16:51:54 +02:00
|
|
|
// Define Sg Rs and Rv in terms of xvar.
|
2014-12-18 12:23:58 +01:00
|
|
|
// Xvar is only defined if gas phase is active
|
2015-05-26 11:16:21 +02:00
|
|
|
const ADB& xvar = vars[indices[Xvar]];
|
2014-12-18 12:23:58 +01:00
|
|
|
ADB& sg = state.saturation[ pu.phase_pos[ Gas ] ];
|
2015-05-26 14:38:25 +02:00
|
|
|
sg = isSg_*xvar + isRv_*so;
|
2014-12-18 12:23:58 +01:00
|
|
|
so -= sg;
|
|
|
|
|
|
2016-04-20 18:02:03 +02:00
|
|
|
//Compute the phase pressures before computing RS/RV
|
|
|
|
|
{
|
2015-01-07 11:24:11 +01:00
|
|
|
const ADB& sw = (active_[ Water ]
|
|
|
|
|
? state.saturation[ pu.phase_pos[ Water ] ]
|
2016-04-21 16:19:45 +02:00
|
|
|
: ADB::null());
|
2015-03-06 11:43:16 +01:00
|
|
|
state.canonical_phase_pressures = computePressures(state.pressure, sw, so, sg);
|
2016-04-20 18:02:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (active_[ Oil ]) {
|
|
|
|
|
// RS and RV is only defined if both oil and gas phase are active.
|
2015-03-06 11:43:16 +01:00
|
|
|
const ADB rsSat = fluidRsSat(state.canonical_phase_pressures[ Oil ], so , cells_);
|
2014-12-18 12:23:58 +01:00
|
|
|
if (has_disgas_) {
|
2015-05-26 14:38:25 +02:00
|
|
|
state.rs = (1-isRs_)*rsSat + isRs_*xvar;
|
2014-12-18 12:23:58 +01:00
|
|
|
} else {
|
|
|
|
|
state.rs = rsSat;
|
|
|
|
|
}
|
2015-03-06 11:43:16 +01:00
|
|
|
const ADB rvSat = fluidRvSat(state.canonical_phase_pressures[ Gas ], so , cells_);
|
2014-12-18 12:23:58 +01:00
|
|
|
if (has_vapoil_) {
|
2015-05-26 14:38:25 +02:00
|
|
|
state.rv = (1-isRv_)*rvSat + isRv_*xvar;
|
2014-12-18 12:23:58 +01:00
|
|
|
} else {
|
|
|
|
|
state.rv = rvSat;
|
|
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
2016-04-20 18:02:03 +02:00
|
|
|
else {
|
|
|
|
|
// Compute phase pressures also if gas phase is not active
|
|
|
|
|
const ADB& sw = (active_[ Water ]
|
|
|
|
|
? state.saturation[ pu.phase_pos[ Water ] ]
|
2016-04-21 16:19:45 +02:00
|
|
|
: ADB::null());
|
|
|
|
|
const ADB& sg = ADB::null();
|
2016-04-20 18:02:03 +02:00
|
|
|
state.canonical_phase_pressures = computePressures(state.pressure, sw, so, sg);
|
|
|
|
|
}
|
2015-01-07 11:24:11 +01:00
|
|
|
|
|
|
|
|
if (active_[ Oil ]) {
|
|
|
|
|
// Note that so is never a primary variable.
|
2015-03-10 10:34:21 +01:00
|
|
|
state.saturation[pu.phase_pos[ Oil ]] = std::move(so);
|
2015-01-07 11:24:11 +01:00
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
2015-06-11 08:03:28 +02:00
|
|
|
// wells
|
2016-05-12 15:14:20 +02:00
|
|
|
asImpl().wellModel().variableStateExtractWellsVars(indices, vars, state);
|
2015-06-11 08:03:28 +02:00
|
|
|
return state;
|
|
|
|
|
}
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-24 11:14:05 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
computeAccum(const SolutionState& state,
|
|
|
|
|
const int aix )
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
|
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
|
|
|
|
|
|
|
|
const ADB& press = state.pressure;
|
2014-11-20 12:31:50 +01:00
|
|
|
const ADB& temp = state.temperature;
|
2013-05-24 11:14:05 +02:00
|
|
|
const std::vector<ADB>& sat = state.saturation;
|
2013-06-02 08:19:21 +02:00
|
|
|
const ADB& rs = state.rs;
|
2014-01-10 14:19:37 +01:00
|
|
|
const ADB& rv = state.rv;
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2014-01-10 14:19:37 +01:00
|
|
|
const std::vector<PhasePresence> cond = phaseCondition();
|
2013-11-28 11:27:25 +01:00
|
|
|
|
2013-06-03 14:14:48 +02:00
|
|
|
const ADB pv_mult = poroMult(press);
|
|
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
const int maxnp = Opm::BlackoilPhases::MaxNumPhases;
|
|
|
|
|
for (int phase = 0; phase < maxnp; ++phase) {
|
|
|
|
|
if (active_[ phase ]) {
|
|
|
|
|
const int pos = pu.phase_pos[ phase ];
|
2016-02-03 11:26:11 +01:00
|
|
|
rq_[pos].b = asImpl().fluidReciprocFVF(phase, state.canonical_phase_pressures[phase], temp, rs, rv, cond);
|
2013-06-03 14:14:48 +02:00
|
|
|
rq_[pos].accum[aix] = pv_mult * rq_[pos].b * sat[pos];
|
2015-05-24 09:59:40 +02:00
|
|
|
// OPM_AD_DUMP(rq_[pos].b);
|
|
|
|
|
// OPM_AD_DUMP(rq_[pos].accum[aix]);
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (active_[ Oil ] && active_[ Gas ]) {
|
2014-01-10 14:19:37 +01:00
|
|
|
// Account for gas dissolved in oil and vaporized oil
|
2013-05-24 11:14:05 +02:00
|
|
|
const int po = pu.phase_pos[ Oil ];
|
|
|
|
|
const int pg = pu.phase_pos[ Gas ];
|
|
|
|
|
|
2015-01-12 07:19:18 +01:00
|
|
|
// Temporary copy to avoid contribution of dissolved gas in the vaporized oil
|
|
|
|
|
// when both dissolved gas and vaporized oil are present.
|
|
|
|
|
const ADB accum_gas_copy =rq_[pg].accum[aix];
|
|
|
|
|
|
2013-09-19 10:44:10 +02:00
|
|
|
rq_[pg].accum[aix] += state.rs * rq_[po].accum[aix];
|
2015-01-12 07:19:18 +01:00
|
|
|
rq_[po].accum[aix] += state.rv * accum_gas_copy;
|
2015-05-24 09:59:40 +02:00
|
|
|
// OPM_AD_DUMP(rq_[pg].accum[aix]);
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-18 10:48:54 +01:00
|
|
|
|
2015-09-21 10:57:12 +02:00
|
|
|
|
|
|
|
|
|
2016-03-18 10:48:54 +01:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2016-06-28 14:37:48 +08:00
|
|
|
IterationReport
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-05-25 23:49:09 +02:00
|
|
|
assemble(const ReservoirState& reservoir_state,
|
|
|
|
|
WellState& well_state,
|
2016-06-28 14:37:48 +08:00
|
|
|
const bool initial_assembly)
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2014-02-20 13:15:02 +01:00
|
|
|
using namespace Opm::AutoDiffGrid;
|
2015-04-29 06:42:58 +02:00
|
|
|
|
2015-08-18 10:24:57 +02:00
|
|
|
// If we have VFP tables, we need the well connection
|
|
|
|
|
// pressures for the "simple" hydrostatic correction
|
|
|
|
|
// between well depth and vfp table depth.
|
2015-08-18 14:53:36 +02:00
|
|
|
if (isVFPActive()) {
|
2015-08-17 13:05:32 +02:00
|
|
|
SolutionState state = asImpl().variableState(reservoir_state, well_state);
|
2015-08-18 10:24:57 +02:00
|
|
|
SolutionState state0 = state;
|
|
|
|
|
asImpl().makeConstantState(state0);
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().computeWellConnectionPressures(state0, well_state);
|
2015-08-17 13:05:32 +02:00
|
|
|
}
|
|
|
|
|
|
2015-04-29 06:42:58 +02:00
|
|
|
// Possibly switch well controls and updating well state to
|
|
|
|
|
// get reasonable initial conditions for the wells
|
2016-06-07 16:56:37 +00:00
|
|
|
asImpl().wellModel().updateWellControls(well_state);
|
2015-04-29 06:42:58 +02:00
|
|
|
|
2015-08-27 10:03:59 +02:00
|
|
|
// Create the primary variables.
|
2015-05-26 01:48:45 +02:00
|
|
|
SolutionState state = asImpl().variableState(reservoir_state, well_state);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-03-06 13:05:37 +01:00
|
|
|
if (initial_assembly) {
|
|
|
|
|
// Create the (constant, derivativeless) initial state.
|
|
|
|
|
SolutionState state0 = state;
|
2015-05-26 01:48:45 +02:00
|
|
|
asImpl().makeConstantState(state0);
|
2015-03-06 13:05:37 +01:00
|
|
|
// Compute initial accumulation contributions
|
|
|
|
|
// and well connection pressures.
|
2015-05-26 01:48:45 +02:00
|
|
|
asImpl().computeAccum(state0, 0);
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().computeWellConnectionPressures(state0, well_state);
|
2015-03-06 13:05:37 +01:00
|
|
|
}
|
|
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
// OPM_AD_DISKVAL(state.pressure);
|
|
|
|
|
// OPM_AD_DISKVAL(state.saturation[0]);
|
|
|
|
|
// OPM_AD_DISKVAL(state.saturation[1]);
|
|
|
|
|
// OPM_AD_DISKVAL(state.saturation[2]);
|
|
|
|
|
// OPM_AD_DISKVAL(state.rs);
|
|
|
|
|
// OPM_AD_DISKVAL(state.rv);
|
|
|
|
|
// OPM_AD_DISKVAL(state.qs);
|
|
|
|
|
// OPM_AD_DISKVAL(state.bhp);
|
2014-08-14 14:20:43 +02:00
|
|
|
|
2013-05-24 23:20:15 +02:00
|
|
|
// -------- Mass balance equations --------
|
2015-05-26 01:48:45 +02:00
|
|
|
asImpl().assembleMassBalanceEq(state);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-05-26 00:31:50 +02:00
|
|
|
// -------- Well equations ----------
|
2016-06-30 09:04:44 +08:00
|
|
|
IterationReport iter_report = {false, false, 0, std::numeric_limits<int>::min()};
|
2016-04-06 12:52:54 +02:00
|
|
|
if ( ! wellsActive() ) {
|
2016-06-28 14:37:48 +08:00
|
|
|
return iter_report;
|
2015-06-16 11:33:52 +02:00
|
|
|
}
|
|
|
|
|
|
2015-10-13 08:50:13 +02:00
|
|
|
std::vector<ADB> mob_perfcells;
|
|
|
|
|
std::vector<ADB> b_perfcells;
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().extractWellPerfProperties(state, rq_, mob_perfcells, b_perfcells);
|
2015-06-16 13:12:49 +02:00
|
|
|
if (param_.solve_welleq_initially_ && initial_assembly) {
|
2015-06-12 06:49:25 +02:00
|
|
|
// solve the well equations as a pre-processing step
|
2016-06-28 14:37:48 +08:00
|
|
|
iter_report = asImpl().solveWellEq(mob_perfcells, b_perfcells, state, well_state);
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
2015-10-13 08:50:13 +02:00
|
|
|
V aliveWells;
|
|
|
|
|
std::vector<ADB> cq_s;
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().computeWellFlux(state, mob_perfcells, b_perfcells, aliveWells, cq_s);
|
|
|
|
|
asImpl().wellModel().updatePerfPhaseRatesAndPressures(cq_s, state, well_state);
|
|
|
|
|
asImpl().wellModel().addWellFluxEq(cq_s, state, residual_);
|
Refactor addWellEq().
The method has been split in three parts:
computeWellFlux(const SolutionState& state,
const std::vector<ADB>& mob_perfcells,
const std::vector<ADB>& b_perfcells,
V& aliveWells,
std::vector<ADB>& cq_s);
void
updatePerfPhaseRatesAndPressures(const std::vector<ADB>& cq_s,
const SolutionState& state,
WellState& xw);
void
addWellFluxEq(const std::vector<ADB>& cq_s,
const SolutionState& state);
This reduces the function length, although most of the content of addWellEq()
now is in computeWellFlux(), so that function is still quite long. It also
allows us to use smaller sets of function arguments, which makes methods easier
to understand.
Finally, it makes it easier to create derived models with custom behaviour.
2015-06-22 11:34:10 +02:00
|
|
|
asImpl().addWellContributionToMassBalanceEq(cq_s, state, well_state);
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().addWellControlEq(state, well_state, aliveWells, residual_);
|
2016-05-11 13:13:08 +02:00
|
|
|
|
|
|
|
|
if (param_.compute_well_potentials_) {
|
2016-04-21 15:46:20 +02:00
|
|
|
SolutionState state0 = state;
|
|
|
|
|
asImpl().makeConstantState(state0);
|
2016-05-11 13:13:08 +02:00
|
|
|
asImpl().wellModel().computeWellPotentials(mob_perfcells, b_perfcells, state0, well_state);
|
2016-06-28 14:37:48 +08:00
|
|
|
}
|
|
|
|
|
return iter_report;
|
2015-05-26 00:31:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-05-26 00:31:50 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-09-21 17:11:28 +02:00
|
|
|
assembleMassBalanceEq(const SolutionState& state)
|
2015-05-26 00:31:50 +02:00
|
|
|
{
|
2015-09-21 17:11:28 +02:00
|
|
|
// Compute b_p and the accumulation term b_p*s_p for each phase,
|
|
|
|
|
// except gas. For gas, we compute b_g*s_g + Rs*b_o*s_o.
|
|
|
|
|
// These quantities are stored in rq_[phase].accum[1].
|
|
|
|
|
// The corresponding accumulation terms from the start of
|
|
|
|
|
// the timestep (b^0_p*s^0_p etc.) were already computed
|
|
|
|
|
// on the initial call to assemble() and stored in rq_[phase].accum[0].
|
|
|
|
|
asImpl().computeAccum(state, 1);
|
2015-06-10 09:25:45 +02:00
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
// Set up the common parts of the mass balance equations
|
|
|
|
|
// for each active phase.
|
|
|
|
|
const V transi = subset(geo_.transmissibility(), ops_.internal_faces);
|
|
|
|
|
const V trans_nnc = ops_.nnc_trans;
|
|
|
|
|
V trans_all(transi.size() + trans_nnc.size());
|
|
|
|
|
trans_all << transi, trans_nnc;
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
const std::vector<ADB> kr = asImpl().computeRelPerm(state);
|
2015-11-23 15:04:51 +01:00
|
|
|
#pragma omp parallel for schedule(static)
|
2015-09-21 17:11:28 +02:00
|
|
|
for (int phaseIdx = 0; phaseIdx < fluid_.numPhases(); ++phaseIdx) {
|
2016-02-03 11:26:11 +01:00
|
|
|
const std::vector<PhasePresence>& cond = phaseCondition();
|
|
|
|
|
const ADB mu = asImpl().fluidViscosity(canph_[phaseIdx], state.canonical_phase_pressures[canph_[phaseIdx]], state.temperature, state.rs, state.rv, cond);
|
|
|
|
|
const ADB rho = asImpl().fluidDensity(canph_[phaseIdx], rq_[phaseIdx].b, state.rs, state.rv);
|
|
|
|
|
asImpl().computeMassFlux(phaseIdx, trans_all, kr[canph_[phaseIdx]], mu, rho, state.canonical_phase_pressures[canph_[phaseIdx]], state);
|
2015-08-28 15:57:27 +02:00
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
residual_.material_balance_eq[ phaseIdx ] =
|
|
|
|
|
pvdt_ * (rq_[phaseIdx].accum[1] - rq_[phaseIdx].accum[0])
|
|
|
|
|
+ ops_.div*rq_[phaseIdx].mflux;
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
// -------- Extra (optional) rs and rv contributions to the mass balance equations --------
|
2013-05-27 11:32:35 +02:00
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
// Add the extra (flux) terms to the mass balance equations
|
|
|
|
|
// From gas dissolved in the oil phase (rs) and oil vaporized in the gas phase (rv)
|
|
|
|
|
// The extra terms in the accumulation part of the equation are already handled.
|
|
|
|
|
if (active_[ Oil ] && active_[ Gas ]) {
|
|
|
|
|
const int po = fluid_.phaseUsage().phase_pos[ Oil ];
|
|
|
|
|
const int pg = fluid_.phaseUsage().phase_pos[ Gas ];
|
2014-05-27 18:54:17 +02:00
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
const UpwindSelector<double> upwindOil(grid_, ops_,
|
|
|
|
|
rq_[po].dh.value());
|
|
|
|
|
const ADB rs_face = upwindOil.select(state.rs);
|
2013-05-27 11:32:35 +02:00
|
|
|
|
2015-09-21 17:11:28 +02:00
|
|
|
const UpwindSelector<double> upwindGas(grid_, ops_,
|
|
|
|
|
rq_[pg].dh.value());
|
|
|
|
|
const ADB rv_face = upwindGas.select(state.rv);
|
2015-08-28 15:57:27 +02:00
|
|
|
|
|
|
|
|
residual_.material_balance_eq[ pg ] += ops_.div * (rs_face * rq_[po].mflux);
|
2014-05-27 18:54:17 +02:00
|
|
|
residual_.material_balance_eq[ po ] += ops_.div * (rv_face * rq_[pg].mflux);
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
// OPM_AD_DUMP(residual_.material_balance_eq[ Gas ]);
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
2015-08-27 16:58:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if (param_.update_equations_scaling_) {
|
2015-09-21 14:04:41 +02:00
|
|
|
asImpl().updateEquationsScaling();
|
2015-08-27 16:58:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-08-27 16:58:44 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
updateEquationsScaling() {
|
2015-08-27 16:58:44 +02:00
|
|
|
ADB::V B;
|
|
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
|
|
|
for ( int idx=0; idx<MaxNumPhases; ++idx )
|
|
|
|
|
{
|
|
|
|
|
if (active_[idx]) {
|
|
|
|
|
const int pos = pu.phase_pos[idx];
|
2015-09-08 10:33:06 +02:00
|
|
|
const ADB& temp_b = rq_[pos].b;
|
|
|
|
|
B = 1. / temp_b.value();
|
2015-10-27 00:29:22 +01:00
|
|
|
#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[idx] = B_global_sum / global_nc_;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
residual_.matbalscale[idx] = B.mean();
|
|
|
|
|
}
|
2015-08-27 16:58:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-03-11 14:28:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-03-17 10:14:45 +01:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-06-16 10:45:42 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
addWellContributionToMassBalanceEq(const std::vector<ADB>& cq_s,
|
|
|
|
|
const SolutionState&,
|
|
|
|
|
const WellState&)
|
2015-06-11 08:03:28 +02:00
|
|
|
{
|
2016-04-06 12:52:54 +02:00
|
|
|
if ( !asImpl().localWellsActive() )
|
2015-12-09 15:18:03 +01:00
|
|
|
{
|
|
|
|
|
// If there are no wells in the subdomain of the proces then
|
|
|
|
|
// cq_s has zero size and will cause a segmentation fault below.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-11 08:03:28 +02:00
|
|
|
// Add well contributions to mass balance equations
|
|
|
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
2015-10-13 11:28:48 +02:00
|
|
|
const int np = asImpl().numPhases();
|
2015-06-11 08:03:28 +02:00
|
|
|
for (int phase = 0; phase < np; ++phase) {
|
2016-05-10 11:40:43 +02:00
|
|
|
residual_.material_balance_eq[phase] -= superset(cq_s[phase], wellModel().wellOps().well_cells, nc);
|
2015-06-11 08:03:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-03-17 10:14:45 +01:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-10-13 08:50:13 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
|
|
|
|
bool
|
|
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
isVFPActive() const
|
2015-08-18 10:24:57 +02:00
|
|
|
{
|
2016-04-06 12:52:54 +02:00
|
|
|
if( ! localWellsActive() ) {
|
2015-08-18 10:24:57 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-19 11:46:29 +02:00
|
|
|
if ( vfp_properties_.getProd()->empty() && vfp_properties_.getInj()->empty() ) {
|
2015-08-18 10:24:57 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-06 12:52:54 +02:00
|
|
|
const int nw = wells().number_of_wells;
|
2015-08-18 10:24:57 +02:00
|
|
|
//Loop over all wells
|
|
|
|
|
for (int w = 0; w < nw; ++w) {
|
2016-04-06 12:52:54 +02:00
|
|
|
const WellControls* wc = wells().ctrls[w];
|
2015-08-18 10:24:57 +02:00
|
|
|
|
|
|
|
|
const int nwc = well_controls_get_num(wc);
|
|
|
|
|
|
|
|
|
|
//Loop over all controls
|
|
|
|
|
for (int c=0; c < nwc; ++c) {
|
|
|
|
|
const WellControlType ctrl_type = well_controls_iget_type(wc, c);
|
2014-03-25 14:31:06 +01:00
|
|
|
|
2015-08-18 10:24:57 +02:00
|
|
|
if (ctrl_type == THP) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2014-03-25 14:31:06 +01:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2016-06-28 14:37:48 +08:00
|
|
|
IterationReport
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
solveWellEq(const std::vector<ADB>& mob_perfcells,
|
|
|
|
|
const std::vector<ADB>& b_perfcells,
|
|
|
|
|
SolutionState& state,
|
2016-06-28 14:37:48 +08:00
|
|
|
WellState& well_state)
|
2015-06-12 06:49:25 +02:00
|
|
|
{
|
|
|
|
|
V aliveWells;
|
2016-04-06 12:52:54 +02:00
|
|
|
const int np = wells().number_of_phases;
|
2015-06-12 06:49:25 +02:00
|
|
|
std::vector<ADB> cq_s(np, ADB::null());
|
2016-05-10 11:40:43 +02:00
|
|
|
std::vector<int> indices = asImpl().wellModel().variableWellStateIndices();
|
2015-06-12 06:49:25 +02:00
|
|
|
SolutionState state0 = state;
|
2015-11-19 14:15:41 +01:00
|
|
|
WellState well_state0 = well_state;
|
2015-06-12 06:49:25 +02:00
|
|
|
asImpl().makeConstantState(state0);
|
2015-06-16 14:14:53 +02:00
|
|
|
|
2015-06-12 06:49:25 +02:00
|
|
|
std::vector<ADB> mob_perfcells_const(np, ADB::null());
|
|
|
|
|
std::vector<ADB> b_perfcells_const(np, ADB::null());
|
2015-12-09 15:18:03 +01:00
|
|
|
|
2016-04-06 12:52:54 +02:00
|
|
|
if (asImpl().localWellsActive() ){
|
2015-12-09 15:18:03 +01:00
|
|
|
// If there are non well in the sudomain of the process
|
|
|
|
|
// thene mob_perfcells_const and b_perfcells_const would be empty
|
|
|
|
|
for (int phase = 0; phase < np; ++phase) {
|
|
|
|
|
mob_perfcells_const[phase] = ADB::constant(mob_perfcells[phase].value());
|
|
|
|
|
b_perfcells_const[phase] = ADB::constant(b_perfcells[phase].value());
|
|
|
|
|
}
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
|
|
|
|
|
2015-06-16 14:14:53 +02:00
|
|
|
int it = 0;
|
|
|
|
|
bool converged;
|
|
|
|
|
do {
|
|
|
|
|
// bhp and Q for the wells
|
|
|
|
|
std::vector<V> vars0;
|
|
|
|
|
vars0.reserve(2);
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().variableWellStateInitials(well_state, vars0);
|
2015-06-16 14:14:53 +02:00
|
|
|
std::vector<ADB> vars = ADB::variables(vars0);
|
|
|
|
|
|
|
|
|
|
SolutionState wellSolutionState = state0;
|
2016-05-12 15:14:20 +02:00
|
|
|
asImpl().wellModel().variableStateExtractWellsVars(indices, vars, wellSolutionState);
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().computeWellFlux(wellSolutionState, mob_perfcells_const, b_perfcells_const, aliveWells, cq_s);
|
|
|
|
|
asImpl().wellModel().updatePerfPhaseRatesAndPressures(cq_s, wellSolutionState, well_state);
|
|
|
|
|
asImpl().wellModel().addWellFluxEq(cq_s, wellSolutionState, residual_);
|
|
|
|
|
asImpl().wellModel().addWellControlEq(wellSolutionState, well_state, aliveWells, residual_);
|
2015-06-16 14:14:53 +02:00
|
|
|
converged = getWellConvergence(it);
|
|
|
|
|
|
|
|
|
|
if (converged) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++it;
|
2016-04-06 12:52:54 +02:00
|
|
|
if( localWellsActive() )
|
2015-09-05 20:17:43 +02:00
|
|
|
{
|
|
|
|
|
std::vector<ADB> eqs;
|
|
|
|
|
eqs.reserve(2);
|
|
|
|
|
eqs.push_back(residual_.well_flux_eq);
|
|
|
|
|
eqs.push_back(residual_.well_eq);
|
|
|
|
|
ADB total_residual = vertcatCollapseJacs(eqs);
|
|
|
|
|
const std::vector<M>& Jn = total_residual.derivative();
|
2015-08-27 10:03:59 +02:00
|
|
|
typedef Eigen::SparseMatrix<double> Sp;
|
|
|
|
|
Sp Jn0;
|
|
|
|
|
Jn[0].toSparse(Jn0);
|
2015-08-25 16:15:29 +02:00
|
|
|
const Eigen::SparseLU< Sp > solver(Jn0);
|
2015-08-31 14:45:03 +02:00
|
|
|
ADB::V total_residual_v = total_residual.value();
|
|
|
|
|
const Eigen::VectorXd& dx = solver.solve(total_residual_v.matrix());
|
2015-11-19 13:49:42 +01:00
|
|
|
assert(dx.size() == total_residual_v.size());
|
2016-05-10 11:40:43 +02:00
|
|
|
asImpl().wellModel().updateWellState(dx.array(), dpMaxRel(), well_state);
|
2016-06-07 16:56:37 +00:00
|
|
|
asImpl().wellModel().updateWellControls(well_state);
|
2015-09-05 20:17:43 +02:00
|
|
|
}
|
2015-06-16 14:14:53 +02:00
|
|
|
} while (it < 15);
|
|
|
|
|
|
2015-06-12 06:49:25 +02:00
|
|
|
if (converged) {
|
2016-06-21 09:27:19 +08:00
|
|
|
OpmLog::note("well converged iter: " + std::to_string(it));
|
2016-04-06 12:52:54 +02:00
|
|
|
const int nw = wells().number_of_wells;
|
2015-06-12 06:49:25 +02:00
|
|
|
{
|
|
|
|
|
// We will set the bhp primary variable to the new ones,
|
|
|
|
|
// but we do not change the derivatives here.
|
|
|
|
|
ADB::V new_bhp = Eigen::Map<ADB::V>(well_state.bhp().data(), nw);
|
|
|
|
|
// Avoiding the copy below would require a value setter method
|
|
|
|
|
// in AutoDiffBlock.
|
|
|
|
|
std::vector<ADB::M> old_derivs = state.bhp.derivative();
|
|
|
|
|
state.bhp = ADB::function(std::move(new_bhp), std::move(old_derivs));
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// Need to reshuffle well rates, from phase running fastest
|
|
|
|
|
// to wells running fastest.
|
|
|
|
|
// The transpose() below switches the ordering.
|
|
|
|
|
const DataBlock wrates = Eigen::Map<const DataBlock>(well_state.wellRates().data(), nw, np).transpose();
|
|
|
|
|
ADB::V new_qs = Eigen::Map<const V>(wrates.data(), nw*np);
|
|
|
|
|
std::vector<ADB::M> old_derivs = state.qs.derivative();
|
|
|
|
|
state.qs = ADB::function(std::move(new_qs), std::move(old_derivs));
|
|
|
|
|
}
|
2016-05-10 14:58:45 +02:00
|
|
|
asImpl().computeWellConnectionPressures(state, well_state);
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
|
|
|
|
|
2015-11-19 14:15:41 +01:00
|
|
|
if (!converged) {
|
|
|
|
|
well_state = well_state0;
|
|
|
|
|
}
|
2016-06-28 14:37:48 +08:00
|
|
|
const bool failed = false; // Not needed in this method.
|
|
|
|
|
const int linear_iters = 0; // Not needed in this method
|
|
|
|
|
return IterationReport{failed, converged, linear_iters, it};
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-25 11:14:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2016-04-18 12:14:01 +02:00
|
|
|
V
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
solveJacobianSystem() const
|
2013-05-26 11:49:44 +02:00
|
|
|
{
|
2014-04-08 16:16:54 +02:00
|
|
|
return linsolver_.computeNewtonIncrement(residual_);
|
2013-05-30 14:43:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-02-19 09:07:42 +01:00
|
|
|
namespace detail
|
|
|
|
|
{
|
2015-05-13 12:14:52 +02:00
|
|
|
/// \brief Compute the L-infinity norm of a vector
|
|
|
|
|
/// \warn This function is not suitable to compute on the well equations.
|
|
|
|
|
/// \param a The container to compute the infinity norm on.
|
|
|
|
|
/// It has to have one entry for each cell.
|
|
|
|
|
/// \param info In a parallel this holds the information about the data distribution.
|
2015-09-02 13:02:27 +02:00
|
|
|
inline
|
2015-05-24 09:59:40 +02:00
|
|
|
double infinityNorm( const ADB& a, const boost::any& pinfo = boost::any() )
|
2015-01-19 14:14:18 +01:00
|
|
|
{
|
2015-05-24 17:36:29 +02:00
|
|
|
static_cast<void>(pinfo); // Suppress warning in non-MPI case.
|
2015-05-13 12:14:52 +02:00
|
|
|
#if HAVE_MPI
|
|
|
|
|
if ( pinfo.type() == typeid(ParallelISTLInformation) )
|
|
|
|
|
{
|
|
|
|
|
const ParallelISTLInformation& real_info =
|
|
|
|
|
boost::any_cast<const ParallelISTLInformation&>(pinfo);
|
|
|
|
|
double result=0;
|
2016-06-07 15:10:25 +02:00
|
|
|
real_info.computeReduction(a.value(), Reduction::makeLInfinityNormFunctor<double>(), result);
|
2015-05-13 12:14:52 +02:00
|
|
|
return result;
|
2015-01-19 14:14:18 +01:00
|
|
|
}
|
2015-05-13 12:14:52 +02:00
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
if( a.value().size() > 0 ) {
|
|
|
|
|
return a.value().matrix().lpNorm<Eigen::Infinity> ();
|
|
|
|
|
}
|
|
|
|
|
else { // this situation can occur when no wells are present
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
2015-01-19 14:14:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-10 10:05:06 -07:00
|
|
|
/// \brief Compute the Euclidian norm of a vector
|
2016-06-08 11:07:34 +02:00
|
|
|
/// \warning In the case that num_components is greater than 1
|
|
|
|
|
/// an interleaved ordering is assumed. E.g. for each cell
|
|
|
|
|
/// all phases of that cell are stored consecutively. First
|
|
|
|
|
/// the ones for cell 0, then the ones for cell 1, ... .
|
2015-11-10 10:05:06 -07:00
|
|
|
/// \param it begin iterator for the given vector
|
|
|
|
|
/// \param end end iterator for the given vector
|
|
|
|
|
/// \param num_components number of components (i.e. phases) in the vector
|
|
|
|
|
/// \param pinfo In a parallel this holds the information about the data distribution.
|
2015-10-31 12:32:54 +01:00
|
|
|
template <class Iterator>
|
|
|
|
|
inline
|
|
|
|
|
double euclidianNormSquared( Iterator it, const Iterator end, int num_components, const boost::any& pinfo = boost::any() )
|
|
|
|
|
{
|
|
|
|
|
static_cast<void>(num_components); // Suppress warning in the serial case.
|
|
|
|
|
static_cast<void>(pinfo); // Suppress warning in non-MPI case.
|
|
|
|
|
#if HAVE_MPI
|
|
|
|
|
if ( pinfo.type() == typeid(ParallelISTLInformation) )
|
|
|
|
|
{
|
|
|
|
|
const ParallelISTLInformation& info =
|
|
|
|
|
boost::any_cast<const ParallelISTLInformation&>(pinfo);
|
2016-06-08 11:07:34 +02:00
|
|
|
typedef typename Iterator::value_type Scalar;
|
|
|
|
|
Scalar product = 0.0;
|
|
|
|
|
int size_per_component = (end - it);
|
|
|
|
|
size_per_component /= num_components; // two lines to supresse unused warning.
|
2015-10-31 12:32:54 +01:00
|
|
|
assert((end - it) == num_components * size_per_component);
|
2016-06-08 11:07:34 +02:00
|
|
|
|
|
|
|
|
if( num_components == 1 )
|
2015-10-31 12:32:54 +01:00
|
|
|
{
|
|
|
|
|
auto component_container =
|
2016-06-08 11:07:34 +02:00
|
|
|
boost::make_iterator_range(it, end);
|
2015-10-31 12:32:54 +01:00
|
|
|
info.computeReduction(component_container,
|
|
|
|
|
Opm::Reduction::makeInnerProductFunctor<double>(),
|
2016-06-08 11:07:34 +02:00
|
|
|
product);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
auto& maskContainer = info.getOwnerMask();
|
|
|
|
|
auto mask = maskContainer.begin();
|
2016-06-27 15:42:07 +02:00
|
|
|
assert(static_cast<int>(maskContainer.size()) == size_per_component);
|
2016-06-08 11:07:34 +02:00
|
|
|
|
|
|
|
|
for(int cell = 0; cell < size_per_component; ++cell, ++mask)
|
|
|
|
|
{
|
|
|
|
|
Scalar cell_product = (*it) * (*it);
|
|
|
|
|
++it;
|
|
|
|
|
for(int component=1; component < num_components;
|
|
|
|
|
++component, ++it)
|
|
|
|
|
{
|
|
|
|
|
cell_product += (*it) * (*it);
|
|
|
|
|
}
|
|
|
|
|
product += cell_product * (*mask);
|
|
|
|
|
}
|
2015-10-31 12:32:54 +01:00
|
|
|
}
|
2016-06-27 15:33:09 +02:00
|
|
|
return info.communicator().sum(product);
|
2015-10-31 12:32:54 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
double product = 0.0 ;
|
|
|
|
|
for( ; it != end; ++it ) {
|
|
|
|
|
product += ( *it * *it );
|
|
|
|
|
}
|
|
|
|
|
return product;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-13 12:16:36 +02:00
|
|
|
/// \brief Compute the L-infinity norm of a vector representing a well equation.
|
|
|
|
|
/// \param a The container to compute the infinity norm on.
|
|
|
|
|
/// \param info In a parallel this holds the information about the data distribution.
|
2015-09-02 13:02:27 +02:00
|
|
|
inline
|
2015-05-13 12:16:36 +02:00
|
|
|
double infinityNormWell( const ADB& a, const boost::any& pinfo )
|
|
|
|
|
{
|
2015-05-24 17:36:29 +02:00
|
|
|
static_cast<void>(pinfo); // Suppress warning in non-MPI case.
|
2015-05-13 12:16:36 +02:00
|
|
|
double result=0;
|
|
|
|
|
if( a.value().size() > 0 ) {
|
|
|
|
|
result = a.value().matrix().lpNorm<Eigen::Infinity> ();
|
|
|
|
|
}
|
|
|
|
|
#if HAVE_MPI
|
|
|
|
|
if ( pinfo.type() == typeid(ParallelISTLInformation) )
|
|
|
|
|
{
|
|
|
|
|
const ParallelISTLInformation& real_info =
|
|
|
|
|
boost::any_cast<const ParallelISTLInformation&>(pinfo);
|
|
|
|
|
result = real_info.communicator().max(result);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-19 09:07:42 +01:00
|
|
|
} // namespace detail
|
2013-05-30 14:43:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-05-26 11:49:44 +02:00
|
|
|
|
2013-05-30 14:43:32 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2016-04-18 12:14:01 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
updateState(const V& dx,
|
|
|
|
|
ReservoirState& reservoir_state,
|
|
|
|
|
WellState& well_state)
|
2013-05-30 14:43:32 +02:00
|
|
|
{
|
2014-02-20 13:15:02 +01:00
|
|
|
using namespace Opm::AutoDiffGrid;
|
2013-05-30 14:43:32 +02:00
|
|
|
const int np = fluid_.numPhases();
|
2014-02-20 13:15:02 +01:00
|
|
|
const int nc = numCells(grid_);
|
2013-05-30 14:43:32 +02:00
|
|
|
const V null;
|
2013-09-03 13:55:36 +02:00
|
|
|
assert(null.size() == 0);
|
2013-05-30 14:43:32 +02:00
|
|
|
const V zero = V::Zero(nc);
|
|
|
|
|
|
|
|
|
|
// 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();
|
2014-01-10 14:19:37 +01:00
|
|
|
|
|
|
|
|
const V dxvar = active_[Gas] ? subset(dx, Span(nc, 1, varstart)): null;
|
|
|
|
|
varstart += dxvar.size();
|
|
|
|
|
|
2015-06-11 08:03:28 +02:00
|
|
|
// Extract well parts np phase rates + bhp
|
2016-05-11 10:35:11 +02:00
|
|
|
const V dwells = subset(dx, Span(asImpl().wellModel().numWellVars(), 1, varstart));
|
2015-06-11 08:03:28 +02:00
|
|
|
varstart += dwells.size();
|
|
|
|
|
|
2013-09-03 13:55:36 +02:00
|
|
|
assert(varstart == dx.size());
|
2013-05-26 11:49:44 +02:00
|
|
|
|
|
|
|
|
// Pressure update.
|
2014-05-16 18:02:55 +02:00
|
|
|
const double dpmaxrel = dpMaxRel();
|
2015-05-24 09:59:40 +02:00
|
|
|
const V p_old = Eigen::Map<const V>(&reservoir_state.pressure()[0], nc, 1);
|
2013-05-30 14:43:32 +02:00
|
|
|
const V absdpmax = dpmaxrel*p_old.abs();
|
2013-06-03 00:33:05 +02:00
|
|
|
const V dp_limited = sign(dp) * dp.abs().min(absdpmax);
|
2013-05-30 14:43:32 +02:00
|
|
|
const V p = (p_old - dp_limited).max(zero);
|
2015-05-24 09:59:40 +02:00
|
|
|
std::copy(&p[0], &p[0] + nc, reservoir_state.pressure().begin());
|
2013-05-26 11:49:44 +02:00
|
|
|
|
2013-05-30 14:43:32 +02:00
|
|
|
|
2013-05-26 11:49:44 +02:00
|
|
|
// Saturation updates.
|
2014-01-10 14:19:37 +01:00
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
2015-05-24 09:59:40 +02:00
|
|
|
const DataBlock s_old = Eigen::Map<const DataBlock>(& reservoir_state.saturation()[0], nc, np);
|
2014-05-16 18:02:55 +02:00
|
|
|
const double dsmax = dsMax();
|
2015-03-06 16:02:22 +01:00
|
|
|
|
2014-09-08 11:14:44 +02:00
|
|
|
V so;
|
2014-01-10 14:19:37 +01:00
|
|
|
V sw;
|
2014-05-16 13:26:44 +02:00
|
|
|
V sg;
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2014-05-16 13:26:44 +02:00
|
|
|
{
|
|
|
|
|
V maxVal = zero;
|
|
|
|
|
V dso = zero;
|
|
|
|
|
if (active_[Water]){
|
|
|
|
|
maxVal = dsw.abs().max(maxVal);
|
|
|
|
|
dso = dso - dsw;
|
2013-05-26 11:49:44 +02:00
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2014-05-16 13:26:44 +02:00
|
|
|
V dsg;
|
|
|
|
|
if (active_[Gas]){
|
2015-05-26 14:38:25 +02:00
|
|
|
dsg = isSg_ * dxvar - isRv_ * dsw;
|
2014-05-16 13:26:44 +02:00
|
|
|
maxVal = dsg.abs().max(maxVal);
|
|
|
|
|
dso = dso - dsg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maxVal = dso.abs().max(maxVal);
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2014-05-16 13:26:44 +02:00
|
|
|
V step = dsmax/maxVal;
|
|
|
|
|
step = step.min(1.);
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2014-05-16 13:26:44 +02:00
|
|
|
if (active_[Water]) {
|
|
|
|
|
const int pos = pu.phase_pos[ Water ];
|
|
|
|
|
const V sw_old = s_old.col(pos);
|
|
|
|
|
sw = sw_old - step * dsw;
|
|
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2014-05-16 13:26:44 +02:00
|
|
|
if (active_[Gas]) {
|
|
|
|
|
const int pos = pu.phase_pos[ Gas ];
|
|
|
|
|
const V sg_old = s_old.col(pos);
|
|
|
|
|
sg = sg_old - step * dsg;
|
2014-09-08 11:14:44 +02:00
|
|
|
}
|
|
|
|
|
|
2016-04-21 16:19:45 +02:00
|
|
|
assert(active_[Oil]);
|
2014-09-08 11:14:44 +02:00
|
|
|
const int pos = pu.phase_pos[ Oil ];
|
|
|
|
|
const V so_old = s_old.col(pos);
|
|
|
|
|
so = so_old - step * dso;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-10 14:31:51 +02:00
|
|
|
// Appleyard chop process.
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[Gas]) {
|
|
|
|
|
auto ixg = sg < 0;
|
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
|
|
|
if (ixg[c]) {
|
2016-04-25 15:31:03 +02:00
|
|
|
if (active_[Water]) {
|
|
|
|
|
sw[c] = sw[c] / (1-sg[c]);
|
|
|
|
|
}
|
2016-04-21 16:19:45 +02:00
|
|
|
so[c] = so[c] / (1-sg[c]);
|
|
|
|
|
sg[c] = 0;
|
|
|
|
|
}
|
2014-09-08 11:14:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[Oil]) {
|
|
|
|
|
auto ixo = so < 0;
|
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
|
|
|
if (ixo[c]) {
|
2016-04-25 15:31:03 +02:00
|
|
|
if (active_[Water]) {
|
|
|
|
|
sw[c] = sw[c] / (1-so[c]);
|
|
|
|
|
}
|
|
|
|
|
if (active_[Gas]) {
|
|
|
|
|
sg[c] = sg[c] / (1-so[c]);
|
|
|
|
|
}
|
2016-04-21 16:19:45 +02:00
|
|
|
so[c] = 0;
|
|
|
|
|
}
|
2014-09-08 11:14:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[Water]) {
|
|
|
|
|
auto ixw = sw < 0;
|
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
|
|
|
if (ixw[c]) {
|
|
|
|
|
so[c] = so[c] / (1-sw[c]);
|
2016-04-25 15:31:03 +02:00
|
|
|
if (active_[Gas]) {
|
|
|
|
|
sg[c] = sg[c] / (1-sw[c]);
|
|
|
|
|
}
|
2016-04-21 16:19:45 +02:00
|
|
|
sw[c] = 0;
|
|
|
|
|
}
|
2014-05-16 13:26:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2015-07-06 07:14:55 +02:00
|
|
|
//const V sumSat = sw + so + sg;
|
|
|
|
|
//sw = sw / sumSat;
|
|
|
|
|
//so = so / sumSat;
|
|
|
|
|
//sg = sg / sumSat;
|
2014-09-08 11:14:44 +02:00
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
// Update the reservoir_state
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[Water]) {
|
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
|
|
|
reservoir_state.saturation()[c*np + pu.phase_pos[ Water ]] = sw[c];
|
|
|
|
|
}
|
2014-09-10 14:31:51 +02:00
|
|
|
}
|
|
|
|
|
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[Gas]) {
|
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
|
|
|
reservoir_state.saturation()[c*np + pu.phase_pos[ Gas ]] = sg[c];
|
|
|
|
|
}
|
2014-09-10 14:31:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (active_[ Oil ]) {
|
|
|
|
|
for (int c = 0; c < nc; ++c) {
|
2016-04-21 16:19:45 +02:00
|
|
|
reservoir_state.saturation()[c*np + pu.phase_pos[ Oil ]] = so[c];
|
2014-09-10 14:31:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update rs and rv
|
2014-11-05 13:03:00 +01:00
|
|
|
const double drmaxrel = drMaxRel();
|
2014-01-10 14:19:37 +01:00
|
|
|
V rs;
|
2014-05-27 15:09:58 +02:00
|
|
|
if (has_disgas_) {
|
2015-05-24 09:59:40 +02:00
|
|
|
const V rs_old = Eigen::Map<const V>(&reservoir_state.gasoilratio()[0], nc);
|
2015-05-26 14:38:25 +02:00
|
|
|
const V drs = isRs_ * dxvar;
|
2014-11-05 13:03:00 +01:00
|
|
|
const V drs_limited = sign(drs) * drs.abs().min(rs_old.abs()*drmaxrel);
|
2014-01-10 14:19:37 +01:00
|
|
|
rs = rs_old - drs_limited;
|
|
|
|
|
}
|
|
|
|
|
V rv;
|
2014-05-27 15:09:58 +02:00
|
|
|
if (has_vapoil_) {
|
2015-05-24 09:59:40 +02:00
|
|
|
const V rv_old = Eigen::Map<const V>(&reservoir_state.rv()[0], nc);
|
2015-05-26 14:38:25 +02:00
|
|
|
const V drv = isRv_ * dxvar;
|
2014-11-05 13:03:00 +01:00
|
|
|
const V drv_limited = sign(drv) * drv.abs().min(rv_old.abs()*drmaxrel);
|
2014-01-10 14:19:37 +01:00
|
|
|
rv = rv_old - drv_limited;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-10 14:31:51 +02:00
|
|
|
// Sg is used as primal variable for water only cells.
|
2014-01-10 14:19:37 +01:00
|
|
|
const double epsilon = std::sqrt(std::numeric_limits<double>::epsilon());
|
|
|
|
|
auto watOnly = sw > (1 - epsilon);
|
|
|
|
|
|
|
|
|
|
// phase translation sg <-> rs
|
2016-05-13 12:40:50 +02:00
|
|
|
std::vector<HydroCarbonState>& hydroCarbonState = reservoir_state.hydroCarbonState();
|
2016-05-13 09:04:48 +02:00
|
|
|
std::fill(hydroCarbonState.begin(), hydroCarbonState.end(), HydroCarbonState::GasAndOil);
|
2014-05-27 15:09:58 +02:00
|
|
|
|
|
|
|
|
if (has_disgas_) {
|
2014-12-18 12:23:58 +01:00
|
|
|
const V rsSat0 = fluidRsSat(p_old, s_old.col(pu.phase_pos[Oil]), cells_);
|
|
|
|
|
const V rsSat = fluidRsSat(p, so, cells_);
|
2014-05-27 15:09:58 +02:00
|
|
|
// The obvious case
|
2015-05-26 14:38:25 +02:00
|
|
|
auto hasGas = (sg > 0 && isRs_ == 0);
|
2014-01-10 14:19:37 +01:00
|
|
|
|
|
|
|
|
// Set oil saturated if previous rs is sufficiently large
|
2015-05-24 09:59:40 +02:00
|
|
|
const V rs_old = Eigen::Map<const V>(&reservoir_state.gasoilratio()[0], nc);
|
2015-05-26 14:38:25 +02:00
|
|
|
auto gasVaporized = ( (rs > rsSat * (1+epsilon) && isRs_ == 1 ) && (rs_old > rsSat0 * (1-epsilon)) );
|
2014-11-05 13:01:07 +01:00
|
|
|
auto useSg = watOnly || hasGas || gasVaporized;
|
2014-01-10 14:19:37 +01:00
|
|
|
for (int c = 0; c < nc; ++c) {
|
2014-12-18 12:23:58 +01:00
|
|
|
if (useSg[c]) {
|
|
|
|
|
rs[c] = rsSat[c];
|
|
|
|
|
} else {
|
2016-05-13 09:04:48 +02:00
|
|
|
hydroCarbonState[c] = HydroCarbonState::OilOnly;
|
2014-12-18 12:23:58 +01:00
|
|
|
}
|
2013-05-30 14:43:32 +02:00
|
|
|
}
|
2014-12-18 12:23:58 +01:00
|
|
|
|
2014-01-10 14:19:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// phase transitions so <-> rv
|
2014-05-27 15:09:58 +02:00
|
|
|
if (has_vapoil_) {
|
2014-12-18 12:23:58 +01:00
|
|
|
|
|
|
|
|
// The gas pressure is needed for the rvSat calculations
|
2015-03-06 16:02:22 +01:00
|
|
|
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_);
|
2014-12-18 12:23:58 +01:00
|
|
|
|
2014-01-10 14:19:37 +01:00
|
|
|
// The obvious case
|
2015-05-26 14:38:25 +02:00
|
|
|
auto hasOil = (so > 0 && isRv_ == 0);
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2014-05-27 15:09:58 +02:00
|
|
|
// Set oil saturated if previous rv is sufficiently large
|
2015-05-24 09:59:40 +02:00
|
|
|
const V rv_old = Eigen::Map<const V>(&reservoir_state.rv()[0], nc);
|
2015-05-26 14:38:25 +02:00
|
|
|
auto oilCondensed = ( (rv > rvSat * (1+epsilon) && isRv_ == 1) && (rv_old > rvSat0 * (1-epsilon)) );
|
2014-11-05 13:01:07 +01:00
|
|
|
auto useSg = watOnly || hasOil || oilCondensed;
|
2013-05-26 11:49:44 +02:00
|
|
|
for (int c = 0; c < nc; ++c) {
|
2014-12-18 12:23:58 +01:00
|
|
|
if (useSg[c]) {
|
|
|
|
|
rv[c] = rvSat[c];
|
|
|
|
|
} else {
|
2016-05-13 09:04:48 +02:00
|
|
|
hydroCarbonState[c] = HydroCarbonState::GasOnly;
|
2014-12-18 12:23:58 +01:00
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
// Update the reservoir_state
|
2014-11-05 12:35:32 +01:00
|
|
|
if (has_disgas_) {
|
2015-05-24 09:59:40 +02:00
|
|
|
std::copy(&rs[0], &rs[0] + nc, reservoir_state.gasoilratio().begin());
|
2014-11-05 12:35:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (has_vapoil_) {
|
2015-05-24 09:59:40 +02:00
|
|
|
std::copy(&rv[0], &rv[0] + nc, reservoir_state.rv().begin());
|
2014-11-05 12:35:32 +01:00
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
|
2016-05-10 14:19:15 +02:00
|
|
|
|
|
|
|
|
asImpl().wellModel().updateWellState(dwells, dpMaxRel(), well_state);
|
2015-06-11 08:03:28 +02:00
|
|
|
|
|
|
|
|
// Update phase conditions used for property calculations.
|
2016-05-13 09:04:48 +02:00
|
|
|
updatePhaseCondFromPrimalVariable(reservoir_state);
|
2015-06-11 08:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-20 14:42:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-24 11:14:05 +02:00
|
|
|
std::vector<ADB>
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
computeRelPerm(const SolutionState& state) const
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2014-02-20 13:15:02 +01:00
|
|
|
using namespace Opm::AutoDiffGrid;
|
|
|
|
|
const int nc = numCells(grid_);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-03-10 10:34:58 +01:00
|
|
|
const ADB zero = ADB::constant(V::Zero(nc));
|
2013-05-24 11:14:05 +02:00
|
|
|
|
|
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
2015-03-10 10:34:58 +01:00
|
|
|
const ADB& sw = (active_[ Water ]
|
|
|
|
|
? state.saturation[ pu.phase_pos[ Water ] ]
|
|
|
|
|
: zero);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-03-10 10:34:58 +01:00
|
|
|
const ADB& so = (active_[ Oil ]
|
|
|
|
|
? state.saturation[ pu.phase_pos[ Oil ] ]
|
|
|
|
|
: zero);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-03-10 10:34:58 +01:00
|
|
|
const ADB& sg = (active_[ Gas ]
|
|
|
|
|
? state.saturation[ pu.phase_pos[ Gas ] ]
|
|
|
|
|
: zero);
|
2013-05-24 11:14:05 +02:00
|
|
|
|
|
|
|
|
return fluid_.relperm(sw, so, sg, cells_);
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2015-03-06 16:02:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-01-07 11:24:11 +01:00
|
|
|
std::vector<ADB>
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2015-01-07 11:24:11 +01:00
|
|
|
computePressures(const ADB& po,
|
|
|
|
|
const ADB& sw,
|
|
|
|
|
const ADB& so,
|
|
|
|
|
const ADB& sg) const
|
|
|
|
|
{
|
2013-11-26 13:51:10 +01:00
|
|
|
// convert the pressure offsets to the capillary pressures
|
|
|
|
|
std::vector<ADB> pressure = fluid_.capPress(sw, so, sg, cells_);
|
|
|
|
|
for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) {
|
2014-04-15 20:48:15 +02:00
|
|
|
// The reference pressure is always the liquid phase (oil) pressure.
|
2013-11-26 13:51:10 +01:00
|
|
|
if (phaseIdx == BlackoilPhases::Liquid)
|
|
|
|
|
continue;
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[phaseIdx]) {
|
|
|
|
|
pressure[phaseIdx] = pressure[phaseIdx] - pressure[BlackoilPhases::Liquid];
|
|
|
|
|
}
|
2013-11-26 13:51:10 +01:00
|
|
|
}
|
|
|
|
|
|
2014-04-28 13:19:18 +02:00
|
|
|
// Since pcow = po - pw, but pcog = pg - po,
|
|
|
|
|
// we have
|
|
|
|
|
// pw = po - pcow
|
|
|
|
|
// pg = po + pcgo
|
|
|
|
|
// This is an unfortunate inconsistency, but a convention we must handle.
|
2013-11-26 13:51:10 +01:00
|
|
|
for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) {
|
2016-04-21 16:19:45 +02:00
|
|
|
if (active_[phaseIdx]) {
|
|
|
|
|
if (phaseIdx == BlackoilPhases::Aqua) {
|
|
|
|
|
pressure[phaseIdx] = po - pressure[phaseIdx];
|
|
|
|
|
} else {
|
|
|
|
|
pressure[phaseIdx] += po;
|
|
|
|
|
}
|
2014-04-28 13:19:18 +02:00
|
|
|
}
|
2013-11-26 13:51:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pressure;
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
2015-03-06 16:02:22 +01:00
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-03-06 16:02:22 +01:00
|
|
|
V
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
computeGasPressure(const V& po,
|
|
|
|
|
const V& sw,
|
|
|
|
|
const V& so,
|
|
|
|
|
const V& sg) const
|
2015-03-06 16:02:22 +01:00
|
|
|
{
|
|
|
|
|
assert (active_[Gas]);
|
|
|
|
|
std::vector<ADB> cp = fluid_.capPress(ADB::constant(sw),
|
|
|
|
|
ADB::constant(so),
|
|
|
|
|
ADB::constant(sg),
|
|
|
|
|
cells_);
|
|
|
|
|
return cp[Gas].value() + po;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-24 11:14:05 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
computeMassFlux(const int actph ,
|
|
|
|
|
const V& transi,
|
|
|
|
|
const ADB& kr ,
|
|
|
|
|
const ADB& mu ,
|
|
|
|
|
const ADB& rho ,
|
|
|
|
|
const ADB& phasePressure,
|
2016-04-18 15:32:47 +02:00
|
|
|
const SolutionState& state)
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2015-05-26 16:16:27 +02:00
|
|
|
// Compute and store mobilities.
|
2013-12-06 10:33:58 +01:00
|
|
|
const ADB tr_mult = transMult(state.pressure);
|
2013-11-26 13:51:10 +01:00
|
|
|
rq_[ actph ].mob = tr_mult * kr / mu;
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-05-26 16:16:27 +02:00
|
|
|
// Compute head differentials. Gravity potential is done using the face average as in eclipse and MRST.
|
2013-09-19 14:47:12 +02:00
|
|
|
const ADB rhoavg = ops_.caver * rho;
|
2015-06-29 16:44:10 +02:00
|
|
|
rq_[ actph ].dh = ops_.ngrad * phasePressure - geo_.gravity()[2] * (rhoavg * (ops_.ngrad * geo_.z().matrix()));
|
2014-08-27 10:27:49 +02:00
|
|
|
if (use_threshold_pressure_) {
|
2015-05-26 16:33:00 +02:00
|
|
|
applyThresholdPressures(rq_[ actph ].dh);
|
2014-08-27 10:27:49 +02:00
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
|
2015-05-26 16:16:27 +02:00
|
|
|
// Compute phase fluxes with upwinding of formation value factor and mobility.
|
2015-05-26 16:33:00 +02:00
|
|
|
const ADB& b = rq_[ actph ].b;
|
|
|
|
|
const ADB& mob = rq_[ actph ].mob;
|
|
|
|
|
const ADB& dh = rq_[ actph ].dh;
|
|
|
|
|
UpwindSelector<double> upwind(grid_, ops_, dh.value());
|
2015-06-29 16:44:10 +02:00
|
|
|
rq_[ actph ].mflux = upwind.select(b * mob) * (transi * dh);
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-08-27 10:27:49 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
applyThresholdPressures(ADB& dp)
|
2014-08-27 10:27:49 +02:00
|
|
|
{
|
|
|
|
|
// We support reversible threshold pressures only.
|
|
|
|
|
// Method: if the potential difference is lower (in absolute
|
|
|
|
|
// value) than the threshold for any face, then the potential
|
|
|
|
|
// (and derivatives) is set to zero. If it is above the
|
|
|
|
|
// threshold, the threshold pressure is subtracted from the
|
|
|
|
|
// absolute potential (the potential is moved towards zero).
|
|
|
|
|
|
|
|
|
|
// Identify the set of faces where the potential is under the
|
|
|
|
|
// threshold, that shall have zero flow. Storing the bool
|
2014-08-27 15:10:48 +02:00
|
|
|
// Array as a V (a double Array) with 1 and 0 elements, a
|
|
|
|
|
// 1 where flow is allowed, a 0 where it is not.
|
2015-12-08 10:56:42 +01:00
|
|
|
const V high_potential = (dp.value().abs() >= threshold_pressures_by_connection_).template cast<double>();
|
2014-08-27 10:27:49 +02:00
|
|
|
|
|
|
|
|
// Create a sparse vector that nullifies the low potential elements.
|
2015-08-25 16:15:29 +02:00
|
|
|
const M keep_high_potential(high_potential.matrix().asDiagonal());
|
2014-08-27 10:27:49 +02:00
|
|
|
|
2014-09-03 09:59:04 +02:00
|
|
|
// Find the current sign for the threshold modification
|
|
|
|
|
const V sign_dp = sign(dp.value());
|
2015-12-08 10:56:42 +01:00
|
|
|
const V threshold_modification = sign_dp * threshold_pressures_by_connection_;
|
2014-08-27 10:27:49 +02:00
|
|
|
|
|
|
|
|
// Modify potential and nullify where appropriate.
|
2014-09-03 09:59:04 +02:00
|
|
|
dp = keep_high_potential * (dp - threshold_modification);
|
2014-08-27 10:27:49 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-05-19 10:41:23 +02:00
|
|
|
std::vector<double>
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
computeResidualNorms() const
|
2014-05-19 10:41:23 +02:00
|
|
|
{
|
2015-01-27 11:57:36 +01:00
|
|
|
std::vector<double> residualNorms;
|
2014-05-19 13:06:44 +02:00
|
|
|
|
|
|
|
|
std::vector<ADB>::const_iterator massBalanceIt = residual_.material_balance_eq.begin();
|
|
|
|
|
const std::vector<ADB>::const_iterator endMassBalanceIt = residual_.material_balance_eq.end();
|
|
|
|
|
|
|
|
|
|
for (; massBalanceIt != endMassBalanceIt; ++massBalanceIt) {
|
2015-05-13 12:14:52 +02:00
|
|
|
const double massBalanceResid = detail::infinityNorm( (*massBalanceIt),
|
|
|
|
|
linsolver_.parallelInformation() );
|
2014-05-19 13:06:44 +02:00
|
|
|
if (!std::isfinite(massBalanceResid)) {
|
|
|
|
|
OPM_THROW(Opm::NumericalProblem,
|
|
|
|
|
"Encountered a non-finite residual");
|
|
|
|
|
}
|
2015-01-27 11:57:36 +01:00
|
|
|
residualNorms.push_back(massBalanceResid);
|
2014-05-19 13:06:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// the following residuals are not used in the oscillation detection now
|
2015-05-13 12:16:36 +02:00
|
|
|
const double wellFluxResid = detail::infinityNormWell( residual_.well_flux_eq,
|
|
|
|
|
linsolver_.parallelInformation() );
|
2014-05-19 13:06:44 +02:00
|
|
|
if (!std::isfinite(wellFluxResid)) {
|
2015-01-19 14:14:18 +01:00
|
|
|
OPM_THROW(Opm::NumericalProblem,
|
2014-05-19 13:06:44 +02:00
|
|
|
"Encountered a non-finite residual");
|
|
|
|
|
}
|
2015-01-27 11:57:36 +01:00
|
|
|
residualNorms.push_back(wellFluxResid);
|
2014-05-19 13:06:44 +02:00
|
|
|
|
2015-05-13 12:16:36 +02:00
|
|
|
const double wellResid = detail::infinityNormWell( residual_.well_eq,
|
|
|
|
|
linsolver_.parallelInformation() );
|
2014-05-19 13:06:44 +02:00
|
|
|
if (!std::isfinite(wellResid)) {
|
|
|
|
|
OPM_THROW(Opm::NumericalProblem,
|
|
|
|
|
"Encountered a non-finite residual");
|
|
|
|
|
}
|
2015-01-27 11:57:36 +01:00
|
|
|
residualNorms.push_back(wellResid);
|
2014-05-19 13:06:44 +02:00
|
|
|
|
2015-01-27 11:57:36 +01:00
|
|
|
return residualNorms;
|
2014-05-19 10:41:23 +02:00
|
|
|
}
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2014-05-19 15:43:56 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-10-31 12:32:54 +01:00
|
|
|
double
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-02-28 11:27:00 +01:00
|
|
|
relativeChange(const SimulationDataContainer& previous,
|
|
|
|
|
const SimulationDataContainer& current ) const
|
2015-10-31 12:32:54 +01:00
|
|
|
{
|
|
|
|
|
std::vector< double > p0 ( previous.pressure() );
|
|
|
|
|
std::vector< double > sat0( previous.saturation() );
|
|
|
|
|
|
|
|
|
|
const std::size_t pSize = p0.size();
|
|
|
|
|
const std::size_t satSize = sat0.size();
|
|
|
|
|
|
|
|
|
|
// compute u^n - u^n+1
|
|
|
|
|
for( std::size_t i=0; i<pSize; ++i ) {
|
2015-11-10 10:07:57 -07:00
|
|
|
p0[ i ] -= current.pressure()[ i ];
|
2015-10-31 12:32:54 +01:00
|
|
|
}
|
2014-05-23 14:50:49 +02:00
|
|
|
|
2015-10-31 12:32:54 +01:00
|
|
|
for( std::size_t i=0; i<satSize; ++i ) {
|
|
|
|
|
sat0[ i ] -= current.saturation()[ i ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// compute || u^n - u^n+1 ||
|
|
|
|
|
const double stateOld = detail::euclidianNormSquared( p0.begin(), p0.end(), 1, linsolver_.parallelInformation() ) +
|
|
|
|
|
detail::euclidianNormSquared( sat0.begin(), sat0.end(),
|
|
|
|
|
current.numPhases(),
|
|
|
|
|
linsolver_.parallelInformation() );
|
2014-05-20 11:25:04 +02:00
|
|
|
|
2015-10-31 12:32:54 +01:00
|
|
|
// compute || u^n+1 ||
|
|
|
|
|
const double stateNew = detail::euclidianNormSquared( current.pressure().begin(), current.pressure().end(), 1, linsolver_.parallelInformation() ) +
|
|
|
|
|
detail::euclidianNormSquared( current.saturation().begin(), current.saturation().end(),
|
|
|
|
|
current.numPhases(),
|
|
|
|
|
linsolver_.parallelInformation() );
|
|
|
|
|
|
2015-11-10 10:07:57 -07:00
|
|
|
if( stateNew > 0.0 ) {
|
2015-10-31 12:32:54 +01:00
|
|
|
return stateOld / stateNew ;
|
2015-11-10 10:07:57 -07:00
|
|
|
}
|
|
|
|
|
else {
|
2015-10-31 12:32:54 +01:00
|
|
|
return 0.0;
|
2015-11-10 10:07:57 -07:00
|
|
|
}
|
2015-10-31 12:32:54 +01:00
|
|
|
}
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-01-27 12:49:55 +01:00
|
|
|
double
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
convergenceReduction(const Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>& B,
|
|
|
|
|
const Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>& tempV,
|
|
|
|
|
const Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>& R,
|
|
|
|
|
std::vector<double>& R_sum,
|
|
|
|
|
std::vector<double>& maxCoeff,
|
|
|
|
|
std::vector<double>& B_avg,
|
|
|
|
|
std::vector<double>& maxNormWell,
|
|
|
|
|
int nc) const
|
2015-01-20 14:21:45 +01:00
|
|
|
{
|
2015-09-30 15:20:04 +02:00
|
|
|
const int np = asImpl().numPhases();
|
|
|
|
|
const int nm = asImpl().numMaterials();
|
2015-10-12 15:49:10 +02:00
|
|
|
const int nw = residual_.well_flux_eq.size() / np;
|
|
|
|
|
assert(nw * np == int(residual_.well_flux_eq.size()));
|
2015-09-30 10:03:48 +02:00
|
|
|
|
2015-01-20 14:21:45 +01:00
|
|
|
// Do the global reductions
|
2015-02-05 09:47:44 +01:00
|
|
|
#if HAVE_MPI
|
2015-02-20 16:06:19 +01:00
|
|
|
if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) )
|
2015-01-20 14:21:45 +01:00
|
|
|
{
|
2015-01-29 11:32:39 +01:00
|
|
|
const ParallelISTLInformation& info =
|
|
|
|
|
boost::any_cast<const ParallelISTLInformation&>(linsolver_.parallelInformation());
|
2015-02-05 15:05:38 +01:00
|
|
|
|
2015-02-05 09:47:44 +01:00
|
|
|
// Compute the global number of cells and porevolume
|
|
|
|
|
std::vector<int> v(nc, 1);
|
2015-02-11 11:23:43 +01:00
|
|
|
auto nc_and_pv = std::tuple<int, double>(0, 0.0);
|
2015-02-05 09:47:44 +01:00
|
|
|
auto nc_and_pv_operators = std::make_tuple(Opm::Reduction::makeGlobalSumFunctor<int>(),
|
|
|
|
|
Opm::Reduction::makeGlobalSumFunctor<double>());
|
|
|
|
|
auto nc_and_pv_containers = std::make_tuple(v, geo_.poreVolume());
|
|
|
|
|
info.computeReduction(nc_and_pv_containers, nc_and_pv_operators, nc_and_pv);
|
2015-02-05 15:05:38 +01:00
|
|
|
|
2015-10-01 11:05:35 +02:00
|
|
|
for ( int idx = 0; idx < nm; ++idx )
|
2015-01-29 11:32:39 +01:00
|
|
|
{
|
2015-10-01 11:05:35 +02:00
|
|
|
auto values = std::tuple<double,double,double>(0.0 ,0.0 ,0.0);
|
|
|
|
|
auto containers = std::make_tuple(B.col(idx),
|
|
|
|
|
tempV.col(idx),
|
|
|
|
|
R.col(idx));
|
|
|
|
|
auto operators = std::make_tuple(Opm::Reduction::makeGlobalSumFunctor<double>(),
|
|
|
|
|
Opm::Reduction::makeGlobalMaxFunctor<double>(),
|
|
|
|
|
Opm::Reduction::makeGlobalSumFunctor<double>());
|
|
|
|
|
info.computeReduction(containers, operators, values);
|
|
|
|
|
B_avg[idx] = std::get<0>(values)/std::get<0>(nc_and_pv);
|
|
|
|
|
maxCoeff[idx] = std::get<1>(values);
|
|
|
|
|
R_sum[idx] = std::get<2>(values);
|
|
|
|
|
assert(nm >= np);
|
|
|
|
|
if (idx < np) {
|
2015-05-13 12:00:38 +02:00
|
|
|
maxNormWell[idx] = 0.0;
|
2015-10-01 11:05:35 +02:00
|
|
|
for ( int w = 0; w < nw; ++w ) {
|
2015-05-13 12:00:38 +02:00
|
|
|
maxNormWell[idx] = std::max(maxNormWell[idx], std::abs(residual_.well_flux_eq.value()[nw*idx + w]));
|
|
|
|
|
}
|
2015-01-29 11:32:39 +01:00
|
|
|
}
|
2015-01-30 15:26:55 +01:00
|
|
|
}
|
2015-10-01 11:05:35 +02:00
|
|
|
info.communicator().max(maxNormWell.data(), np);
|
2015-02-05 09:47:44 +01:00
|
|
|
// Compute pore volume
|
|
|
|
|
return std::get<1>(nc_and_pv);
|
2015-01-29 11:32:39 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
{
|
2015-09-30 15:20:04 +02:00
|
|
|
B_avg.resize(nm);
|
|
|
|
|
maxCoeff.resize(nm);
|
|
|
|
|
R_sum.resize(nm);
|
|
|
|
|
maxNormWell.resize(np);
|
|
|
|
|
for ( int idx = 0; idx < nm; ++idx )
|
2015-01-30 15:26:55 +01:00
|
|
|
{
|
2015-09-30 10:03:48 +02:00
|
|
|
B_avg[idx] = B.col(idx).sum()/nc;
|
|
|
|
|
maxCoeff[idx] = tempV.col(idx).maxCoeff();
|
|
|
|
|
R_sum[idx] = R.col(idx).sum();
|
|
|
|
|
|
2015-09-30 15:20:04 +02:00
|
|
|
assert(nm >= np);
|
|
|
|
|
if (idx < np) {
|
|
|
|
|
maxNormWell[idx] = 0.0;
|
|
|
|
|
for ( int w = 0; w < nw; ++w ) {
|
|
|
|
|
maxNormWell[idx] = std::max(maxNormWell[idx], std::abs(residual_.well_flux_eq.value()[nw*idx + w]));
|
|
|
|
|
}
|
2015-05-13 12:00:38 +02:00
|
|
|
}
|
2015-01-20 14:21:45 +01:00
|
|
|
}
|
2015-02-05 09:47:44 +01:00
|
|
|
// Compute total pore volume
|
|
|
|
|
return geo_.poreVolume().sum();
|
2015-01-20 14:21:45 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-05-06 15:47:08 +02:00
|
|
|
bool
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
getConvergence(const double dt, const int iteration)
|
2014-05-06 15:47:08 +02:00
|
|
|
{
|
2015-03-04 12:31:04 +01:00
|
|
|
const double tol_mb = param_.tolerance_mb_;
|
|
|
|
|
const double tol_cnv = param_.tolerance_cnv_;
|
|
|
|
|
const double tol_wells = param_.tolerance_wells_;
|
2014-05-06 15:47:08 +02:00
|
|
|
|
|
|
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
2015-09-30 13:23:41 +02:00
|
|
|
const int np = asImpl().numPhases();
|
2015-09-30 14:44:50 +02:00
|
|
|
const int nm = asImpl().numMaterials();
|
|
|
|
|
assert(int(rq_.size()) == nm);
|
2014-05-06 15:47:08 +02:00
|
|
|
|
2015-10-29 15:50:22 +01:00
|
|
|
const V& pv = geo_.poreVolume();
|
2014-05-06 15:47:08 +02:00
|
|
|
|
2015-09-30 14:44:50 +02:00
|
|
|
std::vector<double> R_sum(nm);
|
|
|
|
|
std::vector<double> B_avg(nm);
|
|
|
|
|
std::vector<double> maxCoeff(nm);
|
2015-09-30 10:03:48 +02:00
|
|
|
std::vector<double> maxNormWell(np);
|
2015-09-30 14:44:50 +02:00
|
|
|
Eigen::Array<V::Scalar, Eigen::Dynamic, Eigen::Dynamic> B(nc, nm);
|
|
|
|
|
Eigen::Array<V::Scalar, Eigen::Dynamic, Eigen::Dynamic> R(nc, nm);
|
|
|
|
|
Eigen::Array<V::Scalar, Eigen::Dynamic, Eigen::Dynamic> tempV(nc, nm);
|
2015-01-19 20:22:34 +01:00
|
|
|
|
2015-09-30 14:44:50 +02:00
|
|
|
for ( int idx = 0; idx < nm; ++idx )
|
2015-01-19 20:22:34 +01:00
|
|
|
{
|
2015-09-30 10:03:48 +02:00
|
|
|
const ADB& tempB = rq_[idx].b;
|
|
|
|
|
B.col(idx) = 1./tempB.value();
|
|
|
|
|
R.col(idx) = residual_.material_balance_eq[idx].value();
|
|
|
|
|
tempV.col(idx) = R.col(idx).abs()/pv;
|
2014-05-06 15:47:08 +02:00
|
|
|
}
|
|
|
|
|
|
2015-09-30 10:03:48 +02:00
|
|
|
const double pvSum = convergenceReduction(B, tempV, R,
|
|
|
|
|
R_sum, maxCoeff, B_avg, maxNormWell,
|
2015-10-12 15:49:10 +02:00
|
|
|
nc);
|
2015-09-30 10:03:48 +02:00
|
|
|
|
2015-09-30 14:44:50 +02:00
|
|
|
std::vector<double> CNV(nm);
|
|
|
|
|
std::vector<double> mass_balance_residual(nm);
|
2015-09-30 10:03:48 +02:00
|
|
|
std::vector<double> well_flux_residual(np);
|
2014-05-06 15:47:08 +02:00
|
|
|
|
2015-01-19 20:22:34 +01:00
|
|
|
bool converged_MB = true;
|
|
|
|
|
bool converged_CNV = true;
|
2015-05-04 11:47:54 +02:00
|
|
|
bool converged_Well = true;
|
2015-01-19 20:22:34 +01:00
|
|
|
// Finish computation
|
2015-09-30 14:44:50 +02:00
|
|
|
for ( int idx = 0; idx < nm; ++idx )
|
2015-01-19 20:22:34 +01:00
|
|
|
{
|
2015-05-04 11:47:54 +02:00
|
|
|
CNV[idx] = B_avg[idx] * dt * maxCoeff[idx];
|
|
|
|
|
mass_balance_residual[idx] = std::abs(B_avg[idx]*R_sum[idx]) * dt / pvSum;
|
|
|
|
|
converged_MB = converged_MB && (mass_balance_residual[idx] < tol_mb);
|
|
|
|
|
converged_CNV = converged_CNV && (CNV[idx] < tol_cnv);
|
2015-09-30 14:44:50 +02:00
|
|
|
// Well flux convergence is only for fluid phases, not other materials
|
|
|
|
|
// in our current implementation.
|
|
|
|
|
assert(nm >= np);
|
|
|
|
|
if (idx < np) {
|
|
|
|
|
well_flux_residual[idx] = B_avg[idx] * maxNormWell[idx];
|
|
|
|
|
converged_Well = converged_Well && (well_flux_residual[idx] < tol_wells);
|
|
|
|
|
}
|
2014-05-06 15:47:08 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-13 12:16:36 +02:00
|
|
|
const double residualWell = detail::infinityNormWell(residual_.well_eq,
|
|
|
|
|
linsolver_.parallelInformation());
|
2015-09-30 10:03:48 +02:00
|
|
|
converged_Well = converged_Well && (residualWell < Opm::unit::barsa);
|
|
|
|
|
const bool converged = converged_MB && converged_CNV && converged_Well;
|
2014-05-06 15:47:08 +02:00
|
|
|
|
2015-08-19 08:07:51 +02:00
|
|
|
// Residual in Pascal can have high values and still be ok.
|
|
|
|
|
const double maxWellResidualAllowed = 1000.0 * maxResidualAllowed();
|
|
|
|
|
|
2015-02-20 16:02:06 +01:00
|
|
|
if ( terminal_output_ )
|
2015-02-20 11:35:47 +01:00
|
|
|
{
|
|
|
|
|
// Only rank 0 does print to std::cout
|
|
|
|
|
if (iteration == 0) {
|
2016-05-09 13:33:44 +08:00
|
|
|
std::string msg = "Iter";
|
2015-09-30 14:44:50 +02:00
|
|
|
for (int idx = 0; idx < nm; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
msg += " MB(" + materialName(idx).substr(0, 3) + ") ";
|
2015-09-30 13:10:48 +02:00
|
|
|
}
|
2015-09-30 14:44:50 +02:00
|
|
|
for (int idx = 0; idx < nm; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
msg += " CNV(" + materialName(idx).substr(0, 1) + ") ";
|
2015-09-30 13:10:48 +02:00
|
|
|
}
|
|
|
|
|
for (int idx = 0; idx < np; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
msg += " W-FLUX(" + materialName(idx).substr(0, 1) + ")";
|
2015-09-30 13:10:48 +02:00
|
|
|
}
|
2015-11-23 13:51:12 +01:00
|
|
|
// std::cout << " WELL-CONT ";
|
2016-06-21 09:27:19 +08:00
|
|
|
OpmLog::note(msg);
|
2015-02-20 11:35:47 +01:00
|
|
|
}
|
2016-05-09 13:33:44 +08:00
|
|
|
std::ostringstream ss;
|
|
|
|
|
const std::streamsize oprec = ss.precision(3);
|
|
|
|
|
const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
|
|
|
|
|
ss << std::setw(4) << iteration;
|
2015-09-30 14:44:50 +02:00
|
|
|
for (int idx = 0; idx < nm; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
ss << std::setw(11) << mass_balance_residual[idx];
|
2015-09-30 13:10:48 +02:00
|
|
|
}
|
2015-09-30 14:44:50 +02:00
|
|
|
for (int idx = 0; idx < nm; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
ss << std::setw(11) << CNV[idx];
|
2015-09-30 13:10:48 +02:00
|
|
|
}
|
|
|
|
|
for (int idx = 0; idx < np; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
ss << std::setw(11) << well_flux_residual[idx];
|
2015-09-30 13:10:48 +02:00
|
|
|
}
|
2015-11-23 13:51:12 +01:00
|
|
|
// std::cout << std::setw(11) << residualWell;
|
2016-05-09 13:33:44 +08:00
|
|
|
ss.precision(oprec);
|
|
|
|
|
ss.flags(oflags);
|
2016-06-21 09:27:19 +08:00
|
|
|
OpmLog::note(ss.str());
|
2015-02-20 11:35:47 +01:00
|
|
|
}
|
2016-02-08 17:13:43 +01:00
|
|
|
|
|
|
|
|
for (int idx = 0; idx < nm; ++idx) {
|
|
|
|
|
if (std::isnan(mass_balance_residual[idx])
|
|
|
|
|
|| std::isnan(CNV[idx])
|
|
|
|
|
|| (idx < np && std::isnan(well_flux_residual[idx]))) {
|
|
|
|
|
OPM_THROW(Opm::NumericalProblem, "NaN residual for phase " << materialName(idx));
|
|
|
|
|
}
|
|
|
|
|
if (mass_balance_residual[idx] > maxResidualAllowed()
|
|
|
|
|
|| CNV[idx] > maxResidualAllowed()
|
|
|
|
|
|| (idx < np && well_flux_residual[idx] > maxResidualAllowed())) {
|
|
|
|
|
OPM_THROW(Opm::NumericalProblem, "Too large residual for phase " << materialName(idx));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (std::isnan(residualWell) || residualWell > maxWellResidualAllowed) {
|
|
|
|
|
OPM_THROW(Opm::NumericalProblem, "NaN or too large residual for well control equation");
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-06 15:47:08 +02:00
|
|
|
return converged;
|
|
|
|
|
}
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2015-06-12 06:49:25 +02:00
|
|
|
bool
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
getWellConvergence(const int iteration)
|
2015-06-12 06:49:25 +02:00
|
|
|
{
|
|
|
|
|
const double tol_wells = param_.tolerance_wells_;
|
|
|
|
|
|
|
|
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
2015-09-30 13:23:41 +02:00
|
|
|
const int np = asImpl().numPhases();
|
2015-09-30 14:44:50 +02:00
|
|
|
const int nm = asImpl().numMaterials();
|
2015-06-12 06:49:25 +02:00
|
|
|
|
2015-10-29 15:50:22 +01:00
|
|
|
const V& pv = geo_.poreVolume();
|
2015-09-30 14:44:50 +02:00
|
|
|
std::vector<double> R_sum(nm);
|
|
|
|
|
std::vector<double> B_avg(nm);
|
|
|
|
|
std::vector<double> maxCoeff(nm);
|
|
|
|
|
std::vector<double> maxNormWell(np);
|
|
|
|
|
Eigen::Array<V::Scalar, Eigen::Dynamic, Eigen::Dynamic> B(nc, nm);
|
|
|
|
|
Eigen::Array<V::Scalar, Eigen::Dynamic, Eigen::Dynamic> R(nc, nm);
|
|
|
|
|
Eigen::Array<V::Scalar, Eigen::Dynamic, Eigen::Dynamic> tempV(nc, nm);
|
|
|
|
|
for ( int idx = 0; idx < nm; ++idx )
|
2015-06-12 06:49:25 +02:00
|
|
|
{
|
2015-09-30 10:03:48 +02:00
|
|
|
const ADB& tempB = rq_[idx].b;
|
|
|
|
|
B.col(idx) = 1./tempB.value();
|
|
|
|
|
R.col(idx) = residual_.material_balance_eq[idx].value();
|
|
|
|
|
tempV.col(idx) = R.col(idx).abs()/pv;
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
2015-06-17 09:49:11 +02:00
|
|
|
|
2015-10-12 15:49:10 +02:00
|
|
|
convergenceReduction(B, tempV, R, R_sum, maxCoeff, B_avg, maxNormWell, nc);
|
2015-06-12 06:49:25 +02:00
|
|
|
|
2015-09-30 10:03:48 +02:00
|
|
|
std::vector<double> well_flux_residual(np);
|
2015-06-12 06:49:25 +02:00
|
|
|
bool converged_Well = true;
|
|
|
|
|
// Finish computation
|
2015-09-30 10:03:48 +02:00
|
|
|
for ( int idx = 0; idx < np; ++idx )
|
2015-06-12 06:49:25 +02:00
|
|
|
{
|
|
|
|
|
well_flux_residual[idx] = B_avg[idx] * maxNormWell[idx];
|
|
|
|
|
converged_Well = converged_Well && (well_flux_residual[idx] < tol_wells);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const double residualWell = detail::infinityNormWell(residual_.well_eq,
|
|
|
|
|
linsolver_.parallelInformation());
|
2015-09-30 13:20:42 +02:00
|
|
|
converged_Well = converged_Well && (residualWell < Opm::unit::barsa);
|
|
|
|
|
const bool converged = converged_Well;
|
2015-06-12 06:49:25 +02:00
|
|
|
|
|
|
|
|
// if one of the residuals is NaN, throw exception, so that the solver can be restarted
|
2015-09-30 13:20:42 +02:00
|
|
|
for (int idx = 0; idx < np; ++idx) {
|
|
|
|
|
if (std::isnan(well_flux_residual[idx])) {
|
2015-09-30 14:44:50 +02:00
|
|
|
OPM_THROW(Opm::NumericalProblem, "NaN residual for phase " << materialName(idx));
|
2015-09-30 13:20:42 +02:00
|
|
|
}
|
|
|
|
|
if (well_flux_residual[idx] > maxResidualAllowed()) {
|
2015-09-30 14:44:50 +02:00
|
|
|
OPM_THROW(Opm::NumericalProblem, "Too large residual for phase " << materialName(idx));
|
2015-09-30 13:20:42 +02:00
|
|
|
}
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( terminal_output_ )
|
|
|
|
|
{
|
|
|
|
|
// Only rank 0 does print to std::cout
|
|
|
|
|
if (iteration == 0) {
|
2016-05-09 13:33:44 +08:00
|
|
|
std::string msg;
|
|
|
|
|
msg = "Iter";
|
2015-09-30 13:20:42 +02:00
|
|
|
for (int idx = 0; idx < np; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
msg += " W-FLUX(" + materialName(idx).substr(0, 1) + ")";
|
2015-09-30 13:20:42 +02:00
|
|
|
}
|
2016-06-21 09:27:19 +08:00
|
|
|
OpmLog::note(msg);
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
2016-05-09 13:33:44 +08:00
|
|
|
std::ostringstream ss;
|
|
|
|
|
const std::streamsize oprec = ss.precision(3);
|
|
|
|
|
const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
|
|
|
|
|
ss << std::setw(4) << iteration;
|
2015-09-30 13:20:42 +02:00
|
|
|
for (int idx = 0; idx < np; ++idx) {
|
2016-05-09 13:33:44 +08:00
|
|
|
ss << std::setw(11) << well_flux_residual[idx];
|
2015-09-30 13:20:42 +02:00
|
|
|
}
|
2016-05-09 13:33:44 +08:00
|
|
|
ss.precision(oprec);
|
|
|
|
|
ss.flags(oflags);
|
2016-06-21 09:27:19 +08:00
|
|
|
OpmLog::note(ss.str());
|
2015-06-12 06:49:25 +02:00
|
|
|
}
|
|
|
|
|
return converged;
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
2015-05-24 09:59:40 +02:00
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-24 11:14:05 +02:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidViscosity(const int phase,
|
|
|
|
|
const ADB& p ,
|
|
|
|
|
const ADB& temp ,
|
|
|
|
|
const ADB& rs ,
|
|
|
|
|
const ADB& rv ,
|
|
|
|
|
const std::vector<PhasePresence>& cond) const
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
|
|
|
|
switch (phase) {
|
|
|
|
|
case Water:
|
2015-06-24 10:22:23 +02:00
|
|
|
return fluid_.muWat(p, temp, cells_);
|
|
|
|
|
case Oil:
|
|
|
|
|
return fluid_.muOil(p, temp, rs, cond, cells_);
|
2013-05-24 11:14:05 +02:00
|
|
|
case Gas:
|
2015-06-24 10:22:23 +02:00
|
|
|
return fluid_.muGas(p, temp, rv, cond, cells_);
|
2013-05-24 11:14:05 +02:00
|
|
|
default:
|
2013-09-03 15:00:29 +02:00
|
|
|
OPM_THROW(std::runtime_error, "Unknown phase index " << phase);
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-24 11:14:05 +02:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidReciprocFVF(const int phase,
|
|
|
|
|
const ADB& p ,
|
|
|
|
|
const ADB& temp ,
|
|
|
|
|
const ADB& rs ,
|
|
|
|
|
const ADB& rv ,
|
|
|
|
|
const std::vector<PhasePresence>& cond) const
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
|
|
|
|
switch (phase) {
|
|
|
|
|
case Water:
|
2015-06-24 10:22:23 +02:00
|
|
|
return fluid_.bWat(p, temp, cells_);
|
|
|
|
|
case Oil:
|
|
|
|
|
return fluid_.bOil(p, temp, rs, cond, cells_);
|
2013-05-24 11:14:05 +02:00
|
|
|
case Gas:
|
2015-06-24 10:22:23 +02:00
|
|
|
return fluid_.bGas(p, temp, rv, cond, cells_);
|
2013-05-24 11:14:05 +02:00
|
|
|
default:
|
2013-09-03 15:00:29 +02:00
|
|
|
OPM_THROW(std::runtime_error, "Unknown phase index " << phase);
|
2013-05-24 11:14:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-24 11:14:05 +02:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidDensity(const int phase,
|
|
|
|
|
const ADB& b,
|
|
|
|
|
const ADB& rs,
|
|
|
|
|
const ADB& rv) const
|
2013-05-24 11:14:05 +02:00
|
|
|
{
|
2015-11-10 14:22:14 +01:00
|
|
|
const V& rhos = fluid_.surfaceDensity(phase, cells_);
|
|
|
|
|
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
|
|
|
|
|
ADB rho = rhos * b;
|
2013-05-31 14:59:56 +02:00
|
|
|
if (phase == Oil && active_[Gas]) {
|
2015-11-10 14:22:14 +01:00
|
|
|
rho += fluid_.surfaceDensity(pu.phase_pos[ Gas ], cells_) * rs * b;
|
2013-05-31 14:59:56 +02:00
|
|
|
}
|
2014-01-10 14:19:37 +01:00
|
|
|
if (phase == Gas && active_[Oil]) {
|
2015-11-10 14:22:14 +01:00
|
|
|
rho += fluid_.surfaceDensity(pu.phase_pos[ Oil ], cells_) * rv * b;
|
2014-01-10 14:19:37 +01:00
|
|
|
}
|
2013-05-24 11:14:05 +02:00
|
|
|
return rho;
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-27 10:29:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-30 14:43:32 +02:00
|
|
|
V
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidRsSat(const V& p,
|
|
|
|
|
const V& satOil,
|
|
|
|
|
const std::vector<int>& cells) const
|
2013-05-30 14:43:32 +02:00
|
|
|
{
|
2015-03-05 18:47:04 +01:00
|
|
|
return fluid_.rsSat(ADB::constant(p), ADB::constant(satOil), cells).value();
|
2013-05-30 14:43:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-05-27 10:29:04 +02:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidRsSat(const ADB& p,
|
|
|
|
|
const ADB& satOil,
|
|
|
|
|
const std::vector<int>& cells) const
|
2014-01-10 14:19:37 +01:00
|
|
|
{
|
2014-07-05 15:06:12 +02:00
|
|
|
return fluid_.rsSat(p, satOil, cells);
|
2014-01-10 14:19:37 +01:00
|
|
|
}
|
|
|
|
|
|
2015-03-05 18:47:04 +01:00
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-01-10 14:19:37 +01:00
|
|
|
V
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidRvSat(const V& p,
|
|
|
|
|
const V& satOil,
|
|
|
|
|
const std::vector<int>& cells) const
|
2013-05-27 10:29:04 +02:00
|
|
|
{
|
2015-03-05 18:47:04 +01:00
|
|
|
return fluid_.rvSat(ADB::constant(p), ADB::constant(satOil), cells).value();
|
2013-05-27 10:29:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-03 14:14:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-01-10 14:19:37 +01:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
fluidRvSat(const ADB& p,
|
|
|
|
|
const ADB& satOil,
|
|
|
|
|
const std::vector<int>& cells) const
|
2014-01-10 14:19:37 +01:00
|
|
|
{
|
2014-07-05 15:06:12 +02:00
|
|
|
return fluid_.rvSat(p, satOil, cells);
|
2014-01-10 14:19:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-16 10:58:14 +02:00
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-06-03 14:14:48 +02:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
poroMult(const ADB& p) const
|
2013-06-03 14:14:48 +02:00
|
|
|
{
|
|
|
|
|
const int n = p.size();
|
|
|
|
|
if (rock_comp_props_ && rock_comp_props_->isActive()) {
|
|
|
|
|
V pm(n);
|
|
|
|
|
V dpm(n);
|
2015-09-01 15:56:46 +02:00
|
|
|
#pragma omp parallel for schedule(static)
|
2013-06-03 14:14:48 +02:00
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
|
pm[i] = rock_comp_props_->poroMult(p.value()[i]);
|
|
|
|
|
dpm[i] = rock_comp_props_->poroMultDeriv(p.value()[i]);
|
|
|
|
|
}
|
2015-08-25 16:15:29 +02:00
|
|
|
ADB::M dpm_diag(dpm.matrix().asDiagonal());
|
2013-06-03 14:14:48 +02:00
|
|
|
const int num_blocks = p.numBlocks();
|
|
|
|
|
std::vector<ADB::M> jacs(num_blocks);
|
2015-09-01 15:56:46 +02:00
|
|
|
#pragma omp parallel for schedule(dynamic)
|
2013-06-03 14:14:48 +02:00
|
|
|
for (int block = 0; block < num_blocks; ++block) {
|
2014-12-05 12:59:04 +01:00
|
|
|
fastSparseProduct(dpm_diag, p.derivative()[block], jacs[block]);
|
2013-06-03 14:14:48 +02:00
|
|
|
}
|
2015-03-10 12:36:14 +01:00
|
|
|
return ADB::function(std::move(pm), std::move(jacs));
|
2013-06-03 14:14:48 +02:00
|
|
|
} else {
|
2015-03-10 12:36:14 +01:00
|
|
|
return ADB::constant(V::Constant(n, 1.0));
|
2013-06-03 14:14:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2013-06-03 14:14:48 +02:00
|
|
|
ADB
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
transMult(const ADB& p) const
|
2013-06-03 14:14:48 +02:00
|
|
|
{
|
|
|
|
|
const int n = p.size();
|
|
|
|
|
if (rock_comp_props_ && rock_comp_props_->isActive()) {
|
|
|
|
|
V tm(n);
|
|
|
|
|
V dtm(n);
|
2015-09-01 15:56:46 +02:00
|
|
|
#pragma omp parallel for schedule(static)
|
2013-06-03 14:14:48 +02:00
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
|
tm[i] = rock_comp_props_->transMult(p.value()[i]);
|
|
|
|
|
dtm[i] = rock_comp_props_->transMultDeriv(p.value()[i]);
|
|
|
|
|
}
|
2015-08-25 16:15:29 +02:00
|
|
|
ADB::M dtm_diag(dtm.matrix().asDiagonal());
|
2013-06-03 14:14:48 +02:00
|
|
|
const int num_blocks = p.numBlocks();
|
|
|
|
|
std::vector<ADB::M> jacs(num_blocks);
|
2015-09-01 15:56:46 +02:00
|
|
|
#pragma omp parallel for schedule(dynamic)
|
2013-06-03 14:14:48 +02:00
|
|
|
for (int block = 0; block < num_blocks; ++block) {
|
2014-12-05 12:59:04 +01:00
|
|
|
fastSparseProduct(dtm_diag, p.derivative()[block], jacs[block]);
|
2013-06-03 14:14:48 +02:00
|
|
|
}
|
2015-03-10 12:36:14 +01:00
|
|
|
return ADB::function(std::move(tm), std::move(jacs));
|
2013-06-03 14:14:48 +02:00
|
|
|
} else {
|
2015-03-10 12:36:14 +01:00
|
|
|
return ADB::constant(V::Constant(n, 1.0));
|
2013-06-03 14:14:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-12-03 18:12:54 +01:00
|
|
|
|
2014-01-24 13:35:04 +01:00
|
|
|
|
2013-11-28 11:27:25 +01:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-01-10 14:19:37 +01:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
classifyCondition(const ReservoirState& state)
|
2014-01-10 14:19:37 +01:00
|
|
|
{
|
2014-02-20 13:17:18 +01:00
|
|
|
using namespace Opm::AutoDiffGrid;
|
|
|
|
|
const int nc = numCells(grid_);
|
2014-01-10 14:19:37 +01:00
|
|
|
const int np = state.numPhases();
|
|
|
|
|
|
|
|
|
|
const PhaseUsage& pu = fluid_.phaseUsage();
|
|
|
|
|
const DataBlock s = Eigen::Map<const DataBlock>(& state.saturation()[0], nc, np);
|
|
|
|
|
if (active_[ Gas ]) {
|
|
|
|
|
// Oil/Gas or Water/Oil/Gas system
|
|
|
|
|
const V so = s.col(pu.phase_pos[ Oil ]);
|
|
|
|
|
const V sg = s.col(pu.phase_pos[ Gas ]);
|
|
|
|
|
|
|
|
|
|
for (V::Index c = 0, e = sg.size(); c != e; ++c) {
|
|
|
|
|
if (so[c] > 0) { phaseCondition_[c].setFreeOil (); }
|
|
|
|
|
if (sg[c] > 0) { phaseCondition_[c].setFreeGas (); }
|
|
|
|
|
if (active_[ Water ]) { phaseCondition_[c].setFreeWater(); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Water/Oil system
|
|
|
|
|
assert (active_[ Water ]);
|
|
|
|
|
|
|
|
|
|
const V so = s.col(pu.phase_pos[ Oil ]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (V::Index c = 0, e = so.size(); c != e; ++c) {
|
|
|
|
|
phaseCondition_[c].setFreeWater();
|
|
|
|
|
|
|
|
|
|
if (so[c] > 0) { phaseCondition_[c].setFreeOil(); }
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-05-26 01:29:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-01-10 14:19:37 +01:00
|
|
|
|
|
|
|
|
|
2013-11-28 11:27:25 +01:00
|
|
|
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-05-27 15:09:58 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-04-18 12:14:01 +02:00
|
|
|
updatePrimalVariableFromState(const ReservoirState& state)
|
2014-05-27 15:09:58 +02:00
|
|
|
{
|
2016-05-13 09:04:48 +02:00
|
|
|
updatePhaseCondFromPrimalVariable(state);
|
2014-07-17 14:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-05-27 15:09:58 +02:00
|
|
|
|
2014-07-17 14:34:07 +02:00
|
|
|
|
|
|
|
|
/// Update the phaseCondition_ member based on the primalVariable_ member.
|
2016-04-18 15:32:47 +02:00
|
|
|
template <class Grid, class WellModel, class Implementation>
|
2014-07-17 14:34:07 +02:00
|
|
|
void
|
2016-04-18 15:32:47 +02:00
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
2016-05-13 09:04:48 +02:00
|
|
|
updatePhaseCondFromPrimalVariable(const ReservoirState& state)
|
2014-07-17 14:34:07 +02:00
|
|
|
{
|
2016-04-21 16:19:45 +02:00
|
|
|
const int nc = Opm::AutoDiffGrid::numCells(grid_);
|
2015-05-26 14:38:25 +02:00
|
|
|
isRs_ = V::Zero(nc);
|
|
|
|
|
isRv_ = V::Zero(nc);
|
|
|
|
|
isSg_ = V::Zero(nc);
|
2016-04-21 16:19:45 +02:00
|
|
|
|
|
|
|
|
if (! (active_[Gas] && active_[Oil])) {
|
|
|
|
|
// updatePhaseCondFromPrimarVariable() logic requires active gas and oil phase.
|
|
|
|
|
phaseCondition_.assign(nc, PhasePresence());
|
|
|
|
|
return;
|
2016-05-13 09:04:48 +02:00
|
|
|
}
|
2014-07-17 14:34:07 +02:00
|
|
|
for (int c = 0; c < nc; ++c) {
|
|
|
|
|
phaseCondition_[c] = PhasePresence(); // No free phases.
|
|
|
|
|
phaseCondition_[c].setFreeWater(); // Not necessary for property calculation usage.
|
2016-05-13 09:04:48 +02:00
|
|
|
switch (state.hydroCarbonState()[c]) {
|
|
|
|
|
case HydroCarbonState::GasAndOil:
|
2014-07-17 14:34:07 +02:00
|
|
|
phaseCondition_[c].setFreeOil();
|
|
|
|
|
phaseCondition_[c].setFreeGas();
|
2015-05-26 14:38:25 +02:00
|
|
|
isSg_[c] = 1;
|
2014-07-17 14:34:07 +02:00
|
|
|
break;
|
2016-05-13 09:04:48 +02:00
|
|
|
case HydroCarbonState::OilOnly:
|
2014-07-17 14:34:07 +02:00
|
|
|
phaseCondition_[c].setFreeOil();
|
2015-05-26 14:38:25 +02:00
|
|
|
isRs_[c] = 1;
|
2014-07-17 14:34:07 +02:00
|
|
|
break;
|
2016-05-13 09:04:48 +02:00
|
|
|
case HydroCarbonState::GasOnly:
|
2014-07-17 14:34:07 +02:00
|
|
|
phaseCondition_[c].setFreeGas();
|
2015-05-26 14:38:25 +02:00
|
|
|
isRv_[c] = 1;
|
2014-07-17 14:34:07 +02:00
|
|
|
break;
|
|
|
|
|
default:
|
2016-05-13 09:04:48 +02:00
|
|
|
OPM_THROW(std::logic_error, "Unknown primary variable enum value in cell " << c << ": " << state.hydroCarbonState()[c]);
|
2014-07-17 14:34:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
2014-05-27 15:09:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-07-17 14:34:07 +02:00
|
|
|
|
2016-05-10 14:58:45 +02:00
|
|
|
|
|
|
|
|
template <class Grid, class WellModel, class Implementation>
|
|
|
|
|
void
|
|
|
|
|
BlackoilModelBase<Grid, WellModel, Implementation>::
|
|
|
|
|
computeWellConnectionPressures(const SolutionState& state,
|
|
|
|
|
const WellState& well_state)
|
|
|
|
|
{
|
|
|
|
|
asImpl().wellModel().computeWellConnectionPressures(state, well_state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-05-24 11:14:05 +02:00
|
|
|
} // namespace Opm
|
2015-05-24 09:59:40 +02:00
|
|
|
|
|
|
|
|
#endif // OPM_BLACKOILMODELBASE_IMPL_HEADER_INCLUDED
|