Moved files to opm/simulators/ subdirs.

This commit is contained in:
Atgeirr Flø Rasmussen
2019-05-07 13:06:02 +02:00
parent 589db89b09
commit c17adf788f
69 changed files with 153 additions and 153 deletions

View File

@@ -0,0 +1,432 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 - 2017 Statoil ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 IRIS AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILWELLMODEL_HEADER_INCLUDED
#define OPM_BLACKOILWELLMODEL_HEADER_INCLUDED
#include <ebos/eclproblem.hh>
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/utility/platform_dependent/disable_warnings.h>
#include <opm/common/utility/platform_dependent/reenable_warnings.h>
#include <cassert>
#include <tuple>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/WellTestState.hpp>
#include <opm/core/wells.h>
#include <opm/core/wells/WellCollection.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/wells/VFPInjProperties.hpp>
#include <opm/simulators/wells/VFPProdProperties.hpp>
#include <opm/autodiff/BlackoilDetails.hpp>
#include <opm/simulators/wells/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/RateConverter.hpp>
#include <opm/simulators/wells/WellInterface.hpp>
#include <opm/simulators/wells/StandardWell.hpp>
#include <opm/simulators/wells/StandardWellV.hpp>
#include <opm/simulators/wells/MultisegmentWell.hpp>
#include <opm/simulators/timestepping/gatherConvergenceReport.hpp>
#include <opm/autodiff/SimFIBODetails.hpp>
#include <dune/common/fmatrix.hh>
#include <dune/istl/bcrsmatrix.hh>
#include <dune/istl/matrixmatrix.hh>
#include <opm/material/densead/Math.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
BEGIN_PROPERTIES
NEW_PROP_TAG(EnableTerminalOutput);
END_PROPERTIES
namespace Opm {
/// Class for handling the blackoil well model.
template<typename TypeTag>
class BlackoilWellModel : public Ewoms::BaseAuxiliaryModule<TypeTag>
{
public:
// --------- Types ---------
typedef WellStateFullyImplicitBlackoil WellState;
typedef BlackoilModelParametersEbos<TypeTag> ModelParameters;
typedef typename GET_PROP_TYPE(TypeTag, Grid) Grid;
typedef typename GET_PROP_TYPE(TypeTag, FluidSystem) FluidSystem;
typedef typename GET_PROP_TYPE(TypeTag, ElementContext) ElementContext;
typedef typename GET_PROP_TYPE(TypeTag, Indices) Indices;
typedef typename GET_PROP_TYPE(TypeTag, Simulator) Simulator;
typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar;
typedef typename GET_PROP_TYPE(TypeTag, RateVector) RateVector;
typedef typename GET_PROP_TYPE(TypeTag, GlobalEqVector) GlobalEqVector;
typedef typename GET_PROP_TYPE(TypeTag, SparseMatrixAdapter) SparseMatrixAdapter;
typedef typename Ewoms::BaseAuxiliaryModule<TypeTag>::NeighborSet NeighborSet;
static const int numEq = Indices::numEq;
static const int solventSaturationIdx = Indices::solventSaturationIdx;
// TODO: where we should put these types, WellInterface or Well Model?
// or there is some other strategy, like TypeTag
typedef Dune::FieldVector<Scalar, numEq > VectorBlockType;
typedef Dune::BlockVector<VectorBlockType> BVector;
#if DUNE_VERSION_NEWER_REV(DUNE_ISTL, 2 , 5, 1)
// 3x3 matrix block inversion was unstable from at least 2.3 until and
// including 2.5.0
typedef Dune::FieldMatrix<Scalar, numEq, numEq > MatrixBlockType;
#else
typedef Dune::FieldMatrix<Scalar, numEq, numEq > MatrixBlockType;
#endif
typedef typename SparseMatrixAdapter::IstlMatrix Mat;
typedef Ewoms::BlackOilPolymerModule<TypeTag> PolymerModule;
// For the conversion between the surface volume rate and resrevoir voidage rate
using RateConverterType = RateConverter::
SurfaceToReservoirVoidage<FluidSystem, std::vector<int> >;
BlackoilWellModel(Simulator& ebosSimulator);
void init();
/////////////
// <eWoms auxiliary module stuff>
/////////////
unsigned numDofs() const
// No extra dofs are inserted for wells. (we use a Schur complement.)
{ return 0; }
void addNeighbors(std::vector<NeighborSet>& neighbors) const;
void applyInitial()
{}
void linearize(SparseMatrixAdapter& mat , GlobalEqVector& res);
void postSolve(GlobalEqVector& deltaX)
{
recoverWellSolutionAndUpdateWellState(deltaX);
}
/////////////
// </ eWoms auxiliary module stuff>
/////////////
template <class Restarter>
void deserialize(Restarter& /* res */)
{
// TODO (?)
}
/*!
* \brief This method writes the complete state of the well
* to the harddisk.
*/
template <class Restarter>
void serialize(Restarter& /* res*/)
{
// TODO (?)
}
void beginEpisode()
{
beginReportStep(ebosSimulator_.episodeIndex());
}
void beginTimeStep();
void beginIteration()
{
assemble(ebosSimulator_.model().newtonMethod().numIterations(),
ebosSimulator_.timeStepSize());
}
void endIteration()
{ }
void endTimeStep()
{
timeStepSucceeded(ebosSimulator_.time(), ebosSimulator_.timeStepSize());
}
void endEpisode()
{
endReportStep();
}
template <class Context>
void computeTotalRatesForDof(RateVector& rate,
const Context& context,
unsigned spaceIdx,
unsigned timeIdx) const;
using WellInterfacePtr = std::shared_ptr<WellInterface<TypeTag> >;
WellInterfacePtr well(const std::string& wellName) const;
void initFromRestartFile(const RestartValue& restartValues);
Opm::data::Wells wellData() const
{ return well_state_.report(phase_usage_, Opm::UgGridHelpers::globalCell(grid())); }
// substract Binv(D)rw from r;
void apply( BVector& r) const;
// subtract B*inv(D)*C * x from A*x
void apply(const BVector& x, BVector& Ax) const;
// apply well model with scaling of alpha
void applyScaleAdd(const Scalar alpha, const BVector& x, BVector& Ax) const;
// Check if well equations is converged.
ConvergenceReport getWellConvergence(const std::vector<Scalar>& B_avg) const;
// return all the wells.
const WellCollection& wellCollection() const;
// return non const reference to all the wells.
WellCollection& wellCollection();
// return the internal well state, ignore the passed one.
// Used by the legacy code to make it compatible with the legacy well models.
const WellState& wellState(const WellState& well_state OPM_UNUSED) const;
// return the internal well state
const WellState& wellState() const;
const SimulatorReport& lastReport() const;
void addWellContributions(Mat& mat) const
{
for ( const auto& well: well_container_ ) {
well->addWellContributions(mat);
}
}
// called at the beginning of a report step
void beginReportStep(const int time_step);
/// Return true if any well has a THP constraint.
bool hasTHPConstraints() const;
/// Shut down any single well, but only if it is in prediction mode.
/// Returns true if the well was actually found and shut.
bool forceShutWellByNameIfPredictionMode(const std::string& wellname, const double simulation_time);
protected:
void extractLegacyPressure_(std::vector<double>& cellPressure) const
{
size_t nc = number_of_cells_;
std::vector<double> cellPressures(nc, 0.0);
ElementContext elemCtx(ebosSimulator_);
const auto& gridView = ebosSimulator_.vanguard().gridView();
const auto& elemEndIt = gridView.template end</*codim=*/0>();
for (auto elemIt = gridView.template begin</*codim=*/0>();
elemIt != elemEndIt;
++elemIt)
{
const auto& elem = *elemIt;
if (elem.partitionType() != Dune::InteriorEntity) {
continue;
}
elemCtx.updatePrimaryStencil(elem);
elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
const unsigned cellIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
const auto& fs = intQuants.fluidState();
const double p = fs.pressure(FluidSystem::oilPhaseIdx).value();
cellPressures[cellIdx] = p;
}
}
Simulator& ebosSimulator_;
std::unique_ptr<WellsManager> wells_manager_;
std::vector< const Well* > wells_ecl_;
bool wells_active_;
// a vector of all the wells.
std::vector<WellInterfacePtr > well_container_;
// map from logically cartesian cell indices to compressed ones
std::vector<int> cartesian_to_compressed_;
std::vector<bool> is_cell_perforated_;
// create the well container
std::vector<WellInterfacePtr > createWellContainer(const int time_step, Opm::DeferredLogger& deferred_logger);
WellInterfacePtr createWellForWellTest(const std::string& well_name, const int report_step, Opm::DeferredLogger& deferred_logger) const;
WellState well_state_;
WellState previous_well_state_;
const ModelParameters param_;
bool terminal_output_;
bool has_solvent_;
bool has_polymer_;
std::vector<int> pvt_region_idx_;
PhaseUsage phase_usage_;
size_t global_nc_;
// the number of the cells in the local grid
size_t number_of_cells_;
double gravity_;
std::vector<double> depth_;
bool initial_step_;
std::unique_ptr<RateConverterType> rateConverter_;
std::unique_ptr<VFPProperties<VFPInjProperties,VFPProdProperties>> vfp_properties_;
SimulatorReport last_report_;
WellTestState wellTestState_;
// used to better efficiency of calcuation
mutable BVector scaleAddRes_;
const Wells* wells() const { return wells_manager_->c_wells(); }
const Grid& grid() const
{ return ebosSimulator_.vanguard().grid(); }
const EclipseState& eclState() const
{ return ebosSimulator_.vanguard().eclState(); }
const Schedule& schedule() const
{ return ebosSimulator_.vanguard().schedule(); }
// compute the well fluxes and assemble them in to the reservoir equations as source terms
// and in the well equations.
void assemble(const int iterationIdx,
const double dt);
// called at the end of a time step
void timeStepSucceeded(const double& simulationTime, const double dt);
// called at the end of a report step
void endReportStep();
// using the solution x to recover the solution xw for wells and applying
// xw to update Well State
void recoverWellSolutionAndUpdateWellState(const BVector& x);
void updateWellControls(Opm::DeferredLogger& deferred_logger);
void updateGroupControls(Opm::DeferredLogger& deferred_logger);
// setting the well_solutions_ based on well_state.
void updatePrimaryVariables(Opm::DeferredLogger& deferred_logger);
void setupCartesianToCompressed_(const int* global_cell, int number_of_cells);
void computeRepRadiusPerfLength(const Grid& grid, Opm::DeferredLogger& deferred_logger);
void computeAverageFormationFactor(std::vector<Scalar>& B_avg) const;
void applyVREPGroupControl();
void computeWellVoidageRates(std::vector<double>& well_voidage_rates,
std::vector<double>& voidage_conversion_coeffs) const;
// Calculating well potentials for each well
void computeWellPotentials(std::vector<double>& well_potentials, Opm::DeferredLogger& deferred_logger);
const std::vector<double>& wellPerfEfficiencyFactors() const;
void calculateEfficiencyFactors();
// it should be able to go to prepareTimeStep(), however, the updateWellControls() and initPrimaryVariablesEvaluation()
// makes it a little more difficult. unless we introduce if (iterationIdx != 0) to avoid doing the above functions
// twice at the beginning of the time step
/// Calculating the explict quantities used in the well calculation. By explicit, we mean they are cacluated
/// at the beginning of the time step and no derivatives are included in these quantities
void calculateExplicitQuantities(Opm::DeferredLogger& deferred_logger) const;
SimulatorReport solveWellEq(const std::vector<Scalar>& B_avg, const double dt, Opm::DeferredLogger& deferred_logger);
void initPrimaryVariablesEvaluation() const;
// The number of components in the model.
int numComponents() const;
int numWells() const;
int numPhases() const;
void resetWellControlFromState() const;
void assembleWellEq(const std::vector<Scalar>& B_avg, const double dt, Opm::DeferredLogger& deferred_logger);
// some preparation work, mostly related to group control and RESV,
// at the beginning of each time step (Not report step)
void prepareTimeStep(Opm::DeferredLogger& deferred_logger);
void prepareGroupControl(Opm::DeferredLogger& deferred_logger);
void computeRESV(const std::size_t step, Opm::DeferredLogger& deferred_logger);
void extractLegacyCellPvtRegionIndex_();
void extractLegacyDepth_();
/// return true if wells are available in the reservoir
bool wellsActive() const;
void setWellsActive(const bool wells_active);
/// return true if wells are available on this process
bool localWellsActive() const;
/// upate the wellTestState related to economic limits
void updateWellTestState(const double& simulationTime, WellTestState& wellTestState) const;
void updatePerforationIntensiveQuantities();
void wellTesting(const int timeStepIdx, const double simulationTime, Opm::DeferredLogger& deferred_logger);
// convert well data from opm-common to well state from opm-core
void wellsToState( const data::Wells& wells,
const PhaseUsage& phases,
const bool handle_ms_well,
const int report_step,
WellStateFullyImplicitBlackoil& state ) const;
// whether there exists any multisegment well open on this process
bool anyMSWellOpenLocal(const Wells* wells, const int report_step) const;
const Well* getWellEcl(const std::string& well_name) const;
};
} // namespace Opm
#include "BlackoilWellModel_impl.hpp"
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
/*
Copyright 2017 SINTEF Digital, Mathematics and Cybernetics.
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_MSWELLHELPERS_HEADER_INCLUDED
#define OPM_MSWELLHELPERS_HEADER_INCLUDED
#include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <dune/istl/solvers.hh>
#if HAVE_UMFPACK
#include <dune/istl/umfpack.hh>
#endif // HAVE_UMFPACK
#include <cmath>
namespace Opm {
namespace mswellhelpers
{
// obtain y = D^-1 * x with a direct solver
template <typename MatrixType, typename VectorType>
VectorType
invDXDirect(const MatrixType& D, VectorType x)
{
#if HAVE_UMFPACK
VectorType y(x.size());
y = 0.;
Dune::UMFPack<MatrixType> linsolver(D, 0);
// Object storing some statistics about the solving process
Dune::InverseOperatorResult res;
// Solve
linsolver.apply(y, x, res);
// Checking if there is any inf or nan in y
// it will be the solution before we find a way to catch the singularity of the matrix
for (size_t i_block = 0; i_block < y.size(); ++i_block) {
for (size_t i_elem = 0; i_elem < y[i_block].size(); ++i_elem) {
if (std::isinf(y[i_block][i_elem]) || std::isnan(y[i_block][i_elem]) ) {
OPM_THROW(Opm::NumericalIssue, "nan or inf value found in invDXDirect due to singular matrix");
}
}
}
return y;
#else
// this is not thread safe
OPM_THROW(std::runtime_error, "Cannot use invDXDirect() without UMFPACK. "
"Reconfigure opm-simulator with SuiteSparse/UMFPACK support and recompile.");
#endif // HAVE_UMFPACK
}
// obtain y = D^-1 * x with a BICSSTAB iterative solver
template <typename MatrixType, typename VectorType>
VectorType
invDX(const MatrixType& D, VectorType x, Opm::DeferredLogger& deferred_logger)
{
// the function will change the value of x, so we should not use reference of x here.
// TODO: store some of the following information to avoid to call it again and again for
// efficiency improvement.
// Bassically, only the solve / apply step is different.
VectorType y(x.size());
y = 0.;
Dune::MatrixAdapter<MatrixType, VectorType, VectorType> linearOperator(D);
// Sequential incomplete LU decomposition as the preconditioner
Dune::SeqILU0<MatrixType, VectorType, VectorType> preconditioner(D, 1.0);
// Dune::SeqILUn<MatrixType, VectorType, VectorType> preconditioner(D, 1, 0.92);
// Dune::SeqGS<MatrixType, VectorType, VectorType> preconditioner(D, 1, 1);
// Dune::SeqJac<MatrixType, VectorType, VectorType> preconditioner(D, 1, 1);
// Preconditioned BICGSTAB solver
Dune::BiCGSTABSolver<VectorType> linsolver(linearOperator,
preconditioner,
1.e-8, // desired residual reduction factor
250, // maximum number of iterations
0); // verbosity of the solver */
// Object storing some statistics about the solving process
Dune::InverseOperatorResult res;
// Solve
linsolver.apply(y, x, res);
if ( !res.converged ) {
OPM_DEFLOG_THROW(Opm::NumericalIssue, "the invDX does not get converged! ", deferred_logger);
}
return y;
}
inline double haalandFormular(const double re, const double diameter, const double roughness)
{
const double value = -3.6 * std::log10(6.9 / re + std::pow(roughness / (3.7 * diameter), 10. / 9.) );
// sqrt(1/f) should be non-positive
assert(value >= 0.0);
return 1. / (value * value);
}
inline double calculateFrictionFactor(const double area, const double diameter,
const double w, const double roughness, const double mu)
{
double f = 0.;
// Reynolds number
const double re = std::abs(diameter * w / (area * mu));
if ( re == 0.0 ) {
// make sure it is because the mass rate is zero
assert(w == 0.);
return 0.0;
}
const double re_value1 = 200.;
const double re_value2 = 4000.;
if (re < re_value1) {
f = 16. / re;
} else if (re > re_value2){
f = haalandFormular(re, diameter, roughness);
} else { // in between
const double f1 = 16. / re_value1;
const double f2 = haalandFormular(re_value2, diameter, roughness);
f = (f2 - f1) / (re_value2 - re_value1) * (re - re_value1) + f1;
}
return f;
}
// calculating the friction pressure loss
// l is the segment length
// area is the segment cross area
// diameter is the segment inner diameter
// w is mass flow rate through the segment
// density is density
// roughness is the absolute roughness
// mu is the average phase viscosity
template <typename ValueType>
ValueType frictionPressureLoss(const double l, const double diameter, const double area, const double roughness,
const ValueType& density, const ValueType& w, const ValueType& mu)
{
const double f = calculateFrictionFactor(area, diameter, w.value(), roughness, mu.value());
// \Note: a factor of 2 needs to be here based on the dimensional analysis
return 2. * f * l * w * w / (area * area * diameter * density);
}
template <typename ValueType>
ValueType velocityHead(const double area, const ValueType& mass_rate, const ValueType& density)
{
return (0.5 * mass_rate * mass_rate / (area * area * density));
}
} // namespace mswellhelpers
}
#endif

