WellBhpThpCalculator: template Scalar type

This commit is contained in:
Arne Morten Kvarving 2024-02-20 11:14:54 +01:00
parent 5a340258aa
commit 9c431d1921
2 changed files with 296 additions and 264 deletions

View File

@ -48,11 +48,11 @@
static constexpr bool extraBhpAtThpLimitOutput = false;
static constexpr bool extraThpFromBhpOutput = false;
namespace Opm
{
namespace Opm {
bool
WellBhpThpCalculator::wellHasTHPConstraints(const SummaryState& summaryState) const
template<class Scalar>
bool WellBhpThpCalculator<Scalar>::
wellHasTHPConstraints(const SummaryState& summaryState) const
{
const auto& well_ecl = well_.wellEcl();
if (well_ecl.isInjector()) {
@ -70,7 +70,9 @@ WellBhpThpCalculator::wellHasTHPConstraints(const SummaryState& summaryState) co
return false;
}
double WellBhpThpCalculator::getTHPConstraint(const SummaryState& summaryState) const
template<class Scalar>
Scalar WellBhpThpCalculator<Scalar>::
getTHPConstraint(const SummaryState& summaryState) const
{
const auto& well_ecl = well_.wellEcl();
if (well_ecl.isInjector()) {
@ -86,7 +88,9 @@ double WellBhpThpCalculator::getTHPConstraint(const SummaryState& summaryState)
return 0.0;
}
double WellBhpThpCalculator::mostStrictBhpFromBhpLimits(const SummaryState& summaryState) const
template<class Scalar>
Scalar WellBhpThpCalculator<Scalar>::
mostStrictBhpFromBhpLimits(const SummaryState& summaryState) const
{
const auto& well_ecl = well_.wellEcl();
if (well_ecl.isInjector()) {
@ -102,12 +106,14 @@ double WellBhpThpCalculator::mostStrictBhpFromBhpLimits(const SummaryState& summ
return 0.0;
}
double WellBhpThpCalculator::calculateThpFromBhp(const std::vector<double>& rates,
const double bhp,
const double rho,
const std::optional<double>& alq,
const double thp_limit,
DeferredLogger& deferred_logger) const
template<class Scalar>
Scalar WellBhpThpCalculator<Scalar>::
calculateThpFromBhp(const std::vector<Scalar>& rates,
const Scalar bhp,
const Scalar rho,
const std::optional<Scalar>& alq,
const Scalar thp_limit,
DeferredLogger& deferred_logger) const
{
assert(int(rates.size()) == 3); // the vfp related only supports three phases now.
@ -115,31 +121,31 @@ double WellBhpThpCalculator::calculateThpFromBhp(const std::vector<double>& rate
static constexpr int Oil = BlackoilPhases::Liquid;
static constexpr int Gas = BlackoilPhases::Vapour;
const double aqua = rates[Water];
const double liquid = rates[Oil];
const double vapour = rates[Gas];
const Scalar aqua = rates[Water];
const Scalar liquid = rates[Oil];
const Scalar vapour = rates[Gas];
// pick the density in the top layer
double thp = 0.0;
Scalar thp = 0.0;
const int table_id = well_.wellEcl().vfp_table_number();
if (well_.isInjector()) {
assert(!alq.has_value());
const double vfp_ref_depth = well_.vfpProperties()->getInj()->getTable(table_id).getDatumDepth();
const double dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth, rho, well_.gravity());
const Scalar vfp_ref_depth = well_.vfpProperties()->getInj()->getTable(table_id).getDatumDepth();
const Scalar dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth, rho, well_.gravity());
auto thp_func =
[this, table_id, aqua, liquid, vapour, dp](
const double bhp_value, const double pressure_loss) {
const Scalar bhp_value, const Scalar pressure_loss) {
return this->well_.vfpProperties()->getInj()->thp(
table_id, aqua, liquid, vapour, bhp_value + dp - pressure_loss);
};
thp = findThpFromBhpIteratively(thp_func, bhp, thp_limit, dp, deferred_logger);
}
else if (well_.isProducer()) {
const double vfp_ref_depth = well_.vfpProperties()->getProd()->getTable(table_id).getDatumDepth();
const double dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth, rho, well_.gravity());
const Scalar vfp_ref_depth = well_.vfpProperties()->getProd()->getTable(table_id).getDatumDepth();
const Scalar dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth, rho, well_.gravity());
auto thp_func =
[this, table_id, aqua, liquid, vapour, dp, &alq]
(const double bhp_value, const double pressure_loss) {
(const Scalar bhp_value, const Scalar pressure_loss) {
return this->well_.vfpProperties()->getProd()->thp(
table_id, aqua, liquid, vapour, bhp_value + dp - pressure_loss, alq.value());
};
@ -151,36 +157,35 @@ double WellBhpThpCalculator::calculateThpFromBhp(const std::vector<double>& rate
return thp;
}
double
WellBhpThpCalculator::
findThpFromBhpIteratively(
const std::function<double(const double, const double)>& thp_func,
const double bhp,
const double thp_limit,
const double dp,
DeferredLogger& deferred_logger) const
template<class Scalar>
Scalar WellBhpThpCalculator<Scalar>::
findThpFromBhpIteratively(const std::function<Scalar(const Scalar, const Scalar)>& thp_func,
const Scalar bhp,
const Scalar thp_limit,
const Scalar dp,
DeferredLogger& deferred_logger) const
{
auto pressure_loss = getVfpBhpAdjustment(bhp + dp, thp_limit);
auto thp = thp_func(bhp, pressure_loss);
const double tolerance = 1e-5 * unit::barsa;
const Scalar tolerance = 1e-5 * unit::barsa;
bool do_iterate = true;
int it = 1;
int max_iterations = 50;
while(do_iterate) {
while (do_iterate) {
if (it > max_iterations) {
break;
}
double thp_prev = thp;
Scalar thp_prev = thp;
pressure_loss = getVfpBhpAdjustment(bhp + dp - pressure_loss, thp_prev);
thp = thp_func(bhp, pressure_loss);
auto error = std::fabs(thp-thp_prev);
auto error = std::fabs(thp - thp_prev);
if (extraThpFromBhpOutput) {
const std::string msg = fmt::format(
"findThpFromBhpIteratively(): iteration {}, thp = {}, bhp = {}, "
"pressure_loss = {}, error = {}", it, thp, bhp+dp-pressure_loss, pressure_loss, error);
deferred_logger.debug(msg);
}
if (std::fabs(thp-thp_prev) < tolerance) {
if (std::fabs(thp - thp_prev) < tolerance) {
break;
}
it++;
@ -188,14 +193,15 @@ findThpFromBhpIteratively(
return thp;
}
std::optional<double>
WellBhpThpCalculator::
computeBhpAtThpLimitProd(const std::function<std::vector<double>(const double)>& frates,
template<class Scalar>
std::optional<Scalar>
WellBhpThpCalculator<Scalar>::
computeBhpAtThpLimitProd(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const SummaryState& summary_state,
const double maxPerfPress,
const double rho,
const double alq_value,
const double thp_limit,
const Scalar maxPerfPress,
const Scalar rho,
const Scalar alq_value,
const Scalar thp_limit,
DeferredLogger& deferred_logger) const
{
// Given a VFP function returning bhp as a function of phase
@ -221,34 +227,43 @@ computeBhpAtThpLimitProd(const std::function<std::vector<double>(const double)>&
// Make the fbhp() function.
const auto& controls = well_.wellEcl().productionControls(summary_state);
const auto& table = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number);
const double vfp_ref_depth = table.getDatumDepth();
const double dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth, rho, well_.gravity());
const Scalar vfp_ref_depth = table.getDatumDepth();
const Scalar dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(),
vfp_ref_depth,
rho,
well_.gravity());
auto fbhp = [this, &controls, thp_limit, dp, alq_value](const std::vector<double>& rates) {
auto fbhp = [this, &controls, thp_limit, dp, alq_value](const std::vector<Scalar>& rates) {
assert(rates.size() == 3);
const auto& wfr = well_.vfpProperties()->getExplicitWFR(controls.vfp_table_number, well_.indexOfWell());
const auto& gfr = well_.vfpProperties()->getExplicitGFR(controls.vfp_table_number, well_.indexOfWell());
const auto& wfr = well_.vfpProperties()->getExplicitWFR(controls.vfp_table_number,
well_.indexOfWell());
const auto& gfr = well_.vfpProperties()->getExplicitGFR(controls.vfp_table_number,
well_.indexOfWell());
const bool use_vfpexp = well_.useVfpExplicit();
const double bhp = well_.vfpProperties()->getProd()->bhp(controls.vfp_table_number,
rates[Water],
rates[Oil],
rates[Gas],
thp_limit,
alq_value,
wfr,
gfr,
use_vfpexp);
const Scalar bhp = well_.vfpProperties()->getProd()->bhp(controls.vfp_table_number,
rates[Water],
rates[Oil],
rates[Gas],
thp_limit,
alq_value,
wfr,
gfr,
use_vfpexp);
return bhp - dp + getVfpBhpAdjustment(bhp, thp_limit);
};
// Make the flo() function.
auto flo = [&table](const std::vector<double>& rates) {
auto flo = [&table](const std::vector<Scalar>& rates) {
return detail::getFlo(table, rates[Water], rates[Oil], rates[Gas]);
};
// Find the bhp-point where production becomes nonzero.
auto fflo = [&flo, &frates](double bhp) { return flo(frates(bhp)); };
auto bhp_max = this->bhpMax(fflo, controls.bhp_limit, maxPerfPress, table.getFloAxis().front(), deferred_logger);
auto fflo = [&flo, &frates](Scalar bhp) { return flo(frates(bhp)); };
auto bhp_max = this->bhpMax(fflo,
controls.bhp_limit,
maxPerfPress,
table.getFloAxis().front(),
deferred_logger);
// could not solve for the bhp-point, we could not continue to find the bhp
if (!bhp_max.has_value()) {
@ -257,16 +272,17 @@ computeBhpAtThpLimitProd(const std::function<std::vector<double>(const double)>&
"find bhp-point where production becomes non-zero for well " + well_.name());
return std::nullopt;
}
const std::array<double, 2> range {controls.bhp_limit, *bhp_max};
const std::array<Scalar, 2> range {controls.bhp_limit, *bhp_max};
return this->computeBhpAtThpLimit(frates, fbhp, range, deferred_logger);
}
std::optional<double>
WellBhpThpCalculator::
computeBhpAtThpLimitInj(const std::function<std::vector<double>(const double)>& frates,
template<class Scalar>
std::optional<Scalar>
WellBhpThpCalculator<Scalar>::
computeBhpAtThpLimitInj(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const SummaryState& summary_state,
const double rho,
const double flo_rel_tol,
const Scalar rho,
const Scalar flo_rel_tol,
const int max_iteration,
const bool throwOnError,
DeferredLogger& deferred_logger) const
@ -282,13 +298,15 @@ computeBhpAtThpLimitInj(const std::function<std::vector<double>(const double)>&
}
}
void WellBhpThpCalculator::updateThp(const double rho,
const bool stop_or_zero_rate_target,
const std::function<double()>& alq_value,
const std::array<unsigned,3>& active,
WellState<double>& well_state,
const SummaryState& summary_state,
DeferredLogger& deferred_logger) const
template<class Scalar>
void WellBhpThpCalculator<Scalar>::
updateThp(const Scalar rho,
const bool stop_or_zero_rate_target,
const std::function<Scalar()>& alq_value,
const std::array<unsigned,3>& active,
WellState<Scalar>& well_state,
const SummaryState& summary_state,
DeferredLogger& deferred_logger) const
{
static constexpr int Gas = BlackoilPhases::Vapour;
static constexpr int Oil = BlackoilPhases::Liquid;
@ -308,7 +326,7 @@ void WellBhpThpCalculator::updateThp(const double rho,
}
// the well is under other control types, we calculate the thp based on bhp and rates
std::vector<double> rates(3, 0.0);
std::vector<Scalar> rates(3, 0.0);
const PhaseUsage& pu = well_.phaseUsage();
if (active[Water]) {
@ -320,18 +338,19 @@ void WellBhpThpCalculator::updateThp(const double rho,
if (active[Gas]) {
rates[ Gas ] = ws.surface_rates[pu.phase_pos[ Gas ] ];
}
const std::optional<double> alq = this->well_.isProducer() ? std::optional<double>(alq_value()) : std::nullopt;
const double thp_limit = well_.getTHPConstraint(summary_state);
const std::optional<Scalar> alq = this->well_.isProducer() ? std::optional<Scalar>(alq_value()) : std::nullopt;
const Scalar thp_limit = well_.getTHPConstraint(summary_state);
ws.thp = this->calculateThpFromBhp(rates, ws.bhp, rho, alq, thp_limit, deferred_logger);
}
template<class Scalar>
template<class EvalWell>
EvalWell WellBhpThpCalculator::
calculateBhpFromThp(const WellState<double>& well_state,
EvalWell WellBhpThpCalculator<Scalar>::
calculateBhpFromThp(const WellState<Scalar>& well_state,
const std::vector<EvalWell>& rates,
const Well& well,
const SummaryState& summaryState,
const double rho,
const Scalar rho,
DeferredLogger& deferred_logger) const
{
// TODO: when well is under THP control, the BHP is dependent on the rates,
@ -349,8 +368,8 @@ calculateBhpFromThp(const WellState<double>& well_state,
const EvalWell aqua = rates[Water];
const EvalWell liquid = rates[Oil];
const EvalWell vapour = rates[Gas];
const double thp_limit = well_.getTHPConstraint(summaryState);
double vfp_ref_depth;
const Scalar thp_limit = well_.getTHPConstraint(summaryState);
Scalar vfp_ref_depth;
EvalWell bhp_tab;
if (well_.isInjector() )
{
@ -375,57 +394,62 @@ calculateBhpFromThp(const WellState<double>& well_state,
else {
OPM_DEFLOG_THROW(std::logic_error, "Expected INJECTOR or PRODUCER for well " + well_.name(), deferred_logger);
}
double bhp_tab_double_value;
if constexpr (std::is_same_v<EvalWell, double>) {
Scalar bhp_tab_double_value;
if constexpr (std::is_same_v<EvalWell, Scalar>) {
bhp_tab_double_value = bhp_tab;
}
else { // EvalWell and bhp_tab is of type DenseAd::Evaluation<double,...,...>
bhp_tab_double_value = bhp_tab.value();
}
const auto bhp_adjustment = getVfpBhpAdjustment(bhp_tab_double_value, thp_limit);
const double dp_hydro = wellhelpers::computeHydrostaticCorrection(
well_.refDepth(), vfp_ref_depth, rho, well_.gravity());
const Scalar dp_hydro = wellhelpers::computeHydrostaticCorrection(well_.refDepth(),
vfp_ref_depth,
rho,
well_.gravity());
return bhp_tab - dp_hydro + bhp_adjustment;
}
double
WellBhpThpCalculator::
calculateMinimumBhpFromThp(const WellState<double>& well_state,
template<class Scalar>
Scalar WellBhpThpCalculator<Scalar>::
calculateMinimumBhpFromThp(const WellState<Scalar>& well_state,
const Well& well,
const SummaryState& summaryState,
const double rho) const
const Scalar rho) const
{
assert(well_.isProducer()); // only producers can go here for now
const double thp_limit = well_.getTHPConstraint(summaryState);
const Scalar thp_limit = well_.getTHPConstraint(summaryState);
const auto& controls = well.productionControls(summaryState);
const auto& wfr = well_.vfpProperties()->getExplicitWFR(controls.vfp_table_number, well_.indexOfWell());
const auto& gfr = well_.vfpProperties()->getExplicitGFR(controls.vfp_table_number, well_.indexOfWell());
const double bhp_min = well_.vfpProperties()->getProd()->minimumBHP(controls.vfp_table_number,
const Scalar bhp_min = well_.vfpProperties()->getProd()->minimumBHP(controls.vfp_table_number,
thp_limit, wfr, gfr,
well_.getALQ(well_state));
const double vfp_ref_depth = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number).getDatumDepth();
const Scalar vfp_ref_depth = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number).getDatumDepth();
const auto bhp_adjustment = getVfpBhpAdjustment(bhp_min, thp_limit);
const double dp_hydro = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth,
const Scalar dp_hydro = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth,
rho, well_.gravity());
return bhp_min - dp_hydro + bhp_adjustment;
}
double WellBhpThpCalculator::getVfpBhpAdjustment(const double bhp_tab, const double thp_limit) const
template<class Scalar>
Scalar WellBhpThpCalculator<Scalar>::
getVfpBhpAdjustment(const Scalar bhp_tab, const Scalar thp_limit) const
{
return well_.wellEcl().getWVFPDP().getPressureLoss(bhp_tab, thp_limit);
}
template<class Scalar>
template<class ErrorPolicy>
std::optional<double>
WellBhpThpCalculator::
computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double)>& frates,
std::optional<Scalar>
WellBhpThpCalculator<Scalar>::
computeBhpAtThpLimitInjImpl(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const SummaryState& summary_state,
const double rho,
const double flo_rel_tol,
const Scalar rho,
const Scalar flo_rel_tol,
const int max_iteration,
DeferredLogger& deferred_logger) const
{
@ -475,12 +499,12 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
// Make the fbhp() function.
const auto& controls = well_.wellEcl().injectionControls(summary_state);
const auto& table = well_.vfpProperties()->getInj()->getTable(controls.vfp_table_number);
const double vfp_ref_depth = table.getDatumDepth();
const double thp_limit = this->getTHPConstraint(summary_state);
const double dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(),
const Scalar vfp_ref_depth = table.getDatumDepth();
const Scalar thp_limit = this->getTHPConstraint(summary_state);
const Scalar dp = wellhelpers::computeHydrostaticCorrection(well_.refDepth(),
vfp_ref_depth,
rho, well_.gravity());
auto fbhp = [this, &controls, thp_limit, dp](const std::vector<double>& rates) {
auto fbhp = [this, &controls, thp_limit, dp](const std::vector<Scalar>& rates) {
assert(rates.size() == 3);
const auto bhp = well_.vfpProperties()->getInj()
->bhp(controls.vfp_table_number, rates[Water], rates[Oil], rates[Gas], thp_limit);
@ -488,25 +512,25 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
};
// Make the flo() function.
auto flo = [&table](const std::vector<double>& rates) {
auto flo = [&table](const std::vector<Scalar>& rates) {
return detail::getFlo(table, rates[Water], rates[Oil], rates[Gas]);
};
// Get the flo samples, add extra samples at low rates and bhp
// limit point if necessary.
std::vector<double> flo_samples = table.getFloAxis();
std::vector<Scalar> flo_samples = table.getFloAxis();
if (flo_samples[0] > 0.0) {
const double f0 = flo_samples[0];
const Scalar f0 = flo_samples[0];
flo_samples.insert(flo_samples.begin(), { f0/20.0, f0/10.0, f0/5.0, f0/2.0 });
}
const double flo_bhp_limit = flo(frates(controls.bhp_limit));
const Scalar flo_bhp_limit = flo(frates(controls.bhp_limit));
if (flo_samples.back() < flo_bhp_limit) {
flo_samples.push_back(flo_bhp_limit);
}
// Find bhp values for inflow relation corresponding to flo samples.
std::vector<double> bhp_samples;
for (double flo_sample : flo_samples) {
std::vector<Scalar> bhp_samples;
for (Scalar flo_sample : flo_samples) {
if (flo_sample > flo_bhp_limit) {
// We would have to go over the bhp limit to obtain a
// flow of this magnitude. We associate all such flows
@ -516,16 +540,16 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
bhp_samples.push_back(controls.bhp_limit);
break;
}
auto eq = [&flo, &frates, flo_sample](double bhp) {
auto eq = [&flo, &frates, flo_sample](Scalar bhp) {
return flo(frates(bhp)) - flo_sample;
};
// TODO: replace hardcoded low/high limits.
const double low = 10.0 * unit::barsa;
const double high = 800.0 * unit::barsa;
const double flo_tolerance = flo_rel_tol * std::fabs(flo_samples.back());
const Scalar low = 10.0 * unit::barsa;
const Scalar high = 800.0 * unit::barsa;
const Scalar flo_tolerance = flo_rel_tol * std::fabs(flo_samples.back());
int iteration = 0;
try {
const double solved_bhp = RegulaFalsiBisection<ErrorPolicy>::
const Scalar solved_bhp = RegulaFalsiBisection<ErrorPolicy>::
solve(eq, low, high, max_iteration, flo_tolerance, iteration);
bhp_samples.push_back(solved_bhp);
}
@ -539,7 +563,7 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
// Find bhp values for VFP relation corresponding to flo samples.
const int num_samples = bhp_samples.size(); // Note that this can be smaller than flo_samples.size()
std::vector<double> fbhp_samples(num_samples);
std::vector<Scalar> fbhp_samples(num_samples);
for (int ii = 0; ii < num_samples; ++ii) {
fbhp_samples[ii] = fbhp(frates(bhp_samples[ii]));
}
@ -564,8 +588,8 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
// We only look at the valid
int sign_change_index = -1;
for (int ii = 0; ii < num_samples - 1; ++ii) {
const double curr = fbhp_samples[ii] - bhp_samples[ii];
const double next = fbhp_samples[ii + 1] - bhp_samples[ii + 1];
const Scalar curr = fbhp_samples[ii] - bhp_samples[ii];
const Scalar next = fbhp_samples[ii + 1] - bhp_samples[ii + 1];
if (curr * next < 0.0) {
// Sign change in the [ii, ii + 1] interval.
sign_change_index = ii; // May overwrite, thereby choosing the highest-flo solution.
@ -578,13 +602,13 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
}
// Solve for the proper solution in the given interval.
auto eq = [&fbhp, &frates](double bhp) {
auto eq = [&fbhp, &frates](Scalar bhp) {
return fbhp(frates(bhp)) - bhp;
};
// TODO: replace hardcoded low/high limits.
const double low = bhp_samples[sign_change_index + 1];
const double high = bhp_samples[sign_change_index];
const double bhp_tolerance = 0.01 * unit::barsa;
const Scalar low = bhp_samples[sign_change_index + 1];
const Scalar high = bhp_samples[sign_change_index];
const Scalar bhp_tolerance = 0.01 * unit::barsa;
int iteration = 0;
if (low == high) {
// We are in the high flow regime where the bhp_samples
@ -595,7 +619,7 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
return std::nullopt;
}
try {
const double solved_bhp = RegulaFalsiBisection<ErrorPolicy>::
const Scalar solved_bhp = RegulaFalsiBisection<ErrorPolicy>::
solve(eq, low, high, max_iteration, bhp_tolerance, iteration);
if constexpr (extraBhpAtThpLimitOutput) {
OpmLog::debug("***** " + well_.name() + " solved_bhp = " + std::to_string(solved_bhp)
@ -610,20 +634,21 @@ computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double
}
}
std::optional<double>
WellBhpThpCalculator::
bhpMax(const std::function<double(const double)>& fflo,
const double bhp_limit,
const double maxPerfPress,
const double vfp_flo_front,
template<class Scalar>
std::optional<Scalar>
WellBhpThpCalculator<Scalar>::
bhpMax(const std::function<Scalar(const Scalar)>& fflo,
const Scalar bhp_limit,
const Scalar maxPerfPress,
const Scalar vfp_flo_front,
DeferredLogger& deferred_logger) const
{
// Find the bhp-point where production becomes nonzero.
double bhp_max = 0.0;
double low = bhp_limit;
double high = maxPerfPress + 1.0 * unit::barsa;
double f_low = fflo(low);
double f_high = fflo(high);
Scalar bhp_max = 0.0;
Scalar low = bhp_limit;
Scalar high = maxPerfPress + 1.0 * unit::barsa;
Scalar f_low = fflo(low);
Scalar f_high = fflo(high);
if constexpr (extraBhpAtThpLimitOutput) {
deferred_logger.debug("computeBhpAtThpLimitProd(): well = " + well_.name() +
" low = " + std::to_string(low) +
@ -633,7 +658,7 @@ bhpMax(const std::function<double(const double)>& fflo,
}
int adjustments = 0;
const int max_adjustments = 10;
const double adjust_amount = 5.0 * unit::barsa;
const Scalar adjust_amount = 5.0 * unit::barsa;
while (f_low * f_high > 0.0 && adjustments < max_adjustments) {
// Same sign, adjust high to see if we can flip it.
high += adjust_amount;
@ -656,12 +681,12 @@ bhpMax(const std::function<double(const double)>& fflo,
} else {
// Bisect to find a bhp point where we produce, but
// not a large amount ('eps' below).
const double eps = 0.1 * std::fabs(vfp_flo_front);
const Scalar eps = 0.1 * std::fabs(vfp_flo_front);
const int maxit = 50;
int it = 0;
while (std::fabs(f_low) > eps && it < maxit) {
const double curr = 0.5*(low + high);
const double f_curr = fflo(curr);
const Scalar curr = 0.5*(low + high);
const Scalar f_curr = fflo(curr);
if (f_curr * f_low > 0.0) {
low = curr;
f_low = f_curr;
@ -690,11 +715,12 @@ bhpMax(const std::function<double(const double)>& fflo,
return bhp_max;
}
std::optional<double>
WellBhpThpCalculator::
computeBhpAtThpLimit(const std::function<std::vector<double>(const double)>& frates,
const std::function<double(const std::vector<double>)>& fbhp,
const std::array<double, 2>& range,
template<class Scalar>
std::optional<Scalar>
WellBhpThpCalculator<Scalar>::
computeBhpAtThpLimit(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const std::function<Scalar(const std::vector<Scalar>)>& fbhp,
const std::array<Scalar, 2>& range,
DeferredLogger& deferred_logger) const
{
// Given a VFP function returning bhp as a function of phase
@ -714,15 +740,16 @@ computeBhpAtThpLimit(const std::function<std::vector<double>(const double)>& fra
// highest rate) should be returned.
// Define the equation we want to solve.
auto eq = [&fbhp, &frates](double bhp) {
auto eq = [&fbhp, &frates](Scalar bhp) {
return fbhp(frates(bhp)) - bhp;
};
// Find appropriate brackets for the solution.
std::optional<double> approximate_solution;
double low, high;
std::optional<Scalar> approximate_solution;
Scalar low, high;
// trying to use bisect way to locate a bracket
bool finding_bracket = this->bisectBracket(eq, range, low, high, approximate_solution, deferred_logger);
bool finding_bracket = this->bisectBracket(eq, range, low, high,
approximate_solution, deferred_logger);
// based on the origional design, if an approximate solution is suggested, we use this value directly
// in the long run, we might change it
@ -744,10 +771,10 @@ computeBhpAtThpLimit(const std::function<std::vector<double>(const double)>& fra
// Solve for the proper solution in the given interval.
const int max_iteration = 100;
const double bhp_tolerance = 0.01 * unit::barsa;
const Scalar bhp_tolerance = 0.01 * unit::barsa;
int iteration = 0;
try {
const double solved_bhp = RegulaFalsiBisection<ThrowOnError>::
const Scalar solved_bhp = RegulaFalsiBisection<ThrowOnError>::
solve(eq, low, high, max_iteration, bhp_tolerance, iteration);
return solved_bhp;
}
@ -758,21 +785,21 @@ computeBhpAtThpLimit(const std::function<std::vector<double>(const double)>& fra
}
}
bool
WellBhpThpCalculator::
bisectBracket(const std::function<double(const double)>& eq,
const std::array<double, 2>& range,
double& low, double& high,
std::optional<double>& approximate_solution,
template<class Scalar>
bool WellBhpThpCalculator<Scalar>::
bisectBracket(const std::function<Scalar(const Scalar)>& eq,
const std::array<Scalar, 2>& range,
Scalar& low, Scalar& high,
std::optional<Scalar>& approximate_solution,
DeferredLogger& deferred_logger) const
{
bool finding_bracket = false;
low = range[0];
high = range[1];
double eq_high = eq(high);
double eq_low = eq(low);
const double eq_bhplimit = eq_low;
Scalar eq_high = eq(high);
Scalar eq_low = eq(low);
const Scalar eq_bhplimit = eq_low;
if constexpr (extraBhpAtThpLimitOutput) {
deferred_logger.debug("computeBhpAtThpLimitProd(): well = " + well_.name() +
" low = " + std::to_string(low) +
@ -783,12 +810,12 @@ bisectBracket(const std::function<double(const double)>& eq,
if (eq_low * eq_high > 0.0) {
// Failed to bracket the zero.
// If this is due to having two solutions, bisect until bracketed.
double abs_low = std::fabs(eq_low);
double abs_high = std::fabs(eq_high);
Scalar abs_low = std::fabs(eq_low);
Scalar abs_high = std::fabs(eq_high);
int bracket_attempts = 0;
const int max_bracket_attempts = 20;
double interval = high - low;
const double min_interval = 1.0 * unit::barsa;
Scalar interval = high - low;
const Scalar min_interval = 1.0 * unit::barsa;
while (eq_low * eq_high > 0.0 && bracket_attempts < max_bracket_attempts && interval > min_interval) {
if (abs_high < abs_low) {
low = 0.5 * (low + high);
@ -815,7 +842,7 @@ bisectBracket(const std::function<double(const double)>& eq,
}
} else { // eq_low * eq_high > 0.0
// Still failed bracketing!
const double limit = 0.1 * unit::barsa;
const Scalar limit = 0.1 * unit::barsa;
if (std::min(abs_low, abs_high) < limit) {
// Return the least bad solution if less off than 0.1 bar.
deferred_logger.warning("FAILED_ROBUST_BHP_THP_SOLVE_BRACKETING_FAILURE",
@ -834,20 +861,20 @@ bisectBracket(const std::function<double(const double)>& eq,
return finding_bracket;
}
bool
WellBhpThpCalculator::
bruteForceBracket(const std::function<double(const double)>& eq,
const std::array<double, 2>& range,
double& low, double& high,
template<class Scalar>
bool WellBhpThpCalculator<Scalar>::
bruteForceBracket(const std::function<Scalar(const Scalar)>& eq,
const std::array<Scalar, 2>& range,
Scalar& low, Scalar& high,
DeferredLogger& deferred_logger)
{
bool bracket_found = false;
low = range[0];
high = range[1];
const int sample_number = 200;
const double interval = (high - low) / sample_number;
double eq_low = eq(low);
double eq_high = 0.0;
const Scalar interval = (high - low) / sample_number;
Scalar eq_low = eq(low);
Scalar eq_high = 0.0;
for (int i = 0; i < sample_number + 1; ++i) {
high = range[0] + interval * i;
eq_high = eq(high);
@ -866,10 +893,11 @@ bruteForceBracket(const std::function<double(const double)>& eq,
return bracket_found;
}
bool WellBhpThpCalculator::
isStableSolution(const WellState<double>& well_state,
template<class Scalar>
bool WellBhpThpCalculator<Scalar>::
isStableSolution(const WellState<Scalar>& well_state,
const Well& well,
const std::vector<double>& rates,
const std::vector<Scalar>& rates,
const SummaryState& summaryState) const
{
assert(int(rates.size()) == 3); // the vfp related only supports three phases now.
@ -879,10 +907,10 @@ isStableSolution(const WellState<double>& well_state,
static constexpr int Oil = BlackoilPhases::Liquid;
static constexpr int Water = BlackoilPhases::Aqua;
const double aqua = rates[Water];
const double liquid = rates[Oil];
const double vapour = rates[Gas];
const double thp = well_.getTHPConstraint(summaryState);
const Scalar aqua = rates[Water];
const Scalar liquid = rates[Oil];
const Scalar vapour = rates[Gas];
const Scalar thp = well_.getTHPConstraint(summaryState);
const auto& controls = well.productionControls(summaryState);
const auto& wfr = well_.vfpProperties()->getExplicitWFR(controls.vfp_table_number, well_.indexOfWell());
@ -897,27 +925,28 @@ isStableSolution(const WellState<double>& well_state,
return true;
} else { // maybe check if ipr is available
const auto ipr = getFloIPR(well_state, well, summaryState);
return bhp.dflo + 1/ipr.second >= 0;
return bhp.dflo + 1.0 / ipr.second >= 0;
}
}
std::optional<double> WellBhpThpCalculator::
estimateStableBhp(const WellState<double>& well_state,
template<class Scalar>
std::optional<Scalar> WellBhpThpCalculator<Scalar>::
estimateStableBhp(const WellState<Scalar>& well_state,
const Well& well,
const std::vector<double>& rates,
const double rho,
const std::vector<Scalar>& rates,
const Scalar rho,
const SummaryState& summaryState) const
{
// Given a *converged* well_state with ipr, estimate bhp of the stable solution
const auto& controls = well.productionControls(summaryState);
const double thp = well_.getTHPConstraint(summaryState);
const Scalar thp = well_.getTHPConstraint(summaryState);
const auto& table = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number);
const double aqua = rates[BlackoilPhases::Aqua];
const double liquid = rates[BlackoilPhases::Liquid];
const double vapour = rates[BlackoilPhases::Vapour];
double flo = detail::getFlo(table, aqua, liquid, vapour);
double wfr, gfr;
const Scalar aqua = rates[BlackoilPhases::Aqua];
const Scalar liquid = rates[BlackoilPhases::Liquid];
const Scalar vapour = rates[BlackoilPhases::Vapour];
Scalar flo = detail::getFlo(table, aqua, liquid, vapour);
Scalar wfr, gfr;
if (well_.useVfpExplicit() || -flo < table.getFloAxis().front()) {
wfr = well_.vfpProperties()->getExplicitWFR(controls.vfp_table_number, well_.indexOfWell());
gfr = well_.vfpProperties()->getExplicitGFR(controls.vfp_table_number, well_.indexOfWell());
@ -928,11 +957,11 @@ estimateStableBhp(const WellState<double>& well_state,
auto ipr = getFloIPR(well_state, well, summaryState);
const double vfp_ref_depth = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number).getDatumDepth();
const Scalar vfp_ref_depth = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number).getDatumDepth();
const double dp_hydro = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth,
const Scalar dp_hydro = wellhelpers::computeHydrostaticCorrection(well_.refDepth(), vfp_ref_depth,
rho, well_.gravity());
auto bhp_adjusted = [this, &thp, &dp_hydro](const double bhp) {
auto bhp_adjusted = [this, &thp, &dp_hydro](const Scalar bhp) {
return bhp - dp_hydro + getVfpBhpAdjustment(bhp, thp);
};
const auto retval = detail::intersectWithIPR(table, thp, wfr, gfr, well_.getALQ(well_state), ipr.first, ipr.second, bhp_adjusted);
@ -942,10 +971,11 @@ estimateStableBhp(const WellState<double>& well_state,
} else {
return std::nullopt;
}
}
}
std::pair<double, double> WellBhpThpCalculator::
getFloIPR(const WellState<double>& well_state,
template<class Scalar>
std::pair<Scalar, Scalar> WellBhpThpCalculator<Scalar>::
getFloIPR(const WellState<Scalar>& well_state,
const Well& well,
const SummaryState& summary_state) const
{
@ -954,21 +984,23 @@ getFloIPR(const WellState<double>& well_state,
const auto& table = well_.vfpProperties()->getProd()->getTable(controls.vfp_table_number);
const auto& pu = well_.phaseUsage();
const auto& ipr_a = well_state.well(well_.indexOfWell()).implicit_ipr_a;
const double& aqua_a = pu.phase_used[BlackoilPhases::Aqua]? ipr_a[pu.phase_pos[BlackoilPhases::Aqua]] : 0.0;
const double& liquid_a = pu.phase_used[BlackoilPhases::Liquid]? ipr_a[pu.phase_pos[BlackoilPhases::Liquid]] : 0.0;
const double& vapour_a = pu.phase_used[BlackoilPhases::Vapour]? ipr_a[pu.phase_pos[BlackoilPhases::Vapour]] : 0.0;
const Scalar& aqua_a = pu.phase_used[BlackoilPhases::Aqua]? ipr_a[pu.phase_pos[BlackoilPhases::Aqua]] : 0.0;
const Scalar& liquid_a = pu.phase_used[BlackoilPhases::Liquid]? ipr_a[pu.phase_pos[BlackoilPhases::Liquid]] : 0.0;
const Scalar& vapour_a = pu.phase_used[BlackoilPhases::Vapour]? ipr_a[pu.phase_pos[BlackoilPhases::Vapour]] : 0.0;
const auto& ipr_b = well_state.well(well_.indexOfWell()).implicit_ipr_b;
const double& aqua_b = pu.phase_used[BlackoilPhases::Aqua]? ipr_b[pu.phase_pos[BlackoilPhases::Aqua]] : 0.0;
const double& liquid_b = pu.phase_used[BlackoilPhases::Liquid]? ipr_b[pu.phase_pos[BlackoilPhases::Liquid]] : 0.0;
const double& vapour_b = pu.phase_used[BlackoilPhases::Vapour]? ipr_b[pu.phase_pos[BlackoilPhases::Vapour]] : 0.0;
const Scalar& aqua_b = pu.phase_used[BlackoilPhases::Aqua]? ipr_b[pu.phase_pos[BlackoilPhases::Aqua]] : 0.0;
const Scalar& liquid_b = pu.phase_used[BlackoilPhases::Liquid]? ipr_b[pu.phase_pos[BlackoilPhases::Liquid]] : 0.0;
const Scalar& vapour_b = pu.phase_used[BlackoilPhases::Vapour]? ipr_b[pu.phase_pos[BlackoilPhases::Vapour]] : 0.0;
// The getFlo helper is indended to pick one or add two of the phase rates (depending on FLO-type),
// but we can equally use it to pick/add the corresponding ipr_a, ipr_b
return std::make_pair(detail::getFlo(table, aqua_a, liquid_a, vapour_a),
detail::getFlo(table, aqua_b, liquid_b, vapour_b));
}
template class WellBhpThpCalculator<double>;
#define INSTANCE(...) \
template __VA_ARGS__ WellBhpThpCalculator:: \
template __VA_ARGS__ WellBhpThpCalculator<double>:: \
calculateBhpFromThp<__VA_ARGS__>(const WellState<double>&, \
const std::vector<__VA_ARGS__>&, \
const Well&, \
@ -993,4 +1025,5 @@ INSTANCE(DenseAd::Evaluation<double,-1,8u>)
INSTANCE(DenseAd::Evaluation<double,-1,9u>)
INSTANCE(DenseAd::Evaluation<double,-1,10u>)
INSTANCE(DenseAd::Evaluation<double,-1,11u>)
} // namespace Opm

View File

@ -26,11 +26,9 @@
#include <functional>
#include <optional>
#include <string>
#include <vector>
namespace Opm
{
namespace Opm {
class DeferredLogger;
class SummaryState;
@ -39,136 +37,137 @@ template<class Scalar> class WellInterfaceGeneric;
template<class Scalar> class WellState;
//! \brief Class for computing BHP limits.
template<class Scalar>
class WellBhpThpCalculator {
public:
//! \brief Constructor sets reference to well.
WellBhpThpCalculator(const WellInterfaceGeneric<double>& well) : well_(well) {}
WellBhpThpCalculator(const WellInterfaceGeneric<Scalar>& well) : well_(well) {}
//! \brief Checks if well has THP constraints.
bool wellHasTHPConstraints(const SummaryState& summaryState) const;
//! \brief Get THP constraint for well.
double getTHPConstraint(const SummaryState& summaryState) const;
Scalar getTHPConstraint(const SummaryState& summaryState) const;
//! \brief Obtain the most strict BHP from BHP limits.
double mostStrictBhpFromBhpLimits(const SummaryState& summaryState) const;
Scalar mostStrictBhpFromBhpLimits(const SummaryState& summaryState) const;
//! \brief Calculates THP from BHP.
double calculateThpFromBhp(const std::vector<double>& rates,
const double bhp,
const double rho,
const std::optional<double>& alq,
const double thp_limit,
Scalar calculateThpFromBhp(const std::vector<Scalar>& rates,
const Scalar bhp,
const Scalar rho,
const std::optional<Scalar>& alq,
const Scalar thp_limit,
DeferredLogger& deferred_logger) const;
//! \brief Compute BHP from THP limit for a producer.
std::optional<double>
computeBhpAtThpLimitProd(const std::function<std::vector<double>(const double)>& frates,
std::optional<Scalar>
computeBhpAtThpLimitProd(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const SummaryState& summary_state,
const double maxPerfPress,
const double rho,
const double alq_value,
const double thp_limit,
const Scalar maxPerfPress,
const Scalar rho,
const Scalar alq_value,
const Scalar thp_limit,
DeferredLogger& deferred_logger) const;
//! \brief Compute BHP from THP limit for an injector.
std::optional<double>
computeBhpAtThpLimitInj(const std::function<std::vector<double>(const double)>& frates,
std::optional<Scalar>
computeBhpAtThpLimitInj(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const SummaryState& summary_state,
const double rho,
const double flo_rel_tol,
const Scalar rho,
const Scalar flo_rel_tol,
const int max_iteration,
const bool throwOnError,
DeferredLogger& deferred_logger) const;
//! \brief Update THP.
void updateThp(const double rho,
void updateThp(const Scalar rho,
const bool stop_or_zero_rate_target,
const std::function<double()>& alq_value,
const std::function<Scalar()>& alq_value,
const std::array<unsigned,3>& active,
WellState<double>& well_state,
WellState<Scalar>& well_state,
const SummaryState& summary_state,
DeferredLogger& deferred_logger) const;
template<class EvalWell>
EvalWell calculateBhpFromThp(const WellState<double>& well_state,
EvalWell calculateBhpFromThp(const WellState<Scalar>& well_state,
const std::vector<EvalWell>& rates,
const Well& well,
const SummaryState& summaryState,
const double rho,
const Scalar rho,
DeferredLogger& deferred_logger) const;
double calculateMinimumBhpFromThp(const WellState<double>& well_state,
const Well& well,
const SummaryState& summaryState,
const double rho) const;
Scalar calculateMinimumBhpFromThp(const WellState<Scalar>& well_state,
const Well& well,
const SummaryState& summaryState,
const Scalar rho) const;
bool isStableSolution(const WellState<double>& well_state,
bool isStableSolution(const WellState<Scalar>& well_state,
const Well& well,
const std::vector<double>& rates,
const std::vector<Scalar>& rates,
const SummaryState& summaryState) const;
std::optional<double>
estimateStableBhp (const WellState<double>& well_state,
std::optional<Scalar>
estimateStableBhp (const WellState<Scalar>& well_state,
const Well& well,
const std::vector<double>& rates,
const double rho,
const std::vector<Scalar>& rates,
const Scalar rho,
const SummaryState& summaryState) const;
std::pair<double, double>
getFloIPR(const WellState<double>& well_state,
std::pair<Scalar, Scalar>
getFloIPR(const WellState<Scalar>& well_state,
const Well& well,
const SummaryState& summary_state) const;
private:
//! \brief Compute BHP from THP limit for an injector - implementation.
template<class ErrorPolicy>
std::optional<double>
computeBhpAtThpLimitInjImpl(const std::function<std::vector<double>(const double)>& frates,
std::optional<Scalar>
computeBhpAtThpLimitInjImpl(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const SummaryState& summary_state,
const double rho,
const double flo_rel_tol,
const Scalar rho,
const Scalar flo_rel_tol,
const int max_iteration,
DeferredLogger& deferred_logger) const;
//! \brief Calculate max BHP.
std::optional<double>
bhpMax(const std::function<double(const double)>& fflo,
const double bhp_limit,
const double maxPerfPress,
const double vfp_flo_front,
std::optional<Scalar>
bhpMax(const std::function<Scalar(const Scalar)>& fflo,
const Scalar bhp_limit,
const Scalar maxPerfPress,
const Scalar vfp_flo_front,
DeferredLogger& deferred_logger) const;
//! \brief Common code for finding BHP from THP limit for producers/injectors.
std::optional<double>
computeBhpAtThpLimit(const std::function<std::vector<double>(const double)>& frates,
const std::function<double(const std::vector<double>)>& fbhp,
const std::array<double, 2>& range,
std::optional<Scalar>
computeBhpAtThpLimit(const std::function<std::vector<Scalar>(const Scalar)>& frates,
const std::function<Scalar(const std::vector<Scalar>)>& fbhp,
const std::array<Scalar, 2>& range,
DeferredLogger& deferred_logger) const;
//! \brief Get pressure adjustment to the bhp calculated from VFP table
double getVfpBhpAdjustment(const double bph_tab, const double thp_limit) const;
Scalar getVfpBhpAdjustment(const Scalar bph_tab, const Scalar thp_limit) const;
//! \brief Find limits using bisection.
bool bisectBracket(const std::function<double(const double)>& eq,
const std::array<double, 2>& range,
double& low, double& high,
std::optional<double>& approximate_solution,
bool bisectBracket(const std::function<Scalar(const Scalar)>& eq,
const std::array<Scalar, 2>& range,
Scalar& low, Scalar& high,
std::optional<Scalar>& approximate_solution,
DeferredLogger& deferred_logger) const;
//! \brief Find limits using brute-force solver.
static bool bruteForceBracket(const std::function<double(const double)>& eq,
const std::array<double, 2>& range,
double& low, double& high,
static bool bruteForceBracket(const std::function<Scalar(const Scalar)>& eq,
const std::array<Scalar, 2>& range,
Scalar& low, Scalar& high,
DeferredLogger& deferred_logger);
double findThpFromBhpIteratively(const std::function<double(const double, const double)>& thp_func,
const double bhp,
const double thp_limit,
const double dp,
Scalar findThpFromBhpIteratively(const std::function<Scalar(const Scalar, const Scalar)>& thp_func,
const Scalar bhp,
const Scalar thp_limit,
const Scalar dp,
DeferredLogger& deferred_logger) const;
const WellInterfaceGeneric<double>& well_; //!< Reference to well interface
const WellInterfaceGeneric<Scalar>& well_; //!< Reference to well interface
};
}