BlockOilSimulator: allow to run without wells (mainly for testing and debugging).

This commit is contained in:
Robert K 2015-01-19 14:14:18 +01:00
parent bfe5f57c9a
commit 463e4bc5e3
5 changed files with 159 additions and 107 deletions

View File

@ -90,7 +90,7 @@ namespace Opm {
const BlackoilPropsAdInterface& fluid,
const DerivedGeology& geo ,
const RockCompressibility* rock_comp_props,
const Wells& wells,
const Wells* wells,
const NewtonIterationBlackoilInterface& linsolver,
const bool has_disgas,
const bool has_vapoil );
@ -147,11 +147,11 @@ namespace Opm {
ADB rs;
ADB rv;
ADB qs;
ADB bhp;
ADB bhp;
};
struct WellOps {
WellOps(const Wells& wells);
WellOps(const Wells* wells);
M w2p; // well -> perf (scatter)
M p2w; // perf -> well (gather)
};
@ -169,7 +169,7 @@ namespace Opm {
const BlackoilPropsAdInterface& fluid_;
const DerivedGeology& geo_;
const RockCompressibility* rock_comp_props_;
const Wells& wells_;
const Wells* wells_;
const NewtonIterationBlackoilInterface& linsolver_;
// For each canonical phase -> true if active
const std::vector<bool> active_;
@ -194,6 +194,12 @@ namespace Opm {
std::vector<int> primalVariable_;
// Private methods.
// return true if wells are available
bool wellsActive() const { return wells_ ? wells_->number_of_wells > 0 : false ; }
// return wells object
const Wells& wells () const { assert( wells_ ); return *wells_; }
SolutionState
constantState(const BlackoilState& x,
const WellStateFullyImplicitBlackoil& xw);

View File

@ -211,7 +211,7 @@ namespace {
const BlackoilPropsAdInterface& fluid,
const DerivedGeology& geo ,
const RockCompressibility* rock_comp_props,
const Wells& wells,
const Wells* wells,
const NewtonIterationBlackoilInterface& linsolver,
const bool has_disgas,
const bool has_vapoil)
@ -225,7 +225,7 @@ namespace {
, canph_ (active2Canonical(fluid.phaseUsage()))
, cells_ (buildAllCells(Opm::AutoDiffGrid::numCells(grid)))
, ops_ (grid)
, wops_ (wells)
, wops_ (wells_)
, has_disgas_(has_disgas)
, has_vapoil_(has_vapoil)
, param_( param )
@ -372,30 +372,34 @@ namespace {
template<class T>
FullyImplicitBlackoilSolver<T>::
WellOps::WellOps(const Wells& wells)
: w2p(wells.well_connpos[ wells.number_of_wells ],
wells.number_of_wells)
, p2w(wells.number_of_wells,
wells.well_connpos[ wells.number_of_wells ])
WellOps::WellOps(const Wells* wells)
: w2p(),
p2w()
{
const int nw = wells.number_of_wells;
const int* const wpos = wells.well_connpos;
if( wells )
{
w2p = M(wells->well_connpos[ wells->number_of_wells ], wells->number_of_wells);
p2w = M(wells->number_of_wells, wells->well_connpos[ wells->number_of_wells ]);
typedef Eigen::Triplet<double> Tri;
const int nw = wells->number_of_wells;
const int* const wpos = wells->well_connpos;
std::vector<Tri> scatter, gather;
scatter.reserve(wpos[nw]);
gather .reserve(wpos[nw]);
typedef Eigen::Triplet<double> Tri;
for (int w = 0, i = 0; w < nw; ++w) {
for (; i < wpos[ w + 1 ]; ++i) {
scatter.push_back(Tri(i, w, 1.0));
gather .push_back(Tri(w, i, 1.0));
std::vector<Tri> scatter, gather;
scatter.reserve(wpos[nw]);
gather .reserve(wpos[nw]);
for (int w = 0, i = 0; w < nw; ++w) {
for (; i < wpos[ w + 1 ]; ++i) {
scatter.push_back(Tri(i, w, 1.0));
gather .push_back(Tri(w, i, 1.0));
}
}
}
w2p.setFromTriplets(scatter.begin(), scatter.end());
p2w.setFromTriplets(gather .begin(), gather .end());
w2p.setFromTriplets(scatter.begin(), scatter.end());
p2w.setFromTriplets(gather .begin(), gather .end());
}
}
@ -490,21 +494,29 @@ namespace {
}
// Initial well rates.
assert (not xw.wellRates().empty());
// Need to reshuffle well rates, from phase running fastest
// to wells running fastest.
const int nw = wells_.number_of_wells;
// The transpose() below switches the ordering.
const DataBlock wrates = Eigen::Map<const DataBlock>(& xw.wellRates()[0], nw, np).transpose();
const V qs = Eigen::Map<const V>(wrates.data(), nw*np);
vars0.push_back(qs);
if( wellsActive() )
{
// Need to reshuffle well rates, from phase running fastest
// to wells running fastest.
const int nw = wells().number_of_wells;
// Initial well bottom-hole pressure.
assert (not xw.bhp().empty());
const V bhp = Eigen::Map<const V>(& xw.bhp()[0], xw.bhp().size());
vars0.push_back(bhp);
// The transpose() below switches the ordering.
const DataBlock wrates = Eigen::Map<const DataBlock>(& xw.wellRates()[0], nw, np).transpose();
const V qs = Eigen::Map<const V>(wrates.data(), nw*np);
vars0.push_back(qs);
// Initial well bottom-hole pressure.
assert (not xw.bhp().empty());
const V bhp = Eigen::Map<const V>(& xw.bhp()[0], xw.bhp().size());
vars0.push_back(bhp);
}
else
{
// push null states for qs and bhp
vars0.push_back(V());
vars0.push_back(V());
}
std::vector<ADB> vars = ADB::variables(vars0);
@ -564,6 +576,7 @@ namespace {
state.saturation[pu.phase_pos[ Oil ]] = so;
}
}
// Qs.
state.qs = vars[ nextvar++ ];
@ -631,12 +644,14 @@ namespace {
void FullyImplicitBlackoilSolver<T>::computeWellConnectionPressures(const SolutionState& state,
const WellStateFullyImplicitBlackoil& xw)
{
if( ! wellsActive() ) return ;
using namespace Opm::AutoDiffGrid;
// 1. Compute properties required by computeConnectionPressureDelta().
// Note that some of the complexity of this part is due to the function
// taking std::vector<double> arguments, and not Eigen objects.
const int nperf = wells_.well_connpos[wells_.number_of_wells];
const std::vector<int> well_cells(wells_.well_cells, wells_.well_cells + nperf);
const int nperf = wells().well_connpos[wells().number_of_wells];
const std::vector<int> well_cells(wells().well_cells, wells().well_cells + nperf);
// Compute b, rsmax, rvmax values for perforations.
const std::vector<ADB> pressures = computePressures(state);
const ADB perf_temp = subset(state.temperature, well_cells);
@ -694,7 +709,7 @@ namespace {
// 2. Compute pressure deltas, and store the results.
std::vector<double> cdp = WellDensitySegmented
::computeConnectionPressureDelta(wells_, xw, fluid_.phaseUsage(),
::computeConnectionPressureDelta(wells(), xw, fluid_.phaseUsage(),
b_perf, rssat_perf, rvsat_perf, perf_depth,
surf_dens, grav);
well_perforation_pressure_diffs_ = Eigen::Map<const V>(cdp.data(), nperf);
@ -796,13 +811,15 @@ namespace {
WellStateFullyImplicitBlackoil& xw,
V& aliveWells)
{
if( ! wellsActive() ) return ;
const int nc = Opm::AutoDiffGrid::numCells(grid_);
const int np = wells_.number_of_phases;
const int nw = wells_.number_of_wells;
const int nperf = wells_.well_connpos[nw];
const int np = wells().number_of_phases;
const int nw = wells().number_of_wells;
const int nperf = wells().well_connpos[nw];
const Opm::PhaseUsage& pu = fluid_.phaseUsage();
V Tw = Eigen::Map<const V>(wells_.WI, nperf);
const std::vector<int> well_cells(wells_.well_cells, wells_.well_cells + nperf);
V Tw = Eigen::Map<const V>(wells().WI, nperf);
const std::vector<int> well_cells(wells().well_cells, wells().well_cells + nperf);
// pressure diffs computed already (once per step, not changing per iteration)
const V& cdp = well_perforation_pressure_diffs_;
@ -824,7 +841,7 @@ namespace {
// injector == 1, producer == 0
V isInj = V::Zero(nw);
for (int w = 0; w < nw; ++w) {
if (wells_.type[w] == INJECTOR) {
if (wells().type[w] == INJECTOR) {
isInj[w] = 1;
}
}
@ -889,7 +906,7 @@ namespace {
}
// compute avg. and total wellbore phase volumetric rates at std. conds
const DataBlock compi = Eigen::Map<const DataBlock>(wells_.comp_frac, nw, np);
const DataBlock compi = Eigen::Map<const DataBlock>(wells().comp_frac, nw, np);
std::vector<ADB> wbq(np, ADB::null());
ADB wbqt = ADB::constant(V::Zero(nw), state.pressure.blockPattern());
for (int phase = 0; phase < np; ++phase) {
@ -1083,15 +1100,17 @@ namespace {
ADB& well_phase_flow_rate,
WellStateFullyImplicitBlackoil& xw) const
{
if( ! wellsActive() ) return ;
std::string modestring[3] = { "BHP", "RESERVOIR_RATE", "SURFACE_RATE" };
// Find, for each well, if any constraints are broken. If so,
// switch control to first broken constraint.
const int np = wells_.number_of_phases;
const int nw = wells_.number_of_wells;
const int np = wells().number_of_phases;
const int nw = wells().number_of_wells;
bool bhp_changed = false;
bool rates_changed = false;
for (int w = 0; w < nw; ++w) {
const WellControls* wc = wells_.ctrls[w];
const WellControls* wc = wells().ctrls[w];
// The current control in the well state overrides
// the current control set in the Wells struct, which
// is instead treated as a default.
@ -1108,14 +1127,14 @@ namespace {
// inequality constraint, and therefore skipped.
continue;
}
if (constraintBroken(bhp, well_phase_flow_rate, w, np, wells_.type[w], wc, ctrl_index)) {
if (constraintBroken(bhp, well_phase_flow_rate, w, np, wells().type[w], wc, ctrl_index)) {
// ctrl_index will be the index of the broken constraint after the loop.
break;
}
}
if (ctrl_index != nwc) {
// Constraint number ctrl_index was broken, switch to it.
std::cout << "Switching control mode for well " << wells_.name[w]
std::cout << "Switching control mode for well " << wells().name[w]
<< " from " << modestring[well_controls_iget_type(wc, current)]
<< " to " << modestring[well_controls_iget_type(wc, ctrl_index)] << std::endl;
xw.currentControls()[w] = ctrl_index;
@ -1176,14 +1195,16 @@ namespace {
const WellStateFullyImplicitBlackoil& xw,
const V& aliveWells)
{
const int np = wells_.number_of_phases;
const int nw = wells_.number_of_wells;
if( ! wellsActive() ) return;
const int np = wells().number_of_phases;
const int nw = wells().number_of_wells;
V bhp_targets = V::Zero(nw);
V rate_targets = V::Zero(nw);
M rate_distr(nw, np*nw);
for (int w = 0; w < nw; ++w) {
const WellControls* wc = wells_.ctrls[w];
const WellControls* wc = wells().ctrls[w];
// The current control in the well state overrides
// the current control set in the Wells struct, which
// is instead treated as a default.
@ -1254,6 +1275,17 @@ namespace {
struct Chop01 {
double operator()(double x) const { return std::max(std::min(x, 1.0), 0.0); }
};
double infinityNorm( const ADB& a )
{
if( a.value().size() > 0 ) {
return a.value().matrix().template lpNorm<Eigen::Infinity> ();
}
else { // this situation can occur when no wells are present
return 0.0;
}
}
}
@ -1268,7 +1300,7 @@ namespace {
using namespace Opm::AutoDiffGrid;
const int np = fluid_.numPhases();
const int nc = numCells(grid_);
const int nw = wells_.number_of_wells;
const int nw = wellsActive() ? wells().number_of_wells : 0;
const V null;
assert(null.size() == 0);
const V zero = V::Zero(nc);
@ -1497,21 +1529,24 @@ namespace {
std::copy(&rv[0], &rv[0] + nc, state.rv().begin());
}
// Qs update.
// Since we need to update the wellrates, that are ordered by wells,
// from dqs which are ordered by phase, the simplest is to compute
// dwr, which is the data from dqs but ordered by wells.
const DataBlock wwr = Eigen::Map<const DataBlock>(dqs.data(), np, nw).transpose();
const V dwr = Eigen::Map<const V>(wwr.data(), nw*np);
const V wr_old = Eigen::Map<const V>(&well_state.wellRates()[0], nw*np);
const V wr = wr_old - dwr;
std::copy(&wr[0], &wr[0] + wr.size(), well_state.wellRates().begin());
if( wellsActive() )
{
// Qs update.
// Since we need to update the wellrates, that are ordered by wells,
// from dqs which are ordered by phase, the simplest is to compute
// dwr, which is the data from dqs but ordered by wells.
const DataBlock wwr = Eigen::Map<const DataBlock>(dqs.data(), np, nw).transpose();
const V dwr = Eigen::Map<const V>(wwr.data(), nw*np);
const V wr_old = Eigen::Map<const V>(&well_state.wellRates()[0], nw*np);
const V wr = wr_old - dwr;
std::copy(&wr[0], &wr[0] + wr.size(), well_state.wellRates().begin());
// Bhp update.
const V bhp_old = Eigen::Map<const V>(&well_state.bhp()[0], nw, 1);
const V dbhp_limited = sign(dbhp) * dbhp.abs().min(bhp_old.abs()*dpmaxrel);
const V bhp = bhp_old - dbhp_limited;
std::copy(&bhp[0], &bhp[0] + bhp.size(), well_state.bhp().begin());
// Bhp update.
const V bhp_old = Eigen::Map<const V>(&well_state.bhp()[0], nw, 1);
const V dbhp_limited = sign(dbhp) * dbhp.abs().min(bhp_old.abs()*dpmaxrel);
const V bhp = bhp_old - dbhp_limited;
std::copy(&bhp[0], &bhp[0] + bhp.size(), well_state.bhp().begin());
}
// Update phase conditions used for property calculations.
updatePhaseCondFromPrimalVariable();
@ -1615,8 +1650,8 @@ namespace {
const DataBlock& well_s,
const std::vector<int>& well_cells) const
{
const int nw = wells_.number_of_wells;
const int nperf = wells_.well_connpos[nw];
const int nw = wells().number_of_wells;
const int nperf = wells().well_connpos[nw];
const std::vector<int>& bpat = state.pressure.blockPattern();
const ADB null = ADB::constant(V::Zero(nperf), bpat);
@ -1753,7 +1788,7 @@ namespace {
const std::vector<ADB>::const_iterator endMassBalanceIt = residual_.material_balance_eq.end();
for (; massBalanceIt != endMassBalanceIt; ++massBalanceIt) {
const double massBalanceResid = (*massBalanceIt).value().matrix().template lpNorm<Eigen::Infinity>();
const double massBalanceResid = infinityNorm( (*massBalanceIt) );
if (!std::isfinite(massBalanceResid)) {
OPM_THROW(Opm::NumericalProblem,
"Encountered a non-finite residual");
@ -1762,14 +1797,14 @@ namespace {
}
// the following residuals are not used in the oscillation detection now
const double wellFluxResid = residual_.well_flux_eq.value().matrix().template lpNorm<Eigen::Infinity>();
const double wellFluxResid = infinityNorm( residual_.well_flux_eq );
if (!std::isfinite(wellFluxResid)) {
OPM_THROW(Opm::NumericalProblem,
OPM_THROW(Opm::NumericalProblem,
"Encountered a non-finite residual");
}
residual.push_back(wellFluxResid);
const double wellResid = residual_.well_eq.value().matrix().template lpNorm<Eigen::Infinity>();
const double wellResid = infinityNorm( residual_.well_eq );
if (!std::isfinite(wellResid)) {
OPM_THROW(Opm::NumericalProblem,
"Encountered a non-finite residual");
@ -1917,16 +1952,15 @@ namespace {
const double mass_balance_residual_gas = fabs(BG_avg*RG_sum) * dt / pvSum;
bool converged_MB = (mass_balance_residual_water < tol_mb)
&& (mass_balance_residual_oil< tol_mb)
&& (mass_balance_residual_gas < tol_mb);
&& (mass_balance_residual_oil < tol_mb)
&& (mass_balance_residual_gas < tol_mb);
bool converged_CNV = (CNVW < tol_cnv) && (CNVO < tol_cnv) && (CNVG < tol_cnv);
double residualWellFlux = residual_.well_flux_eq.value().matrix().template lpNorm<Eigen::Infinity>();
double residualWell = residual_.well_eq.value().matrix().template lpNorm<Eigen::Infinity>();
const double residualWellFlux = infinityNorm( residual_.well_flux_eq );
const double residualWell = infinityNorm( residual_.well_eq );
bool converged_Well = (residualWellFlux < 1./Opm::unit::day) && (residualWell < Opm::unit::barsa);
bool converged = converged_MB && converged_CNV && converged_Well;
if (iteration == 0) {
@ -2241,7 +2275,7 @@ namespace {
// For oil only cells Rs is used as primal variable. For cells almost full of water
// the default primal variable (Sg) is used.
if (has_disgas_) {
if (has_disgas_) {
for (V::Index c = 0, e = sg.size(); c != e; ++c) {
if ( !watOnly[c] && hasOil[c] && !hasGas[c] ) {primalVariable_[c] = PrimalVariables::RS; }
}

View File

@ -142,17 +142,23 @@ namespace Opm
for (int phase = 0; phase < np; ++phase) {
eqs.push_back(residual.material_balance_eq[phase]);
}
eqs.push_back(residual.well_flux_eq);
eqs.push_back(residual.well_eq);
// Eliminate the well-related unknowns, and corresponding equations.
// check if wells are present
const bool hasWells = residual.well_flux_eq.size() > 0 ;
std::vector<ADB> elim_eqs;
elim_eqs.reserve(2);
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well flux unknowns.
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well bhp unknowns.
assert(int(eqs.size()) == np);
if( hasWells )
{
eqs.push_back(residual.well_flux_eq);
eqs.push_back(residual.well_eq);
// Eliminate the well-related unknowns, and corresponding equations.
elim_eqs.reserve(2);
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well flux unknowns.
elim_eqs.push_back(eqs[np]);
eqs = eliminateVariable(eqs, np); // Eliminate well bhp unknowns.
assert(int(eqs.size()) == np);
}
// Scale material balance equations.
const double matbalscale[3] = { 1.1169, 1.0031, 0.0031 }; // HACK hardcoded instead of computed.
@ -219,10 +225,13 @@ namespace Opm
// Copy solver output to dx.
std::copy(x.begin(), x.end(), dx.data());
// Compute full solution using the eliminated equations.
// Recovery in inverse order of elimination.
dx = recoverVariable(elim_eqs[1], dx, np);
dx = recoverVariable(elim_eqs[0], dx, np);
if( hasWells )
{
// Compute full solution using the eliminated equations.
// Recovery in inverse order of elimination.
dx = recoverVariable(elim_eqs[1], dx, np);
dx = recoverVariable(elim_eqs[0], dx, np);
}
return dx;
}

View File

@ -356,7 +356,7 @@ namespace Opm
// Run a multiple steps of the solver depending on the time step control.
solver_timer.start();
FullyImplicitBlackoilSolver<T> solver(solverParam, grid_, props_, geo_, rock_comp_props_, *wells, solver_, has_disgas_, has_vapoil_);
FullyImplicitBlackoilSolver<T> solver(solverParam, grid_, props_, geo_, rock_comp_props_, wells, solver_, has_disgas_, has_vapoil_);
if (!threshold_pressures_by_face_.empty()) {
solver.setThresholdPressures(threshold_pressures_by_face_);
}
@ -478,18 +478,20 @@ namespace Opm
}
inline std::vector<int>
resvProducers(const Wells& wells,
resvProducers(const Wells* wells,
const std::size_t step,
const WellMap& wmap)
{
std::vector<int> resv_prod;
for (int w = 0, nw = wells.number_of_wells; w < nw; ++w) {
if (is_resv_prod(wells, w) ||
((wells.name[w] != 0) &&
is_resv_prod(wmap, wells.name[w], step)))
{
resv_prod.push_back(w);
if( wells )
{
for (int w = 0, nw = wells->number_of_wells; w < nw; ++w) {
if (is_resv_prod(*wells, w) ||
((wells->name[w] != 0) &&
is_resv_prod(wmap, wells->name[w], step)))
{
resv_prod.push_back(w);
}
}
}
@ -541,8 +543,7 @@ namespace Opm
const std::vector<WellConstPtr>& w_ecl = eclipse_state_->getSchedule()->getWells(step);
const WellMap& wmap = SimFIBODetails::mapWells(w_ecl);
const std::vector<int>& resv_prod =
SimFIBODetails::resvProducers(*wells, step, wmap);
const std::vector<int>& resv_prod = SimFIBODetails::resvProducers(wells, step, wmap);
if (! resv_prod.empty()) {
const PhaseUsage& pu = props_.phaseUsage();

View File

@ -62,12 +62,14 @@ namespace Opm
return;
}
const int nw = wells->number_of_wells;
if( nw == 0 ) return ;
// We use the WellState::init() function to do bhp and well rates init.
// The alternative would be to copy that function wholesale.
BaseType :: init(wells, state);
// Initialize perfphaserates_, which must be done here.
const int nw = wells->number_of_wells;
const int np = wells->number_of_phases;
const int nperf = wells->well_connpos[nw];
perfphaserates_.resize(nperf * np, 0.0);