View File

@@ -0,0 +1,390 @@
/*
Copyright 2017 SINTEF Digital, Mathematics and Cybernetics.
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_MULTISEGMENTWELL_HEADER_INCLUDED
#define OPM_MULTISEGMENTWELL_HEADER_INCLUDED
#include <opm/simulators/wells/WellInterface.hpp>
namespace Opm
{
template<typename TypeTag>
class MultisegmentWell: public WellInterface<TypeTag>
{
public:
typedef WellInterface<TypeTag> Base;
using typename Base::WellState;
using typename Base::Simulator;
using typename Base::IntensiveQuantities;
using typename Base::FluidSystem;
using typename Base::ModelParameters;
using typename Base::MaterialLaw;
using typename Base::Indices;
using typename Base::RateConverterType;
/// the number of reservior equations
using Base::numEq;
using Base::has_solvent;
using Base::has_polymer;
using Base::Water;
using Base::Oil;
using Base::Gas;
// TODO: for now, not considering the polymer, solvent and so on to simplify the development process.
// TODO: we need to have order for the primary variables and also the order for the well equations.
// sometimes, they are similar, while sometimes, they can have very different forms.
// TODO: the following system looks not rather flexible. Looking into all kinds of possibilities
// TODO: gas is always there? how about oil water case?
// Is it gas oil two phase case?
static const bool gasoil = numEq == 2 && (Indices::compositionSwitchIdx >= 0);
static const int GTotal = 0;
static const int WFrac = gasoil? -1000: 1;
static const int GFrac = gasoil? 1 : 2;
static const int SPres = gasoil? 2 : 3;
/// the number of well equations // TODO: it should have a more general strategy for it
static const int numWellEq = GET_PROP_VALUE(TypeTag, EnablePolymer)? numEq : numEq + 1;
using typename Base::Scalar;
/// the matrix and vector types for the reservoir
using typename Base::Mat;
using typename Base::BVector;
using typename Base::Eval;
// sparsity pattern for the matrices
// [A C^T [x = [ res
// B D ] x_well] res_well]
// the vector type for the res_well and x_well
typedef Dune::FieldVector<Scalar, numWellEq> VectorBlockWellType;
typedef Dune::BlockVector<VectorBlockWellType> BVectorWell;
// the matrix type for the diagonal matrix D
typedef Dune::FieldMatrix<Scalar, numWellEq, numWellEq > DiagMatrixBlockWellType;
typedef Dune::BCRSMatrix <DiagMatrixBlockWellType> DiagMatWell;
// the matrix type for the non-diagonal matrix B and C^T
typedef Dune::FieldMatrix<Scalar, numWellEq, numEq> OffDiagMatrixBlockWellType;
typedef Dune::BCRSMatrix<OffDiagMatrixBlockWellType> OffDiagMatWell;
// TODO: for more efficient implementation, we should have EvalReservoir, EvalWell, and EvalRerservoirAndWell
// EvalR (Eval), EvalW, EvalRW
// TODO: for now, we only use one type to save some implementation efforts, while improve later.
typedef DenseAd::Evaluation<double, /*size=*/numEq + numWellEq> EvalWell;
MultisegmentWell(const Well* well, const int time_step, const Wells* wells,
const ModelParameters& param,
const RateConverterType& rate_converter,
const int pvtRegionIdx,
const int num_components);
virtual void init(const PhaseUsage* phase_usage_arg,
const std::vector<double>& depth_arg,
const double gravity_arg,
const int num_cells) override;
virtual void initPrimaryVariablesEvaluation() const override;
virtual void assembleWellEq(const Simulator& ebosSimulator,
const std::vector<Scalar>& B_avg,
const double dt,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) override;
/// updating the well state based the current control mode
virtual void updateWellStateWithTarget(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const override;
/// check whether the well equations get converged for this well
virtual ConvergenceReport getWellConvergence(const std::vector<double>& B_avg, Opm::DeferredLogger& deferred_logger) const override;
/// Ax = Ax - C D^-1 B x
virtual void apply(const BVector& x, BVector& Ax) const override;
/// r = r - C D^-1 Rw
virtual void apply(BVector& r) const override;
/// using the solution x to recover the solution xw for wells and applying
/// xw to update Well State
virtual void recoverWellSolutionAndUpdateWellState(const BVector& x,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const override;
/// computing the well potentials for group control
virtual void computeWellPotentials(const Simulator& ebosSimulator,
const WellState& well_state,
std::vector<double>& well_potentials,
Opm::DeferredLogger& deferred_logger) override;
virtual void updatePrimaryVariables(const WellState& well_state, Opm::DeferredLogger& deferred_logger) const override;
virtual void solveEqAndUpdateWellState(WellState& well_state, Opm::DeferredLogger& deferred_logger) override; // const?
virtual void calculateExplicitQuantities(const Simulator& ebosSimulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) override; // should be const?
/// number of segments for this well
/// int number_of_segments_;
int numberOfSegments() const;
int numberOfPerforations() const;
protected:
int number_segments_;
// components of the pressure drop to be included
WellSegment::CompPressureDropEnum compPressureDrop() const;
// multi-phase flow model
WellSegment::MultiPhaseModelEnum multiphaseModel() const;
// get the WellSegments from the well_ecl_
const WellSegments& segmentSet() const;
// protected member variables from the Base class
using Base::well_ecl_;
using Base::number_of_perforations_; // TODO: can use well_ecl_?
using Base::current_step_;
using Base::index_of_well_;
using Base::number_of_phases_;
// TODO: the current implementation really relies on the order of the
// perforation does not change from the parser to Wells structure.
using Base::well_cells_;
using Base::param_;
using Base::well_index_;
using Base::well_type_;
using Base::first_perf_;
using Base::saturation_table_number_;
using Base::well_efficiency_factor_;
using Base::gravity_;
using Base::well_controls_;
using Base::perf_depth_;
using Base::num_components_;
using Base::connectionRates_;
// protected functions from the Base class
using Base::phaseUsage;
using Base::name;
using Base::flowPhaseToEbosCompIdx;
using Base::ebosCompIdxToFlowCompIdx;
using Base::getAllowCrossFlow;
using Base::scalingFactor;
// TODO: trying to use the information from the Well opm-parser as much
// as possible, it will possibly be re-implemented later for efficiency reason.
// the completions that is related to each segment
// the completions's ids are their index in the vector well_index_, well_cell_
// This is also assuming the order of the completions in Well is the same with
// the order of the completions in wells.
// it is for convinience reason. we can just calcuate the inforation for segment once then using it for all the perofrations
// belonging to this segment
std::vector<std::vector<int> > segment_perforations_;
// the inlet segments for each segment. It is for convinience and efficiency reason
std::vector<std::vector<int> > segment_inlets_;
// segment number is an ID of the segment, it is specified in the deck
// get the loation of the segment with a segment number in the segmentSet
int segmentNumberToIndex(const int segment_number) const;
// TODO, the following should go to a class for computing purpose
// two off-diagonal matrices
mutable OffDiagMatWell duneB_;
mutable OffDiagMatWell duneC_;
// diagonal matrix for the well
mutable DiagMatWell duneD_;
// residuals of the well equations
mutable BVectorWell resWell_;
// the values for the primary varibles
// based on different solutioin strategies, the wells can have different primary variables
mutable std::vector<std::array<double, numWellEq> > primary_variables_;
// the Evaluation for the well primary variables, which contain derivativles and are used in AD calculation
mutable std::vector<std::array<EvalWell, numWellEq> > primary_variables_evaluation_;
// depth difference between perforations and the perforated grid cells
std::vector<double> cell_perforation_depth_diffs_;
// pressure correction due to the different depth of the perforation and
// center depth of the grid block
std::vector<double> cell_perforation_pressure_diffs_;
// depth difference between the segment and the peforation
// or in another way, the depth difference between the perforation and
// the segment the perforation belongs to
std::vector<double> perforation_segment_depth_diffs_;
// the intial amount of fluids in each segment under surface condition
std::vector<std::vector<double> > segment_fluid_initial_;
// the densities of segment fluids
// we should not have this member variable
std::vector<EvalWell> segment_densities_;
// the viscosity of the segments
std::vector<EvalWell> segment_viscosities_;
// the mass rate of the segments
std::vector<EvalWell> segment_mass_rates_;
std::vector<double> segment_depth_diffs_;
void initMatrixAndVectors(const int num_cells) const;
// protected functions
// EvalWell getBhp(); this one should be something similar to getSegmentPressure();
// EvalWell getQs(); this one should be something similar to getSegmentRates()
// EValWell wellVolumeFractionScaled, wellVolumeFraction, wellSurfaceVolumeFraction ... these should have different names, and probably will be needed.
// bool crossFlowAllowed(const Simulator& ebosSimulator) const; probably will be needed
// xw = inv(D)*(rw - C*x)
void recoverSolutionWell(const BVector& x, BVectorWell& xw) const;
// updating the well_state based on well solution dwells
void updateWellState(const BVectorWell& dwells,
WellState& well_state,
Opm::DeferredLogger& deferred_logger,
const double relaxation_factor=1.0) const;
// initialize the segment rates with well rates
// when there is no more accurate way to initialize the segment rates, we initialize
// the segment rates based on well rates with a simple strategy
void initSegmentRatesWithWellRates(WellState& well_state) const;
// computing the accumulation term for later use in well mass equations
void computeInitialSegmentFluids(const Simulator& ebos_simulator);
// compute the pressure difference between the perforation and cell center
void computePerfCellPressDiffs(const Simulator& ebosSimulator);
// fraction value of the primary variables
// should we just use member variables to store them instead of calculating them again and again
EvalWell volumeFraction(const int seg, const unsigned comp_idx) const;
// F_p / g_p, the basic usage of this value is because Q_p = G_t * F_p / G_p
EvalWell volumeFractionScaled(const int seg, const int comp_idx) const;
// basically Q_p / \sigma_p Q_p
EvalWell surfaceVolumeFraction(const int seg, const int comp_idx) const;
void computePerfRatePressure(const IntensiveQuantities& int_quants,
const std::vector<EvalWell>& mob_perfcells,
const int seg,
const int perf,
const EvalWell& segment_pressure,
const bool& allow_cf,
std::vector<EvalWell>& cq_s,
EvalWell& perf_press,
Opm::DeferredLogger& deferred_logger) const;
// convert a Eval from reservoir to contain the derivative related to wells
EvalWell extendEval(const Eval& in) const;
// compute the fluid properties, such as densities, viscosities, and so on, in the segments
// They will be treated implicitly, so they need to be of Evaluation type
void computeSegmentFluidProperties(const Simulator& ebosSimulator);
EvalWell getSegmentPressure(const int seg) const;
EvalWell getSegmentRate(const int seg, const int comp_idx) const;
EvalWell getSegmentRateUpwinding(const int seg, const int comp_idx, const bool upwinding, int& seg_upwind) const;
EvalWell getSegmentGTotal(const int seg) const;
// get the mobility for specific perforation
void getMobility(const Simulator& ebosSimulator,
const int perf,
std::vector<EvalWell>& mob) const;
void assembleControlEq(Opm::DeferredLogger& deferred_logger) const;
void assemblePressureEq(const int seg) const;
// hytrostatic pressure loss
EvalWell getHydroPressureLoss(const int seg) const;
// frictinal pressure loss
EvalWell getFrictionPressureLoss(const int seg) const;
void handleAccelerationPressureLoss(const int seg) const;
// handling the overshooting and undershooting of the fractions
void processFractions(const int seg) const;
// checking the operability of the well based on current reservoir condition
// it is not implemented for multisegment well yet
virtual void checkWellOperability(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) override;
void updateWellStateFromPrimaryVariables(WellState& well_state) const;
bool frictionalPressureLossConsidered() const;
bool accelerationalPressureLossConsidered() const;
// TODO: try to make ebosSimulator const, as it should be
void iterateWellEquations(const Simulator& ebosSimulator,
const std::vector<Scalar>& B_avg,
const double dt,
WellState& well_state,
Opm::DeferredLogger& deferred_logger);
void assembleWellEqWithoutIteration(const Simulator& ebosSimulator,
const double dt,
WellState& well_state,
Opm::DeferredLogger& deferred_logger);
virtual void wellTestingPhysical(Simulator& simulator, const std::vector<double>& B_avg,
const double simulation_time, const int report_step,
WellState& well_state, WellTestState& welltest_state, Opm::DeferredLogger& deferred_logger) override;
virtual void updateWaterThroughput(const double dt, WellState& well_state) const override;
EvalWell getSegmentSurfaceVolume(const Simulator& ebos_simulator, const int seg_idx) const;
std::vector<Scalar> getWellResiduals(const std::vector<Scalar>& B_avg) const;
void detectOscillations(const std::vector<double>& measure_history,
const int it, bool& oscillate, bool& stagnate) const;
double getResidualMeasureValue(const std::vector<double>& residuals) const;
};
}
#include "MultisegmentWell_impl.hpp"
#endif // OPM_MULTISEGMENTWELL_HEADER_INCLUDED

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,448 @@
/*
Copyright 2017 SINTEF Digital, Mathematics and Cybernetics.
Copyright 2017 Statoil ASA.
Copyright 2016 - 2017 IRIS AS.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_STANDARDWELL_HEADER_INCLUDED
#define OPM_STANDARDWELL_HEADER_INCLUDED
#include <opm/simulators/wells/WellInterface.hpp>
#include <opm/simulators/linalg/ISTLSolverEbos.hpp>
#include <opm/autodiff/RateConverter.hpp>
namespace Opm
{
template<typename TypeTag>
class StandardWell: public WellInterface<TypeTag>
{
public:
typedef WellInterface<TypeTag> Base;
// TODO: some functions working with AD variables handles only with values (double) without
// dealing with derivatives. It can be beneficial to make functions can work with either AD or scalar value.
// And also, it can also be beneficial to make these functions hanle different types of AD variables.
using typename Base::Simulator;
using typename Base::WellState;
using typename Base::IntensiveQuantities;
using typename Base::FluidSystem;
using typename Base::MaterialLaw;
using typename Base::ModelParameters;
using typename Base::Indices;
using typename Base::PolymerModule;
using typename Base::RateConverterType;
using Base::numEq;
using Base::has_solvent;
using Base::has_polymer;
using Base::has_energy;
// polymer concentration and temperature are already known by the well, so
// polymer and energy conservation do not need to be considered explicitly
static const int numPolymerEq = Indices::numPolymers;
static const int numEnergyEq = Indices::numEnergy;
// number of the conservation equations
static const int numWellConservationEq = numEq - numPolymerEq - numEnergyEq;
// number of the well control equations
static const int numWellControlEq = 1;
// number of the well equations that will always be used
// based on the solution strategy, there might be other well equations be introduced
static const int numStaticWellEq = numWellConservationEq + numWellControlEq;
// the positions of the primary variables for StandardWell
// the first one is the weighted total rate (WQ_t), the second and the third ones are F_w and F_g,
// which represent the fraction of Water and Gas based on the weighted total rate, the last one is BHP.
// correspondingly, we have four well equations for blackoil model, the first three are mass
// converstation equations, and the last one is the well control equation.
// primary variables related to other components, will be before the Bhp and after F_g.
// well control equation is always the last well equation.
// TODO: in the current implementation, we use the well rate as the first primary variables for injectors,
// instead of G_t.
static const bool gasoil = numEq == 2 && (Indices::compositionSwitchIdx >= 0);
static const int WQTotal = 0;
static const int WFrac = gasoil? -1000: 1;
static const int GFrac = gasoil? 1: 2;
static const int SFrac = !has_solvent ? -1000 : 3;
// the index for Bhp in primary variables and also the index of well control equation
// they both will be the last one in their respective system.
// TODO: we should have indices for the well equations and well primary variables separately
static const int Bhp = numStaticWellEq - numWellControlEq;
// total number of the well equations and primary variables
// for StandardWell, no extra well equations will be used.
static const int numWellEq = numStaticWellEq;
using typename Base::Scalar;
using Base::name;
using Base::Water;
using Base::Oil;
using Base::Gas;
using typename Base::Mat;
using typename Base::BVector;
using typename Base::Eval;
// sparsity pattern for the matrices
//[A C^T [x = [ res
// B D ] x_well] res_well]
// the vector type for the res_well and x_well
typedef Dune::FieldVector<Scalar, numWellEq> VectorBlockWellType;
typedef Dune::BlockVector<VectorBlockWellType> BVectorWell;
// the matrix type for the diagonal matrix D
typedef Dune::FieldMatrix<Scalar, numWellEq, numWellEq > DiagMatrixBlockWellType;
typedef Dune::BCRSMatrix <DiagMatrixBlockWellType> DiagMatWell;
// the matrix type for the non-diagonal matrix B and C^T
typedef Dune::FieldMatrix<Scalar, numWellEq, numEq> OffDiagMatrixBlockWellType;
typedef Dune::BCRSMatrix<OffDiagMatrixBlockWellType> OffDiagMatWell;
typedef DenseAd::Evaluation<double, /*size=*/numEq + numWellEq> EvalWell;
using Base::contiSolventEqIdx;
using Base::contiPolymerEqIdx;
static const int contiEnergyEqIdx = Indices::contiEnergyEqIdx;
StandardWell(const Well* well, const int time_step, const Wells* wells,
const ModelParameters& param,
const RateConverterType& rate_converter,
const int pvtRegionIdx,
const int num_components);
virtual void init(const PhaseUsage* phase_usage_arg,
const std::vector<double>& depth_arg,
const double gravity_arg,
const int num_cells) override;
virtual void initPrimaryVariablesEvaluation() const override;
virtual void assembleWellEq(const Simulator& ebosSimulator,
const std::vector<Scalar>& B_avg,
const double dt,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) override;
virtual void updateWellStateWithTarget(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const override;
/// check whether the well equations get converged for this well
virtual ConvergenceReport getWellConvergence(const std::vector<double>& B_avg,
Opm::DeferredLogger& deferred_logger) const override;
/// Ax = Ax - C D^-1 B x
virtual void apply(const BVector& x, BVector& Ax) const override;
/// r = r - C D^-1 Rw
virtual void apply(BVector& r) const override;
/// using the solution x to recover the solution xw for wells and applying
/// xw to update Well State
virtual void recoverWellSolutionAndUpdateWellState(const BVector& x,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const override;
/// computing the well potentials for group control
virtual void computeWellPotentials(const Simulator& ebosSimulator,
const WellState& well_state,
std::vector<double>& well_potentials,
Opm::DeferredLogger& deferred_logger) /* const */ override;
virtual void updatePrimaryVariables(const WellState& well_state, Opm::DeferredLogger& deferred_logger) const override;
virtual void solveEqAndUpdateWellState(WellState& well_state, Opm::DeferredLogger& deferred_logger) override;
virtual void calculateExplicitQuantities(const Simulator& ebosSimulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) override; // should be const?
virtual void addWellContributions(Mat& mat) const override;
/// \brief Wether the Jacobian will also have well contributions in it.
virtual bool jacobianContainsWellContributions() const override
{
return param_.matrix_add_well_contributions_;
}
protected:
// protected functions from the Base class
using Base::getAllowCrossFlow;
using Base::phaseUsage;
using Base::flowPhaseToEbosCompIdx;
using Base::ebosCompIdxToFlowCompIdx;
using Base::wsolvent;
using Base::wpolymer;
using Base::wellHasTHPConstraints;
using Base::mostStrictBhpFromBhpLimits;
using Base::scalingFactor;
using Base::scaleProductivityIndex;
// protected member variables from the Base class
using Base::current_step_;
using Base::well_ecl_;
using Base::vfp_properties_;
using Base::gravity_;
using Base::param_;
using Base::well_efficiency_factor_;
using Base::first_perf_;
using Base::ref_depth_;
using Base::perf_depth_;
using Base::well_cells_;
using Base::number_of_perforations_;
using Base::number_of_phases_;
using Base::saturation_table_number_;
using Base::comp_frac_;
using Base::well_index_;
using Base::index_of_well_;
using Base::well_controls_;
using Base::well_type_;
using Base::num_components_;
using Base::connectionRates_;
using Base::perf_rep_radius_;
using Base::perf_length_;
using Base::bore_diameters_;
// densities of the fluid in each perforation
std::vector<double> perf_densities_;
// pressure drop between different perforations
std::vector<double> perf_pressure_diffs_;
// residuals of the well equations
BVectorWell resWell_;
// two off-diagonal matrices
OffDiagMatWell duneB_;
OffDiagMatWell duneC_;
// diagonal matrix for the well
DiagMatWell invDuneD_;
// several vector used in the matrix calculation
mutable BVectorWell Bx_;
mutable BVectorWell invDrw_;
// the values for the primary varibles
// based on different solutioin strategies, the wells can have different primary variables
mutable std::vector<double> primary_variables_;
// the Evaluation for the well primary variables, which contain derivativles and are used in AD calculation
mutable std::vector<EvalWell> primary_variables_evaluation_;
// the saturations in the well bore under surface conditions at the beginning of the time step
std::vector<double> F0_;
// the vectors used to describe the inflow performance relationship (IPR)
// Q = IPR_A - BHP * IPR_B
// TODO: it minght need to go to WellInterface, let us implement it in StandardWell first
// it is only updated and used for producers for now
mutable std::vector<double> ipr_a_;
mutable std::vector<double> ipr_b_;
const EvalWell& getBhp() const;
EvalWell getQs(const int comp_idx) const;
const EvalWell& getWQTotal() const;
EvalWell wellVolumeFractionScaled(const int phase) const;
EvalWell wellVolumeFraction(const unsigned compIdx) const;
EvalWell wellSurfaceVolumeFraction(const int phase) const;
EvalWell extendEval(const Eval& in) const;
// xw = inv(D)*(rw - C*x)
void recoverSolutionWell(const BVector& x, BVectorWell& xw) const;
// updating the well_state based on well solution dwells
void updateWellState(const BVectorWell& dwells,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
// calculate the properties for the well connections
// to calulate the pressure difference between well connections.
void computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator,
const WellState& well_state,
std::vector<double>& b_perf,
std::vector<double>& rsmax_perf,
std::vector<double>& rvmax_perf,
std::vector<double>& surf_dens_perf) const;
// TODO: not total sure whether it is a good idea to put this function here
// the major reason to put here is to avoid the usage of Wells struct
void computeConnectionDensities(const std::vector<double>& perfComponentRates,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf);
void computeConnectionPressureDelta();
void computeWellConnectionDensitesPressures(const WellState& well_state,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf);
// computing the accumulation term for later use in well mass equations
void computeAccumWell();
void computeWellConnectionPressures(const Simulator& ebosSimulator,
const WellState& well_state);
void computePerfRate(const IntensiveQuantities& intQuants,
const std::vector<EvalWell>& mob,
const EvalWell& bhp,
const double Tw,
const int perf,
const bool allow_cf,
std::vector<EvalWell>& cq_s,
double& perf_dis_gas_rate,
double& perf_vap_oil_rate,
Opm::DeferredLogger& deferred_logger) const;
// TODO: maybe we should provide a light version of computePerfRate, which does not include the
// calculation of the derivatives
void computeWellRatesWithBhp(const Simulator& ebosSimulator,
const EvalWell& bhp,
std::vector<double>& well_flux,
Opm::DeferredLogger& deferred_logger) const;
std::vector<double> computeWellPotentialWithTHP(const Simulator& ebosSimulator,
const double initial_bhp, // bhp from BHP constraints
const std::vector<double>& initial_potential,
Opm::DeferredLogger& deferred_logger) const;
template <class ValueType>
ValueType calculateBhpFromThp(const std::vector<ValueType>& rates, const int control_index, Opm::DeferredLogger& deferred_logger) const;
double calculateThpFromBhp(const std::vector<double>& rates, const double bhp, Opm::DeferredLogger& deferred_logger) const;
// get the mobility for specific perforation
void getMobility(const Simulator& ebosSimulator,
const int perf,
std::vector<EvalWell>& mob,
Opm::DeferredLogger& deferred_logger) const;
void updateWaterMobilityWithPolymer(const Simulator& ebos_simulator,
const int perf,
std::vector<EvalWell>& mob_water,
Opm::DeferredLogger& deferred_logger) const;
void updatePrimaryVariablesNewton(const BVectorWell& dwells,
const WellState& well_state) const;
void updateWellStateFromPrimaryVariables(WellState& well_state, Opm::DeferredLogger& deferred_logger) const;
void updateThp(WellState& well_state, Opm::DeferredLogger& deferred_logger) const;
void assembleControlEq(Opm::DeferredLogger& deferred_logger);
// handle the non reasonable fractions due to numerical overshoot
void processFractions() const;
// updating the inflow based on the current reservoir condition
void updateIPR(const Simulator& ebos_simulator, Opm::DeferredLogger& deferred_logger) const;
// update the operability status of the well is operable under the current reservoir condition
// mostly related to BHP limit and THP limit
virtual void checkWellOperability(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger
) override;
// check whether the well is operable under the current reservoir condition
// mostly related to BHP limit and THP limit
void updateWellOperability(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger
);
// check whether the well is operable under BHP limit with current reservoir condition
void checkOperabilityUnderBHPLimitProducer(const Simulator& ebos_simulator, Opm::DeferredLogger& deferred_logger);
// check whether the well is operable under THP limit with current reservoir condition
void checkOperabilityUnderTHPLimitProducer(const Simulator& ebos_simulator, Opm::DeferredLogger& deferred_logger);
// update WellState based on IPR and associated VFP table
void updateWellStateWithTHPTargetIPR(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
void updateWellStateWithTHPTargetIPRProducer(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
// for a well, when all drawdown are in the wrong direction, then this well will not
// be able to produce/inject .
bool allDrawDownWrongDirection(const Simulator& ebos_simulator) const;
// whether the well can produce / inject based on the current well state (bhp)
bool canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger);
// turn on crossflow to avoid singular well equations
// when the well is banned from cross-flow and the BHP is not properly initialized,
// we turn on crossflow to avoid singular well equations. It can result in wrong-signed
// well rates, it can cause problem for THP calculation
// TODO: looking for better alternative to avoid wrong-signed well rates
bool openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const;
// calculate the BHP from THP target based on IPR
// TODO: we need to check the operablility here first, if not operable, then maybe there is
// no point to do this
double calculateBHPWithTHPTargetIPR(Opm::DeferredLogger& deferred_logger) const;
// relaxation factor considering only one fraction value
static double relaxationFactorFraction(const double old_value,
const double dx);
// calculate a relaxation factor to avoid overshoot of the fractions for producers
// which might result in negative rates
static double relaxationFactorFractionsProducer(const std::vector<double>& primary_variables,
const BVectorWell& dwells);
// calculate a relaxation factor to avoid overshoot of total rates
static double relaxationFactorRate(const std::vector<double>& primary_variables,
const BVectorWell& dwells);
virtual void wellTestingPhysical(Simulator& simulator, const std::vector<double>& B_avg,
const double simulation_time, const int report_step,
WellState& well_state, WellTestState& welltest_state,
Opm::DeferredLogger& deferred_logger) override;
virtual void updateWaterThroughput(const double dt, WellState& well_state) const override;
};
}
#include "StandardWell_impl.hpp"
#endif // OPM_STANDARDWELL_HEADER_INCLUDED

View File

@@ -0,0 +1,474 @@
/*
Copyright 2017 SINTEF Digital, Mathematics and Cybernetics.
Copyright 2017 Statoil ASA.
Copyright 2016 - 2017 IRIS AS.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_STANDARDWELLV_HEADER_INCLUDED
#define OPM_STANDARDWELLV_HEADER_INCLUDED
#include <opm/simulators/wells/WellInterface.hpp>
#include <opm/simulators/linalg/ISTLSolverEbos.hpp>
#include <opm/autodiff/RateConverter.hpp>
#include <opm/material/densead/DynamicEvaluation.hpp>
#include <dune/common/dynvector.hh>
#include <dune/common/dynmatrix.hh>
namespace Opm
{
template<typename TypeTag>
class StandardWellV: public WellInterface<TypeTag>
{
public:
typedef WellInterface<TypeTag> Base;
// TODO: some functions working with AD variables handles only with values (double) without
// dealing with derivatives. It can be beneficial to make functions can work with either AD or scalar value.
// And also, it can also be beneficial to make these functions hanle different types of AD variables.
using typename Base::Simulator;
using typename Base::WellState;
using typename Base::IntensiveQuantities;
using typename Base::FluidSystem;
using typename Base::MaterialLaw;
using typename Base::ModelParameters;
using typename Base::Indices;
using typename Base::PolymerModule;
using typename Base::RateConverterType;
using Base::numEq;
using Base::has_solvent;
using Base::has_polymer;
using Base::has_energy;
// polymer concentration and temperature are already known by the well, so
// polymer and energy conservation do not need to be considered explicitly
static const int numPolymerEq = Indices::numPolymers;
static const int numEnergyEq = Indices::numEnergy;
// number of the conservation equations
static const int numWellConservationEq = numEq - numPolymerEq - numEnergyEq;
// number of the well control equations
static const int numWellControlEq = 1;
// number of the well equations that will always be used
// based on the solution strategy, there might be other well equations be introduced
static const int numStaticWellEq = numWellConservationEq + numWellControlEq;
// the positions of the primary variables for StandardWell
// the first one is the weighted total rate (WQ_t), the second and the third ones are F_w and F_g,
// which represent the fraction of Water and Gas based on the weighted total rate, the last one is BHP.
// correspondingly, we have four well equations for blackoil model, the first three are mass
// converstation equations, and the last one is the well control equation.
// primary variables related to other components, will be before the Bhp and after F_g.
// well control equation is always the last well equation.
// TODO: in the current implementation, we use the well rate as the first primary variables for injectors,
// instead of G_t.
static const bool gasoil = numEq == 2 && (Indices::compositionSwitchIdx >= 0);
static const int WQTotal = 0;
static const int WFrac = gasoil? -1000: 1;
static const int GFrac = gasoil? 1: 2;
static const int SFrac = !has_solvent ? -1000 : 3;
// the index for Bhp in primary variables and also the index of well control equation
// they both will be the last one in their respective system.
// TODO: we should have indices for the well equations and well primary variables separately
static const int Bhp = numStaticWellEq - numWellControlEq;
using typename Base::Scalar;
using Base::name;
using Base::Water;
using Base::Oil;
using Base::Gas;
using typename Base::Mat;
using typename Base::BVector;
using typename Base::Eval;
// sparsity pattern for the matrices
//[A C^T [x = [ res
// B D ] x_well] res_well]
// the vector type for the res_well and x_well
typedef Dune::DynamicVector<Scalar> VectorBlockWellType;
typedef Dune::BlockVector<VectorBlockWellType> BVectorWell;
// the matrix type for the diagonal matrix D
typedef Dune::DynamicMatrix<Scalar> DiagMatrixBlockWellType;
typedef Dune::BCRSMatrix <DiagMatrixBlockWellType> DiagMatWell;
// the matrix type for the non-diagonal matrix B and C^T
typedef Dune::DynamicMatrix<Scalar> OffDiagMatrixBlockWellType;
typedef Dune::BCRSMatrix<OffDiagMatrixBlockWellType> OffDiagMatWell;
typedef DenseAd::DynamicEvaluation<Scalar> EvalWell;
using Base::contiSolventEqIdx;
using Base::contiPolymerEqIdx;
static const int contiEnergyEqIdx = Indices::contiEnergyEqIdx;
StandardWellV(const Well* well, const int time_step, const Wells* wells,
const ModelParameters& param,
const RateConverterType& rate_converter,
const int pvtRegionIdx,
const int num_components);
virtual void init(const PhaseUsage* phase_usage_arg,
const std::vector<double>& depth_arg,
const double gravity_arg,
const int num_cells) override;
virtual void initPrimaryVariablesEvaluation() const override;
virtual void assembleWellEq(const Simulator& ebosSimulator,
const std::vector<Scalar>& B_avg,
const double dt,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) override;
virtual void updateWellStateWithTarget(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const override;
/// check whether the well equations get converged for this well
virtual ConvergenceReport getWellConvergence(const std::vector<double>& B_avg, Opm::DeferredLogger& deferred_logger) const override;
/// Ax = Ax - C D^-1 B x
virtual void apply(const BVector& x, BVector& Ax) const override;
/// r = r - C D^-1 Rw
virtual void apply(BVector& r) const override;
/// using the solution x to recover the solution xw for wells and applying
/// xw to update Well State
virtual void recoverWellSolutionAndUpdateWellState(const BVector& x,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const override;
/// computing the well potentials for group control
virtual void computeWellPotentials(const Simulator& ebosSimulator,
const WellState& well_state,
std::vector<double>& well_potentials,
Opm::DeferredLogger& deferred_logger) /* const */ override;
virtual void updatePrimaryVariables(const WellState& well_state, Opm::DeferredLogger& deferred_logger) const override;
virtual void solveEqAndUpdateWellState(WellState& well_state, Opm::DeferredLogger& deferred_logger) override;
virtual void calculateExplicitQuantities(const Simulator& ebosSimulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) override; // should be const?
virtual void addWellContributions(Mat& mat) const override;
/// \brief Wether the Jacobian will also have well contributions in it.
virtual bool jacobianContainsWellContributions() const override
{
return param_.matrix_add_well_contributions_;
}
protected:
// protected functions from the Base class
using Base::getAllowCrossFlow;
using Base::phaseUsage;
using Base::flowPhaseToEbosCompIdx;
using Base::ebosCompIdxToFlowCompIdx;
using Base::wsolvent;
using Base::wpolymer;
using Base::wellHasTHPConstraints;
using Base::mostStrictBhpFromBhpLimits;
using Base::scalingFactor;
using Base::scaleProductivityIndex;
// protected member variables from the Base class
using Base::current_step_;
using Base::well_ecl_;
using Base::vfp_properties_;
using Base::gravity_;
using Base::param_;
using Base::well_efficiency_factor_;
using Base::first_perf_;
using Base::ref_depth_;
using Base::perf_depth_;
using Base::well_cells_;
using Base::number_of_perforations_;
using Base::number_of_phases_;
using Base::saturation_table_number_;
using Base::comp_frac_;
using Base::well_index_;
using Base::index_of_well_;
using Base::well_controls_;
using Base::well_type_;
using Base::num_components_;
using Base::connectionRates_;
using Base::perf_rep_radius_;
using Base::perf_length_;
using Base::bore_diameters_;
// total number of the well equations and primary variables
// there might be extra equations be used, numWellEq will be updated during the initialization
int numWellEq_ = numStaticWellEq;
// densities of the fluid in each perforation
std::vector<double> perf_densities_;
// pressure drop between different perforations
std::vector<double> perf_pressure_diffs_;
// residuals of the well equations
BVectorWell resWell_;
// two off-diagonal matrices
OffDiagMatWell duneB_;
OffDiagMatWell duneC_;
// diagonal matrix for the well
DiagMatWell invDuneD_;
// several vector used in the matrix calculation
mutable BVectorWell Bx_;
mutable BVectorWell invDrw_;
// the values for the primary varibles
// based on different solutioin strategies, the wells can have different primary variables
mutable std::vector<double> primary_variables_;
// the Evaluation for the well primary variables, which contain derivativles and are used in AD calculation
mutable std::vector<EvalWell> primary_variables_evaluation_;
// the saturations in the well bore under surface conditions at the beginning of the time step
std::vector<double> F0_;
// the vectors used to describe the inflow performance relationship (IPR)
// Q = IPR_A - BHP * IPR_B
// TODO: it minght need to go to WellInterface, let us implement it in StandardWell first
// it is only updated and used for producers for now
mutable std::vector<double> ipr_a_;
mutable std::vector<double> ipr_b_;
const EvalWell& getBhp() const;
EvalWell getQs(const int comp_idx) const;
const EvalWell& getWQTotal() const;
EvalWell wellVolumeFractionScaled(const int phase) const;
EvalWell wellVolumeFraction(const unsigned compIdx) const;
EvalWell wellSurfaceVolumeFraction(const int phase) const;
EvalWell extendEval(const Eval& in) const;
// xw = inv(D)*(rw - C*x)
void recoverSolutionWell(const BVector& x, BVectorWell& xw) const;
// updating the well_state based on well solution dwells
void updateWellState(const BVectorWell& dwells,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
// calculate the properties for the well connections
// to calulate the pressure difference between well connections.
void computePropertiesForWellConnectionPressures(const Simulator& ebosSimulator,
const WellState& well_state,
std::vector<double>& b_perf,
std::vector<double>& rsmax_perf,
std::vector<double>& rvmax_perf,
std::vector<double>& surf_dens_perf) const;
// TODO: not total sure whether it is a good idea to put this function here
// the major reason to put here is to avoid the usage of Wells struct
void computeConnectionDensities(const std::vector<double>& perfComponentRates,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf);
void computeConnectionPressureDelta();
void computeWellConnectionDensitesPressures(const WellState& well_state,
const std::vector<double>& b_perf,
const std::vector<double>& rsmax_perf,
const std::vector<double>& rvmax_perf,
const std::vector<double>& surf_dens_perf);
// computing the accumulation term for later use in well mass equations
void computeAccumWell();
void computeWellConnectionPressures(const Simulator& ebosSimulator,
const WellState& well_state);
void computePerfRate(const IntensiveQuantities& intQuants,
const std::vector<EvalWell>& mob,
const EvalWell& bhp,
const int perf,
const bool allow_cf,
std::vector<EvalWell>& cq_s,
double& perf_dis_gas_rate,
double& perf_vap_oil_rate,
Opm::DeferredLogger& deferred_logger) const;
// TODO: maybe we should provide a light version of computePerfRate, which does not include the
// calculation of the derivatives
void computeWellRatesWithBhp(const Simulator& ebosSimulator,
const EvalWell& bhp,
std::vector<double>& well_flux,
Opm::DeferredLogger& deferred_logger) const;
std::vector<double> computeWellPotentialWithTHP(const Simulator& ebosSimulator,
const double initial_bhp, // bhp from BHP constraints
const std::vector<double>& initial_potential,
Opm::DeferredLogger& deferred_logger) const;
template <class ValueType>
ValueType calculateBhpFromThp(const std::vector<ValueType>& rates, const int control_index, Opm::DeferredLogger& deferred_logger) const;
double calculateThpFromBhp(const std::vector<double>& rates, const double bhp, Opm::DeferredLogger& deferred_logger) const;
// get the mobility for specific perforation
void getMobility(const Simulator& ebosSimulator,
const int perf,
std::vector<EvalWell>& mob,
Opm::DeferredLogger& deferred_logger) const;
void updateWaterMobilityWithPolymer(const Simulator& ebos_simulator,
const int perf,
std::vector<EvalWell>& mob_water,
Opm::DeferredLogger& deferred_logger) const;
void updatePrimaryVariablesNewton(const BVectorWell& dwells,
const WellState& well_state) const;
void updateWellStateFromPrimaryVariables(WellState& well_state, Opm::DeferredLogger& deferred_logger) const;
void updateThp(WellState& well_state, Opm::DeferredLogger& deferred_logger) const;
void assembleControlEq(Opm::DeferredLogger& deferred_logger);
// handle the non reasonable fractions due to numerical overshoot
void processFractions() const;
// updating the inflow based on the current reservoir condition
void updateIPR(const Simulator& ebos_simulator, Opm::DeferredLogger& deferred_logger) const;
// update the operability status of the well is operable under the current reservoir condition
// mostly related to BHP limit and THP limit
virtual void checkWellOperability(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) override;
// check whether the well is operable under the current reservoir condition
// mostly related to BHP limit and THP limit
void updateWellOperability(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger);
// check whether the well is operable under BHP limit with current reservoir condition
void checkOperabilityUnderBHPLimitProducer(const Simulator& ebos_simulator, Opm::DeferredLogger& deferred_logger);
// check whether the well is operable under THP limit with current reservoir condition
void checkOperabilityUnderTHPLimitProducer(const Simulator& ebos_simulator, Opm::DeferredLogger& deferred_logger);
// update WellState based on IPR and associated VFP table
void updateWellStateWithTHPTargetIPR(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
void updateWellStateWithTHPTargetIPRProducer(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
// for a well, when all drawdown are in the wrong direction, then this well will not
// be able to produce/inject .
bool allDrawDownWrongDirection(const Simulator& ebos_simulator) const;
// whether the well can produce / inject based on the current well state (bhp)
bool canProduceInjectWithCurrentBhp(const Simulator& ebos_simulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger);
// turn on crossflow to avoid singular well equations
// when the well is banned from cross-flow and the BHP is not properly initialized,
// we turn on crossflow to avoid singular well equations. It can result in wrong-signed
// well rates, it can cause problem for THP calculation
// TODO: looking for better alternative to avoid wrong-signed well rates
bool openCrossFlowAvoidSingularity(const Simulator& ebos_simulator) const;
// calculate the BHP from THP target based on IPR
// TODO: we need to check the operablility here first, if not operable, then maybe there is
// no point to do this
double calculateBHPWithTHPTargetIPR(Opm::DeferredLogger& deferred_logger) const;
// relaxation factor considering only one fraction value
static double relaxationFactorFraction(const double old_value,
const double dx);
// calculate a relaxation factor to avoid overshoot of the fractions for producers
// which might result in negative rates
static double relaxationFactorFractionsProducer(const std::vector<double>& primary_variables,
const BVectorWell& dwells);
// calculate a relaxation factor to avoid overshoot of total rates
static double relaxationFactorRate(const std::vector<double>& primary_variables,
const BVectorWell& dwells);
virtual void wellTestingPhysical(Simulator& simulator, const std::vector<double>& B_avg,
const double simulation_time, const int report_step,
WellState& well_state, WellTestState& welltest_state, Opm::DeferredLogger& deferred_logger) override;
// calculate the skin pressure based on water velocity, throughput and polymer concentration.
// throughput is used to describe the formation damage during water/polymer injection.
// calculated skin pressure will be applied to the drawdown during perforation rate calculation
// to handle the effect from formation damage.
EvalWell pskin(const double throuhgput,
const EvalWell& water_velocity,
const EvalWell& poly_inj_conc,
Opm::DeferredLogger& deferred_logger) const;
// calculate the skin pressure based on water velocity, throughput during water injection.
EvalWell pskinwater(const double throughput,
const EvalWell& water_velocity,
Opm::DeferredLogger& deferred_logger) const;
// calculate the injecting polymer molecular weight based on the througput and water velocity
EvalWell wpolymermw(const double throughput,
const EvalWell& water_velocity,
Opm::DeferredLogger& deferred_logger) const;
// handle the extra equations for polymer injectivity study
void handleInjectivityRateAndEquations(const IntensiveQuantities& int_quants,
const WellState& well_state,
const int perf,
std::vector<EvalWell>& cq_s,
Opm::DeferredLogger& deferred_logger);
virtual void updateWaterThroughput(const double dt, WellState& well_state) const override;
};
}
#include "StandardWellV_impl.hpp"
#endif // OPM_STANDARDWELLV_HEADER_INCLUDED

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,866 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPHELPERS_HPP_
#define OPM_AUTODIFF_VFPHELPERS_HPP_
#include <opm/common/OpmLog/OpmLog.hpp>
#include <cmath>
#include <opm/common/ErrorMacros.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
/**
* This file contains a set of helper functions used by VFPProd / VFPInj.
*/
namespace Opm {
namespace detail {
/**
* Returns zero if input value is NaN of INF
*/
inline double zeroIfNanInf(const double& value) {
const bool nan_or_inf = std::isnan(value) || std::isinf(value);
if (nan_or_inf) {
OpmLog::warning("NAN_OR_INF_VFP", "NAN or INF value encountered during VFP calculation, the value is set to zero");
}
return nan_or_inf ? 0.0 : value;
}
/**
* Returns zero if input value is NaN or INF
*/
template <class EvalWell>
inline EvalWell zeroIfNanInf(const EvalWell& value) {
const bool nan_or_inf = std::isnan(value.value()) || std::isinf(value.value());
if (nan_or_inf) {
OpmLog::warning("NAN_OR_INF_VFP_EVAL", "NAN or INF Evalution encountered during VFP calculation, the Evalution is set to zero");
}
using Toolbox = MathToolbox<EvalWell>;
return nan_or_inf ? Toolbox::createBlank(value) : value;
}
/**
* Computes the flo parameter according to the flo_type_
* for production tables
* @return Production rate of oil, gas or liquid.
*/
template <typename T>
static T getFlo(const T& aqua, const T& liquid, const T& vapour,
const VFPProdTable::FLO_TYPE& type) {
switch (type) {
case VFPProdTable::FLO_OIL:
//Oil = liquid phase
return liquid;
case VFPProdTable::FLO_LIQ:
//Liquid = aqua + liquid phases
return aqua + liquid;
case VFPProdTable::FLO_GAS:
//Gas = vapor phase
return vapour;
case VFPProdTable::FLO_INVALID: //Intentional fall-through
default:
OPM_THROW(std::logic_error, "Invalid FLO_TYPE: '" << type << "'");
}
}
/**
* Computes the flo parameter according to the flo_type_
* for injection tables
* @return Production rate of oil, gas or liquid.
*/
template <typename T>
static T getFlo(const T& aqua, const T& liquid, const T& vapour,
const VFPInjTable::FLO_TYPE& type) {
switch (type) {
case VFPInjTable::FLO_OIL:
//Oil = liquid phase
return liquid;
case VFPInjTable::FLO_WAT:
//Liquid = aqua phase
return aqua;
case VFPInjTable::FLO_GAS:
//Gas = vapor phase
return vapour;
case VFPInjTable::FLO_INVALID: //Intentional fall-through
default:
OPM_THROW(std::logic_error, "Invalid FLO_TYPE: '" << type << "'");
}
}
/**
* Computes the wfr parameter according to the wfr_type_
* @return Production rate of oil, gas or liquid.
*/
template <typename T>
static T getWFR(const T& aqua, const T& liquid, const T& vapour,
const VFPProdTable::WFR_TYPE& type) {
switch(type) {
case VFPProdTable::WFR_WOR: {
//Water-oil ratio = water / oil
T wor = aqua / liquid;
return zeroIfNanInf(wor);
}
case VFPProdTable::WFR_WCT:
//Water cut = water / (water + oil)
return zeroIfNanInf(aqua / (aqua + liquid));
case VFPProdTable::WFR_WGR:
//Water-gas ratio = water / gas
return zeroIfNanInf(aqua / vapour);
case VFPProdTable::WFR_INVALID: //Intentional fall-through
default:
OPM_THROW(std::logic_error, "Invalid WFR_TYPE: '" << type << "'");
}
}
/**
* Computes the gfr parameter according to the gfr_type_
* @return Production rate of oil, gas or liquid.
*/
template <typename T>
static T getGFR(const T& aqua, const T& liquid, const T& vapour,
const VFPProdTable::GFR_TYPE& type) {
switch(type) {
case VFPProdTable::GFR_GOR:
// Gas-oil ratio = gas / oil
return zeroIfNanInf(vapour / liquid);
case VFPProdTable::GFR_GLR:
// Gas-liquid ratio = gas / (oil + water)
return zeroIfNanInf(vapour / (liquid + aqua));
case VFPProdTable::GFR_OGR:
// Oil-gas ratio = oil / gas
return zeroIfNanInf(liquid / vapour);
case VFPProdTable::GFR_INVALID: //Intentional fall-through
default:
OPM_THROW(std::logic_error, "Invalid GFR_TYPE: '" << type << "'");
}
}
/**
* Helper struct for linear interpolation
*/
struct InterpData {
InterpData() : ind_{0, 0}, inv_dist_(0.0), factor_(0.0) {}
int ind_[2]; //[First element greater than or equal to value, Last element smaller than or equal to value]
double inv_dist_; // 1 / distance between the two end points of the segment. Used to calculate derivatives and uses 1.0 / 0.0 = 0.0 as a convention
double factor_; // Interpolation factor
};
/**
* Helper function to find indices etc. for linear interpolation and extrapolation
* @param value Value to find in values
* @param values Sorted list of values to search for value in.
* @return Data required to find the interpolated value
*/
inline InterpData findInterpData(const double& value, const std::vector<double>& values) {
InterpData retval;
const int nvalues = values.size();
//If we only have one value in our vector, return that
if (values.size() == 1) {
retval.ind_[0] = 0;
retval.ind_[1] = 0;
retval.inv_dist_ = 0.0;
retval.factor_ = 0.0;
}
// Else search in the vector
else {
//If value is less than all values, use first interval
if (value < values.front()) {
retval.ind_[0] = 0;
retval.ind_[1] = 1;
}
//If value is greater than all values, use last interval
else if (value >= values.back()) {
retval.ind_[0] = nvalues-2;
retval.ind_[1] = nvalues-1;
}
else {
//Search internal intervals
for (int i=1; i<nvalues; ++i) {
if (values[i] >= value) {
retval.ind_[0] = i-1;
retval.ind_[1] = i;
break;
}
}
}
const double start = values[retval.ind_[0]];
const double end = values[retval.ind_[1]];
//Find interpolation ratio
if (end > start) {
//FIXME: Possible source for floating point error here if value and floor are large,
//but very close to each other
retval.inv_dist_ = 1.0 / (end-start);
retval.factor_ = (value-start) * retval.inv_dist_;
}
else {
retval.inv_dist_ = 0.0;
retval.factor_ = 0.0;
}
}
return retval;
}
/**
* An "ADB-like" structure with a single value and a set of derivatives
*/
struct VFPEvaluation {
VFPEvaluation() : value(0.0), dthp(0.0), dwfr(0.0), dgfr(0.0), dalq(0.0), dflo(0.0) {};
double value;
double dthp;
double dwfr;
double dgfr;
double dalq;
double dflo;
};
inline VFPEvaluation operator+(
VFPEvaluation lhs,
const VFPEvaluation& rhs) {
lhs.value += rhs.value;
lhs.dthp += rhs.dthp;
lhs.dwfr += rhs.dwfr;
lhs.dgfr += rhs.dgfr;
lhs.dalq += rhs.dalq;
lhs.dflo += rhs.dflo;
return lhs;
}
inline VFPEvaluation operator-(
VFPEvaluation lhs,
const VFPEvaluation& rhs) {
lhs.value -= rhs.value;
lhs.dthp -= rhs.dthp;
lhs.dwfr -= rhs.dwfr;
lhs.dgfr -= rhs.dgfr;
lhs.dalq -= rhs.dalq;
lhs.dflo -= rhs.dflo;
return lhs;
}
inline VFPEvaluation operator*(
double lhs,
const VFPEvaluation& rhs) {
VFPEvaluation retval;
retval.value = rhs.value * lhs;
retval.dthp = rhs.dthp * lhs;
retval.dwfr = rhs.dwfr * lhs;
retval.dgfr = rhs.dgfr * lhs;
retval.dalq = rhs.dalq * lhs;
retval.dflo = rhs.dflo * lhs;
return retval;
}
/**
* Helper function which interpolates data using the indices etc. given in the inputs.
*/
inline VFPEvaluation interpolate(
const VFPProdTable::array_type& array,
const InterpData& flo_i,
const InterpData& thp_i,
const InterpData& wfr_i,
const InterpData& gfr_i,
const InterpData& alq_i) {
//Values and derivatives in a 5D hypercube
VFPEvaluation nn[2][2][2][2][2];
//Pick out nearest neighbors (nn) to our evaluation point
//This is not really required, but performance-wise it may pay off, since the 32-elements
//we copy to (nn) will fit better in cache than the full original table for the
//interpolation below.
//The following ladder of for loops will presumably be unrolled by a reasonable compiler.
for (int t=0; t<=1; ++t) {
for (int w=0; w<=1; ++w) {
for (int g=0; g<=1; ++g) {
for (int a=0; a<=1; ++a) {
for (int f=0; f<=1; ++f) {
//Shorthands for indexing
const int ti = thp_i.ind_[t];
const int wi = wfr_i.ind_[w];
const int gi = gfr_i.ind_[g];
const int ai = alq_i.ind_[a];
const int fi = flo_i.ind_[f];
//Copy element
nn[t][w][g][a][f].value = array[ti][wi][gi][ai][fi];
}
}
}
}
}
//Calculate derivatives
//Note that the derivative of the two end points of a line aligned with the
//"axis of the derivative" are equal
for (int i=0; i<=1; ++i) {
for (int j=0; j<=1; ++j) {
for (int k=0; k<=1; ++k) {
for (int l=0; l<=1; ++l) {
nn[0][i][j][k][l].dthp = (nn[1][i][j][k][l].value - nn[0][i][j][k][l].value) * thp_i.inv_dist_;
nn[i][0][j][k][l].dwfr = (nn[i][1][j][k][l].value - nn[i][0][j][k][l].value) * wfr_i.inv_dist_;
nn[i][j][0][k][l].dgfr = (nn[i][j][1][k][l].value - nn[i][j][0][k][l].value) * gfr_i.inv_dist_;
nn[i][j][k][0][l].dalq = (nn[i][j][k][1][l].value - nn[i][j][k][0][l].value) * alq_i.inv_dist_;
nn[i][j][k][l][0].dflo = (nn[i][j][k][l][1].value - nn[i][j][k][l][0].value) * flo_i.inv_dist_;
nn[1][i][j][k][l].dthp = nn[0][i][j][k][l].dthp;
nn[i][1][j][k][l].dwfr = nn[i][0][j][k][l].dwfr;
nn[i][j][1][k][l].dgfr = nn[i][j][0][k][l].dgfr;
nn[i][j][k][1][l].dalq = nn[i][j][k][0][l].dalq;
nn[i][j][k][l][1].dflo = nn[i][j][k][l][0].dflo;
}
}
}
}
double t1, t2; //interpolation variables, so that t1 = (1-t) and t2 = t.
// Remove dimensions one by one
// Example: going from 3D to 2D to 1D, we start by interpolating along
// the z axis first, leaving a 2D problem. Then interpolating along the y
// axis, leaving a 1D, problem, etc.
t2 = flo_i.factor_;
t1 = (1.0-t2);
for (int t=0; t<=1; ++t) {
for (int w=0; w<=1; ++w) {
for (int g=0; g<=1; ++g) {
for (int a=0; a<=1; ++a) {
nn[t][w][g][a][0] = t1*nn[t][w][g][a][0] + t2*nn[t][w][g][a][1];
}
}
}
}
t2 = alq_i.factor_;
t1 = (1.0-t2);
for (int t=0; t<=1; ++t) {
for (int w=0; w<=1; ++w) {
for (int g=0; g<=1; ++g) {
nn[t][w][g][0][0] = t1*nn[t][w][g][0][0] + t2*nn[t][w][g][1][0];
}
}
}
t2 = gfr_i.factor_;
t1 = (1.0-t2);
for (int t=0; t<=1; ++t) {
for (int w=0; w<=1; ++w) {
nn[t][w][0][0][0] = t1*nn[t][w][0][0][0] + t2*nn[t][w][1][0][0];
}
}
t2 = wfr_i.factor_;
t1 = (1.0-t2);
for (int t=0; t<=1; ++t) {
nn[t][0][0][0][0] = t1*nn[t][0][0][0][0] + t2*nn[t][1][0][0][0];
}
t2 = thp_i.factor_;
t1 = (1.0-t2);
nn[0][0][0][0][0] = t1*nn[0][0][0][0][0] + t2*nn[1][0][0][0][0];
return nn[0][0][0][0][0];
}
/**
* This basically models interpolate(VFPProdTable::array_type, ...)
* which performs 5D interpolation, but here for the 2D case only
*/
inline VFPEvaluation interpolate(
const VFPInjTable::array_type& array,
const InterpData& flo_i,
const InterpData& thp_i) {
//Values and derivatives in a 2D plane
VFPEvaluation nn[2][2];
//Pick out nearest neighbors (nn) to our evaluation point
//The following ladder of for loops will presumably be unrolled by a reasonable compiler.
for (int t=0; t<=1; ++t) {
for (int f=0; f<=1; ++f) {
//Shorthands for indexing
const int ti = thp_i.ind_[t];
const int fi = flo_i.ind_[f];
//Copy element
nn[t][f].value = array[ti][fi];
}
}
//Calculate derivatives
//Note that the derivative of the two end points of a line aligned with the
//"axis of the derivative" are equal
for (int i=0; i<=1; ++i) {
nn[0][i].dthp = (nn[1][i].value - nn[0][i].value) * thp_i.inv_dist_;
nn[i][0].dwfr = -1e100;
nn[i][0].dgfr = -1e100;
nn[i][0].dalq = -1e100;
nn[i][0].dflo = (nn[i][1].value - nn[i][0].value) * flo_i.inv_dist_;
nn[1][i].dthp = nn[0][i].dthp;
nn[i][1].dwfr = nn[i][0].dwfr;
nn[i][1].dgfr = nn[i][0].dgfr;
nn[i][1].dalq = nn[i][0].dalq;
nn[i][1].dflo = nn[i][0].dflo;
}
double t1, t2; //interpolation variables, so that t1 = (1-t) and t2 = t.
// Go from 2D to 1D
t2 = flo_i.factor_;
t1 = (1.0-t2);
nn[0][0] = t1*nn[0][0] + t2*nn[0][1];
nn[1][0] = t1*nn[1][0] + t2*nn[1][1];
// Go from line to point on line
t2 = thp_i.factor_;
t1 = (1.0-t2);
nn[0][0] = t1*nn[0][0] + t2*nn[1][0];
return nn[0][0];
}
inline VFPEvaluation bhp(const VFPProdTable* table,
const double& aqua,
const double& liquid,
const double& vapour,
const double& thp,
const double& alq) {
//Find interpolation variables
double flo = detail::getFlo(aqua, liquid, vapour, table->getFloType());
double wfr = detail::getWFR(aqua, liquid, vapour, table->getWFRType());
double gfr = detail::getGFR(aqua, liquid, vapour, table->getGFRType());
//First, find the values to interpolate between
//Recall that flo is negative in Opm, so switch sign.
auto flo_i = detail::findInterpData(-flo, table->getFloAxis());
auto thp_i = detail::findInterpData( thp, table->getTHPAxis());
auto wfr_i = detail::findInterpData( wfr, table->getWFRAxis());
auto gfr_i = detail::findInterpData( gfr, table->getGFRAxis());
auto alq_i = detail::findInterpData( alq, table->getALQAxis());
detail::VFPEvaluation retval = detail::interpolate(table->getTable(), flo_i, thp_i, wfr_i, gfr_i, alq_i);
return retval;
}
inline VFPEvaluation bhp(const VFPInjTable* table,
const double& aqua,
const double& liquid,
const double& vapour,
const double& thp) {
//Find interpolation variables
double flo = detail::getFlo(aqua, liquid, vapour, table->getFloType());
//First, find the values to interpolate between
auto flo_i = detail::findInterpData(flo, table->getFloAxis());
auto thp_i = detail::findInterpData(thp, table->getTHPAxis());
//Then perform the interpolation itself
detail::VFPEvaluation retval = detail::interpolate(table->getTable(), flo_i, thp_i);
return retval;
}
/**
* Returns the table from the map if found, or throws an exception
*/
template <typename T>
const T* getTable(const std::map<int, T*> tables, int table_id) {
auto entry = tables.find(table_id);
if (entry == tables.end()) {
OPM_THROW(std::invalid_argument, "Nonexistent VFP table " << table_id << " referenced.");
}
else {
return entry->second;
}
}
/**
* Check whether we have a table with the table number
*/
template <typename T>
bool hasTable(const std::map<int, T*> tables, int table_id) {
const auto entry = tables.find(table_id);
return (entry != tables.end() );
}
/**
* Returns the type variable for FLO/GFR/WFR for production tables
*/
template <typename TYPE, typename TABLE>
TYPE getType(const TABLE* table);
template <>
inline
VFPProdTable::FLO_TYPE getType(const VFPProdTable* table) {
return table->getFloType();
}
template <>
inline
VFPProdTable::WFR_TYPE getType(const VFPProdTable* table) {
return table->getWFRType();
}
template <>
inline
VFPProdTable::GFR_TYPE getType(const VFPProdTable* table) {
return table->getGFRType();
}
/**
* Returns the type variable for FLO for injection tables
*/
template <>
inline
VFPInjTable::FLO_TYPE getType(const VFPInjTable* table) {
return table->getFloType();
}
/**
* Helper function that finds x for a given value of y for a line
* *NOTE ORDER OF ARGUMENTS*
*/
inline double findX(const double& x0,
const double& x1,
const double& y0,
const double& y1,
const double& y) {
const double dx = x1 - x0;
const double dy = y1 - y0;
/**
* y = y0 + (dy / dx) * (x - x0)
* => x = x0 + (y - y0) * (dx / dy)
*
* If dy is zero, use x1 as the value.
*/
double x = 0.0;
if (dy != 0.0) {
x = x0 + (y-y0) * (dx/dy);
}
else {
x = x1;
}
return x;
}
/**
* This function finds the value of THP given a specific BHP.
* Essentially:
* Given the function f(thp_array(x)) = bhp_array(x), which is piecewise linear,
* find thp so that f(thp) = bhp.
*/
inline double findTHP(
const std::vector<double>& bhp_array,
const std::vector<double>& thp_array,
double bhp) {
int nthp = thp_array.size();
double thp = -1e100;
//Check that our thp axis is sorted
assert(std::is_sorted(thp_array.begin(), thp_array.end()));
/**
* Our *interpolated* bhp_array will be montonic increasing for increasing
* THP if our input BHP values are monotonic increasing for increasing
* THP values. However, if we have to *extrapolate* along any of the other
* axes, this guarantee holds no more, and bhp_array may be "random"
*/
if (std::is_sorted(bhp_array.begin(), bhp_array.end())) {
//Target bhp less than all values in array, extrapolate
if (bhp <= bhp_array[0]) {
//TODO: LOG extrapolation
const double& x0 = thp_array[0];
const double& x1 = thp_array[1];
const double& y0 = bhp_array[0];
const double& y1 = bhp_array[1];
thp = detail::findX(x0, x1, y0, y1, bhp);
}
//Target bhp greater than all values in array, extrapolate
else if (bhp > bhp_array[nthp-1]) {
//TODO: LOG extrapolation
const double& x0 = thp_array[nthp-2];
const double& x1 = thp_array[nthp-1];
const double& y0 = bhp_array[nthp-2];
const double& y1 = bhp_array[nthp-1];
thp = detail::findX(x0, x1, y0, y1, bhp);
}
//Target bhp within table ranges, interpolate
else {
//Loop over the values and find min(bhp_array(thp)) == bhp
//so that we maximize the rate.
//Find i so that bhp_array[i-1] <= bhp <= bhp_array[i];
//Assuming a small number of values in bhp_array, this should be quite
//efficient. Other strategies might be bisection, etc.
int i=0;
bool found = false;
for (; i<nthp-1; ++i) {
const double& y0 = bhp_array[i ];
const double& y1 = bhp_array[i+1];
if (y0 < bhp && bhp <= y1) {
found = true;
break;
}
}
//Canary in a coal mine: shouldn't really be required
assert(found == true);
static_cast<void>(found); //Silence compiler warning
const double& x0 = thp_array[i ];
const double& x1 = thp_array[i+1];
const double& y0 = bhp_array[i ];
const double& y1 = bhp_array[i+1];
thp = detail::findX(x0, x1, y0, y1, bhp);
}
}
//bhp_array not sorted, raw search.
else {
//Find i so that bhp_array[i-1] <= bhp <= bhp_array[i];
//Since the BHP values might not be sorted, first search within
//our interpolation values, and then try to extrapolate.
int i=0;
bool found = false;
for (; i<nthp-1; ++i) {
const double& y0 = bhp_array[i ];
const double& y1 = bhp_array[i+1];
if (y0 < bhp && bhp <= y1) {
found = true;
break;
}
}
if (found) {
const double& x0 = thp_array[i ];
const double& x1 = thp_array[i+1];
const double& y0 = bhp_array[i ];
const double& y1 = bhp_array[i+1];
thp = detail::findX(x0, x1, y0, y1, bhp);
}
else if (bhp <= bhp_array[0]) {
//TODO: LOG extrapolation
const double& x0 = thp_array[0];
const double& x1 = thp_array[1];
const double& y0 = bhp_array[0];
const double& y1 = bhp_array[1];
thp = detail::findX(x0, x1, y0, y1, bhp);
}
//Target bhp greater than all values in array, extrapolate
else if (bhp > bhp_array[nthp-1]) {
//TODO: LOG extrapolation
const double& x0 = thp_array[nthp-2];
const double& x1 = thp_array[nthp-1];
const double& y0 = bhp_array[nthp-2];
const double& y1 = bhp_array[nthp-1];
thp = detail::findX(x0, x1, y0, y1, bhp);
}
else {
OPM_THROW(std::logic_error, "Programmer error: Unable to find THP in THP array");
}
}
return thp;
}
// a data type use to do the intersection calculation to get the intial bhp under THP control
struct RateBhpPair {
double rate;
double bhp;
};
// looking for a intersection point a line segment and a line, they are both defined with two points
// it is copied from #include <opm/polymer/Point2D.hpp>, which should be removed since it is only required by the lagacy polymer
inline bool findIntersection(const std::array<RateBhpPair, 2>& line_segment, const std::array<RateBhpPair, 2>& line, double& bhp) {
const double x1 = line_segment[0].rate;
const double y1 = line_segment[0].bhp;
const double x2 = line_segment[1].rate;
const double y2 = line_segment[1].bhp;
const double x3 = line[0].rate;
const double y3 = line[0].bhp;
const double x4 = line[1].rate;
const double y4 = line[1].bhp;
const double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (d == 0.) {
return false;
}
const double x = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
const double y = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
if (x >= std::min(x1,x2) && x <= std::max(x1,x2)) {
bhp = y;
return true;
} else {
return false;
}
}
// calculating the BHP from thp through the intersection of VFP curves and inflow performance relationship
inline bool findIntersectionForBhp(const std::vector<RateBhpPair>& ratebhp_samples,
const std::array<RateBhpPair, 2>& ratebhp_twopoints_ipr,
double& obtained_bhp)
{
// there possibly two intersection point, then we choose the one corresponding with the bigger rate
const double bhp1 = ratebhp_twopoints_ipr[0].bhp;
const double rate1 = ratebhp_twopoints_ipr[0].rate;
const double bhp2 = ratebhp_twopoints_ipr[1].bhp;
const double rate2 = ratebhp_twopoints_ipr[1].rate;
assert(rate1 != rate2);
const double line_slope = (bhp2 - bhp1) / (rate2 - rate1);
// line equation will be
// bhp - bhp1 - line_slope * (flo_rate - flo_rate1) = 0
auto flambda = [&](const double flo_rate, const double bhp) {
return bhp - bhp1 - line_slope * (flo_rate - rate1);
};
int number_intersection_found = 0;
int index_segment = 0; // the intersection segment that intersection happens
const size_t num_samples = ratebhp_samples.size();
for (size_t i = 0; i < num_samples - 1; ++i) {
const double temp1 = flambda(ratebhp_samples[i].rate, ratebhp_samples[i].bhp);
const double temp2 = flambda(ratebhp_samples[i+1].rate, ratebhp_samples[i+1].bhp);
if (temp1 * temp2 <= 0.) { // intersection happens
// in theory there should be maximum two intersection points
// while considering the situation == 0. here, we might find more
// we always use the last one, which is the one corresponds to the biggest rate,
// which we assume is the more stable one
++number_intersection_found;
index_segment = i;
}
}
if (number_intersection_found == 0) { // there is not intersection point
return false;
}
// then we pick the segment from the VFP curve to do the line intersection calculation
const std::array<RateBhpPair, 2> line_segment{ ratebhp_samples[index_segment], ratebhp_samples[index_segment + 1] };
const bool intersection_found = findIntersection(line_segment, ratebhp_twopoints_ipr, obtained_bhp);
return intersection_found;
}
} // namespace detail
} // namespace
#endif /* OPM_AUTODIFF_VFPHELPERS_HPP_ */

View File

@@ -0,0 +1,102 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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/>.
*/
#include "config.h"
#include <opm/simulators/wells/VFPInjProperties.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/simulators/wells/VFPHelpers.hpp>
namespace Opm {
VFPInjProperties::VFPInjProperties() {
}
VFPInjProperties::VFPInjProperties(const VFPInjTable* table){
m_tables[table->getTableNum()] = table;
}
VFPInjProperties::VFPInjProperties(const VFPInjProperties::InjTable& tables) {
for (const auto& table : tables) {
m_tables[table.first] = table.second.get();
}
}
double VFPInjProperties::bhp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& thp_arg) const {
const VFPInjTable* table = detail::getTable(m_tables, table_id);
detail::VFPEvaluation retval = detail::bhp(table, aqua, liquid, vapour, thp_arg);
return retval.value;
}
double VFPInjProperties::thp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& bhp_arg) const {
const VFPInjTable* table = detail::getTable(m_tables, table_id);
const VFPInjTable::array_type& data = table->getTable();
//Find interpolation variables
double flo = detail::getFlo(aqua, liquid, vapour, table->getFloType());
const std::vector<double> thp_array = table->getTHPAxis();
int nthp = thp_array.size();
/**
* Find the function bhp_array(thp) by creating a 1D view of the data
* by interpolating for every value of thp. This might be somewhat
* expensive, but let us assome that nthp is small
*/
auto flo_i = detail::findInterpData(flo, table->getFloAxis());
std::vector<double> bhp_array(nthp);
for (int i=0; i<nthp; ++i) {
auto thp_i = detail::findInterpData(thp_array[i], thp_array);
bhp_array[i] = detail::interpolate(data, flo_i, thp_i).value;
}
double retval = detail::findTHP(bhp_array, thp_array, bhp_arg);
return retval;
}
const VFPInjTable* VFPInjProperties::getTable(const int table_id) const {
return detail::getTable(m_tables, table_id);
}
bool VFPInjProperties::hasTable(const int table_id) const {
return detail::hasTable(m_tables, table_id);
}
} //Namespace Opm

