opm-simulators/opm/simulators/wells/StandardWellConnections.cpp
Bård Skaflestad 6f2ee80e41 Refactor Perforation Pressure Property Helper Function
In particular, split the sections of the main loop out to helper
functions

  - calculatePerforationOutflow() uses the global container factory
    to compute the outflow from each connection
  - initialiseConnectionMixture() computes the 'mix' array depending
    on the local flowing conditions of the connection.  We have
    renamed 'x' and 'mix' arrays to 'currentMixture' and
    'previousMixture' respectively to give more descriptive names in
    the process.

We've also split out the redistribution of the individual phases to
the new private helper functions reapportionGasOilMixture() and
reapportionGasWaterMixture() in order to reduce the cognitive load
of the main loop in computePropertiesForPressures().  While here,
employ pointer arithmetic to expose the underlying structure of the
assignment expressions.
2024-08-22 14:51:00 +02:00

898 lines
38 KiB
C++

/*
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/>.
*/
#include <config.h>
#include <opm/simulators/wells/StandardWellConnections.hpp>
#include <opm/material/fluidsystems/BlackOilFluidSystem.hpp>
#include <opm/models/blackoil/blackoilindices.hh>
#include <opm/models/blackoil/blackoilonephaseindices.hh>
#include <opm/models/blackoil/blackoiltwophaseindices.hh>
#include <opm/simulators/utils/BlackoilPhases.hpp>
#include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
#include <opm/simulators/wells/ParallelWellInfo.hpp>
#include <opm/simulators/wells/PerfData.hpp>
#include <opm/simulators/wells/WellInterfaceIndices.hpp>
#include <opm/simulators/wells/WellState.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <functional>
#include <limits>
#include <numeric>
#include <stdexcept>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>
#include <fmt/format.h>
namespace {
// Component and phase rates/mixtures at surface conditions are linked as
//
// [ componentMixture[oilpos] ] [ 1 Rv 0 ] [ phaseMixture[oilpos] ]
// [ componentMixture[gaspos] ] = [ Rs 1 Rsw ] [ phaseMixture[gaspos] ]
// [ componentMixture[waterpos] ] [ 0 Rvw 1 ] [ phaseMixture[waterpos ]
//
// The reapportion*Mixture() functions calculates phase rates/mixtures
// in the oil/gas and gas/water systems, respectively, based on known
// component rates/mixtures.
template <typename Scalar, typename Properties>
void reapportionGasOilMixture(std::string_view well,
const typename std::vector<Scalar>::size_type gaspos,
const typename std::vector<Scalar>::size_type oilpos,
const typename std::vector<Scalar>::size_type perf,
const Properties& props,
const std::vector<Scalar>& componentMixture,
std::vector<Scalar>& phaseMixture,
Opm::DeferredLogger& deferred_logger)
{
const auto threshold = static_cast<Scalar>(1.0e-12);
Scalar rs {0};
if (!props.rsmax_perf.empty() && (componentMixture[oilpos] > threshold)) {
rs = std::min(componentMixture[gaspos] / componentMixture[oilpos], props.rsmax_perf[perf]);
}
Scalar rv {0};
if (!props.rvmax_perf.empty() && (componentMixture[gaspos] > threshold)) {
rv = std::min(componentMixture[oilpos] / componentMixture[gaspos], props.rvmax_perf[perf]);
}
const Scalar denom = Scalar{1} - rs*rv;
if (! (denom > Scalar{0})) {
const auto msg = fmt::format(R"(Problematic denominator value {} for well {}.
Connection density calculation with (Rs, Rv) = ({}, {}).
Proceeding as if no dissolution or vaporisation for this connection)",
denom, well, rs, rv);
deferred_logger.debug(msg);
}
else {
if (rs > Scalar{0}) {
// Subtract gas in oil from gas mixture.
phaseMixture[gaspos] =
(componentMixture[gaspos] - componentMixture[oilpos]*rs) / denom;
}
if (rv > Scalar{0}) {
// Subtract oil in gas from oil mixture.
phaseMixture[oilpos] =
(componentMixture[oilpos] - componentMixture[gaspos]*rv) / denom;
}
}
}
template <typename Scalar, typename Properties>
void reapportionGasWaterMixture(std::string_view well,
const typename std::vector<Scalar>::size_type gaspos,
const typename std::vector<Scalar>::size_type waterpos,
const typename std::vector<Scalar>::size_type perf,
const Properties& props,
const std::vector<Scalar>& componentMixture,
std::vector<Scalar>& phaseMixture,
Opm::DeferredLogger& deferred_logger)
{
const auto threshold = static_cast<Scalar>(1.0e-12);
Scalar rvw {0};
if (!props.rvwmax_perf.empty() && (componentMixture[gaspos] > threshold)) {
rvw = std::min(componentMixture[waterpos] / componentMixture[gaspos], props.rvwmax_perf[perf]);
}
Scalar rsw {0};
if (!props.rswmax_perf.empty() && (componentMixture[waterpos] > threshold)) {
rsw = std::min(componentMixture[gaspos] / componentMixture[waterpos], props.rswmax_perf[perf]);
}
const Scalar d = Scalar{1} - rsw*rvw;
if (! (d > Scalar{0})) {
const auto msg = fmt::format(R"(Problematic denominator value {} for well {}.
Connection density calculation with (Rsw, Rvw) = ({}, {}).
Proceeding as if no dissolution or vaporisation for this connection)",
d, well, rsw, rvw);
deferred_logger.debug(msg);
}
else {
if (rsw > Scalar{0}) {
// Subtract gas in water from gas mixture
phaseMixture[gaspos] =
(componentMixture[gaspos] - componentMixture[waterpos]*rsw) / d;
}
if (rvw > Scalar{0}) {
// Subtract water in gas from water mixture
phaseMixture[waterpos] =
(componentMixture[waterpos] - componentMixture[gaspos]*rvw) / d;
}
}
}
} // Anonymous namespace
namespace Opm
{
template<class FluidSystem, class Indices>
StandardWellConnections<FluidSystem,Indices>::
StandardWellConnections(const WellInterfaceIndices<FluidSystem,Indices>& well)
: well_(well)
, perf_densities_(well.numPerfs())
, perf_pressure_diffs_(well.numPerfs())
{
}
template<class FluidSystem, class Indices>
void StandardWellConnections<FluidSystem,Indices>::
computePressureDelta()
{
// Algorithm:
// We'll assume the perforations are given in order from top to
// bottom for each well. By top and bottom we do not necessarily
// mean in a geometric sense (depth), but in a topological sense:
// the 'top' perforation is nearest to the surface topologically.
// Our goal is to compute a pressure delta for each perforation.
// 1. Compute pressure differences between perforations.
// dp_perf will contain the pressure difference between a
// perforation and the one above it, except for the first
// perforation for each well, for which it will be the
// difference to the reference (bhp) depth.
const int nperf = well_.numPerfs();
perf_pressure_diffs_.resize(nperf, 0.0);
auto z_above = well_.parallelWellInfo().communicateAboveValues(well_.refDepth(), well_.perfDepth());
for (int perf = 0; perf < nperf; ++perf) {
const Scalar dz = well_.perfDepth()[perf] - z_above[perf];
perf_pressure_diffs_[perf] = dz * perf_densities_[perf] * well_.gravity();
}
// 2. Compute pressure differences to the reference point (bhp) by
// accumulating the already computed adjacent pressure
// differences, storing the result in dp_perf.
// This accumulation must be done per well.
const auto beg = perf_pressure_diffs_.begin();
const auto end = perf_pressure_diffs_.end();
well_.parallelWellInfo().partialSumPerfValues(beg, end);
}
template<class FluidSystem, class Indices>
void StandardWellConnections<FluidSystem,Indices>::
computeDensities(const std::vector<Scalar>& perfComponentRates,
const Properties& props,
DeferredLogger& deferred_logger)
{
using Ix = typename std::vector<Scalar>::size_type;
const auto activeGas = FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx);
const auto activeOil = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
const auto activeWater = FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx);
// The *pos objects are unused unless the corresponding phase is active.
// Therefore, it's safe to have -1 (== numeric_limits<size_t>::max()) be
// their value in that case.
const auto gaspos = activeGas
? static_cast<Ix>(Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx))
: static_cast<Ix>(-1);
const auto oilpos = activeOil
? static_cast<Ix>(Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx))
: static_cast<Ix>(-1);
const auto waterpos = activeWater
? static_cast<Ix>(Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx))
: static_cast<Ix>(-1);
const int nperf = this->well_.numPerfs();
const int num_comp = this->well_.numComponents();
// 1. Compute the flow (in surface volume units for each component)
// exiting up the wellbore from each perforation, taking into account
// flow from lower in the well, and in/out-flow at each perforation.
const auto q_out_perf = this->calculatePerforationOutflow(perfComponentRates);
// 2. Compute the component mixture at each perforation as the absolute
// values of the surface rates divided by their sum. Then compute
// volume ratios (formation factors) for each perforation. Finally
// compute densities for the segments associated with each
// perforation.
auto componentMixture = std::vector<Scalar>(num_comp, Scalar{0});
auto phaseMixture = std::vector<Scalar>(num_comp, Scalar{0});
for (int perf = 0; perf < nperf; ++perf) {
this->initialiseConnectionMixture(num_comp, perf,
q_out_perf,
phaseMixture,
componentMixture);
// Initial phase mixture for this perforation is the same as the
// initialised component mixture, which in turn is possibly just
// copied from the phase mixture of the previous perforation (i.e.,
// "perf - 1").
phaseMixture = componentMixture;
// Separate components based on flowing rates.
if (activeGas && activeOil) {
reapportionGasOilMixture(this->well_.name(), gaspos, oilpos, perf,
props, componentMixture, phaseMixture,
deferred_logger);
}
if (activeGas && activeWater) {
reapportionGasWaterMixture(this->well_.name(), gaspos, waterpos, perf,
props, componentMixture, phaseMixture,
deferred_logger);
}
// Compute connection level mixture density as a weighted average of
// phase densities.
const auto* const rho_s = &props.surf_dens_perf[perf*num_comp + 0];
const auto* const b = &props.b_perf [perf*num_comp + 0];
auto& rho = this->perf_densities_[perf];
auto volrat = rho = Scalar{0};
for (auto comp = 0*num_comp; comp < num_comp; ++comp) {
rho += componentMixture[comp] * rho_s[comp];
volrat += phaseMixture [comp] / b [comp];
}
rho /= volrat;
}
}
template <class FluidSystem, class Indices>
std::vector<typename StandardWellConnections<FluidSystem, Indices>::Scalar>
StandardWellConnections<FluidSystem, Indices>::
calculatePerforationOutflow(const std::vector<Scalar>& perfComponentRates) const
{
const int nperf = this->well_.numPerfs();
const int num_comp = this->well_.numComponents();
auto q_out_perf = std::vector<Scalar>(nperf * num_comp, Scalar{0});
// Component flow rates depend on the order of the perforations. Thus,
// we must use the global view of the well's perforation to get an
// accurate picture of the component flow rates at the perforation
// level.
const auto& factory = this->well_.parallelWellInfo()
.getGlobalPerfContainerFactory();
auto global_q_out_perf = factory.createGlobal(q_out_perf, num_comp);
const auto global_perf_comp_rates = factory
.createGlobal(perfComponentRates, num_comp);
// TODO: Investigate whether we should use the following techniques to
// calcuate the composition of flows in the wellbore. Iterate over well
// perforations from bottom to top.
for (int perf = factory.numGlobalPerfs() - 1; perf >= 0; --perf) {
for (int component = 0; component < num_comp; ++component) {
const auto index = perf*num_comp + component;
auto& q_out = global_q_out_perf[index];
// Initialise current perforation's component flow rate to that
// of the perforation below. Bottom perforation has zero flow
// component rate.
q_out = (perf == factory.numGlobalPerfs() - 1)
? Scalar{0}
: global_q_out_perf[index + num_comp];
// Subtract outflow through perforation.
q_out -= global_perf_comp_rates[index];
}
}
// Copy the data back to local view.
factory.copyGlobalToLocal(global_q_out_perf, q_out_perf, num_comp);
return q_out_perf;
}
template <class FluidSystem, class Indices>
void StandardWellConnections<FluidSystem, Indices>::
initialiseConnectionMixture(const int num_comp,
const int perf,
const std::vector<Scalar>& q_out_perf,
const std::vector<Scalar>& phaseMixture,
std::vector<Scalar>& componentMixture) const
{
// Find component mix.
const auto tot_surf_rate =
std::accumulate(q_out_perf.begin() + num_comp*(perf + 0),
q_out_perf.begin() + num_comp*(perf + 1), Scalar{0});
if (tot_surf_rate != Scalar{0}) {
const auto* const qo = &q_out_perf[perf*num_comp + 0];
for (int component = 0; component < num_comp; ++component) {
componentMixture[component] = std::abs(qo[component] / tot_surf_rate);
}
}
else if (num_comp == 1) {
componentMixture[num_comp - 1] = Scalar{1};
}
else {
std::fill(componentMixture.begin(), componentMixture.end(), Scalar{0});
// No flow => use fractions defined at well level for componentMixture.
if (this->well_.isInjector()) {
switch (this->well_.wellEcl().injectorType()) {
case InjectorType::WATER:
componentMixture[FluidSystem::waterCompIdx] = Scalar{1};
break;
case InjectorType::GAS:
componentMixture[FluidSystem::gasCompIdx] = Scalar{1};
break;
case InjectorType::OIL:
componentMixture[FluidSystem::oilCompIdx] = Scalar{1};
break;
case InjectorType::MULTI:
// Not supported.
// deferred_logger.warning("MULTI_PHASE_INJECTOR_NOT_SUPPORTED",
// "Multi phase injectors are not supported, requested for well " + name());
break;
}
}
else {
assert(this->well_.isProducer());
if (perf == 0) {
// For the first perforation without flow we use the
// preferred phase to decide the componentMixture initialization.
switch (this->well_.wellEcl().getPreferredPhase()) {
case Phase::OIL:
componentMixture[FluidSystem::oilCompIdx] = Scalar{1};
break;
case Phase::GAS:
componentMixture[FluidSystem::gasCompIdx] = Scalar{1};
break;
case Phase::WATER:
componentMixture[FluidSystem::waterCompIdx] = Scalar{1};
break;
default:
// No others supported.
break;
}
}
else {
// For the rest of the perforations without flow we use the
// componentMixture from the perforation above.
componentMixture = phaseMixture;
}
}
}
}
template<class FluidSystem, class Indices>
void StandardWellConnections<FluidSystem,Indices>::
computePropertiesForPressures(const WellState<Scalar>& well_state,
const std::function<Scalar(int,int)>& getTemperature,
const std::function<Scalar(int)>& getSaltConcentration,
const std::function<int(int)>& pvtRegionIdx,
const std::function<Scalar(int)>& solventInverseFormationVolumeFactor,
const std::function<Scalar(int)>& solventRefDensity,
Properties& props) const
{
const int nperf = well_.numPerfs();
const PhaseUsage& pu = well_.phaseUsage();
props.b_perf .resize(nperf * this->well_.numComponents());
props.surf_dens_perf.resize(nperf * this->well_.numComponents());
const auto& ws = well_state.well(this->well_.indexOfWell());
static constexpr int Water = BlackoilPhases::Aqua;
static constexpr int Oil = BlackoilPhases::Liquid;
static constexpr int Gas = BlackoilPhases::Vapour;
const bool waterPresent = FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx);
const bool oilPresent = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
const bool gasPresent = FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx);
// Rs/Rv are used only if both oil and gas are present.
if (oilPresent && gasPresent) {
props.rsmax_perf.resize(nperf);
props.rvmax_perf.resize(nperf);
}
// Rsw/Rvw are used only if both water and gas are present.
if (waterPresent && gasPresent) {
props.rvwmax_perf.resize(nperf);
props.rswmax_perf.resize(nperf);
}
// Compute the average pressure in each well block
const auto& perf_press = ws.perf_data.pressure;
const auto p_above = this->well_.parallelWellInfo()
.communicateAboveValues(ws.bhp, perf_press.data(), nperf);
for (int perf = 0; perf < nperf; ++perf) {
const int cell_idx = well_.cells()[perf];
const Scalar p_avg = (perf_press[perf] + p_above[perf])/2;
const Scalar temperature = getTemperature(cell_idx, FluidSystem::oilPhaseIdx);
const Scalar saltConcentration = getSaltConcentration(cell_idx);
const int region_idx = pvtRegionIdx(cell_idx);
if (waterPresent) {
const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx);
Scalar rsw = 0.0;
if (FluidSystem::enableDissolvedGasInWater()) {
// TODO support mutual solubility in water and oil
assert(!FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx));
const Scalar waterrate = std::abs(ws.surface_rates[pu.phase_pos[Water]]);
props.rswmax_perf[perf] = FluidSystem::waterPvt().saturatedGasDissolutionFactor(region_idx, temperature, p_avg, saltConcentration);
if (waterrate > 0) {
const Scalar gasrate = std::abs(ws.surface_rates[pu.phase_pos[Gas]]) - (Indices::enableSolvent ? ws.sum_solvent_rates() : 0.0);
if (gasrate > 0) {
rsw = gasrate / waterrate;
}
rsw = std::min(rsw, props.rswmax_perf[perf]);
}
}
props.b_perf[waterCompIdx + perf * well_.numComponents()] = FluidSystem::waterPvt()
.inverseFormationVolumeFactor(region_idx, temperature, p_avg, rsw, saltConcentration);
}
if (gasPresent) {
const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx);
const int gaspos = gasCompIdx + perf * well_.numComponents();
Scalar rvw = 0.0;
Scalar rv = 0.0;
if (oilPresent) {
// in order to handle negative rates in producers
const Scalar oilrate = std::abs(ws.surface_rates[pu.phase_pos[Oil]]);
props.rvmax_perf[perf] = FluidSystem::gasPvt()
.saturatedOilVaporizationFactor(region_idx, temperature, p_avg);
if (oilrate > 0) {
const Scalar gasrate = std::abs(ws.surface_rates[pu.phase_pos[Gas]]) - (Indices::enableSolvent ? ws.sum_solvent_rates() : 0.0);
if (gasrate > 0) {
rv = oilrate / gasrate;
}
rv = std::min(rv, props.rvmax_perf[perf]);
}
}
if (waterPresent) {
// in order to handle negative rates in producers
const Scalar waterrate = std::abs(ws.surface_rates[pu.phase_pos[Water]]);
props.rvwmax_perf[perf] = FluidSystem::gasPvt()
.saturatedWaterVaporizationFactor(region_idx, temperature, p_avg);
if (waterrate > 0) {
const Scalar gasrate = std::abs(ws.surface_rates[pu.phase_pos[Gas]])
- (Indices::enableSolvent ? ws.sum_solvent_rates() : 0.0);
if (gasrate > 0) {
rvw = waterrate / gasrate;
}
rvw = std::min(rvw, props.rvwmax_perf[perf]);
}
}
props.b_perf[gaspos] = FluidSystem::gasPvt()
.inverseFormationVolumeFactor(region_idx, temperature, p_avg, rv, rvw);
}
if (oilPresent) {
const unsigned oilCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::oilCompIdx);
const int oilpos = oilCompIdx + perf * well_.numComponents();
Scalar rs = 0.0;
if (gasPresent) {
props.rsmax_perf[perf] = FluidSystem::oilPvt()
.saturatedGasDissolutionFactor(region_idx, temperature, p_avg);
const Scalar gasrate = std::abs(ws.surface_rates[pu.phase_pos[Gas]])
- (Indices::enableSolvent ? ws.sum_solvent_rates() : 0.0);
if (gasrate > 0) {
const Scalar oilrate = std::abs(ws.surface_rates[pu.phase_pos[Oil]]);
if (oilrate > 0) {
rs = gasrate / oilrate;
}
rs = std::min(rs, props.rsmax_perf[perf]);
}
}
props.b_perf[oilpos] = FluidSystem::oilPvt()
.inverseFormationVolumeFactor(region_idx, temperature, p_avg, rs);
}
// Surface density.
for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) {
if (!FluidSystem::phaseIsActive(phaseIdx)) {
continue;
}
const unsigned compIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::solventComponentIndex(phaseIdx));
props.surf_dens_perf[well_.numComponents() * perf + compIdx] =
FluidSystem::referenceDensity( phaseIdx, region_idx );
}
// We use cell values for solvent injector
if constexpr (Indices::enableSolvent) {
props.b_perf[well_.numComponents() * perf + Indices::contiSolventEqIdx] = solventInverseFormationVolumeFactor(cell_idx);
props.surf_dens_perf[well_.numComponents() * perf + Indices::contiSolventEqIdx] = solventRefDensity(cell_idx);
}
}
}
template <class FluidSystem, class Indices>
std::vector<typename StandardWellConnections<FluidSystem, Indices>::Scalar>
StandardWellConnections<FluidSystem, Indices>::
copyInPerforationRates(const Properties& props,
const PerfData<Scalar>& perf_data) const
{
auto perfRates = std::vector<Scalar>(props.b_perf.size(), Scalar{0});
const int nperf = this->well_.numPerfs();
const int np = this->well_.numPhases();
const int nc = this->well_.numComponents();
const auto srcIx = [this, np]() {
auto ix = std::vector<int>(np);
for (auto comp = 0; comp < np; ++comp) {
ix[comp] = this->well_.modelCompIdxToFlowCompIdx(comp);
}
return ix;
}();
for (int perf = 0; perf < nperf; ++perf) {
const auto* const src = &perf_data.phase_rates[perf*np + 0];
auto* const dst = &perfRates [perf*nc + 0];
for (int comp = 0; comp < np; ++comp) {
dst[comp] = src[srcIx[comp]];
}
}
if constexpr (Indices::enableSolvent) {
for (int perf = 0, ix_s = 0*nc + Indices::contiSolventEqIdx;
perf < nperf; ++perf, ix_s += nc)
{
perfRates[ix_s] = perf_data.solvent_rates[perf];
}
}
return perfRates;
}
template<class FluidSystem, class Indices>
void StandardWellConnections<FluidSystem,Indices>::
computeProperties(const WellState<Scalar>& well_state,
const std::function<Scalar(int,int)>& invB,
const std::function<Scalar(int,int)>& mobility,
const std::function<Scalar(int)>& solventInverseFormationVolumeFactor,
const std::function<Scalar(int)>& solventMobility,
const Properties& props,
DeferredLogger& deferred_logger)
{
// Compute densities
const int nperf = well_.numPerfs();
const int np = well_.numPhases();
const auto& ws = well_state.well(well_.indexOfWell());
const auto& perf_data = ws.perf_data;
const auto& perf_rates_state = perf_data.phase_rates;
auto perfRates = this->copyInPerforationRates
(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->computePressureDelta();
}
template<class FluidSystem, class Indices>
typename StandardWellConnections<FluidSystem,Indices>::Eval
StandardWellConnections<FluidSystem,Indices>::
connectionRateBrine(Scalar& rate,
const Scalar vap_wat_rate,
const std::vector<EvalWell>& cq_s,
const std::variant<Scalar,EvalWell>& saltConcentration) const
{
// TODO: the application of well efficiency factor has not been tested with an example yet
const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx);
// Correction salt rate; evaporated water does not contain salt
EvalWell cq_s_sm = cq_s[waterCompIdx] - vap_wat_rate;
if (well_.isInjector()) {
cq_s_sm *= std::get<Scalar>(saltConcentration);
} else {
cq_s_sm *= std::get<EvalWell>(saltConcentration);
}
// Note. Efficiency factor is handled in the output layer
rate = cq_s_sm.value();
cq_s_sm *= well_.wellEfficiencyFactor();
return well_.restrictEval(cq_s_sm);
}
template<class FluidSystem, class Indices>
typename StandardWellConnections<FluidSystem,Indices>::Eval
StandardWellConnections<FluidSystem,Indices>::
connectionRateFoam(const std::vector<EvalWell>& cq_s,
const std::variant<Scalar,EvalWell>& foamConcentration,
const Phase transportPhase,
DeferredLogger& deferred_logger) const
{
// TODO: the application of well efficiency factor has not been tested with an example yet
auto getFoamTransportIdx = [&deferred_logger,transportPhase] {
switch (transportPhase) {
case Phase::WATER: {
return Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx);
}
case Phase::GAS: {
return Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx);
}
case Phase::SOLVENT: {
if constexpr (Indices::enableSolvent)
return static_cast<unsigned>(Indices::contiSolventEqIdx);
else
OPM_DEFLOG_THROW(std::runtime_error, "Foam transport phase is SOLVENT but SOLVENT is not activated.", deferred_logger);
}
default: {
OPM_DEFLOG_THROW(std::runtime_error, "Foam transport phase must be GAS/WATER/SOLVENT.", deferred_logger);
}
}
};
EvalWell cq_s_foam = cq_s[getFoamTransportIdx()] * well_.wellEfficiencyFactor();;
if (well_.isInjector()) {
cq_s_foam *= std::get<Scalar>(foamConcentration);
} else {
cq_s_foam *= std::get<EvalWell>(foamConcentration);
}
return well_.restrictEval(cq_s_foam);
}
template<class FluidSystem, class Indices>
std::tuple<typename StandardWellConnections<FluidSystem,Indices>::Eval,
typename StandardWellConnections<FluidSystem,Indices>::Eval,
typename StandardWellConnections<FluidSystem,Indices>::Eval>
StandardWellConnections<FluidSystem,Indices>::
connectionRatesMICP(const std::vector<EvalWell>& cq_s,
const std::variant<Scalar,EvalWell>& microbialConcentration,
const std::variant<Scalar,EvalWell>& oxygenConcentration,
const std::variant<Scalar,EvalWell>& ureaConcentration) const
{
const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx);
EvalWell cq_s_microbe = cq_s[waterCompIdx];
if (well_.isInjector()) {
cq_s_microbe *= std::get<Scalar>(microbialConcentration);
} else {
cq_s_microbe *= std::get<EvalWell>(microbialConcentration);
}
EvalWell cq_s_oxygen = cq_s[waterCompIdx];
if (well_.isInjector()) {
cq_s_oxygen *= std::get<Scalar>(oxygenConcentration);
} else {
cq_s_oxygen *= std::get<EvalWell>(oxygenConcentration);
}
EvalWell cq_s_urea = cq_s[waterCompIdx];
if (well_.isInjector()) {
cq_s_urea *= std::get<Scalar>(ureaConcentration);
} else {
cq_s_urea *= std::get<EvalWell>(ureaConcentration);
}
return {well_.restrictEval(cq_s_microbe),
well_.restrictEval(cq_s_oxygen),
well_.restrictEval(cq_s_urea)};
}
template<class FluidSystem, class Indices>
std::tuple<typename StandardWellConnections<FluidSystem,Indices>::Eval,
typename StandardWellConnections<FluidSystem,Indices>::EvalWell>
StandardWellConnections<FluidSystem,Indices>::
connectionRatePolymer(Scalar& rate,
const std::vector<EvalWell>& cq_s,
const std::variant<Scalar,EvalWell>& polymerConcentration) const
{
// TODO: the application of well efficiency factor has not been tested with an example yet
const unsigned waterCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::waterCompIdx);
EvalWell cq_s_poly = cq_s[waterCompIdx];
if (well_.isInjector()) {
cq_s_poly *= std::get<Scalar>(polymerConcentration);
} else {
cq_s_poly *= std::get<EvalWell>(polymerConcentration);
}
// Note. Efficiency factor is handled in the output layer
rate = cq_s_poly.value();
cq_s_poly *= well_.wellEfficiencyFactor();
return {well_.restrictEval(cq_s_poly), cq_s_poly};
}
template<class FluidSystem, class Indices>
std::tuple<typename StandardWellConnections<FluidSystem,Indices>::Eval,
typename StandardWellConnections<FluidSystem,Indices>::EvalWell>
StandardWellConnections<FluidSystem,Indices>::
connectionRatezFraction(Scalar& rate,
const Scalar dis_gas_rate,
const std::vector<EvalWell>& cq_s,
const std::variant<Scalar, std::array<EvalWell,2>>& solventConcentration) const
{
// TODO: the application of well efficiency factor has not been tested with an example yet
const unsigned gasCompIdx = Indices::canonicalToActiveComponentIndex(FluidSystem::gasCompIdx);
EvalWell cq_s_zfrac_effective = cq_s[gasCompIdx];
if (well_.isInjector()) {
cq_s_zfrac_effective *= std::get<Scalar>(solventConcentration);
} else if (cq_s_zfrac_effective.value() != 0.0) {
const Scalar dis_gas_frac = dis_gas_rate / cq_s_zfrac_effective.value();
const auto& vol = std::get<std::array<EvalWell,2>>(solventConcentration);
cq_s_zfrac_effective *= dis_gas_frac * vol[0] + (1.0 - dis_gas_frac) * vol[1];
}
rate = cq_s_zfrac_effective.value();
cq_s_zfrac_effective *= well_.wellEfficiencyFactor();
return {well_.restrictEval(cq_s_zfrac_effective), cq_s_zfrac_effective};
}
template<class Scalar>
using FS = BlackOilFluidSystem<Scalar,BlackOilDefaultIndexTraits>;
#define INSTANTIATE(T,...) \
template class StandardWellConnections<FS<T>, __VA_ARGS__>;
#define INSTANTIATE_TYPE(T) \
INSTANTIATE(T,BlackOilOnePhaseIndices<0u,0u,0u,0u,false,false,0u,1u,0u>) \
INSTANTIATE(T,BlackOilOnePhaseIndices<0u,0u,0u,1u,false,false,0u,1u,0u>) \
INSTANTIATE(T,BlackOilOnePhaseIndices<0u,0u,0u,0u,false,false,0u,1u,5u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,0u,false,false,0u,0u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,0u,false,false,0u,1u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,0u,false,false,0u,2u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,1u,0u,false,false,0u,2u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,1u,0u,false,true,0u,2u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,1u,false,false,0u,1u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,0u,false,true,0u,0u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,0u,false,true,0u,2u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,2u,0u,false,false,0u,2u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,1u,false,false,0u,0u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<0u,0u,0u,1u,false,true,0u,0u,0u>) \
INSTANTIATE(T,BlackOilTwoPhaseIndices<1u,0u,0u,0u,false,false,0u,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,0u,0u,false,false,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<1u,0u,0u,0u,false,false,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,1u,0u,0u,false,false,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,1u,0u,false,false,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,0u,1u,false,false,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,0u,0u,true,false,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,0u,0u,false,true,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,0u,1u,false,true,0u,0u>) \
INSTANTIATE(T,BlackOilIndices<0u,0u,0u,1u,false,false,1u,0u>) \
INSTANTIATE(T,BlackOilIndices<1u,0u,0u,0u,true,false,0u,0u>)
INSTANTIATE_TYPE(double)
#if FLOW_INSTANTIATE_FLOAT
INSTANTIATE_TYPE(float)
#endif
} // namespace Opm