opm-simulators/applications/ebos/eclpeacemanwell.hh
Andreas Lauser 47eafa47f4 move everything which is ECL specific to applications/ebos
this helps to keep the core blackoil model code lean and mean and it
is also less confusing for newbies because the ECL blackoil simulator
is not a "test" anymore.

in case somebody wonders, "ebos" stands for "&eWoms &Black-&Oil
&Simulator". I picked this name because it is short, a syllable, has
not been taken by anything else (as far as I know) and "descriptive"
names are rare for programs anyway: everyone who does not yet know
about 'git' or 'emacs' and tells me that based on their names they
must be a source-code managment system and an editor gets a crate of
beer sponsored by me!
2014-11-28 13:01:32 +01:00

1542 lines
57 KiB
C++

/*
Copyright (C) 2014 by Andreas Lauser
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 2 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/>.
*/
/**
* \file
*
* \copydoc Ewoms::EclPeacemanWell
*/
#ifndef EWOMS_ECL_PEACEMAN_WELL_HH
#define EWOMS_ECL_PEACEMAN_WELL_HH
#include <ewoms/aux/baseauxiliarymodule.hh>
#include <opm/material/fluidstates/CompositionalFluidState.hpp>
#include <opm/core/utility/PropertySystem.hpp>
#include <opm/core/utility/Average.hpp>
#include <dune/common/fmatrix.hh>
#include <dune/common/version.hh>
#include <dune/geometry/referenceelements.hh>
#include <unordered_map>
namespace Opm {
namespace Properties {
NEW_PROP_TAG(Scalar);
NEW_PROP_TAG(Discretization);
NEW_PROP_TAG(FluidSystem);
NEW_PROP_TAG(Simulator);
NEW_PROP_TAG(ElementContext);
NEW_PROP_TAG(RateVector);
NEW_PROP_TAG(GridView);
NEW_PROP_TAG(NumPhases);
NEW_PROP_TAG(NumComponents);
}}
namespace Ewoms {
template <class TypeTag>
class EcfvDiscretization;
/*!
* \brief The well model of Peaceman.
*
* This class is tailored for the element centered finite volume
* discretization, assumes a vertical borehole and is intended to be
* used by the EclWellManager.
*
* See:
*
* Z. Chen, G. Huan, Y. Ma: Computational Methods for Multiphase
* Flows in Porous Media, 1st edition, SIAM, 2006, pp. 445-446
*
* and
*
* D. W. Peaceman: Interpretation of well-block pressures in numerical
* reservoir simulation, The 52nd Annual SPE Fall Technical Conference
* and Exhibition, Denver, CO., 1977
*/
template <class TypeTag>
class EclPeacemanWell : public BaseAuxiliaryModule<TypeTag>
{
typedef BaseAuxiliaryModule<TypeTag> AuxModule;
typedef typename AuxModule::NeighborSet NeighborSet;
typedef typename GET_PROP_TYPE(TypeTag, JacobianMatrix) JacobianMatrix;
typedef typename GET_PROP_TYPE(TypeTag, SolutionVector) SolutionVector;
typedef typename GET_PROP_TYPE(TypeTag, GlobalEqVector) GlobalEqVector;
typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar;
typedef typename GET_PROP_TYPE(TypeTag, Discretization) Discretization;
typedef typename GET_PROP_TYPE(TypeTag, FluidSystem) FluidSystem;
typedef typename GET_PROP_TYPE(TypeTag, Simulator) Simulator;
typedef typename GET_PROP_TYPE(TypeTag, ElementContext) ElementContext;
typedef typename GET_PROP_TYPE(TypeTag, IntensiveQuantities) IntensiveQuantities;
typedef typename GET_PROP_TYPE(TypeTag, RateVector) RateVector;
typedef typename GET_PROP_TYPE(TypeTag, GridView) GridView;
typedef typename GridView::template Codim<0>::EntityPointer ElementPointer;
// the dimension of the simulator's world
static const int dimWorld = GridView::dimensionworld;
// convenient access to the number of phases and the number of
// components
static const int numComponents = GET_PROP_VALUE(TypeTag, NumComponents);
static const int numPhases = GET_PROP_VALUE(TypeTag, NumPhases);
// convenient access to the phase and component indices. If the compiler bails out
// here, you're probably using an incompatible fluid system. This class has only been
// tested with Opm::FluidSystems::BlackOil...
static const int gasPhaseIdx = FluidSystem::gasPhaseIdx;
static const int oilPhaseIdx = FluidSystem::oilPhaseIdx;
static const int waterPhaseIdx = FluidSystem::waterPhaseIdx;
static const int oilCompIdx = FluidSystem::oilCompIdx;
static const int waterCompIdx = FluidSystem::waterCompIdx;
static const int gasCompIdx = FluidSystem::gasCompIdx;
static const int numModelEq = GET_PROP_VALUE(TypeTag, NumEq);
typedef Opm::CompositionalFluidState<Scalar, FluidSystem, /*storeEnthalpy=*/false> FluidState;
typedef Dune::FieldMatrix<Scalar, dimWorld, dimWorld> DimMatrix;
// all quantities that need to be stored per degree of freedom that intersects the
// well.
struct DofVariables {
DofVariables() = default;
DofVariables(const DofVariables&) = default;
// retrieve the solution dependent quantities from the IntensiveQuantities of the
// model
void update(const IntensiveQuantities& intQuants)
{
permeability = intQuants.intrinsicPermeability();
const auto& fs = intQuants.fluidState();
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
pressure[phaseIdx] = fs.pressure(phaseIdx);
density[phaseIdx] = fs.density(phaseIdx);
mobility[phaseIdx] = intQuants.mobility(phaseIdx);
}
for (int compIdx = 0; compIdx < numComponents; ++compIdx) {
oilMassFraction[compIdx] = fs.massFraction(oilPhaseIdx, compIdx);
gasMassFraction[compIdx] = fs.massFraction(gasPhaseIdx, compIdx);
}
}
// the depth of the centroid of the DOF
Scalar depth;
// the volume in m^3 of the DOF
Scalar totalVolume;
// the effective size of an element in each direction. This is defined as the
// distance of the face centers along the respective axis.
std::array<Scalar, dimWorld> effectiveSize;
// the intrinsic permeability matrix for the degree of freedom
DimMatrix permeability;
// the effective permeability of the connection. usually that's the geometric
// mean of the X and Y permeabilities of the DOF times the DOF's height
Scalar effectivePermeability;
// The connection transmissibility factor to be used for a given DOF. this is
// usually computed from the values above but it can be explicitly specified by
// the user...
Scalar connectionTransmissibilityFactor;
// the radius of the well for the given degree of freedom
Scalar boreholeRadius;
// The skin factor of the well at the given degree of freedom
Scalar skinFactor;
//////////////
// the following quantities depend on the considered solution and are thus updated
// at the beginning of each Newton-Raphson iteration.
//////////////
// the phase pressures inside a DOF
std::array<Scalar, numPhases> pressure;
// the phase densities at the DOF
std::array<Scalar, numPhases> density;
// the phase mobilities of the DOF
std::array<Scalar, numPhases> mobility;
// the composition of the oil phase at the DOF
std::array<Scalar, numComponents> oilMassFraction;
// the composition of the gas phase at the DOF
std::array<Scalar, numComponents> gasMassFraction;
std::shared_ptr<ElementPointer> elementPtr;
int localDofIdx;
};
// some safety checks/caveats
static_assert(std::is_same<Discretization, EcfvDiscretization<TypeTag> >::value,
"The Peaceman well model is only implemented for the "
"element-centered finite volume discretization!");
static_assert(dimWorld == 3,
"The Peaceman well model is only implemented for 3D grids!");
public:
enum ControlMode {
BottomHolePressure,
TopHolePressure,
VolumetricSurfaceRate,
VolumetricReservoirRate
};
enum WellType {
Undefined,
Injector,
Producer
};
enum WellStatus {
// production/injection is ongoing
Open,
// no production/injection, but well is only closed above the reservoir, so cross
// flow is possible
Closed,
// well is completely separated from the reservoir, e.g. by filling it with
// concrete.
Shut
};
EclPeacemanWell(const Simulator &simulator)
: simulator_(simulator)
{
// set the composition of the injected fluids based. If
// somebody is stupid enough to inject oil, we assume he wants
// to loose his fortune on dry oil...
for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx)
for (int compIdx = 0; compIdx < numComponents; ++ compIdx)
injectionFluidState_.setMoleFraction(phaseIdx, compIdx, 0.0);
injectionFluidState_.setMoleFraction(gasPhaseIdx, gasCompIdx, 1.0);
injectionFluidState_.setMoleFraction(waterPhaseIdx, waterCompIdx, 1.0);
injectionFluidState_.setMoleFraction(oilPhaseIdx, oilCompIdx, 1.0);
// set the temperature to 25 deg C, just so that it is set
injectionFluidState_.setTemperature(273.15 + 25);
}
/*!
* \copydoc Ewoms::BaseAuxiliaryModule::numDofs()
*/
virtual int numDofs() const
{ return 1; }
/*!
* \copydoc Ewoms::BaseAuxiliaryModule::addNeighbors()
*/
virtual void addNeighbors(std::vector<NeighborSet>& neighbors) const
{
int wellGlobalDof = AuxModule::localToGlobalDof(/*localDofIdx=*/0);
// the well's bottom hole pressure always affects itself...
neighbors[wellGlobalDof].insert(wellGlobalDof);
// add the grid DOFs which are influenced by the well, and add the well dof to
// the ones neighboring the grid ones
auto wellDofIt = dofVariables_.begin();
const auto &wellDofEndIt = dofVariables_.end();
for (; wellDofIt != wellDofEndIt; ++ wellDofIt) {
neighbors[wellGlobalDof].insert(wellDofIt->first);
neighbors[wellDofIt->first].insert(wellGlobalDof);
}
}
/*!
* \copydoc Ewoms::BaseAuxiliaryModule::addNeighbors()
*/
virtual void applyInitial()
{
auto &sol = const_cast<SolutionVector&>(simulator_.model().solution(/*timeIdx=*/0));
int wellGlobalDof = AuxModule::localToGlobalDof(/*localDofIdx=*/0);
sol[wellGlobalDof] = 0.0;
}
/*!
* \copydoc Ewoms::BaseAuxiliaryModule::linearize()
*/
virtual void linearize(JacobianMatrix& matrix, GlobalEqVector& residual)
{
const SolutionVector& curSol = simulator_.model().solution(/*timeIdx=*/0);
int wellGlobalDofIdx = AuxModule::localToGlobalDof(/*localDofIdx=*/0);
residual[wellGlobalDofIdx] = 0.0;
Scalar wellResid = wellResidual_(actualBottomHolePressure_);
residual[wellGlobalDofIdx][0] = wellResid;
auto &diagBlock = matrix[wellGlobalDofIdx][wellGlobalDofIdx];
diagBlock = 0.0;
for (int i = 0; i < numModelEq; ++ i)
diagBlock[i][i] = 1.0;
// account for the effect of the grid DOFs which are influenced by the well on
// the well equation and the effect of the well on the grid DOFs
auto wellDofIt = dofVariables_.begin();
const auto &wellDofEndIt = dofVariables_.end();
ElementContext elemCtx(simulator_);
for (; wellDofIt != wellDofEndIt; ++ wellDofIt) {
unsigned gridDofIdx = wellDofIt->first;
const auto &dofVars = dofVariables_[gridDofIdx];
DofVariables tmpDofVars(dofVars);
auto priVars(curSol[gridDofIdx]);
/////////////
// influence of grid on well
auto &curBlock = matrix[wellGlobalDofIdx][gridDofIdx];
elemCtx.updateStencil(*(*dofVars.elementPtr));
curBlock = 0.0;
for (int priVarIdx = 0; priVarIdx < numModelEq; ++priVarIdx) {
// calculate the derivative of the well equation w.r.t. the current
// primary variable using forward differences
Scalar eps = 1e-6*std::max(1.0, priVars[priVarIdx]);
priVars[priVarIdx] += eps;
elemCtx.updateIntensiveQuantities(priVars, dofVars.localDofIdx, /*timeIdx=*/0);
tmpDofVars.update(elemCtx.intensiveQuantities(dofVars.localDofIdx, /*timeIdx=*/0));
Scalar dWellEq_dPV =
(wellResidual_(actualBottomHolePressure_, &tmpDofVars, gridDofIdx) - wellResid)
/ eps;
curBlock[0][priVarIdx] = dWellEq_dPV;
// go back to the original primary variables
priVars[priVarIdx] -= eps;
}
//
/////////////
/////////////
// influence of well on grid:
RateVector q(0.0);
RateVector modelRate;
std::array<Scalar, numPhases> resvRates;
elemCtx.updateIntensiveQuantities(priVars, dofVars.localDofIdx, /*timeIdx=*/0);
const auto& fluidState = elemCtx.intensiveQuantities(dofVars.localDofIdx, /*timeIdx=*/0).fluidState();
// first, we need the source term of the grid for the slightly disturbed well.
Scalar eps = std::max(1e5, actualBottomHolePressure_)*1e-8;
computeVolumetricDofRates_(resvRates, actualBottomHolePressure_ + eps, dofVariables_[gridDofIdx]);
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
modelRate.setVolumetricRate(fluidState, phaseIdx, resvRates[phaseIdx]);
q += modelRate;
}
// then, we subtract the source rates for a undisturbed well.
computeVolumetricDofRates_(resvRates, actualBottomHolePressure_, dofVariables_[gridDofIdx]);
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
modelRate.setVolumetricRate(fluidState, phaseIdx, resvRates[phaseIdx]);
q -= modelRate;
}
// and finally, we divide by the epsilon to get the derivative
q /= eps;
// now we put this derivative into the right place in the Jacobian
// matrix. This is a bit hacky because it assumes that the model uses a mass
// rate for each component as its first conservation equations, but we
// require the black-oil model for now anyway, so this should not be too much
// of a problem...
assert(numModelEq == numComponents);
Valgrind::CheckDefined(q);
auto &matrixEntry = matrix[gridDofIdx][wellGlobalDofIdx];
matrixEntry = 0.0;
for (int eqIdx = 0; eqIdx < numModelEq; ++ eqIdx)
matrixEntry[eqIdx][0] = - q[eqIdx]/dofVars.totalVolume;
//
/////////////
}
// effect of changing the well's bottom hole pressure on the well equation
Scalar eps = std::min(1e8, std::max(1e6, targetBottomHolePressure_))*1e-8;
Scalar wellResidStar = wellResidual_(actualBottomHolePressure_ + eps);
diagBlock[0][0] = (wellResidStar - wellResid)/eps;
}
// reset the well to the initial state, i.e. remove all degrees of freedom...
void clear()
{
dofVariables_.clear();
}
/*!
* \brief Begin the specification of the well.
*
* The specification process is the following:
*
* beginSpec()
* setName("FOO");
* // add degrees of freedom to the well
* for (dof in wellDofs)
* addDof(dof);
*
* // set the radius of the well at the dof [m].
* // optional, if not specified, it is assumed to be 0.1524m
* setRadius(dof, someRadius);
*
* // set the skin factor of the well.
* // optional, if not specified, it is assumed to be 0
* setSkinFactor(dof, someSkinFactor);
* endSpec()
*
* // specify the phase which is supposed to be injected. (Optional,
* // if unspecified, the well will throw an
* // exception if it would inject something.)
* setInjectedPhaseIndex(phaseIdx);
*
* // set maximum production rate at reservoir conditions
* // (kg/s, optional, if not specified, the well is assumed to be
* // shut for production)
* setMaximumReservoirRate(someMassRate);
*
* // set maximum injection rate at reservoir conditions
* // (kg/s, optional, if not specified, the well is assumed to be
* // shut for injection)
* setMinmumReservoirRate(someMassRate);
*
* // set the relative weight of the mass rate of a fluid phase.
* // (Optional, if unspecified each phase exhibits a weight of 1)
* setPhaseWeight(phaseIdx, someWeight);
*
* // set maximum production rate at surface conditions
* // (kg/s, optional, if not specified, the well is assumed to be
* // not limited by the surface rate)
* setMaximumSurfaceRate(someMassRate);
*
* // set maximum production rate at surface conditions
* // (kg/s, optional, if not specified, the well is assumed to be
* // not limited by the surface rate)
* setMinimumSurfaceRate(someMassRate);
*
* // set the minimum pressure at the bottom of the well (Pa,
* // optional, if not specified, the well is assumes it estimates
* // the bottom hole pressure based on the top hole pressure
* // assuming hydrostatic conditions.)
* setMinimumBottomHolePressure(somePressure);
*
* // set the pressure at the top of the well (Pa,
* // optional, if not specified, the top hole pressure is
* // assumed to be 1 bar)
* setTopHolePressure(somePressure);
*
* // set the control mode of the well [m].
* // optional, if not specified, it is assumed to be "BottomHolePressure"
* setControlMode(Well::TopHolePressure);
*
* // set the top hole pressure of the well [Pa]
* // only require if the control mode is "TopHolePressure"
* setTopHolePressure(1e5);
*/
void beginSpec()
{
// this is going to be increased by any realistic grid. Shall we bet?
bottomDepth_ = -1e100;
bottomDofGlobalIdx_ = -1;
// By default, take the bottom hole pressure as a given
controlMode_ = ControlMode::BottomHolePressure;
// use one bar for the default bottom and top hole
// pressures. For the bottom hole pressure, this is probably
// off by at least one magnitude...
bhpLimit_ = 1e5;
thpLimit_ = 1e5;
// reset the actually observed bottom hole pressure
actualBottomHolePressure_ = 0.0;
// By default, all fluids exhibit the weight 1.0
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
volumetricWeight_[phaseIdx] = 1.0;
wellType_ = Undefined;
wellTotalVolume_ = 0.0;
}
/*!
* \brief Set the relative weight of the volumetric phase rates.
*/
void setVolumetricPhaseWeights(Scalar oilWeight, Scalar gasWeight, Scalar waterWeight)
{
volumetricWeight_[oilPhaseIdx] = oilWeight;
volumetricWeight_[gasPhaseIdx] = gasWeight;
volumetricWeight_[waterPhaseIdx] = waterWeight;
}
/*!
* \brief Return the human-readable name of the well
*
* Well, let's say "readable by some humans".
*/
const std::string &name() const
{ return name_; }
/*!
* \brief Set the human-readable name of the well
*/
void setName(const std::string &newName)
{ name_ = newName; }
/*!
* \brief Add a degree of freedom to the well.
*/
template <class Context>
void addDof(const Context &context, int dofIdx)
{
int globalDofIdx = context.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
if (applies(globalDofIdx))
// we already have this DOF in the well!
return;
const auto &dofPos = context.pos(dofIdx, /*timeIdx=*/0);
DofVariables &dofVars = dofVariables_[globalDofIdx];
wellTotalVolume_ += context.model().dofTotalVolume(globalDofIdx);
dofVars.elementPtr.reset(new ElementPointer(context.element()));
dofVars.localDofIdx = dofIdx;
// determine the size of the element
dofVars.effectiveSize.fill(0.0);
// we assume all elements to be hexahedrons!
assert(context.element().template count</*codim=*/dimWorld>() == 8);
#if DUNE_VERSION_NEWER(DUNE_COMMON, 2,3)
const auto &refElem = Dune::ReferenceElements<Scalar, /*dim=*/3>::cube();
#else
const auto &refElem = Dune::GenericReferenceElements<Scalar, /*dim=*/3>::cube();
#endif
// determine the current element's effective size
const auto &elem = context.element();
int faceIdx = 0;
int numFaces = refElem.size(/*codim=*/1);
for (; faceIdx < numFaces; ++faceIdx) {
const auto &faceCenterLocal = refElem.position(faceIdx, /*codim=*/1);
const auto &faceCenter = elem.geometry().global(faceCenterLocal);
switch (faceIdx) {
case 0:
dofVars.effectiveSize[0] -= faceCenter[0];
break;
case 1:
dofVars.effectiveSize[0] += faceCenter[0];
break;
case 2:
dofVars.effectiveSize[1] -= faceCenter[1];
break;
case 3:
dofVars.effectiveSize[1] += faceCenter[1];
break;
case 4:
dofVars.depth += faceCenter[2];
dofVars.effectiveSize[2] -= faceCenter[2];
break;
case 5:
dofVars.depth += faceCenter[2];
dofVars.effectiveSize[2] += faceCenter[2];
break;
}
}
// the volume associated with the DOF
dofVars.totalVolume = context.model().dofTotalVolume(globalDofIdx);
// the depth of the degree of freedom
dofVars.depth /= 2;
// default borehole radius: 1/2 foot
dofVars.boreholeRadius = 0.3048/2;
// default skin factor: 0
dofVars.skinFactor = 0;
// the permeability tensor of the DOF
const auto& K = context.problem().intrinsicPermeability(context, dofIdx, /*timeIdx=*/0);
dofVars.permeability = K;
// default the effective permeability: Geometric mean of the x and y components
// of the intrinsic permeability of DOF times the DOF's height.
assert(K[0][0] > 0);
assert(K[1][1] > 0);
dofVars.effectivePermeability =
std::sqrt(K[0][0]*K[1][1])*dofVars.effectiveSize[2];
// from that, compute the default connection transmissibility factor
computeConnectionTransmissibilityFactor_(globalDofIdx);
// we assume that the z-coordinate represents depth (and not
// height) here...
if (dofPos[2] > bottomDepth_) {
bottomDofGlobalIdx_ = globalDofIdx;
bottomDepth_ = dofPos[2];
}
}
/*!
* \brief Finalize the specification of the borehole.
*/
void endSpec()
{
const auto& comm = simulator_.gridView().comm();
// determine the maximum depth of the well over all processes
bottomDepth_ = comm.max(bottomDepth_);
// the total volume of the well must also be summed over all processes
wellTotalVolume_ = comm.sum(wellTotalVolume_);
}
/*!
* \brief Set the control mode of the well.
*
* This specifies which quantities are assumed to be externally
* given and which must be calculated based on those.
*/
void setControlMode(ControlMode controlMode)
{ controlMode_ = controlMode; }
/*!
* \brief Set the connection transmissibility factor for a given degree of freedom.
*/
template <class Context>
void setConnectionTransmissibilityFactor(const Context &context, int dofIdx, Scalar value)
{
int globalDofIdx = context.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
dofVariables_[globalDofIdx].connectionTransmissibilityFactor = value;
}
/*!
* \brief Set the effective permeability Kh to be used for a given degree of freedom.
*
* By default, Kh is sqrt(K_xx * K_yy) * h, where K_xx and K_yy is the permeability
* for the DOF in X and Y directions and h is the height associated with the degree
* of freedom.
*
* Note: The connection transmissibility factor is updated after calling this method,
* so if setConnectionTransmissibilityFactor() is to have any effect, it should
* be called after setEffectivePermeability()!
*/
template <class Context>
void setEffectivePermeability(const Context &context, int dofIdx, Scalar value)
{
int globalDofIdx = context.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
dofVariables_[globalDofIdx].effectivePermeability = value;
computeConnectionTransmissibilityFactor_(globalDofIdx);
}
/*!
* \brief Set the type of the well (i.e., injector or producer).
*/
void setWellType(WellType wellType)
{ wellType_ = wellType; }
/*!
* \brief Returns the type of the well (i.e., injector or producer).
*/
WellType wellType() const
{ return wellType_; }
/*!
* \brief Set the index of fluid phase to be injected.
*
* This is only relevant if the well type is an injector.
*/
void setInjectedPhaseIndex(int injPhaseIdx)
{ injectedPhaseIdx_ = injPhaseIdx; }
/*!
* \brief The Z-coordinate of the well's deepest degree of freedom
*/
Scalar bottomDepth() const
{ return bottomDepth_; }
/*!
* \brief Set whether the well is open,closed or shut
*/
void setWellStatus(WellStatus status)
{ wellStatus_ = status; }
/*!
* \brief Return whether the well is open,closed or shut
*/
WellStatus wellStatus() const
{ return wellStatus_; }
/*!
* \brief Return true iff a degree of freedom is directly affected
* by the well
*/
bool applies(int globalDofIdx) const
{ return dofVariables_.count(globalDofIdx) > 0; }
/*!
* \brief Set the maximum/minimum bottom hole pressure [Pa] of the well.
*/
void setTargetBottomHolePressure(Scalar val)
{ bhpLimit_ = val; }
/*!
* \brief Return the maximum/minimum bottom hole pressure [Pa] of the well.
*
* For injectors, this is the maximum, for producers it's the minimum.
*/
Scalar targetBottomHolePressure() const
{ return thpLimit_; }
/*!
* \brief Return the maximum/minimum bottom hole pressure [Pa] of the well.
*/
Scalar bottomHolePressure() const
{ return actualBottomHolePressure_; }
/*!
* \brief Set the top hole pressure [Pa] of the well.
*/
void setTargetTopHolePressure(Scalar val)
{ thpLimit_ = val; }
/*!
* \brief Return the maximum/minimum top hole pressure [Pa] of the well.
*
* For injectors, this is the maximum, for producers it's the minimum.
*/
Scalar targetTopHolePressure() const
{ return thpLimit_; }
/*!
* \brief Return the maximum/minimum top hole pressure [Pa] of the well.
*/
Scalar topHolePressure() const
{
// warning: this is a bit hacky...
Scalar rho = 650; // kg/m^3
Scalar g = 9.81; // m/s^2
return actualBottomHolePressure_ + rho*bottomDepth_*g;
}
/*!
* \brief Set the maximum combined rate of the fluids at the surface.
*/
void setMaximumSurfaceRate(Scalar value)
{ maximumSurfaceRate_ = value; }
/*!
* \brief Return the weighted maximum surface rate [m^3/s] of the well.
*/
Scalar maximumSurfaceRate() const
{ return maximumSurfaceRate_; }
/*!
* \brief Set the maximum combined rate of the fluids at the surface.
*/
void setMaximumReservoirRate(Scalar value)
{ maximumReservoirRate_ = value; }
/*!
* \brief Return the weighted maximum reservoir rate [m^3/s] of the well.
*/
Scalar maximumReservoirRate() const
{ return maximumReservoirRate_; }
/*!
* \brief Return the reservoir rate [m^3/s] actually seen by the well in the current time
* step.
*/
Scalar reservoirRate() const
{ return actualWeightedResvRate_; }
/*!
* \brief Return the weighted surface rate [m^3/s] actually seen by the well in the current time
* step.
*/
Scalar surfaceRate() const
{ return actualWeightedSurfaceRate_; }
/*!
* \brief Return the reservoir rate [m^3/s] of a given fluid which is actually seen
* by the well in the current time step.
*/
Scalar reservoirRate(int phaseIdx) const
{ return actualResvRates_[phaseIdx]; }
/*!
* \brief Return the weighted surface rate [m^3/s] of a given fluid which is actually
* seen by the well in the current time step.
*/
Scalar surfaceRate(int phaseIdx) const
{ return actualSurfaceRates_[phaseIdx]; }
/*!
* \brief Set the skin factor of the well
*
* Note: The connection transmissibility factor is updated after calling this method,
* so if setConnectionTransmissibilityFactor() is to have any effect, it should
* be called after setSkinFactor()!
*/
template <class Context>
void setSkinFactor(const Context &context, int dofIdx, Scalar value)
{
int globalDofIdx = context.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
dofVariables_[globalDofIdx].skinFactor = value;
computeConnectionTransmissibilityFactor_(globalDofIdx);
}
/*!
* \brief Return the well's skin factor at a DOF [-].
*/
Scalar skinFactor(int gridDofIdx) const
{ return dofVariables_.at(gridDofIdx).skinFactor_; }
/*!
* \brief Set the borehole radius of the well
*
* Note: The connection transmissibility factor is updated after calling this method,
* so if setConnectionTransmissibilityFactor() is to have any effect, it should
* be called after setRadius()!
*/
template <class Context>
void setRadius(const Context &context, int dofIdx, Scalar value)
{
int globalDofIdx = context.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
dofVariables_[globalDofIdx].boreholeRadius = value;
computeConnectionTransmissibilityFactor_(globalDofIdx);
}
/*!
* \brief Return the well's radius at a cell [m].
*/
Scalar radius(int gridDofIdx) const
{ return dofVariables_.at(gridDofIdx).radius_; }
/*!
* \brief Informs the well that a time step has just begun.
*/
void beginTimeStep()
{
// calculate the bottom hole pressure to be actually used
if (controlMode_ == ControlMode::TopHolePressure) {
// assume a density of 650 kg/m^3 for the bottom hole pressure
// calculation
Scalar rho = 650.0;
targetBottomHolePressure_ = thpLimit_ + rho*bottomDepth_;
}
else if (controlMode_ == ControlMode::BottomHolePressure)
targetBottomHolePressure_ = bhpLimit_;
else
// TODO: also take the top hole pressure limit into account...
targetBottomHolePressure_ = bhpLimit_;
// make it very likely that we screw up if we control for {surface,reservoir}
// rate, but depend on the {reservoir,surface} rate somewhere...
if (controlMode_ == ControlMode::VolumetricSurfaceRate)
maximumReservoirRate_ = 1e100;
else if (controlMode_ == ControlMode::VolumetricReservoirRate)
maximumSurfaceRate_ = 1e100;
}
/*!
* \brief Informs the well that an iteration has just begun.
*
* The beginIteration*() methods, the well calculates the bottom
* and top hole pressures, the actual unconstraint production and
* injection rates, etc. The callback is split into three parts as
* this arrangement avoids iterating over the whole grid and to
* re-calculate the volume variables for each well.
*
* This is supposed to prepare the well object to do the
* computations which are required to do the DOF specific
* things.
*/
void beginIterationPreProcess()
{ }
/*!
* \brief Do the DOF specific part at the beginning of each iteration
*/
template <class Context>
void beginIterationAccumulate(Context &context, int timeIdx)
{
for (int dofIdx = 0; dofIdx < context.numPrimaryDof(timeIdx); ++dofIdx) {
int globalDofIdx = context.globalSpaceIndex(dofIdx, timeIdx);
if (!applies(globalDofIdx))
continue;
DofVariables &dofVars = dofVariables_.at(globalDofIdx);
const auto& intQuants = context.intensiveQuantities(dofIdx, timeIdx);
const auto& fs = intQuants.fluidState();
for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx) {
dofVars.pressure[phaseIdx] = fs.pressure(phaseIdx);
dofVars.density[phaseIdx] = fs.density(phaseIdx);
dofVars.mobility[phaseIdx] = intQuants.mobility(phaseIdx);
}
for (int compIdx = 0; compIdx < numComponents; ++ compIdx) {
dofVars.oilMassFraction[compIdx] = fs.massFraction(oilPhaseIdx, compIdx);
dofVars.gasMassFraction[compIdx] = fs.massFraction(gasPhaseIdx, compIdx);
}
}
}
/*!
* \brief Informs the well that an iteration has just begun.
*
* This is the post-processing part which uses the results of the
* accumulation callback.
*/
void beginIterationPostProcess()
{
auto &sol = const_cast<SolutionVector&>(simulator_.model().solution(/*timeIdx=*/0));
int wellGlobalDof = AuxModule::localToGlobalDof(/*localDofIdx=*/0);
// retrieve the bottom hole pressure from the global system of equations
actualBottomHolePressure_ = sol[wellGlobalDof][0];
actualBottomHolePressure_ = computeRateEquivalentBhp_();
sol[wellGlobalDof][0] = actualBottomHolePressure_;
computeOverallRates_(actualBottomHolePressure_,
actualResvRates_,
actualSurfaceRates_);
actualWeightedResvRate_ = computeWeightedRate_(actualResvRates_);
actualWeightedSurfaceRate_ = computeWeightedRate_(actualSurfaceRates_);
}
/*!
* \brief Called by the simulator after each Newton-Raphson iteration.
*/
void endIteration()
{ }
/*!
* \brief Called by the simulator after each time step.
*/
void endTimeStep()
{
// we use a condition that is always false here to prevent the code below from
// bitrotting. (i.e., at least it stays compileable)
if (false && simulator_.gridView().comm().rank() == 0) {
std::cout << "Well '" << name() << "':\n";
std::cout << " Control mode: " << controlMode_ << "\n";
std::cout << " BHP limit: " << bhpLimit_/1e5 << " bar\n";
std::cout << " Observed BHP: " << actualBottomHolePressure_/1e5 << " bar\n";
std::cout << " Weighted surface rate limit: " << maximumSurfaceRate_ << "\n";
std::cout << " Weighted surface rate: " << std::abs(actualWeightedSurfaceRate_) << " (="
<< 100*std::abs(actualWeightedSurfaceRate_)/maximumSurfaceRate_ << "%)\n";
std::cout << " Surface rates:\n";
std::cout << " oil: "
<< actualSurfaceRates_[oilPhaseIdx] << " m^3/s = "
<< actualSurfaceRates_[oilPhaseIdx]*(24*60*60) << " m^3/day = "
<< actualSurfaceRates_[oilPhaseIdx]*(24*60*60)/0.15898729 << " STB/day = "
<< actualSurfaceRates_[oilPhaseIdx]*(24*60*60)
*FluidSystem::referenceDensity(oilPhaseIdx) << " kg/day"
<< "\n";
std::cout << " gas: "
<< actualSurfaceRates_[gasPhaseIdx] << " m^3/s = "
<< actualSurfaceRates_[gasPhaseIdx]*(24*60*60) << " m^3/day = "
<< actualSurfaceRates_[gasPhaseIdx]*(24*60*60)/28.316847 << " MCF/day = "
<< actualSurfaceRates_[gasPhaseIdx]*(24*60*60)
*FluidSystem::referenceDensity(gasPhaseIdx) << " kg/day"
<< "\n";
std::cout << " water: "
<< actualSurfaceRates_[waterPhaseIdx] << " m^3/s = "
<< actualSurfaceRates_[waterPhaseIdx]*(24*60*60) << " m^3/day = "
<< actualSurfaceRates_[waterPhaseIdx]*(24*60*60)/0.15898729 << " STB/day = "
<< actualSurfaceRates_[waterPhaseIdx]*(24*60*60)
*FluidSystem::referenceDensity(waterPhaseIdx) << " kg/day"
<< "\n";
}
}
/*!
* \brief Computes the source term for a degree of freedom.
*/
template <class Context>
void computeTotalRatesForDof(RateVector &q,
const Context &context,
int dofIdx,
int timeIdx) const
{
q = 0.0;
int globalDofIdx = context.globalSpaceIndex(dofIdx, timeIdx);
if (wellStatus() == Shut || !applies(globalDofIdx))
return;
// create a DofVariables object for the current evaluation point
DofVariables tmp(dofVariables_.at(globalDofIdx));
tmp.update(context.intensiveQuantities(dofIdx, timeIdx));
Scalar bhp = actualBottomHolePressure_;
std::array<Scalar, numPhases> volumetricRates;
computeVolumetricDofRates_(volumetricRates, bhp, tmp);
// convert to mass rates
RateVector modelRate;
const auto &intQuants = context.intensiveQuantities(dofIdx, timeIdx);
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
modelRate.setVolumetricRate(intQuants.fluidState(), phaseIdx, volumetricRates[phaseIdx]);
q += modelRate;
}
Valgrind::CheckDefined(q);
}
/*!
* \brief This method writes the complete state of the well
* to the harddisk.
*/
template <class Restarter>
void serialize(Restarter &res)
{
res.serializeSectionBegin("PeacemanWell");
res.serializeStream()
<< thpLimit_ << " "
<< bhpLimit_ << " "
<< controlMode_ << " "
<< wellType_ << " "
<< maximumSurfaceRate_ << " "
<< maximumReservoirRate_ << " "
<< wellStatus_ << " "
<< injectedPhaseIdx_ << " ";
// fluid state
for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx)
res.serializeStream()
<< volumetricWeight_[phaseIdx] << " ";
res.serializeSectionEnd();
}
/*!
* \brief This method restores the complete state of the well
* from disk.
*
* It is the inverse of the serialize() method.
*
* \tparam Restarter The deserializer type
*
* \param res The deserializer object
*/
template <class Restarter>
void deserialize(Restarter &res)
{
res.deserializeSectionBegin("PeacemanWell");
res.deserializeStream()
>> thpLimit_
>> bhpLimit_
>> controlMode_
>> wellType_
>> maximumSurfaceRate_
>> maximumReservoirRate_
>> wellStatus_
>> injectedPhaseIdx_;
// fluid state
for (int phaseIdx = 0; phaseIdx < numPhases; ++ phaseIdx)
res.serializeStream()
>> volumetricWeight_[phaseIdx];
res.deserializeSectionEnd();
}
protected:
// compute the connection transmissibility factor based on the effective permeability
// of a connection, the radius of the borehole and the skin factor.
void computeConnectionTransmissibilityFactor_(int globalDofIdx)
{
auto& dofVars = dofVariables_[globalDofIdx];
const auto& D = dofVars.effectiveSize;
const auto& K = dofVars.permeability;
Scalar Kh = dofVars.effectivePermeability;
Scalar S = dofVars.skinFactor;
Scalar rWell = dofVars.boreholeRadius;
// compute the "equivalence radius" r_0 of the connection
assert(K[0][0] > 0.0);
assert(K[1][1] > 0.0);
Scalar tmp1 = std::sqrt(K[1][1]/K[0][0]);
Scalar tmp2 = 1.0 / tmp1;
Scalar r0 = std::sqrt(D[0]*D[0]*tmp1 + D[1]*D[1]*tmp2);
r0 /= std::sqrt(tmp1) + std::sqrt(tmp2);
r0 *= 0.28;
// we assume the well borehole in the center of the dof and that it is vertical,
// i.e., the area which is exposed to the flow is 2*pi*r0*h. (for non-vertical
// wells this would need to be multiplied with the cosine of the angle and the
// height must be adapted...)
const Scalar exposureFactor = 2*M_PI;
dofVars.connectionTransmissibilityFactor = exposureFactor*Kh/(std::log(r0 / rWell) + S);
}
void computeVolumetricDofRates_(std::array<Scalar, numPhases> &volRates,
Scalar bottomHolePressure,
const DofVariables& dofVars) const
{
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
volRates[phaseIdx] = 0.0;
// connection transmissibility factor for the current DOF.
Scalar Twj = dofVars.connectionTransmissibilityFactor;
// bottom hole pressure and depth of the degree of freedom
Scalar pbh = bottomHolePressure;
Scalar depth = dofVars.depth;
// gravity constant
Scalar g = 9.81;
typename FluidSystem::ParameterCache paramCache;
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
// well model due to Peaceman; see Chen et al., p. 449
// phase pressure in grid cell
Scalar p = dofVars.pressure[phaseIdx];
// density and mobility of fluid phase
Scalar rho;
Scalar lambda;
if (wellType_ == Producer) {
//assert(p < pbh);
rho = dofVars.density[phaseIdx];
lambda = dofVars.mobility[phaseIdx];
}
else if (wellType_ == Injector) {
//assert(p > pbh);
if (phaseIdx != injectedPhaseIdx_)
continue;
injectionFluidState_.setPressure(phaseIdx, p);
typename FluidSystem::ParameterCache paramCache;
paramCache.updateAll(injectionFluidState_);
rho = FluidSystem::density(injectionFluidState_, paramCache, phaseIdx);
lambda = 1.0/FluidSystem::viscosity(injectionFluidState_, paramCache, phaseIdx);
}
else
OPM_THROW(std::logic_error,
"Type of well \"" << name() << "\" is undefined");
Valgrind::CheckDefined(pbh);
Valgrind::CheckDefined(p);
Valgrind::CheckDefined(g);
Valgrind::CheckDefined(rho);
Valgrind::CheckDefined(lambda);
Valgrind::CheckDefined(depth);
Valgrind::CheckDefined(bottomDepth_);
// pressure in the borehole ("hole pressure") at the given location
Scalar ph = pbh + rho*g*(bottomDepth_ - depth);
// volumetric flux of the phase from the well to the reservoir
volRates[phaseIdx] = Twj*lambda*(ph - p);
Valgrind::CheckDefined(g);
Valgrind::CheckDefined(ph);
Valgrind::CheckDefined(volRates[phaseIdx]);
}
}
/*!
* \brief Given the volumetric rates for all phases, return the
* corresponding weighted rate
*
* The weights are user-specified and can be set using
* setVolumetricPhaseWeights()
*/
Scalar computeWeightedRate_(const std::array<Scalar, numPhases> &volRates) const
{
Scalar result = 0;
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
result += volRates[phaseIdx]*volumetricWeight_[phaseIdx];
return result;
}
/*!
* \brief Convert volumetric reservoir rates into volumetric volume rates.
*
* This requires the density and composition of the phases and
* thus the applicable fluid state.
*/
void computeSurfaceRates_(std::array<Scalar, numPhases> &surfaceRates,
const std::array<Scalar, numPhases> &reservoirRate,
const DofVariables& dofVars) const
{
// the array for the surface rates and the one for the reservoir rates must not
// be the same!
assert(&surfaceRates != &reservoirRate);
// If your compiler bails out here, you have not chosen the correct fluid
// system. Currently, only Opm::FluidSystems::BlackOil is supported, sorry...
Scalar rhoOilSurface = FluidSystem::referenceDensity(oilPhaseIdx, /*regionIdx=*/0);
Scalar rhoGasSurface = FluidSystem::referenceDensity(gasPhaseIdx, /*regionIdx=*/0);
Scalar rhoWaterSurface = FluidSystem::referenceDensity(waterPhaseIdx, /*regionIdx=*/0);
// oil
surfaceRates[oilPhaseIdx] =
// oil in gas phase
reservoirRate[gasPhaseIdx]
* dofVars.density[gasPhaseIdx]
* dofVars.gasMassFraction[oilCompIdx]
/ rhoOilSurface
+
// oil in oil phase
reservoirRate[oilPhaseIdx]
* dofVars.density[oilPhaseIdx]
* dofVars.oilMassFraction[oilCompIdx]
/ rhoOilSurface;
// gas
surfaceRates[gasPhaseIdx] =
// gas in gas phase
reservoirRate[gasPhaseIdx]
* dofVars.density[gasPhaseIdx]
* dofVars.gasMassFraction[gasCompIdx]
/ rhoGasSurface
+
// gas in oil phase
reservoirRate[oilPhaseIdx]
* dofVars.density[oilPhaseIdx]
* dofVars.oilMassFraction[gasCompIdx]
/ rhoGasSurface;
// water
surfaceRates[waterPhaseIdx] =
reservoirRate[waterPhaseIdx]
* dofVars.density[waterPhaseIdx]
/ rhoWaterSurface;
}
/*!
* \brief Compute the volumetric phase rate of the complete well given a bottom hole
* pressure.
*
* A single degree of freedom may be different from the evaluation point.
*/
void computeOverallRates_(Scalar bottomHolePressure,
std::array<Scalar, numPhases>& overallResvRates,
std::array<Scalar, numPhases>& overallSurfaceRates,
const DofVariables *evalDofVars = 0,
int globalEvalDofIdx = -1) const
{
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
overallResvRates[phaseIdx] = 0.0;
overallSurfaceRates[phaseIdx] = 0.0;
}
auto dofVarsIt = dofVariables_.begin();
const auto &dofVarsEndIt = dofVariables_.end();
for (; dofVarsIt != dofVarsEndIt; ++ dofVarsIt) {
std::array<Scalar, numPhases> volumetricReservoirRates;
const DofVariables *tmp;
if (dofVarsIt->first == globalEvalDofIdx)
tmp = evalDofVars;
else
tmp = &dofVarsIt->second;
computeVolumetricDofRates_(volumetricReservoirRates, bottomHolePressure, *tmp);
std::array<Scalar, numPhases> volumetricSurfaceRates;
computeSurfaceRates_(volumetricSurfaceRates, volumetricReservoirRates, *tmp);
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
overallResvRates[phaseIdx] += volumetricReservoirRates[phaseIdx];
overallSurfaceRates[phaseIdx] += volumetricSurfaceRates[phaseIdx];
}
}
}
/*!
* \brief Compute the weighted volumetric rate of the complete well given a bottom
* hole pressure.
*
* A single degree of freedom may be different from the evaluation point.
*/
Scalar computeOverallWeightedSurfaceRate_(Scalar bottomHolePressure,
std::array<Scalar, numPhases>& overallSurfaceRates,
const DofVariables &evalDofVars,
int globalEvalDofIdx) const
{
static std::array<Scalar, numPhases> resvRatesDummy;
computeOverallRates_(bottomHolePressure,
overallSurfaceRates,
resvRatesDummy,
evalDofVars,
globalEvalDofIdx);
return computeWeightedRate_(overallSurfaceRates);
}
// this is a more convenient version of the method above if all degrees of freedom
// are supposed to be at their evaluation points.
Scalar computeOverallWeightedSurfaceRate_(Scalar bottomHolePressure,
std::array<Scalar, numPhases>& overallSurfaceRates) const
{
// create a dummy DofVariables object and call the method above using an index
// that is guaranteed to never be part of a well...
static DofVariables dummyDofVars;
return computeOverallWeightedSurfaceRate_(bottomHolePressure,
overallSurfaceRates,
dummyDofVars,
/*globalEvalDofIdx=*/-1);
}
/*!
* \brief Compute the "rate-equivalent bottom hole pressure"
*
* I.e. The bottom hole pressure where the well rate is exactly the one which is
* targeted. This is zero of the "rate-equivalent bottom hole pressure" would be
* smaller than 1 bar.
*/
Scalar computeRateEquivalentBhp_() const
{
if (wellStatus() == Shut)
// there is no flow happening in the well, so the "BHP" is the pressure of
// the well's lowest DOF!
return dofVariables_.at(bottomDofGlobalIdx_).pressure[oilPhaseIdx];
// initialize the bottom hole pressure which we would like to calculate
Scalar bhp = actualBottomHolePressure_;
if (bhp > 1e8)
bhp = 1e8;
if (bhp < 1e5)
bhp = 1e5;
// if the BHP goes below 1 bar for the first time, we reset it to 10 bars and
// are "on bail", i.e. if it goes below 1 bar again, we give up because the
// final pressure would be below 1 bar...
bool onBail = false;
// Newton-Raphson method
for (int iterNum = 0; iterNum < 20; ++iterNum) {
Scalar eps = 1e-9*std::abs(bhp);
Scalar f = wellResidual_(bhp);
Scalar fStar = wellResidual_(bhp + eps);
Scalar fPrime = (fStar - f)/eps;
assert(std::abs(fPrime) > 1e-20);
Scalar delta = f/fPrime;
bhp -= delta;
if (bhp < 1e5) {
bhp = 1e5;
if (onBail)
return bhp;
else
onBail = true;
}
else
onBail = false;
if (std::abs(delta) < 1e3*eps)
return bhp;
}
OPM_THROW(Opm::NumericalProblem,
"Could not determine the bottom hole pressure of well '" << name()
<< "' within 20 iterations.");
}
Scalar wellResidual_(Scalar bhp,
const DofVariables *replacementDofVars = 0,
int replacedGridIdx = -1) const
{
// compute the volumetric reservoir and surface rates for the complete well
Scalar resvRate = 0.0;
std::array<Scalar, numPhases> totalSurfaceRates;
std::fill(totalSurfaceRates.begin(), totalSurfaceRates.end(), 0.0);
auto dofVarsIt = dofVariables_.begin();
const auto &dofVarsEndIt = dofVariables_.end();
for (; dofVarsIt != dofVarsEndIt; ++ dofVarsIt) {
std::array<Scalar, numPhases> resvRates;
const DofVariables *dofVars = &dofVarsIt->second;
if (replacedGridIdx == dofVarsIt->first)
dofVars = replacementDofVars;
computeVolumetricDofRates_(resvRates, bhp, *dofVars);
std::array<Scalar, numPhases> surfaceRates;
computeSurfaceRates_(surfaceRates, resvRates, dofVarsIt->second);
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
totalSurfaceRates[phaseIdx] += surfaceRates[phaseIdx];
resvRate += computeWeightedRate_(resvRates);
}
Scalar surfaceRate = computeWeightedRate_(totalSurfaceRates);
// compute the residual of well equation. we currently use max(rateMax - rate,
// bhp - targetBhp) for producers and max(rateMax - rate, bhp - targetBhp) for
// injectors. (i.e., the target bottom hole pressure is an upper limit for
// injectors and a lower limit for producers.) Note that with this approach, one
// of the limits must always be reached to get the well equation to zero...
Valgrind::CheckDefined(maximumSurfaceRate_);
Valgrind::CheckDefined(maximumReservoirRate_);
Valgrind::CheckDefined(surfaceRate);
Valgrind::CheckDefined(resvRate);
Scalar result = 1e100;
Scalar maxSurfaceRate = maximumSurfaceRate_;
Scalar maxResvRate = maximumReservoirRate_;
if (wellStatus() == Closed) {
// make the weight of the fluids on the surface equal and require that no
// fluids are produced on the surface...
maxSurfaceRate = 0.0;
surfaceRate = 0;
for (int phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx)
surfaceRate += totalSurfaceRates[phaseIdx];
// don't care about the reservoir rate...
maxResvRate = 1e100;
}
if (wellType_ == Injector) {
// for injectors the computed rates are positive and the target BHP is the
// maximum allowed pressure ...
result = std::min(maxSurfaceRate - surfaceRate, result);
result = std::min(maxResvRate - resvRate, result);
result = std::min(1e-7*(targetBottomHolePressure_ - bhp), result);
}
else {
assert(wellType_ == Producer);
// ... for producers the rates are negative and the bottom hole pressure is
// is the minimum
result = std::min(maxSurfaceRate + surfaceRate, result);
result = std::min(maxResvRate + resvRate, result);
result = std::min(1e-7*(bhp - targetBottomHolePressure_), result);
}
const Scalar scalingFactor = 1e-3;
return scalingFactor*result;
}
const Simulator &simulator_;
std::string name_;
std::unordered_map<int, DofVariables> dofVariables_;
// the sum of the total volumes of all the degrees of freedoms that interact with the well
Scalar wellTotalVolume_;
// The assumed bottom and top hole pressures as specified by the user
Scalar bhpLimit_;
Scalar thpLimit_;
// specifies the quantities which are controlled for (i.e., which
// should be assumed to be externally specified and which should
// be computed based on those)
ControlMode controlMode_;
// the type of the well (injector, producer or undefined)
WellType wellType_;
// The bottom hole pressure to be targeted by the well model. This may be computed
// from the top hole pressure (if the control mode is TopHolePressure), or it may be
// just the user-specified bottom hole pressure if the control mode is
// BottomHolePressure.
Scalar targetBottomHolePressure_;
// The bottom hole pressure which is actually observed in the well
Scalar actualBottomHolePressure_;
// The maximum weighted volumetric surface rates specified by the
// user. This is used to apply rate limits and it is to be read as
// the maximum absolute value of the rate, i.e., the well can
// produce or inject the given amount.
Scalar maximumSurfaceRate_;
// The maximum weighted volumetric reservoir rates specified by
// the user. This is used to apply rate limits and it is to be
// read as the maximum absolute value of the rate, i.e., the well
// can produce or inject the given amount.
Scalar maximumReservoirRate_;
// The volumetric surface rate which is actually observed in the well
Scalar actualWeightedSurfaceRate_;
std::array<Scalar, numPhases> actualSurfaceRates_;
// The volumetric reservoir rate which is actually observed in the well
Scalar actualWeightedResvRate_;
std::array<Scalar, numPhases> actualResvRates_;
// Specifies whether the well is currently open, closed or shut. The difference
// between "closed" and "shut" is that for the former, the well is assumed to be
// closed above the reservoir so that cross-flow within the well is possible while
// the well is completely separated from the reservoir if it is shut. (i.e., no
// crossflow is possible in this case.)
WellStatus wellStatus_;
// The relative weight of the volumetric rate of each fluid
Scalar volumetricWeight_[numPhases];
// The thermodynamic state of the fluid which gets injected
//
// The fact that this attribute is mutable is kind of an hack
// which can be avoided using a PressureOverlayFluidState, but
// then performance would be slightly worse...
mutable FluidState injectionFluidState_;
int injectedPhaseIdx_;
// the depth of the deepest DOF. (actually, the center of this
// DOF, but the difference should be minimal.)
Scalar bottomDepth_;
// global index of the DOF at the bottom of the well
int bottomDofGlobalIdx_;
};
} // namespace Ewoms
#endif