View File

@@ -0,0 +1,169 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPINJPROPERTIES_HPP_
#define OPM_AUTODIFF_VFPINJPROPERTIES_HPP_
#include <opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp>
#include <opm/core/wells.h>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
#include <opm/simulators/wells/VFPHelpers.hpp>
#include <vector>
#include <map>
namespace Opm {
class VFPInjProperties {
public:
/**
* Empty constructor
*/
VFPInjProperties();
/**
* Constructor
* Takes *no* ownership of data.
* @param inj_table A *single* VFPINJ table
*/
explicit VFPInjProperties(const VFPInjTable* inj_table);
/**
* Constructor
* Takes *no* ownership of data.
* @param inj_tables A map of different VFPINJ tables.
*/
using InjTable = std::map<int, std::shared_ptr<const VFPInjTable> >;
explicit VFPInjProperties(const InjTable& inj_tables);
/**
* Linear interpolation of bhp as a function of the input parameters given as
* Evaluation
* Each entry corresponds typically to one well.
* @param table_id Table number to use. A negative entry (e.g., -1)
* will indicate that no table is used, and the corresponding
* BHP will be calculated as a constant -1e100.
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param thp Tubing head pressure
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table, for each entry in the
* input ADB objects.
*/
template <class EvalWell>
EvalWell bhp(const int table_id,
const EvalWell& aqua,
const EvalWell& liquid,
const EvalWell& vapour,
const double& thp) const {
//Get the table
const VFPInjTable* table = detail::getTable(m_tables, table_id);
EvalWell bhp = 0.0 * aqua;
//Find interpolation variables
EvalWell flo = detail::getFlo(aqua, liquid, vapour, table->getFloType());
//Compute the BHP for each well independently
if (table != nullptr) {
//First, find the values to interpolate between
//Value of FLO is negative in OPM for producers, but positive in VFP table
auto flo_i = detail::findInterpData(flo.value(), table->getFloAxis());
auto thp_i = detail::findInterpData( thp, table->getTHPAxis()); // assume constant
detail::VFPEvaluation bhp_val = detail::interpolate(table->getTable(), flo_i, thp_i);
bhp = bhp_val.dflo * flo;
bhp.setValue(bhp_val.value); // thp is assumed constant i.e.
}
else {
bhp.setValue(-1e100); //Signal that this value has not been calculated properly, due to "missing" table
}
return bhp;
}
/**
* Returns the table associated with the ID, or throws an exception if
* the table does not exist
*/
const VFPInjTable* getTable(const int table_id) const;
/**
* Check whether there is table associated with ID
*/
bool hasTable(const int table_id) const;
/**
* Returns true if no vfp tables are in the current map
*/
bool empty() const {
return m_tables.empty();
}
/**
* Linear interpolation of bhp as a function of the input parameters
* @param table_id Table number to use
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param thp Tubing head pressure
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table.
*/
double bhp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& thp) const;
/**
* Linear interpolation of thp as a function of the input parameters
* @param table_id Table number to use
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param bhp Bottom hole pressure
*
* @return The tubing hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table.
*/
double thp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& bhp) const;
protected:
// Map which connects the table number with the table itself
std::map<int, const VFPInjTable*> m_tables;
};
} //namespace
#endif /* OPM_AUTODIFF_VFPINJPROPERTIES_HPP_ */

