mirror of
https://github.com/OPM/opm-simulators.git
synced 2025-02-15 11:23:24 -06:00
Revise Mixture Density Method for No-Flow Producers
This commit switches the approach introduced in commit eeb1b7e36
(PR
#3169) to using a mobility weighted average of cell level densities
for the connection level mixture densities in no-flow producing
wells. We also use the recent stoppedOrZeroRateTarget() predicate
to identify those no-flow producing wells instead of inspecting the
connection flow rates.
The mobility weighted average gives a more monotone pressure buildup
for the stopped wells and this is usually what the engineer wants.
This revised approach furthermore needs fewer cell-level dynamic
properties so simplify the computeProperties() signature by
introducing a structure for the property callback functions and
update the callers accordingly.
This commit is contained in:
parent
586d8e2ddc
commit
93c368cbfa
@ -419,6 +419,44 @@ initialiseConnectionMixture(const int num_comp,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class FluidSystem, class Indices>
|
||||||
|
void StandardWellConnections<FluidSystem, Indices>::
|
||||||
|
computeDensitiesForStoppedProducer(const DensityPropertyFunctions& prop_func)
|
||||||
|
{
|
||||||
|
const auto np = this->well_.numPhases();
|
||||||
|
|
||||||
|
const auto modPhIx = [this, np]() {
|
||||||
|
auto phIx = std::vector<int>(np);
|
||||||
|
|
||||||
|
for (auto p = 0*np; p < np; ++p) {
|
||||||
|
phIx[p] = this->well_.flowPhaseToModelPhaseIdx(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return phIx;
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto mob = std::vector<Scalar>(np);
|
||||||
|
auto rho = std::vector<Scalar>(np);
|
||||||
|
|
||||||
|
const auto nperf = this->well_.numPerfs();
|
||||||
|
for (auto perf = 0*nperf; perf < nperf; ++perf) {
|
||||||
|
const auto cell_idx = this->well_.cells()[perf];
|
||||||
|
|
||||||
|
prop_func.mobility (cell_idx, modPhIx, mob);
|
||||||
|
prop_func.densityInCell(cell_idx, modPhIx, rho);
|
||||||
|
|
||||||
|
auto& rho_i = this->perf_densities_[perf];
|
||||||
|
|
||||||
|
auto mt = rho_i = Scalar{0};
|
||||||
|
for (auto p = 0*np; p < np; ++p) {
|
||||||
|
rho_i += mob[p] * rho[p];
|
||||||
|
mt += mob[p];
|
||||||
|
}
|
||||||
|
|
||||||
|
rho_i /= mt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<class FluidSystem, class Indices>
|
template<class FluidSystem, class Indices>
|
||||||
typename StandardWellConnections<FluidSystem, Indices>::Properties
|
typename StandardWellConnections<FluidSystem, Indices>::Properties
|
||||||
StandardWellConnections<FluidSystem,Indices>::
|
StandardWellConnections<FluidSystem,Indices>::
|
||||||
@ -625,85 +663,27 @@ copyInPerforationRates(const Properties& props,
|
|||||||
|
|
||||||
template<class FluidSystem, class Indices>
|
template<class FluidSystem, class Indices>
|
||||||
void StandardWellConnections<FluidSystem,Indices>::
|
void StandardWellConnections<FluidSystem,Indices>::
|
||||||
computeProperties(const WellState<Scalar>& well_state,
|
computeProperties(const bool stopped_or_zero_rate_target,
|
||||||
const std::function<Scalar(int,int)>& invB,
|
const WellState<Scalar>& well_state,
|
||||||
const std::function<Scalar(int,int)>& mobility,
|
const DensityPropertyFunctions& prop_func,
|
||||||
const std::function<Scalar(int)>& solventInverseFormationVolumeFactor,
|
|
||||||
const std::function<Scalar(int)>& solventMobility,
|
|
||||||
const Properties& props,
|
const Properties& props,
|
||||||
DeferredLogger& deferred_logger)
|
DeferredLogger& deferred_logger)
|
||||||
{
|
{
|
||||||
// Compute densities
|
// Step 1: Compute mixture densities in well-bore. Result values stored
|
||||||
const int nperf = well_.numPerfs();
|
// in data member perf_densities_.
|
||||||
const int np = well_.numPhases();
|
if (stopped_or_zero_rate_target && this->well_.isProducer()) {
|
||||||
const auto& ws = well_state.well(well_.indexOfWell());
|
this->computeDensitiesForStoppedProducer(prop_func);
|
||||||
const auto& perf_data = ws.perf_data;
|
}
|
||||||
const auto& perf_rates_state = perf_data.phase_rates;
|
else {
|
||||||
|
// Injector or flowing producer.
|
||||||
auto perfRates = this->copyInPerforationRates
|
const auto perfRates = this->copyInPerforationRates
|
||||||
(props, well_state.well(this->well_.indexOfWell()).perf_data);
|
(props, well_state.well(this->well_.indexOfWell()).perf_data);
|
||||||
|
|
||||||
// for producers where all perforations have zero rate we
|
|
||||||
// approximate the perforation mixture using the mobility ratio
|
|
||||||
// and weight the perforations using the well transmissibility.
|
|
||||||
bool all_zero = std::all_of(perfRates.begin(), perfRates.end(),
|
|
||||||
[](Scalar val) { return val == 0.0; });
|
|
||||||
const auto& comm = well_.parallelWellInfo().communication();
|
|
||||||
if (comm.size() > 1)
|
|
||||||
{
|
|
||||||
all_zero = (comm.min(all_zero ? 1 : 0) == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (all_zero && well_.isProducer()) {
|
|
||||||
Scalar total_tw = 0;
|
|
||||||
for (int perf = 0; perf < nperf; ++perf) {
|
|
||||||
total_tw += well_.wellIndex()[perf];
|
|
||||||
}
|
|
||||||
if (comm.size() > 1)
|
|
||||||
{
|
|
||||||
total_tw = comm.sum(total_tw);
|
|
||||||
}
|
|
||||||
// for producers where all perforations have zero rates we
|
|
||||||
// approximate the perforation mixture ration using the (invB * mobility) ratio,
|
|
||||||
// and weight the perforation rates using the well transmissibility.
|
|
||||||
for (int perf = 0; perf < nperf; ++perf) {
|
|
||||||
const int cell_idx = well_.cells()[perf];
|
|
||||||
const Scalar well_tw_fraction = well_.wellIndex()[perf] / total_tw;
|
|
||||||
Scalar total_mobility = 0.0;
|
|
||||||
Scalar total_invB = 0.;
|
|
||||||
for (int p = 0; p < np; ++p) {
|
|
||||||
int modelPhaseIdx = well_.flowPhaseToModelPhaseIdx(p);
|
|
||||||
total_mobility += invB(cell_idx, modelPhaseIdx) * mobility(cell_idx, modelPhaseIdx);
|
|
||||||
total_invB += invB(cell_idx, modelPhaseIdx);
|
|
||||||
}
|
|
||||||
if constexpr (Indices::enableSolvent) {
|
|
||||||
total_mobility += solventInverseFormationVolumeFactor(cell_idx) * solventMobility(cell_idx);
|
|
||||||
total_invB += solventInverseFormationVolumeFactor(cell_idx);
|
|
||||||
}
|
|
||||||
const bool non_zero_total_mobility = total_mobility > std::numeric_limits<Scalar>::min();
|
|
||||||
assert(total_invB > std::numeric_limits<Scalar>::min());
|
|
||||||
// for the perforation having zero mobility for all the phases, we use a small value to generate a small
|
|
||||||
// perforation rates for those perforations, at the same time, we can use the rates to recover the mixing
|
|
||||||
// ratios for those perforations.
|
|
||||||
constexpr Scalar small_value = 1.e-10;
|
|
||||||
for (int p = 0; p < np; ++p) {
|
|
||||||
const int modelPhaseIdx = well_.flowPhaseToModelPhaseIdx(p);
|
|
||||||
const auto mob_ratio = non_zero_total_mobility
|
|
||||||
? mobility(cell_idx, modelPhaseIdx) / total_mobility
|
|
||||||
: small_value / total_invB;
|
|
||||||
perfRates[perf * well_.numComponents() + p] = well_tw_fraction * invB(cell_idx, modelPhaseIdx) * mob_ratio;
|
|
||||||
}
|
|
||||||
if constexpr (Indices::enableSolvent) {
|
|
||||||
const auto mob_ratio = non_zero_total_mobility
|
|
||||||
? solventMobility(cell_idx) / total_mobility
|
|
||||||
: small_value / total_invB;
|
|
||||||
perfRates[perf * well_.numComponents() + Indices::contiSolventEqIdx] =
|
|
||||||
well_tw_fraction * solventInverseFormationVolumeFactor(cell_idx) * mob_ratio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->computeDensities(perfRates, props, deferred_logger);
|
this->computeDensities(perfRates, props, deferred_logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Use those mixture densities to calculate pressure drops along
|
||||||
|
// well-bore. Result values stored in data member perf_pressure_diffs_.
|
||||||
this->computePressureDelta();
|
this->computePressureDelta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,16 +66,20 @@ public:
|
|||||||
std::function<Scalar(int)> solventRefDensity{};
|
std::function<Scalar(int)> solventRefDensity{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DensityPropertyFunctions
|
||||||
|
{
|
||||||
|
std::function<void(int, const std::vector<int>&, std::vector<Scalar>&)> mobility{};
|
||||||
|
std::function<void(int, const std::vector<int>&, std::vector<Scalar>&)> densityInCell{};
|
||||||
|
};
|
||||||
|
|
||||||
Properties
|
Properties
|
||||||
computePropertiesForPressures(const WellState<Scalar>& well_state,
|
computePropertiesForPressures(const WellState<Scalar>& well_state,
|
||||||
const PressurePropertyFunctions& propFunc) const;
|
const PressurePropertyFunctions& propFunc) const;
|
||||||
|
|
||||||
//! \brief Compute connection properties (densities, pressure drop, ...)
|
//! \brief Compute connection properties (densities, pressure drop, ...)
|
||||||
void computeProperties(const WellState<Scalar>& well_state,
|
void computeProperties(const bool stop_or_zero_rate_target,
|
||||||
const std::function<Scalar(int,int)>& invB,
|
const WellState<Scalar>& well_state,
|
||||||
const std::function<Scalar(int,int)>& mobility,
|
const DensityPropertyFunctions& prop_func,
|
||||||
const std::function<Scalar(int)>& solventInverseFormationVolumeFactor,
|
|
||||||
const std::function<Scalar(int)>& solventMobility,
|
|
||||||
const Properties& props,
|
const Properties& props,
|
||||||
DeferredLogger& deferred_logger);
|
DeferredLogger& deferred_logger);
|
||||||
|
|
||||||
@ -140,6 +144,8 @@ private:
|
|||||||
const Properties& props,
|
const Properties& props,
|
||||||
DeferredLogger& deferred_logger);
|
DeferredLogger& deferred_logger);
|
||||||
|
|
||||||
|
void computeDensitiesForStoppedProducer(const DensityPropertyFunctions& prop_func);
|
||||||
|
|
||||||
std::vector<Scalar>
|
std::vector<Scalar>
|
||||||
calculatePerforationOutflow(const std::vector<Scalar>& perfComponentRates) const;
|
calculatePerforationOutflow(const std::vector<Scalar>& perfComponentRates) const;
|
||||||
|
|
||||||
|
@ -1304,41 +1304,49 @@ namespace Opm
|
|||||||
|
|
||||||
|
|
||||||
template<typename TypeTag>
|
template<typename TypeTag>
|
||||||
void
|
void StandardWell<TypeTag>::
|
||||||
StandardWell<TypeTag>::
|
|
||||||
computeWellConnectionDensitesPressures(const Simulator& simulator,
|
computeWellConnectionDensitesPressures(const Simulator& simulator,
|
||||||
const WellState<Scalar>& well_state,
|
const WellState<Scalar>& well_state,
|
||||||
const WellConnectionProps& props,
|
const WellConnectionProps& props,
|
||||||
DeferredLogger& deferred_logger)
|
DeferredLogger& deferred_logger)
|
||||||
{
|
{
|
||||||
std::function<Scalar(int,int)> invB =
|
// Cell level dynamic property call-back functions as fall-back
|
||||||
[&simulator](int cell_idx, int phase_idx)
|
// option for calculating connection level mixture densities in
|
||||||
|
// stopped or zero-rate producer wells.
|
||||||
|
const auto prop_func = typename StdWellEval::StdWellConnections::DensityPropertyFunctions {
|
||||||
|
// This becomes slightly more palatable with C++20's designated
|
||||||
|
// initialisers.
|
||||||
|
|
||||||
|
// mobility: Phase mobilities in specified cell.
|
||||||
|
[&model = simulator.model()](const int cell,
|
||||||
|
const std::vector<int>& phases,
|
||||||
|
std::vector<Scalar>& mob)
|
||||||
{
|
{
|
||||||
return simulator.model().intensiveQuantities(cell_idx, 0).fluidState().invB(phase_idx).value();
|
const auto& iq = model.intensiveQuantities(cell, /* time_idx = */ 0);
|
||||||
};
|
|
||||||
std::function<Scalar(int,int)> mobility =
|
std::transform(phases.begin(), phases.end(), mob.begin(),
|
||||||
[&simulator](int cell_idx, int phase_idx)
|
[&iq](const int phase) { return iq.mobility(phase).value(); });
|
||||||
|
},
|
||||||
|
|
||||||
|
// densityInCell: Reservoir condition phase densities in
|
||||||
|
// specified cell.
|
||||||
|
[&model = simulator.model()](const int cell,
|
||||||
|
const std::vector<int>& phases,
|
||||||
|
std::vector<Scalar>& rho)
|
||||||
{
|
{
|
||||||
return simulator.model().intensiveQuantities(cell_idx, 0).mobility(phase_idx).value();
|
const auto& fs = model.intensiveQuantities(cell, /* time_idx = */ 0).fluidState();
|
||||||
};
|
|
||||||
std::function<Scalar(int)> invFac =
|
std::transform(phases.begin(), phases.end(), rho.begin(),
|
||||||
[&simulator](int cell_idx)
|
[&fs](const int phase) { return fs.density(phase).value(); });
|
||||||
{
|
}
|
||||||
return simulator.model().intensiveQuantities(cell_idx, 0).solventInverseFormationVolumeFactor().value();
|
|
||||||
};
|
|
||||||
std::function<Scalar(int)> solventMobility =
|
|
||||||
[&simulator](int cell_idx)
|
|
||||||
{
|
|
||||||
return simulator.model().intensiveQuantities(cell_idx, 0).solventMobility().value();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this->connections_.computeProperties(well_state,
|
const auto stopped_or_zero_rate_target = this->
|
||||||
invB,
|
stoppedOrZeroRateTarget(simulator, well_state, deferred_logger);
|
||||||
mobility,
|
|
||||||
invFac,
|
this->connections_
|
||||||
solventMobility,
|
.computeProperties(stopped_or_zero_rate_target, well_state,
|
||||||
props,
|
prop_func, props, deferred_logger);
|
||||||
deferred_logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user