View File

@@ -0,0 +1,255 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
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/>.
*/
#include "config.h"
#include <opm/simulators/wells/VFPProdProperties.hpp>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
#include <opm/simulators/wells/VFPHelpers.hpp>
namespace Opm {
VFPProdProperties::VFPProdProperties() {
}
VFPProdProperties::VFPProdProperties(const VFPProdTable* table){
m_tables[table->getTableNum()] = table;
}
VFPProdProperties::VFPProdProperties(const VFPProdProperties::ProdTable& tables) {
for (const auto& table : tables) {
m_tables[table.first] = table.second.get();
}
}
double VFPProdProperties::thp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& bhp_arg,
const double& alq) const {
const VFPProdTable* table = detail::getTable(m_tables, table_id);
const VFPProdTable::array_type& data = table->getTable();
//Find interpolation variables
double flo = detail::getFlo(aqua, liquid, vapour, table->getFloType());
double wfr = detail::getWFR(aqua, liquid, vapour, table->getWFRType());
double gfr = detail::getGFR(aqua, liquid, vapour, table->getGFRType());
const std::vector<double> thp_array = table->getTHPAxis();
int nthp = thp_array.size();
/**
* Find the function bhp_array(thp) by creating a 1D view of the data
* by interpolating for every value of thp. This might be somewhat
* expensive, but let us assome that nthp is small
* Recall that flo is negative in Opm, so switch the sign
*/
auto flo_i = detail::findInterpData(-flo, table->getFloAxis());
auto wfr_i = detail::findInterpData( wfr, table->getWFRAxis());
auto gfr_i = detail::findInterpData( gfr, table->getGFRAxis());
auto alq_i = detail::findInterpData( alq, table->getALQAxis());
std::vector<double> bhp_array(nthp);
for (int i=0; i<nthp; ++i) {
auto thp_i = detail::findInterpData(thp_array[i], thp_array);
bhp_array[i] = detail::interpolate(data, flo_i, thp_i, wfr_i, gfr_i, alq_i).value;
}
double retval = detail::findTHP(bhp_array, thp_array, bhp_arg);
return retval;
}
double VFPProdProperties::bhp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& thp_arg,
const double& alq) const {
const VFPProdTable* table = detail::getTable(m_tables, table_id);
detail::VFPEvaluation retval = detail::bhp(table, aqua, liquid, vapour, thp_arg, alq);
return retval.value;
}
const VFPProdTable* VFPProdProperties::getTable(const int table_id) const {
return detail::getTable(m_tables, table_id);
}
bool VFPProdProperties::hasTable(const int table_id) const {
return detail::hasTable(m_tables, table_id);
}
std::vector<double>
VFPProdProperties::
bhpwithflo(const std::vector<double>& flos,
const int table_id,
const double wfr,
const double gfr,
const double thp,
const double alq,
const double dp) const
{
// Get the table
const VFPProdTable* table = detail::getTable(m_tables, table_id);
const auto thp_i = detail::findInterpData( thp, table->getTHPAxis()); // assume constant
const auto wfr_i = detail::findInterpData( wfr, table->getWFRAxis());
const auto gfr_i = detail::findInterpData( gfr, table->getGFRAxis());
const auto alq_i = detail::findInterpData( alq, table->getALQAxis()); //assume constant
std::vector<double> bhps(flos.size(), 0.);
for (size_t i = 0; i < flos.size(); ++i) {
// Value of FLO is negative in OPM for producers, but positive in VFP table
const auto flo_i = detail::findInterpData(-flos[i], table->getFloAxis());
const detail::VFPEvaluation bhp_val = detail::interpolate(table->getTable(), flo_i, thp_i, wfr_i, gfr_i, alq_i);
// TODO: this kind of breaks the conventions for the functions here by putting dp within the function
bhps[i] = bhp_val.value - dp;
}
return bhps;
}
double
VFPProdProperties::
calculateBhpWithTHPTarget(const std::vector<double>& ipr_a,
const std::vector<double>& ipr_b,
const double bhp_limit,
const double thp_table_id,
const double thp_limit,
const double alq,
const double dp) const
{
// For producers, bhp_safe_limit is the highest BHP value that can still produce based on IPR
double bhp_safe_limit = 1.e100;
for (size_t i = 0; i < ipr_a.size(); ++i) {
if (ipr_b[i] == 0.) continue;
const double bhp = ipr_a[i] / ipr_b[i];
if (bhp < bhp_safe_limit) {
bhp_safe_limit = bhp;
}
}
// Here, we use the middle point between the bhp_limit and bhp_safe_limit to calculate the ratio of the flow
// and the middle point serves one of the two points to describe inflow performance relationship line
const double bhp_middle = (bhp_limit + bhp_safe_limit) / 2.0;
// FLO is the rate based on the type specified with the VFP table
// The two points correspond to the bhp values of bhp_limit, and the middle of bhp_limit and bhp_safe_limit
// for producers, the rates are negative
std::vector<double> rates_bhp_limit(ipr_a.size());
std::vector<double> rates_bhp_middle(ipr_a.size());
for (size_t i = 0; i < rates_bhp_limit.size(); ++i) {
rates_bhp_limit[i] = bhp_limit * ipr_b[i] - ipr_a[i];
rates_bhp_middle[i] = bhp_middle * ipr_b[i] - ipr_a[i];
}
// TODO: we need to be careful that there is nothings wrong related to the indices here
const int Water = BlackoilPhases::Aqua;
const int Oil = BlackoilPhases::Liquid;
const int Gas = BlackoilPhases::Vapour;
const VFPProdTable* table = detail::getTable(m_tables, thp_table_id);
const double aqua_bhp_limit = rates_bhp_limit[Water];
const double liquid_bhp_limit = rates_bhp_limit[Oil];
const double vapour_bhp_limit = rates_bhp_limit[Gas];
const double flo_bhp_limit = detail::getFlo(aqua_bhp_limit, liquid_bhp_limit, vapour_bhp_limit, table->getFloType() );
const double aqua_bhp_middle = rates_bhp_middle[Water];
const double liquid_bhp_middle = rates_bhp_middle[Oil];
const double vapour_bhp_middle = rates_bhp_middle[Gas];
const double flo_bhp_middle = detail::getFlo(aqua_bhp_middle, liquid_bhp_middle, vapour_bhp_middle, table->getFloType() );
// we use the ratios based on the middle value of bhp_limit and bhp_safe_limit
const double wfr = detail::getWFR(aqua_bhp_middle, liquid_bhp_middle, vapour_bhp_middle, table->getWFRType());
const double gfr = detail::getGFR(aqua_bhp_middle, liquid_bhp_middle, vapour_bhp_middle, table->getGFRType());
// we get the flo sampling points from the table,
// then extend it with zero and rate under bhp_limit for extrapolation
std::vector<double> flo_samples = table->getFloAxis();
if (flo_samples[0] > 0.) {
flo_samples.insert(flo_samples.begin(), 0.);
}
if (flo_samples.back() < std::abs(flo_bhp_limit)) {
flo_samples.push_back(std::abs(flo_bhp_limit));
}
// kind of unncessarily following the tradation that producers should have negative rates
// the key is here that it should be consistent with the function bhpwithflo
for (double& value : flo_samples) {
value = -value;
}
// get the bhp sampling values based on the flo sample values
const std::vector<double> bhp_flo_samples = bhpwithflo(flo_samples, thp_table_id, wfr, gfr, thp_limit, alq, dp);
std::vector<detail::RateBhpPair> ratebhp_samples;
for (size_t i = 0; i < flo_samples.size(); ++i) {
ratebhp_samples.push_back( detail::RateBhpPair{flo_samples[i], bhp_flo_samples[i]} );
}
const std::array<detail::RateBhpPair, 2> ratebhp_twopoints_ipr {detail::RateBhpPair{flo_bhp_middle, bhp_middle},
detail::RateBhpPair{flo_bhp_limit, bhp_limit} };
double obtain_bhp = 0.;
const bool can_obtain_bhp_with_thp_limit = detail::findIntersectionForBhp(ratebhp_samples, ratebhp_twopoints_ipr, obtain_bhp);
// \Note: assuming that negative BHP does not make sense
if (can_obtain_bhp_with_thp_limit && obtain_bhp > 0.) {
// getting too high bhp that might cause negative rates (rates in the undesired direction)
if (obtain_bhp >= bhp_safe_limit) {
const std::string msg (" We are getting a too high BHP value from the THP constraint, which may "
" cause problems later ");
OpmLog::info("TOO_HIGH_BHP_FOUND_THP_TARGET", msg);
const std::string debug_msg = " obtain_bhp " + std::to_string(obtain_bhp)
+ " bhp_safe_limit " + std::to_string(bhp_safe_limit)
+ " thp limit " + std::to_string(thp_limit);
OpmLog::debug(debug_msg);
}
return obtain_bhp;
} else {
OpmLog::warning("NO_BHP_FOUND_THP_TARGET", " we could not find a bhp value with thp target.");
return -100.;
}
}
}

View File

@@ -0,0 +1,207 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPPRODPROPERTIES_HPP_
#define OPM_AUTODIFF_VFPPRODPROPERTIES_HPP_
#include <opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp>
#include <opm/core/wells.h>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
#include <opm/simulators/wells/VFPHelpers.hpp>
#include <vector>
#include <map>
namespace Opm {
/**
* Class which linearly interpolates BHP as a function of rate, tubing head pressure,
* water fraction, gas fraction, and artificial lift for production VFP tables, and similarly
* the BHP as a function of the rate and tubing head pressure.
*/
class VFPProdProperties {
public:
/**
* Empty constructor
*/
VFPProdProperties();
/**
* Constructor
* Takes *no* ownership of data.
* @param prod_table A *single* VFPPROD table
*/
explicit VFPProdProperties(const VFPProdTable* prod_table);
/**
* Constructor
* Takes *no* ownership of data.
* @param prod_tables A map of different VFPPROD tables.
*/
using ProdTable = std::map<int, std::shared_ptr<const VFPProdTable> >;
explicit VFPProdProperties(const ProdTable& prod_tables);
/**
* Linear interpolation of bhp as a function of the input parameters given as
* Evalutions
* Each entry corresponds typically to one well.
* @param table_id Table number to use. A negative entry (e.g., -1)
* will indicate that no table is used, and the corresponding
* BHP will be calculated as a constant -1e100.
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param thp Tubing head pressure
* @param alq Artificial lift or other parameter
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table, for each entry in the
* input ADB objects.
*/
template <class EvalWell>
EvalWell bhp(const int table_id,
const EvalWell& aqua,
const EvalWell& liquid,
const EvalWell& vapour,
const double& thp,
const double& alq) const {
//Get the table
const VFPProdTable* table = detail::getTable(m_tables, table_id);
EvalWell bhp = 0.0 * aqua;
//Find interpolation variables
EvalWell flo = detail::getFlo(aqua, liquid, vapour, table->getFloType());
EvalWell wfr = detail::getWFR(aqua, liquid, vapour, table->getWFRType());
EvalWell gfr = detail::getGFR(aqua, liquid, vapour, table->getGFRType());
//Compute the BHP for each well independently
if (table != nullptr) {
//First, find the values to interpolate between
//Value of FLO is negative in OPM for producers, but positive in VFP table
auto flo_i = detail::findInterpData(-flo.value(), table->getFloAxis());
auto thp_i = detail::findInterpData( thp, table->getTHPAxis()); // assume constant
auto wfr_i = detail::findInterpData( wfr.value(), table->getWFRAxis());
auto gfr_i = detail::findInterpData( gfr.value(), table->getGFRAxis());
auto alq_i = detail::findInterpData( alq, table->getALQAxis()); //assume constant
detail::VFPEvaluation bhp_val = detail::interpolate(table->getTable(), flo_i, thp_i, wfr_i, gfr_i, alq_i);
bhp = (bhp_val.dwfr * wfr) + (bhp_val.dgfr * gfr) - (bhp_val.dflo * flo);
bhp.setValue(bhp_val.value);
}
else {
bhp.setValue(-1e100); //Signal that this value has not been calculated properly, due to "missing" table
}
return bhp;
}
/**
* Linear interpolation of bhp as a function of the input parameters
* @param table_id Table number to use
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param thp Tubing head pressure
* @param alq Artificial lift or other parameter
*
* @return The bottom hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table.
*/
double bhp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& thp,
const double& alq) const;
/**
* Linear interpolation of thp as a function of the input parameters
* @param table_id Table number to use
* @param aqua Water phase
* @param liquid Oil phase
* @param vapour Gas phase
* @param bhp Bottom hole pressure
* @param alq Artificial lift or other parameter
*
* @return The tubing hole pressure, interpolated/extrapolated linearly using
* the above parameters from the values in the input table.
*/
double thp(int table_id,
const double& aqua,
const double& liquid,
const double& vapour,
const double& bhp,
const double& alq) const;
/**
* Returns the table associated with the ID, or throws an exception if
* the table does not exist
*/
const VFPProdTable* getTable(const int table_id) const;
/**
* Check whether there is table associated with ID
*/
bool hasTable(const int table_id) const;
/**
* Returns true if no vfp tables are in the current map
*/
bool empty() const {
return m_tables.empty();
}
/**
* Calculate the Bhp value from the THP target/constraint value
* based on inflow performance relationship and VFP curves
*/
double
calculateBhpWithTHPTarget(const std::vector<double>& ipr_a,
const std::vector<double>& ipr_b,
const double bhp_limit,
const double thp_table_id,
const double thp_limit,
const double alq,
const double dp) const;
protected:
// calculate a group bhp values with a group of flo rate values
std::vector<double> bhpwithflo(const std::vector<double>& flos,
const int table_id,
const double wfr,
const double gfr,
const double thp,
const double alq,
const double dp) const;
// Map which connects the table number with the table itself
std::map<int, const VFPProdTable*> m_tables;
};
} //namespace
#endif /* OPM_AUTODIFF_VFPPRODPROPERTIES_HPP_ */

View File

@@ -0,0 +1,87 @@
/*
Copyright 2015 SINTEF ICT, Applied Mathematics.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_AUTODIFF_VFPPROPERTIES_HPP_
#define OPM_AUTODIFF_VFPPROPERTIES_HPP_
#include <opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/VFPProdTable.hpp>
#include <map>
namespace Opm {
/**
* A thin wrapper class that holds one VFPProdProperties and one
* VFPInjProperties object.
*/
template<typename VFPInjProp, typename VFPProdProp>
class VFPProperties {
public:
VFPProperties() {}
/**
* Constructor
* Takes *no* ownership of data.
* @param inj_table A *single* VFPINJ table or NULL (no table)
* @param prod_table A *single* VFPPROD table or NULL (no table)
*/
explicit VFPProperties(const VFPInjTable* inj_table, const VFPProdTable* prod_table)
{
if (inj_table != nullptr) {
m_inj.reset(new VFPInjProp(inj_table));
}
if (prod_table != nullptr) {
m_prod.reset(new VFPProdProp(prod_table));
}
}
/**
* Constructor
* Takes *no* ownership of data.
* @param inj_tables A map of different VFPINJ tables.
* @param prod_tables A map of different VFPPROD tables.
*/
VFPProperties(const std::map<int, std::shared_ptr<const VFPInjTable> >& inj_tables,
const std::map<int, std::shared_ptr<const VFPProdTable> >& prod_tables)
: m_inj(new VFPInjProp(inj_tables)), m_prod(new VFPProdProp(prod_tables)) {}
/**
* Returns the VFP properties for injection wells
*/
const VFPInjProp* getInj() const {
return m_inj.get();
}
/**
* Returns the VFP properties for production wells
*/
const VFPProdProp* getProd() const {
return m_prod.get();
}
private:
std::shared_ptr<VFPInjProp> m_inj;
std::shared_ptr<VFPProdProp> m_prod;
};
} //Namespace
#endif /* OPM_AUTODIFF_VFPPROPERTIES_HPP_ */

View File

@@ -0,0 +1,130 @@
/*
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2017 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_WELLCONNECTIONAUXILIARYMODULE_HEADER_INCLUDED
#define OPM_WELLCONNECTIONAUXILIARYMODULE_HEADER_INCLUDED
#include <ewoms/disc/common/baseauxiliarymodule.hh>
#include <opm/grid/CpGrid.hpp>
#include <opm/core/wells.h>
#include <opm/parser/eclipse/EclipseState/EclipseState.hpp>
namespace Opm
{
template<class TypeTag>
class WellConnectionAuxiliaryModule
: public Ewoms::BaseAuxiliaryModule<TypeTag>
{
typedef typename GET_PROP_TYPE(TypeTag, GlobalEqVector) GlobalEqVector;
typedef typename GET_PROP_TYPE(TypeTag, SparseMatrixAdapter) SparseMatrixAdapter;
public:
using NeighborSet = typename
Ewoms::BaseAuxiliaryModule<TypeTag>::NeighborSet;
WellConnectionAuxiliaryModule(const Schedule& schedule,
const Dune::CpGrid& grid)
{
// Create cartesian to compressed mapping
const auto& globalCell = grid.globalCell();
const auto& cartesianSize = grid.logicalCartesianSize();
auto size = cartesianSize[0]*cartesianSize[1]*cartesianSize[2];
std::vector<int> cartesianToCompressed(size, -1);
auto begin = globalCell.begin();
for ( auto cell = begin, end= globalCell.end(); cell != end; ++cell )
{
cartesianToCompressed[ *cell ] = cell - begin;
}
int last_time_step = schedule.getTimeMap().size() - 1;
const auto& schedule_wells = schedule.getWells();
wells_.reserve(schedule_wells.size());
// initialize the additional cell connections introduced by wells.
for ( const auto well : schedule_wells )
{
std::vector<int> compressed_well_perforations;
// All possible completions of the well
const auto& completionSet = well->getConnections(last_time_step);
compressed_well_perforations.reserve(completionSet.size());
for ( size_t c=0; c < completionSet.size(); c++ )
{
const auto& completion = completionSet.get(c);
int i = completion.getI();
int j = completion.getJ();
int k = completion.getK();
int cart_grid_idx = i + cartesianSize[0]*(j + cartesianSize[1]*k);
int compressed_idx = cartesianToCompressed[cart_grid_idx];
if ( compressed_idx >= 0 ) // Ignore completions in inactive/remote cells.
{
compressed_well_perforations.push_back(compressed_idx);
}
}
if ( ! compressed_well_perforations.empty() )
{
std::sort(compressed_well_perforations.begin(),
compressed_well_perforations.end());
wells_.push_back(compressed_well_perforations);
}
}
}
unsigned numDofs() const
{
// No extra dofs are inserted for wells.
return 0;
}
void addNeighbors(std::vector<NeighborSet>& neighbors) const
{
for(const auto well_perforations : wells_)
{
for(const auto& perforation : well_perforations)
neighbors[perforation].insert(well_perforations.begin(),
well_perforations.end());
}
}
void applyInitial()
{}
void linearize(SparseMatrixAdapter& , GlobalEqVector&)
{
// Linearization is done in StandardDenseWells
}
private:
std::vector<std::vector<int> > wells_;
};
} // end namespace OPM
#endif

View File

@@ -0,0 +1,198 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 Statoil ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_WELLHELPERS_HEADER_INCLUDED
#define OPM_WELLHELPERS_HEADER_INCLUDED
#include <opm/core/wells.h>
// #include <opm/autodiff/AutoDiffHelpers.hpp>
#include <vector>
namespace Opm {
namespace wellhelpers
{
inline
double rateToCompare(const std::vector<double>& well_phase_flow_rate,
const int well,
const int num_phases,
const double* distr)
{
double rate = 0.0;
for (int phase = 0; phase < num_phases; ++phase) {
// Important: well_phase_flow_rate is ordered with all phase rates for first
// well first, then all phase rates for second well etc.
rate += well_phase_flow_rate[well*num_phases + phase] * distr[phase];
}
return rate;
}
inline
bool constraintBroken(const std::vector<double>& bhp,
const std::vector<double>& thp,
const std::vector<double>& well_phase_flow_rate,
const int well,
const int num_phases,
const WellType& well_type,
const WellControls* wc,
const int ctrl_index)
{
const WellControlType ctrl_type = well_controls_iget_type(wc, ctrl_index);
const double target = well_controls_iget_target(wc, ctrl_index);
const double* distr = well_controls_iget_distr(wc, ctrl_index);
bool broken = false;
switch (well_type) {
case INJECTOR:
{
switch (ctrl_type) {
case BHP:
broken = bhp[well] > target;
break;
case THP:
broken = thp[well] > target;
break;
case RESERVOIR_RATE: // Intentional fall-through
case SURFACE_RATE:
broken = rateToCompare(well_phase_flow_rate,
well, num_phases, distr) > target;
break;
}
}
break;
case PRODUCER:
{
switch (ctrl_type) {
case BHP:
broken = bhp[well] < target;
break;
case THP:
broken = thp[well] < target;
break;
case RESERVOIR_RATE: // Intentional fall-through
case SURFACE_RATE:
// Note that the rates compared below are negative,
// so breaking the constraints means: too high flow rate
// (as for injection).
broken = rateToCompare(well_phase_flow_rate,
well, num_phases, distr) < target;
break;
}
}
break;
default:
OPM_THROW(std::logic_error, "Can only handle INJECTOR and PRODUCER wells.");
}
return broken;
}
// --------- Types ---------
/**
* Simple hydrostatic correction for VFP table
* @param wells - wells struct
* @param w Well number
* @param vfp_table VFP table
* @param well_perforation_densities Densities at well perforations
* @param gravity Gravitational constant (e.g., 9.81...)
*/
inline
double computeHydrostaticCorrection(const Wells& wells, const int w, double vfp_ref_depth,
const double& rho, const double gravity) {
if ( wells.well_connpos[w] == wells.well_connpos[w+1] )
{
// This is a well with no perforations.
// If this is the last well we would subscript over the
// bounds below.
// we assume well_perforation_densities to be 0
return 0;
}
const double well_ref_depth = wells.depth_ref[w];
const double dh = vfp_ref_depth - well_ref_depth;
const double dp = rho*gravity*dh;
return dp;
}
inline
double computeHydrostaticCorrection(const double well_ref_depth, const double vfp_ref_depth,
const double rho, const double gravity) {
const double dh = vfp_ref_depth - well_ref_depth;
const double dp = rho * gravity * dh;
return dp;
}
template <class Vector>
inline
Vector computeHydrostaticCorrection(const Wells& wells, const Vector vfp_ref_depth,
const Vector& well_perforation_densities, const double gravity) {
const int nw = wells.number_of_wells;
Vector retval = Vector::Zero(nw);
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int i=0; i<nw; ++i) {
const int perf = wells.well_connpos[i];
retval[i] = computeHydrostaticCorrection(wells, i, vfp_ref_depth[i], well_perforation_densities[perf], gravity);
}
return retval;
}
inline
std::vector<double> computeHydrostaticCorrection(const Wells& wells, const std::vector<double>& vfp_ref_depth,
const std::vector<double>& well_perforation_densities, const double gravity) {
const int nw = wells.number_of_wells;
std::vector<double> retval(nw,0.0);
#if HAVE_OPENMP
#pragma omp parallel for schedule(static)
#endif // HAVE_OPENMP
for (int i=0; i<nw; ++i) {
const int perf = wells.well_connpos[i];
retval[i] = computeHydrostaticCorrection(wells, i, vfp_ref_depth[i], well_perforation_densities[perf], gravity);
}
return retval;
}
} // namespace wellhelpers
}
#endif

View File

@@ -0,0 +1,476 @@
/*
Copyright 2017 SINTEF Digital, Mathematics and Cybernetics.
Copyright 2017 Statoil ASA.
Copyright 2017 IRIS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_WELLINTERFACE_HEADER_INCLUDED
#define OPM_WELLINTERFACE_HEADER_INCLUDED
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/common/ErrorMacros.hpp>
#include <opm/common/Exceptions.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/WellTestState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/ScheduleEnums.hpp>
#include <opm/core/wells.h>
#include <opm/core/well_controls.h>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/core/wells/WellsManager.hpp>
#include <opm/core/simulator/SimulatorReport.hpp>
#include <opm/simulators/wells/VFPProperties.hpp>
#include <opm/simulators/wells/WellHelpers.hpp>
#include <opm/simulators/wells/WellStateFullyImplicitBlackoil.hpp>
#include <opm/autodiff/BlackoilModelParametersEbos.hpp>
#include <opm/autodiff/RateConverter.hpp>
#include <opm/simulators/timestepping/ConvergenceReport.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <ewoms/models/blackoil/blackoilpolymermodules.hh>
#include <ewoms/models/blackoil/blackoilsolventmodules.hh>
#include<dune/common/fmatrix.hh>
#include<dune/istl/bcrsmatrix.hh>
#include<dune/istl/matrixmatrix.hh>
#include <opm/material/densead/Math.hpp>
#include <opm/material/densead/Evaluation.hpp>
#include <string>
#include <memory>
#include <vector>
#include <cassert>
namespace Opm
{
template<typename TypeTag>
class WellInterface
{
public:
using WellState = WellStateFullyImplicitBlackoil;
typedef BlackoilModelParametersEbos<TypeTag> ModelParameters;
static const int Water = BlackoilPhases::Aqua;
static const int Oil = BlackoilPhases::Liquid;
static const int Gas = BlackoilPhases::Vapour;
typedef typename GET_PROP_TYPE(TypeTag, Grid) Grid;
typedef typename GET_PROP_TYPE(TypeTag, Simulator) Simulator;
typedef typename GET_PROP_TYPE(TypeTag, FluidSystem) FluidSystem;
typedef typename GET_PROP_TYPE(TypeTag, Indices) Indices;
typedef typename GET_PROP_TYPE(TypeTag, IntensiveQuantities) IntensiveQuantities;
typedef typename GET_PROP_TYPE(TypeTag, MaterialLaw) MaterialLaw;
typedef typename GET_PROP_TYPE(TypeTag, SparseMatrixAdapter) SparseMatrixAdapter;
typedef typename GET_PROP_TYPE(TypeTag, RateVector) RateVector;
static const int numEq = Indices::numEq;
typedef double Scalar;
typedef Dune::FieldVector<Scalar, numEq > VectorBlockType;
typedef Dune::FieldMatrix<Scalar, numEq, numEq > MatrixBlockType;
typedef typename SparseMatrixAdapter::IstlMatrix Mat;
typedef Dune::BlockVector<VectorBlockType> BVector;
typedef DenseAd::Evaluation<double, /*size=*/numEq> Eval;
typedef Ewoms::BlackOilPolymerModule<TypeTag> PolymerModule;
static const bool has_solvent = GET_PROP_VALUE(TypeTag, EnableSolvent);
static const bool has_polymer = GET_PROP_VALUE(TypeTag, EnablePolymer);
static const bool has_energy = GET_PROP_VALUE(TypeTag, EnableEnergy);
// flag for polymer molecular weight related
static const bool has_polymermw = GET_PROP_VALUE(TypeTag, EnablePolymerMW);
static const int contiSolventEqIdx = Indices::contiSolventEqIdx;
static const int contiPolymerEqIdx = Indices::contiPolymerEqIdx;
// index for the polymer molecular weight continuity equation
static const int contiPolymerMWEqIdx = Indices::contiPolymerMWEqIdx;
// For the conversion between the surface volume rate and resrevoir voidage rate
using RateConverterType = RateConverter::
SurfaceToReservoirVoidage<FluidSystem, std::vector<int> >;
/// Constructor
WellInterface(const Well* well, const int time_step, const Wells* wells,
const ModelParameters& param,
const RateConverterType& rate_converter,
const int pvtRegionIdx,
const int num_components);
/// Virutal destructor
virtual ~WellInterface() {}
/// Well name.
const std::string& name() const;
/// Index of well in the wells struct and wellState
int indexOfWell() const;
/// Well cells.
const std::vector<int>& cells() const {return well_cells_; }
/// Well type, INJECTOR or PRODUCER.
WellType wellType() const;
/// Well controls
WellControls* wellControls() const;
void setVFPProperties(const VFPProperties<VFPInjProperties,VFPProdProperties>* vfp_properties_arg);
virtual void init(const PhaseUsage* phase_usage_arg,
const std::vector<double>& depth_arg,
const double gravity_arg,
const int num_cells);
virtual void initPrimaryVariablesEvaluation() const = 0;
virtual ConvergenceReport getWellConvergence(const std::vector<double>& B_avg, Opm::DeferredLogger& deferred_logger) const = 0;
virtual void solveEqAndUpdateWellState(WellState& well_state, Opm::DeferredLogger& deferred_logger) = 0;
virtual void assembleWellEq(const Simulator& ebosSimulator,
const std::vector<Scalar>& B_avg,
const double dt,
WellState& well_state,
Opm::DeferredLogger& deferred_logger
) = 0;
void updateWellTestState(const WellState& well_state,
const double& simulationTime,
const bool& writeMessageToOPMLog,
WellTestState& wellTestState,
Opm::DeferredLogger& deferred_logger) const;
void setWellEfficiencyFactor(const double efficiency_factor);
void computeRepRadiusPerfLength(const Grid& grid, const std::vector<int>& cartesian_to_compressed, Opm::DeferredLogger& deferred_logger);
/// using the solution x to recover the solution xw for wells and applying
/// xw to update Well State
virtual void recoverWellSolutionAndUpdateWellState(const BVector& x,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const = 0;
/// Ax = Ax - C D^-1 B x
virtual void apply(const BVector& x, BVector& Ax) const = 0;
/// r = r - C D^-1 Rw
virtual void apply(BVector& r) const = 0;
// TODO: before we decide to put more information under mutable, this function is not const
virtual void computeWellPotentials(const Simulator& ebosSimulator,
const WellState& well_state,
std::vector<double>& well_potentials,
Opm::DeferredLogger& deferred_logger) = 0;
virtual void updateWellStateWithTarget(const Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) const = 0;
void updateWellControl(/* const */ Simulator& ebos_simulator,
WellState& well_state,
Opm::DeferredLogger& deferred_logger) /* const */;
virtual void updatePrimaryVariables(const WellState& well_state, Opm::DeferredLogger& deferred_logger) const = 0;
virtual void calculateExplicitQuantities(const Simulator& ebosSimulator,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) = 0; // should be const?
/// \brief Wether the Jacobian will also have well contributions in it.
virtual bool jacobianContainsWellContributions() const
{
return false;
}
// updating the voidage rates in well_state when requested
void calculateReservoirRates(WellState& well_state) const;
// Add well contributions to matrix
virtual void addWellContributions(Mat&) const
{}
void addCellRates(RateVector& rates, int cellIdx) const;
Scalar volumetricSurfaceRateForConnection(int cellIdx, int phaseIdx) const;
template <class EvalWell>
Eval restrictEval(const EvalWell& in) const
{
Eval out = 0.0;
out.setValue(in.value());
for(int eqIdx = 0; eqIdx < numEq;++eqIdx) {
out.setDerivative(eqIdx, in.derivative(eqIdx));
}
return out;
}
void closeCompletions(WellTestState& wellTestState);
const Well* wellEcl() const;
// TODO: theoretically, it should be a const function
// Simulator is not const is because that assembleWellEq is non-const Simulator
void wellTesting(Simulator& simulator, const std::vector<double>& B_avg,
const double simulation_time, const int report_step,
const WellTestConfig::Reason testing_reason,
/* const */ WellState& well_state, WellTestState& welltest_state,
Opm::DeferredLogger& deferred_logger);
void updatePerforatedCell(std::vector<bool>& is_cell_perforated);
virtual void checkWellOperability(const Simulator& ebos_simulator, const WellState& well_state, Opm::DeferredLogger& deferred_logger) = 0;
// whether the well is operable
bool isOperable() const;
/// Returns true if the well has one or more THP limits/constraints.
bool wellHasTHPConstraints() const;
/// Returns true if the well is currently in prediction mode (i.e. not history mode).
bool underPredictionMode(Opm::DeferredLogger& deferred_logger) const;
// update perforation water throughput based on solved water rate
virtual void updateWaterThroughput(const double dt, WellState& well_state) const = 0;
protected:
// to indicate a invalid completion
static const int INVALIDCOMPLETION = INT_MAX;
const Well* well_ecl_;
const int current_step_;
// the index of well in Wells struct
int index_of_well_;
// simulation parameters
const ModelParameters& param_;
// well type
// INJECTOR or PRODUCER
enum WellType well_type_;
// number of phases
int number_of_phases_;
// component fractions for each well
// typically, it should apply to injection wells
std::vector<double> comp_frac_;
// controls for this well
struct WellControls* well_controls_;
// number of the perforations for this well
int number_of_perforations_;
// record the index of the first perforation
// of states of individual well.
int first_perf_;
// well index for each perforation
std::vector<double> well_index_;
// depth for each perforation
std::vector<double> perf_depth_;
// reference depth for the BHP
double ref_depth_;
double well_efficiency_factor_;
// cell index for each well perforation
std::vector<int> well_cells_;
// saturation table nubmer for each well perforation
std::vector<int> saturation_table_number_;
// representative radius of the perforations, used in shear calculation
std::vector<double> perf_rep_radius_;
// length of the perforations, use in shear calculation
std::vector<double> perf_length_;
// well bore diameter
std::vector<double> bore_diameters_;
const PhaseUsage* phase_usage_;
bool getAllowCrossFlow() const;
const VFPProperties<VFPInjProperties,VFPProdProperties>* vfp_properties_;
double gravity_;
// For the conversion between the surface volume rate and resrevoir voidage rate
const RateConverterType& rateConverter_;
// The pvt region of the well. We assume
// We assume a well to not penetrate more than one pvt region.
const int pvtRegionIdx_;
const int num_components_;
std::vector<RateVector> connectionRates_;
const PhaseUsage& phaseUsage() const;
int flowPhaseToEbosCompIdx( const int phaseIdx ) const;
int ebosCompIdxToFlowCompIdx( const unsigned compIdx ) const;
double wsolvent() const;
double wpolymer() const;
bool checkRateEconLimits(const WellEconProductionLimits& econ_production_limits,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
double getTHPConstraint(Opm::DeferredLogger& deferred_logger) const;
int getTHPControlIndex() const;
// Component fractions for each phase for the well
const std::vector<double>& compFrac() const;
double mostStrictBhpFromBhpLimits(Opm::DeferredLogger& deferred_logger) const;
// a tuple type for ratio limit check.
// first value indicates whether ratio limit is violated, when the ratio limit is not violated, the following two
// values should not be used.
// second value indicates the index of the worst-offending completion.
// the last value indicates the extent of the violation for the worst-offending completion, which is defined by
// the ratio of the actual value to the value of the violated limit.
using RatioCheckTuple = std::tuple<bool, int, double>;
RatioCheckTuple checkMaxWaterCutLimit(const WellEconProductionLimits& econ_production_limits,
const WellState& well_state) const;
RatioCheckTuple checkRatioEconLimits(const WellEconProductionLimits& econ_production_limits,
const WellState& well_state,
Opm::DeferredLogger& deferred_logger) const;
double scalingFactor(const int comp_idx) const;
// whether a well is specified with a non-zero and valid VFP table number
bool isVFPActive(Opm::DeferredLogger& deferred_logger) const;
struct OperabilityStatus;
OperabilityStatus operability_status_;
void wellTestingEconomic(Simulator& simulator, const std::vector<double>& B_avg,
const double simulation_time, const int report_step,
const WellState& well_state, WellTestState& welltest_state, Opm::DeferredLogger& deferred_logger);
virtual void wellTestingPhysical(Simulator& simulator, const std::vector<double>& B_avg,
const double simulation_time, const int report_step,
WellState& well_state, WellTestState& welltest_state, Opm::DeferredLogger& deferred_logger) = 0;
void updateWellTestStateEconomic(const WellState& well_state,
const double simulation_time,
const bool write_message_to_opmlog,
WellTestState& well_test_state,
Opm::DeferredLogger& deferred_logger) const;
void updateWellTestStatePhysical(const WellState& well_state,
const double simulation_time,
const bool write_message_to_opmlog,
WellTestState& well_test_state,
Opm::DeferredLogger& deferred_logger) const;
void solveWellForTesting(Simulator& ebosSimulator, WellState& well_state,
const std::vector<double>& B_avg,
Opm::DeferredLogger& deferred_logger);
bool solveWellEqUntilConverged(Simulator& ebosSimulator,
const std::vector<double>& B_avg,
WellState& well_state,
Opm::DeferredLogger& deferred_logger);
void scaleProductivityIndex(const int perfIdx, double& productivity_index, const bool new_well, Opm::DeferredLogger& deferred_logger);
// count the number of times an output log message is created in the productivity
// index calculations
int well_productivity_index_logger_counter_;
};
// definition of the struct OperabilityStatus
template<typename TypeTag>
struct
WellInterface<TypeTag>::
OperabilityStatus {
bool isOperable() const {
if (!operable_under_only_bhp_limit) {
return false;
} else {
return ( (isOperableUnderBHPLimit() || isOperableUnderTHPLimit()) );
}
}
bool isOperableUnderBHPLimit() const {
return operable_under_only_bhp_limit && obey_thp_limit_under_bhp_limit;
}
bool isOperableUnderTHPLimit() const {
return can_obtain_bhp_with_thp_limit && obey_bhp_limit_with_thp_limit;
}
void reset() {
operable_under_only_bhp_limit = true;
obey_thp_limit_under_bhp_limit = true;
can_obtain_bhp_with_thp_limit = true;
obey_bhp_limit_with_thp_limit = true;
}
// whether the well can be operated under bhp limit
// without considering other limits.
// if it is false, then the well is not operable for sure.
bool operable_under_only_bhp_limit = true;
// if the well can be operated under bhp limit, will it obey(not violate)
// the thp limit when operated under bhp limit
bool obey_thp_limit_under_bhp_limit = true;
// whether the well operate under the thp limit only
bool can_obtain_bhp_with_thp_limit = true;
// whether the well obey bhp limit when operated under thp limit
bool obey_bhp_limit_with_thp_limit = true;
};
const std::string modestring[4] = { "BHP", "THP", "RESERVOIR_RATE", "SURFACE_RATE" };
}
#include "WellInterface_impl.hpp"
#endif // OPM_WELLINTERFACE_HEADER_INCLUDED

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff