Check group limits in gas lift stage 1.

Check group limits in gas lift stage 1 to avoid adding too much ALQ which must
anyway later be removed in stage 2. This should make the optimization
more efficient for small ALQ increment values. Also adds MPI support.
This commit is contained in:
Håkon Hægland 2021-05-27 08:31:49 +02:00
parent 969fc20154
commit fbb24e2a5a
18 changed files with 1586 additions and 608 deletions

View File

@ -60,6 +60,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/simulators/utils/ParallelRestart.cpp
opm/simulators/wells/ALQState.cpp
opm/simulators/wells/BlackoilWellModelGeneric.cpp
opm/simulators/wells/GasLiftGroupInfo.cpp
opm/simulators/wells/GasLiftSingleWellGeneric.cpp
opm/simulators/wells/GasLiftStage2.cpp
opm/simulators/wells/GlobalWellInfo.cpp

View File

@ -31,6 +31,7 @@
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
@ -50,6 +51,9 @@
#include <opm/simulators/wells/BlackoilWellModelGeneric.hpp>
#include <opm/simulators/wells/GasLiftSingleWell.hpp>
#include <opm/simulators/wells/GasLiftWellState.hpp>
#include <opm/simulators/wells/GasLiftSingleWellGeneric.hpp>
#include <opm/simulators/wells/GasLiftStage2.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <opm/simulators/wells/PerforationData.hpp>
#include <opm/simulators/wells/VFPInjProperties.hpp>
#include <opm/simulators/wells/VFPProdProperties.hpp>
@ -100,6 +104,12 @@ namespace Opm {
using RateVector = GetPropType<TypeTag, Properties::RateVector>;
using GlobalEqVector = GetPropType<TypeTag, Properties::GlobalEqVector>;
using SparseMatrixAdapter = GetPropType<TypeTag, Properties::SparseMatrixAdapter>;
using GLiftOptWells = typename BlackoilWellModelGeneric::GLiftOptWells;
using GLiftProdWells = typename BlackoilWellModelGeneric::GLiftProdWells;
using GLiftWellStateMap =
typename BlackoilWellModelGeneric::GLiftWellStateMap;
using GLiftEclWells = typename GasLiftGroupInfo::GLiftEclWells;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups;
typedef typename BaseAuxiliaryModule<TypeTag>::NeighborSet NeighborSet;
@ -266,6 +276,7 @@ namespace Opm {
void initPrimaryVariablesEvaluation() const;
void updateWellControls(DeferredLogger& deferred_logger, const bool checkGroupControls);
WellInterfacePtr getWell(const std::string& well_name) const;
void initGliftEclWellMap(GLiftEclWells &ecl_well_map);
protected:
Simulator& ebosSimulator_;
@ -360,6 +371,12 @@ namespace Opm {
void maybeDoGasLiftOptimize(DeferredLogger& deferred_logger);
bool checkDoGasLiftOptimization(DeferredLogger& deferred_logger);
void gasLiftOptimizationStage1(DeferredLogger& deferred_logger,
GLiftProdWells &prod_wells, GLiftOptWells &glift_wells,
GasLiftGroupInfo &group_info, GLiftWellStateMap &state_map);
void extractLegacyCellPvtRegionIndex_();
void extractLegacyDepth_();

View File

@ -851,19 +851,165 @@ namespace Opm {
BlackoilWellModel<TypeTag>::
maybeDoGasLiftOptimize(DeferredLogger& deferred_logger)
{
this->wellState().enableGliftOptimization();
GLiftOptWells glift_wells;
GLiftProdWells prod_wells;
GLiftWellStateMap state_map;
// Stage1: Optimize single wells not checking any group limits
for (auto& well : well_container_) {
well->gasLiftOptimizationStage1(
this->wellState(), ebosSimulator_, deferred_logger,
prod_wells, glift_wells, state_map);
if (checkDoGasLiftOptimization(deferred_logger)) {
GLiftOptWells glift_wells;
GLiftProdWells prod_wells;
GLiftWellStateMap state_map;
// NOTE: To make GasLiftGroupInfo (see below) independent of the TypeTag
// associated with *this (i.e. BlackoilWellModel<TypeTag>) we observe
// that GasLiftGroupInfo's only dependence on *this is that it needs to
// access the eclipse Wells in the well container (the eclipse Wells
// themselves are independent of the TypeTag).
// Hence, we extract them from the well container such that we can pass
// them to the GasLiftGroupInfo constructor.
GLiftEclWells ecl_well_map;
initGliftEclWellMap(ecl_well_map);
GasLiftGroupInfo group_info {
ecl_well_map,
ebosSimulator_.vanguard().schedule(),
ebosSimulator_.vanguard().summaryState(),
ebosSimulator_.episodeIndex(),
ebosSimulator_.model().newtonMethod().numIterations(),
phase_usage_,
deferred_logger,
this->wellState()
};
group_info.initialize(ebosSimulator_.vanguard().grid().comm());
gasLiftOptimizationStage1(
deferred_logger, prod_wells, glift_wells, group_info, state_map);
gasLiftOptimizationStage2(
deferred_logger, prod_wells, glift_wells, state_map,
ebosSimulator_.episodeIndex());
if (this->glift_debug) gliftDebugShowALQ(deferred_logger);
}
gasLiftOptimizationStage2(deferred_logger, prod_wells, glift_wells, state_map, ebosSimulator_.episodeIndex());
if (this->glift_debug) gliftDebugShowALQ(deferred_logger);
this->wellState().disableGliftOptimization();
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
gasLiftOptimizationStage1(DeferredLogger& deferred_logger,
GLiftProdWells &prod_wells, GLiftOptWells &glift_wells,
GasLiftGroupInfo &group_info, GLiftWellStateMap &state_map)
{
auto comm = ebosSimulator_.vanguard().grid().comm();
std::size_t num_procs = comm.size();
// NOTE: Gas lift optimization stage 1 seems to be difficult
// to do in parallel since the wells are optimized on different
// processes and each process needs to know the current ALQ allocated
// to each group it is a memeber of in order to check group limits and avoid
// allocating more ALQ than necessary. (Surplus ALQ is removed in
// stage 2). In stage1, as each well is adding ALQ, the current group ALQ needs
// to be communicated to the other processes. But there is no common
// synchronization point that all process will reach in the
// runOptimizeLoop_() in GasLiftSingleWell.cpp.
//
// TODO: Maybe a better solution could be invented by distributing
// wells according to certain parent groups. Then updated group rates
// might not have to be communicated to the other processors.
// Currently, the best option seems to be to run this part sequentially
// (not in parallel).
//
// TODO: The simplest approach seems to be if a) one process could take
// ownership of all the wells (the union of all the wells in the
// well_container_ of each process) then this process could do the
// optimization, while the other processes could wait for it to
// finish (e.g. comm.barrier()), or alternatively, b) if all
// processes could take ownership of all the wells. Then there
// would be no need for synchronization here..
//
for (std::size_t i = 0; i< num_procs; i++) {
int num_rates_to_sync = 0; // communication variable
GLiftSyncGroups groups_to_sync;
if (comm.rank() == i) {
// Run stage1: Optimize single wells while also checking group limits
for (const auto& well : well_container_) {
// NOTE: Only the wells in "group_info" needs to be optimized
if (group_info.hasWell(well->name())) {
well->gasLiftOptimizationStage1(
this->wellState(), ebosSimulator_, deferred_logger,
prod_wells, glift_wells, state_map,
group_info, groups_to_sync);
}
}
num_rates_to_sync = groups_to_sync.size();
}
// Since "group_info" is not used in stage2, there is no need to
// communicate rates if this is the last iteration...
if (i == (num_procs - 1))
break;
num_rates_to_sync = comm.sum(num_rates_to_sync);
if (num_rates_to_sync > 0) {
std::vector<int> group_indexes;
group_indexes.reserve(num_rates_to_sync);
std::vector<double> group_alq_rates;
group_alq_rates.reserve(num_rates_to_sync);
std::vector<double> group_oil_rates;
group_oil_rates.reserve(num_rates_to_sync);
std::vector<double> group_gas_rates;
group_gas_rates.reserve(num_rates_to_sync);
if (comm.rank() == i) {
for (auto idx : groups_to_sync) {
auto [oil_rate, gas_rate, alq] = group_info.getRates(idx);
group_indexes.push_back(idx);
group_oil_rates.push_back(oil_rate);
group_gas_rates.push_back(gas_rate);
group_alq_rates.push_back(alq);
}
}
// TODO: We only need to broadcast to processors with index
// j > i since we do not use the "group_info" in stage 2. In
// this case we should use comm.send() and comm.receive()
// instead of comm.broadcast() to communicate with specific
// processes, and these processes only need to receive the
// data if they are going to check the group rates in stage1
// Another similar idea is to only communicate the rates to
// process j = i + 1
comm.broadcast(group_indexes.data(), num_rates_to_sync, i);
comm.broadcast(group_oil_rates.data(), num_rates_to_sync, i);
comm.broadcast(group_gas_rates.data(), num_rates_to_sync, i);
comm.broadcast(group_alq_rates.data(), num_rates_to_sync, i);
if (comm.rank() != i) {
for (int j=0; j<num_rates_to_sync; j++) {
group_info.updateRate(group_indexes[j],
group_oil_rates[j], group_gas_rates[j], group_alq_rates[j]);
}
}
}
}
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
initGliftEclWellMap(GLiftEclWells &ecl_well_map)
{
for ( const auto& well: well_container_ ) {
ecl_well_map.try_emplace(
well->name(), &(well->wellEcl()), well->indexOfWell());
}
}
template<typename TypeTag>
bool
BlackoilWellModel<TypeTag>::
checkDoGasLiftOptimization(Opm::DeferredLogger& deferred_logger)
{
gliftDebug("checking if GLIFT should be done..", deferred_logger);
/*
std::size_t num_procs = ebosSimulator_.gridView().comm().size();
if (num_procs > 1u) {
const std::string msg = fmt::format(" GLIFT: skipping optimization. "
"Parallel run not supported yet: num procs = {}", num_procs);
deferred_logger.warning(msg);
return false;
}
*/
if (!(this->wellState().gliftOptimizationEnabled())) {
gliftDebug("Optimization disabled in WellState", deferred_logger);
return false;
}
return true;
}
template<typename TypeTag>

View File

@ -0,0 +1,373 @@
/*
Copyright 2021 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
namespace Opm {
GasLiftGroupInfo::
GasLiftGroupInfo(
GLiftEclWells &ecl_wells,
const Schedule &schedule,
const SummaryState &summary_state,
const int report_step_idx,
const int iteration_idx,
const PhaseUsage &phase_usage,
DeferredLogger &deferred_logger,
WellState &well_state
) :
ecl_wells_{ecl_wells},
schedule_{schedule},
summary_state_{summary_state},
report_step_idx_{report_step_idx},
iteration_idx_{iteration_idx},
phase_usage_{phase_usage},
deferred_logger_{deferred_logger},
well_state_{well_state},
glo_{schedule_.glo(report_step_idx_)},
debug{false}
{
}
/****************************************
* Public methods in alphabetical order
****************************************/
double
GasLiftGroupInfo::
alqRate(const std::string& group_name)
{
auto& group_rate = this->group_rate_map_.at(group_name);
return group_rate.alq();
}
int
GasLiftGroupInfo::
getGroupIdx(const std::string& group_name)
{
return this->group_idx_.at(group_name);
}
double
GasLiftGroupInfo::
gasRate(const std::string& group_name)
{
auto& group_rate = this->group_rate_map_.at(group_name);
return group_rate.gasRate();
}
std::optional<double>
GasLiftGroupInfo::
gasTarget(const std::string& group_name)
{
auto& group_rate = this->group_rate_map_.at(group_name);
return group_rate.gasTarget();
}
std::tuple<double, double, double>
GasLiftGroupInfo::
getRates(int group_idx)
{
const auto& group_name = groupIdxToName(group_idx);
auto& rates = this->group_rate_map_.at(group_name);
return std::make_tuple(rates.oilRate(), rates.gasRate(), rates.alq());
}
std::vector<std::pair<std::string,double>>&
GasLiftGroupInfo::
getWellGroups(const std::string& well_name)
{
assert(this->well_group_map_.count(well_name) == 1);
return this->well_group_map_[well_name];
}
const std::string&
GasLiftGroupInfo::
groupIdxToName(int group_idx)
{
const std::string *group_name = nullptr;
// TODO: An alternative to the below loop is to set up a reverse map from idx ->
// string, then we could in theory do faster lookup here..
for (const auto& [key, value] : this->group_idx_) {
if (value == group_idx) {
// NOTE: it is assumed that the mapping from name->idx is one-to-one
// so there can only be one idx with a given group name.
group_name = &key;
break;
}
}
// the caller is responsible for providing a valid idx, so group_name
// cannot be nullptr here..
assert(group_name);
return *group_name;
}
bool
GasLiftGroupInfo::
hasWell(const std::string& well_name)
{
return this->well_group_map_.count(well_name) == 1;
}
std::optional<double>
GasLiftGroupInfo::
maxAlq(const std::string& group_name)
{
auto& group_rate = this->group_rate_map_.at(group_name);
return group_rate.maxAlq();
}
double
GasLiftGroupInfo::
oilRate(const std::string &group_name)
{
auto& group_rate = this->group_rate_map_.at(group_name);
return group_rate.oilRate();
}
std::optional<double>
GasLiftGroupInfo::
oilTarget(const std::string &group_name)
{
auto& group_rate = this->group_rate_map_.at(group_name);
return group_rate.oilTarget();
}
void
GasLiftGroupInfo::
update(
const std::string &group_name, double delta_oil, double delta_gas, double delta_alq)
{
auto& group_rate = this->group_rate_map_.at(group_name);
group_rate.update(delta_oil, delta_gas, delta_alq);
}
void
GasLiftGroupInfo::
updateRate(int idx, double oil_rate, double gas_rate, double alq)
{
const auto& group_name = groupIdxToName(idx);
auto& rates = this->group_rate_map_.at(group_name);
rates.assign(oil_rate, gas_rate, alq);
}
/****************************************
* Private methods in alphabetical order
****************************************/
bool
GasLiftGroupInfo::
checkDoGasLiftOptimization_(const std::string &well_name)
{
if (this->well_state_.gliftCheckAlqOscillation(well_name)) {
displayDebugMessage_(
"further optimization skipped due to oscillation in ALQ", well_name);
return false;
}
if (this->optimize_only_thp_wells_) {
auto itr = this->ecl_wells_.find(well_name);
if (itr != this->ecl_wells_.end()) {
//const Well *well = (itr->second).first;
//assert(well); // Should never be nullptr
const int index = (itr->second).second;
const Well::ProducerCMode& control_mode
= this->well_state_.currentProductionControl(index);
if (control_mode != Well::ProducerCMode::THP ) {
displayDebugMessage_("Not THP control. Skipping.", well_name);
return false;
}
}
else {
// well_name is not present in the well_model's well container
return false;
}
}
if (!checkNewtonIterationIdxOk_(well_name)) {
return false;
}
if (!this->glo_.has_well(well_name)) {
displayDebugMessage_(
"Gas Lift not activated: WLIFTOPT is probably missing", well_name);
return false;
}
auto increment = this->glo_.gaslift_increment();
// NOTE: According to the manual: LIFTOPT, item 1, :
// "Increment size for lift gas injection rate. Lift gas is
// allocated to individual wells in whole numbers of the increment
// size. If gas lift optimization is no longer required, it can be
// turned off by entering a zero or negative number."
if (increment <= 0) {
if (this->debug) {
const std::string msg = fmt::format(
"Gas Lift switched off in LIFTOPT item 1 due to non-positive "
"value: {}", increment);
displayDebugMessage_(msg, well_name);
}
return false;
}
else {
return true;
}
}
bool
GasLiftGroupInfo::
checkNewtonIterationIdxOk_(const std::string &well_name)
{
if (this->glo_.all_newton()) {
const int nupcol = this->schedule_[this->report_step_idx_].nupcol();
if (this->debug) {
const std::string msg = fmt::format(
"LIFTOPT item4 == YES, it = {}, nupcol = {} --> GLIFT optimize = {}",
this->iteration_idx_,
nupcol,
((this->iteration_idx_ <= nupcol) ? "TRUE" : "FALSE"));
displayDebugMessage_(msg, well_name);
}
return this->iteration_idx_ <= nupcol;
}
else {
if (this->debug) {
const std::string msg = fmt::format(
"LIFTOPT item4 == NO, it = {} --> GLIFT optimize = {}",
this->iteration_idx_,
((this->iteration_idx_ == 1) ? "TRUE" : "FALSE"));
displayDebugMessage_(msg, well_name);
}
return this->iteration_idx_ == 1;
}
}
void
GasLiftGroupInfo::
displayDebugMessage_(const std::string &msg)
{
if (this->debug) {
const std::string message = fmt::format(
" GLIFT (DEBUG) : Init group info : {}", msg);
this->deferred_logger_.info(message);
}
}
void
GasLiftGroupInfo::
displayDebugMessage_(const std::string &msg, const std::string &well_name)
{
if (this->debug) {
const std::string message = fmt::format(
" GLIFT (DEBUG) : Init group info : Well {} : {}",
well_name, msg);
this->deferred_logger_.info(message);
}
}
std::pair<double, double>
GasLiftGroupInfo::
getProducerWellRates_(int well_index)
{
const auto& pu = this->phase_usage_;
auto oil_rate =
-this->well_state_.wellRates(well_index)[pu.phase_pos[Oil]];
auto gas_rate =
-this->well_state_.wellRates(well_index)[pu.phase_pos[Gas]];
return {oil_rate, gas_rate};
}
void
GasLiftGroupInfo::
initializeWell2GroupMapRecursive_(
const Group &group,
std::vector<std::string> &group_names,
std::vector<double> &group_efficiency,
double cur_efficiency)
{
double gfac = group.getGroupEfficiencyFactor();
cur_efficiency = gfac * cur_efficiency;
for (auto &item : group_efficiency) {
item *= gfac;
}
if (this->group_rate_map_.count(group.name()) == 1) {
// extract the subset of groups that has limits or targets that can affect
// gas lift optimization.
group_names.push_back(group.name());
group_efficiency.push_back(gfac);
}
if (group.wellgroup()) {
for (const std::string& well_name : group.wells()) {
// TODO: can the same well be memember of two different groups
// (on the same recursion level) ?
assert(this->well_group_map_.count(well_name) == 0);
if (checkDoGasLiftOptimization_(well_name)) {
const auto &well = this->schedule_.getWell(
well_name, this->report_step_idx_);
double wfac = well.getEfficiencyFactor();
auto [itr, success] = this->well_group_map_.insert(
{well_name, /*empty vector*/ {}});
assert(success);
auto &vec = itr->second;
assert(group_names.size() == group_efficiency.size());
auto iter2 = group_efficiency.begin();
for (auto iter1 = group_names.begin();
iter1 != group_names.end(); ++iter1)
{
double efficiency = (*iter2) * wfac;
vec.emplace_back(/*group_name=*/*iter1, efficiency);
++iter2;
}
}
}
}
else {
for (const std::string& group_name : group.groups()) {
if (!this->schedule_.back().groups.has(group_name))
continue;
const Group& sub_group = this->schedule_.getGroup(
group_name, this->report_step_idx_);
initializeWell2GroupMapRecursive_(
sub_group, group_names, group_efficiency, cur_efficiency);
}
}
if (this->group_rate_map_.count(group.name()) == 1) {
group_names.pop_back();
group_efficiency.pop_back();
}
}
// TODO: It would be more efficient if the group idx map was build once
// per time step (or better: once per report step) and saved e.g. in
// the well state object, instead of rebuilding here for each of
// NUPCOL well iteration for each time step.
void
GasLiftGroupInfo::
updateGroupIdxMap_(const std::string &group_name)
{
if (this->group_idx_.count(group_name) == 0) {
//auto [itr, success] =
this->group_idx_.try_emplace(group_name, this->next_group_idx_);
this->next_group_idx_++;
}
}
} // namespace Opm

View File

@ -0,0 +1,271 @@
/*
Copyright 2021 Equinor ASA.
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_GASLIFT_GROUP_INFO_HEADER_INCLUDED
#define OPM_GASLIFT_GROUP_INFO_HEADER_INCLUDED
#include <dune/common/version.hh>
#include <dune/common/parallel/mpihelper.hh>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/models/utils/propertysystem.hh>
#include <opm/models/utils/parametersystem.hh>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Group/Group.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/GasLiftOpt.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/simulators/wells/WellState.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include <fmt/format.h>
namespace Opm
{
class GasLiftGroupInfo
{
class GroupRates;
// NOTE: In the Well2GroupMap below, in the std::vector value we store
// pairs of group names and efficiency factors. The efficiency factors
// are the product of the wells efficiency factor and all the efficiency
// factors of the child groups of the group all the way down
// to the well group.
using Well2GroupMap =
std::map<std::string, std::vector<std::pair<std::string,double>>>;
using GroupRateMap =
std::map<std::string, GroupRates>;
using GroupIdxMap = std::map<std::string, int>;
// TODO: same definition with WellInterface, and
// WellState eventually they should go to a common header file.
static const int Water = BlackoilPhases::Aqua;
static const int Oil = BlackoilPhases::Liquid;
static const int Gas = BlackoilPhases::Vapour;
public:
using GLiftEclWells = std::map<std::string,std::pair<const Well *,int>>;
GasLiftGroupInfo(
GLiftEclWells& ecl_wells,
const Schedule& schedule,
const SummaryState& summary_state,
const int report_step_idx,
const int iteration_idx,
const PhaseUsage& phase_usage,
DeferredLogger& deferred_logger,
WellState& well_state);
std::vector<std::pair<std::string,double>>& getWellGroups(
const std::string& well_name);
template<class Comm>
void
initialize(const Comm& comm)
{
const auto& group = this->schedule_.getGroup("FIELD", this->report_step_idx_);
initializeGroupRatesRecursive_(comm, group);
std::vector<std::string> group_names;
std::vector<double> group_efficiency;
initializeWell2GroupMapRecursive_(
group, group_names, group_efficiency, /*current efficiency=*/1.0);
}
double alqRate(const std::string& group_name);
double gasRate(const std::string& group_name);
int getGroupIdx(const std::string& group_name);
std::tuple<double,double,double> getRates(int group_idx);
std::optional<double> gasTarget(const std::string& group_name);
const std::string& groupIdxToName(int group_idx);
bool hasWell(const std::string& well_name);
std::optional<double> maxAlq(const std::string& group_name);
double oilRate(const std::string& group_name);
std::optional<double> oilTarget(const std::string& group_name);
void update(const std::string& well_name,
double delta_oil, double delta_gas, double delta_alq);
void updateRate(int idx, double oil_rate, double gas_rate, double alq);
const Well2GroupMap& wellGroupMap() { return well_group_map_; }
private:
bool checkDoGasLiftOptimization_(const std::string& well_name);
bool checkNewtonIterationIdxOk_(const std::string& well_name);
void displayDebugMessage_(const std::string& msg);
void displayDebugMessage_(const std::string& msg, const std::string& well_name);
std::pair<double, double> getProducerWellRates_(const int index);
void initializeWell2GroupMapRecursive_(
const Group& group, std::vector<std::string>& group_names,
std::vector<double>& group_efficiency, double cur_efficiency);
void updateGroupIdxMap_(const std::string& group_name);
// TODO: I first tried to pass the MPI Communicator as a constructor argument
// to this class (GasLiftGroupInfo) similar to what is done for
// GasLiftStage2 (see GasLiftStage2.hpp), hower this did not work for this
// class since we are also constructing a GasLiftGroupInfo object in the
// test file test1_glift.cpp and when the linker tries to find a definition
// of the GasLiftGroupInfo(...) constructor in libopmsimulators.a,
// the the template type of the MPI communicator (Dune::Communication<..>)
// is not of the same type as the one needed by the test case.
// The test case needs Dune::Communication<ompi_communicator_t*>, whereas
// the one in libopmsimulators.a is Dune::Communication<Dune::No_Comm>.
//
// The error I got from the linker is:
//
// /bin/ld: CMakeFiles/test_glift1.dir/tests/test_glift1.cpp.o:
// in function `G1::test_method()':
// test_glift1.cpp:(.text+0x15b36): undefined reference to
// `Opm::GasLiftGroupInfo::GasLiftGroupInfo(....)
//
// to work around this issue this function is templetized in terms of Comm
// here in the header file instead of having it in the .cpp file.
// (thanks to Tor Harald S. for the suggestion)
template<class Comm>
std::tuple<double, double, double>
initializeGroupRatesRecursive_(const Comm& comm, const Group &group)
{
double oil_rate = 0.0;
double gas_rate = 0.0;
double alq = 0.0;
if (group.wellgroup()) {
for (const std::string& well_name : group.wells()) {
// NOTE: we cannot simply use:
//
// const auto &well =
// this->schedule_.getWell(well_name, this->report_step_idx_);
//
// since the well may not be active (present in the well container)
auto itr = this->ecl_wells_.find(well_name);
if (itr != this->ecl_wells_.end()) {
const Well *well = (itr->second).first;
assert(well); // Should never be nullptr
const int index = (itr->second).second;
if (well->isProducer()) {
auto [sw_oil_rate, sw_gas_rate] = getProducerWellRates_(index);
auto sw_alq = this->well_state_.getALQ(well_name);
double factor = well->getEfficiencyFactor();
oil_rate += (factor * sw_oil_rate);
gas_rate += (factor * sw_gas_rate);
alq += (factor * sw_alq);
}
}
}
// These sums needs to be communictated
oil_rate = comm.sum(oil_rate);
gas_rate = comm.sum(gas_rate);
alq = comm.sum(alq);
}
else {
for (const std::string& group_name : group.groups()) {
if (!this->schedule_.back().groups.has(group_name))
continue;
const Group& sub_group = this->schedule_.getGroup(
group_name, this->report_step_idx_);
auto [sg_oil_rate, sg_gas_rate, sg_alq]
= initializeGroupRatesRecursive_(comm, sub_group);
const auto gefac = sub_group.getGroupEfficiencyFactor();
oil_rate += (gefac * sg_oil_rate);
gas_rate += (gefac * sg_gas_rate);
alq += (gefac * sg_alq);
}
}
std::optional<double> oil_target, gas_target, max_total_gas, max_alq;
const auto controls = group.productionControls(this->summary_state_);
if (group.has_control(Group::ProductionCMode::ORAT)) {
oil_target = controls.oil_target;
}
if (group.has_control(Group::ProductionCMode::GRAT)) {
gas_target = controls.gas_target;
}
if (this->glo_.has_group(group.name())) {
const auto &gl_group = this->glo_.group(group.name());
max_alq = gl_group.max_lift_gas();
max_total_gas = gl_group.max_total_gas();
}
if (oil_target || gas_target || max_total_gas || max_alq) {
updateGroupIdxMap_(group.name());
this->group_rate_map_.try_emplace(group.name(),
oil_rate, gas_rate, alq, oil_target, gas_target, max_total_gas, max_alq);
}
return std::make_tuple(oil_rate, gas_rate, alq);
}
class GroupRates {
public:
GroupRates( double oil_rate, double gas_rate, double alq,
std::optional<double> oil_target,
std::optional<double> gas_target,
std::optional<double> total_gas,
std::optional<double> max_alq
) :
oil_rate_{oil_rate},
gas_rate_{gas_rate},
alq_{alq},
oil_target_{oil_target},
gas_target_{gas_target},
total_gas_{total_gas},
max_alq_{max_alq}
{}
double alq() const { return alq_; }
void assign(double oil_rate, double gas_rate, double alq)
{
oil_rate_ = oil_rate;
gas_rate_ = gas_rate;
alq_ = alq;
}
double gasRate() const { return gas_rate_; }
std::optional<double> gasTarget() const { return gas_target_; }
std::optional<double> maxAlq() const { return max_alq_; }
double oilRate() const { return oil_rate_; }
std::optional<double> oilTarget() const { return oil_target_; }
void update(double delta_oil, double delta_gas, double delta_alq)
{
oil_rate_ += delta_oil;
gas_rate_ += delta_gas;
alq_ += delta_alq;
}
private:
double oil_rate_;
double gas_rate_;
double alq_;
std::optional<double> oil_target_;
std::optional<double> gas_target_;
std::optional<double> total_gas_;
std::optional<double> max_alq_;
};
GLiftEclWells &ecl_wells_;
const Schedule &schedule_;
const SummaryState &summary_state_;
const int report_step_idx_;
const int iteration_idx_;
const PhaseUsage &phase_usage_;
DeferredLogger &deferred_logger_;
WellState &well_state_;
const GasLiftOpt& glo_;
GroupRateMap group_rate_map_;
Well2GroupMap well_group_map_;
bool debug;
GroupIdxMap group_idx_;
int next_group_idx_ = 0;
// Optimize only wells under THP control
bool optimize_only_thp_wells_ = true;
};
} // namespace Opm
#endif // OPM_GASLIFT_GROUP_INFO_INCLUDED

View File

@ -31,6 +31,7 @@ namespace Opm {
}
#include <opm/simulators/wells/StandardWell.hpp>
#include <opm/simulators/wells/GasLiftSingleWellGeneric.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <optional>
#include <vector>
@ -44,6 +45,7 @@ namespace Opm
{
using Simulator = GetPropType<TypeTag, Properties::Simulator>;
using StdWell = StandardWell<TypeTag>;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups;
public:
GasLiftSingleWell(
@ -51,7 +53,9 @@ namespace Opm
const Simulator &ebos_simulator,
const SummaryState &summary_state,
DeferredLogger &deferred_logger,
WellState &well_state
WellState &well_state,
GasLiftGroupInfo &group_info,
GLiftSyncGroups &sync_groups
);
const WellInterfaceGeneric &getStdWell() const override { return std_well_; }

View File

@ -35,17 +35,23 @@
namespace Opm
{
GasLiftSingleWellGeneric::GasLiftSingleWellGeneric(DeferredLogger& deferred_logger,
WellState& well_state,
const Well& ecl_well,
const SummaryState& summary_state,
const Schedule& schedule,
const int report_step_idx)
: deferred_logger_(deferred_logger)
, well_state_(well_state)
, ecl_well_(ecl_well)
, summary_state_(summary_state)
, controls_(ecl_well_.productionControls(summary_state_))
GasLiftSingleWellGeneric::GasLiftSingleWellGeneric(
DeferredLogger& deferred_logger,
WellState& well_state,
const Well& ecl_well,
const SummaryState& summary_state,
GasLiftGroupInfo &group_info,
const Schedule& schedule,
const int report_step_idx,
GLiftSyncGroups &sync_groups
) :
deferred_logger_{deferred_logger}
, well_state_{well_state}
, ecl_well_{ecl_well}
, summary_state_{summary_state}
, group_info_{group_info}
, sync_groups_{sync_groups}
, controls_{ecl_well_.productionControls(summary_state_)}
, num_phases_{well_state_.numPhases()}
, debug_{false} // extra debugging output
, debug_limit_increase_decrease_{false}
@ -106,33 +112,28 @@ calcIncOrDecGradient(double oil_rate, double gas_rate, double alq, bool increase
}
}
bool
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
computeInitialWellRates_(std::vector<double>& potentials)
runOptimize(const int iteration_idx)
{
if (auto bhp = computeBhpAtThpLimit_(this->orig_alq_); bhp) {
{
const std::string msg = fmt::format(
"computed initial bhp {} given thp limit and given alq {}",
*bhp, this->orig_alq_);
displayDebugMessage_(msg);
std::unique_ptr<GasLiftWellState> state;
if (this->optimize_) {
if (this->debug_limit_increase_decrease_) {
state = runOptimize1_();
}
computeWellRates_(*bhp, potentials);
{
const std::string msg = fmt::format(
"computed initial well potentials given bhp, "
"oil: {}, gas: {}, water: {}",
-potentials[this->oil_pos_],
-potentials[this->gas_pos_],
-potentials[this->water_pos_]);
displayDebugMessage_(msg);
else {
state = runOptimize2_();
}
if (state) {
if (state->increase()) {
double alq = state->alq();
if (this->debug_)
logSuccess_(alq, iteration_idx);
this->well_state_.setALQ(this->well_name_, alq);
}
}
return true;
}
else {
displayDebugMessage_("Aborting optimization.");
return false;
}
return state;
}
/****************************************
@ -229,9 +230,10 @@ checkInitialALQmodified_(double alq, double initial_alq) const
bool
GasLiftSingleWellGeneric::
checkWellRatesViolated_(std::vector<double>& potentials,
const std::function<bool(double, double, const std::string &)>& callback,
bool increase)
checkWellRatesViolated_(
std::vector<double>& potentials,
const std::function<bool(double, double, const std::string &)>& callback,
bool increase)
{
if (!increase) {
auto oil_rate = -potentials[this->oil_pos_];
@ -276,6 +278,35 @@ checkWellRatesViolated_(std::vector<double>& potentials,
return false;
}
bool
GasLiftSingleWellGeneric::
computeInitialWellRates_(std::vector<double>& potentials)
{
if (auto bhp = computeBhpAtThpLimit_(this->orig_alq_); bhp) {
{
const std::string msg = fmt::format(
"computed initial bhp {} given thp limit and given alq {}",
*bhp, this->orig_alq_);
displayDebugMessage_(msg);
}
computeWellRates_(*bhp, potentials);
{
const std::string msg = fmt::format(
"computed initial well potentials given bhp, "
"oil: {}, gas: {}, water: {}",
-potentials[this->oil_pos_],
-potentials[this->gas_pos_],
-potentials[this->water_pos_]);
displayDebugMessage_(msg);
}
return true;
}
else {
displayDebugMessage_("Aborting optimization.");
return false;
}
}
void
GasLiftSingleWellGeneric::
debugCheckNegativeGradient_(double grad, double alq, double new_alq, double oil_rate,
@ -566,6 +597,365 @@ logSuccess_(double alq, const int iteration_idx)
this->deferred_logger_.info(message);
}
std::tuple<double,double,double,bool,bool>
GasLiftSingleWellGeneric::
maybeAdjustALQbeforeOptimizeLoop_(
bool increase, double alq, double oil_rate, double gas_rate,
bool oil_is_limited, bool gas_is_limited,
std::vector<double> &potentials)
{
double orig_alq = alq;
if (this->debug_) {
const std::string msg = fmt::format("initial ALQ: {}", alq);
displayDebugMessage_(msg);
}
if (!increase && oil_is_limited) {
// NOTE: Try to decrease ALQ down to a value where the oil target is
// not exceeded.
// NOTE: This may reduce ALQ below the minimum value set in WLIFTOPT
// item 5. However, this is OK since the rate target is met and there
// is no point in using a higher ALQ value then.
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited, alq) =
reduceALQtoOilTarget_(
alq, oil_rate, gas_rate, oil_is_limited, gas_is_limited, potentials);
}
else {
if (increase && oil_rate < 0) {
// NOTE: Try to increase ALQ up to a value where oil_rate is positive
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited, alq) =
increaseALQtoPositiveOilRate_(alq, oil_rate,
gas_rate, oil_is_limited, gas_is_limited, potentials);
}
if (increase && (this->min_alq_> 0) && (alq < this->min_alq_)) {
// NOTE: Try to increase ALQ up to the minimum limit without checking
// the economic gradient..
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited, alq) =
increaseALQtoMinALQ_(alq, oil_rate, gas_rate,
oil_is_limited, gas_is_limited, potentials);
}
}
if (this->debug_ && (orig_alq != alq)) {
const std::string msg = fmt::format("adjusted ALQ to: {}", alq);
displayDebugMessage_(msg);
}
return std::make_tuple(oil_rate, gas_rate, alq, oil_is_limited, gas_is_limited);
}
std::tuple<double,double,bool,bool,double>
GasLiftSingleWellGeneric::
reduceALQtoOilTarget_(double alq,
double oil_rate,
double gas_rate,
bool oil_is_limited,
bool gas_is_limited,
std::vector<double>& potentials)
{
displayDebugMessage_("Reducing ALQ to meet oil target before iteration starts..");
double orig_oil_rate = oil_rate;
double orig_alq = alq;
// NOTE: This method should only be called if oil_is_limited, and hence
// we know that it has oil rate control
assert(this->controls_.hasControl(Well::ProducerCMode::ORAT));
auto target = this->controls_.oil_rate;
bool stop_iteration = false;
double temp_alq = alq;
while(!stop_iteration) {
temp_alq -= this->increment_;
if (temp_alq <= 0) break;
auto bhp_opt = computeBhpAtThpLimit_(temp_alq);
if (!bhp_opt) break;
alq = temp_alq;
auto [bhp, bhp_is_limited] = getBhpWithLimit_(*bhp_opt);
computeWellRates_(bhp, potentials);
oil_rate = -potentials[this->oil_pos_];
if (oil_rate < target) {
break;
}
}
std::tie(oil_rate, oil_is_limited) = getOilRateWithLimit_(potentials);
std::tie(gas_rate, gas_is_limited) = getGasRateWithLimit_(potentials);
if (this->debug_) {
assert( alq <= orig_alq );
if (alq < orig_alq) {
// NOTE: ALQ may drop below zero before we are able to meet the target
const std::string msg = fmt::format(
"Reduced (oil_rate, alq) from ({}, {}) to ({}, {}) to meet target "
"at {}. ", orig_oil_rate, orig_alq, oil_rate, alq, target);
displayDebugMessage_(msg);
}
else if (alq == orig_alq) {
// We might not be able to reduce ALQ, for example if ALQ starts out at zero.
const std::string msg = fmt::format("Not able to reduce ALQ {} further. "
"Oil rate is {} and oil target is {}", orig_alq, oil_rate, target);
displayDebugMessage_(msg);
}
}
return std::make_tuple(oil_rate, gas_rate, oil_is_limited, gas_is_limited, alq);
}
// INPUT:
// - increase (boolean) :
// - true : try increase the lift gas supply,
// - false : try decrease lift gas supply.
//
// OUTPUT:
//
// - return value: a new GasLiftWellState or nullptr
//
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimizeLoop_(bool increase)
{
std::vector<double> potentials(this->num_phases_, 0.0);
std::unique_ptr<GasLiftWellState> ret_value; // nullptr initially
if (!computeInitialWellRates_(potentials)) return ret_value;
bool alq_is_limited = false;
bool oil_is_limited = false;
bool gas_is_limited = false;
double oil_rate, gas_rate;
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited) =
getInitialRatesWithLimit_(potentials);
//if (this->debug_) debugShowBhpAlqTable_();
if (this->debug_) debugShowAlqIncreaseDecreaseCounts_();
if (this->debug_) debugShowTargets_();
bool success = false; // did we succeed to increase alq?
auto cur_alq = this->orig_alq_;
double new_oil_rate, new_gas_rate, new_alq;
bool new_oil_is_limited, new_gas_is_limited;
std::tie(new_oil_rate, new_gas_rate, new_alq, new_oil_is_limited, new_gas_is_limited)
= maybeAdjustALQbeforeOptimizeLoop_(
increase, cur_alq, oil_rate, gas_rate,
oil_is_limited, gas_is_limited, potentials);
double delta_oil = 0.0;
double delta_gas = 0.0;
double delta_alq = 0.0;
OptimizeState state {*this, increase};
if (checkInitialALQmodified_(new_alq, cur_alq)) {
delta_oil = new_oil_rate - oil_rate;
delta_gas = new_gas_rate - gas_rate;
delta_alq = new_alq - cur_alq;
if (!(state.checkGroupTargetsViolated(delta_oil, delta_gas)) &&
!(state.checkGroupALQrateExceeded(delta_alq)))
{
oil_rate = new_oil_rate;
gas_rate = new_gas_rate;
oil_is_limited = new_oil_is_limited;
gas_is_limited = new_gas_is_limited;
cur_alq = new_alq;
success = true;
}
else {
state.stop_iteration = true;
}
}
auto temp_alq = cur_alq;
if (this->debug_) debugShowStartIteration_(temp_alq, increase, oil_rate);
while (!state.stop_iteration && (++state.it <= this->max_iterations_)) {
if (!increase && state.checkNegativeOilRate(oil_rate)) break;
if (state.checkWellRatesViolated(potentials)) break;
if (state.checkGroupTargetsViolated(delta_oil, delta_gas)) break;
if (state.checkAlqOutsideLimits(temp_alq, oil_rate)) break;
std::optional<double> alq_opt;
std::tie(alq_opt, alq_is_limited)
= state.addOrSubtractAlqIncrement(temp_alq);
if (!alq_opt) break;
delta_alq = *alq_opt - temp_alq;
if (state.checkGroupALQrateExceeded(delta_alq)) break;
temp_alq = *alq_opt;
if (this->debug_) state.debugShowIterationInfo(temp_alq);
if (!state.computeBhpAtThpLimit(temp_alq)) break;
// NOTE: if BHP is below limit, we set state.stop_iteration = true
auto bhp = state.getBhpWithLimit();
computeWellRates_(bhp, potentials);
std::tie(new_oil_rate, new_oil_is_limited) = getOilRateWithLimit_(potentials);
/* if (this->debug_abort_if_decrease_and_oil_is_limited_) {
if (oil_is_limited && !increase) {
// if oil is limited we do not want to decrease
displayDebugMessage_(
"decreasing ALQ and oil is limited -> aborting iteration");
break;
}
}
*/
std::tie(new_gas_rate, new_gas_is_limited) = getGasRateWithLimit_(potentials);
/* if (this->debug_abort_if_increase_and_gas_is_limited_) {
if (gas_is_limited && increase) {
// if gas is limited we do not want to increase
displayDebugMessage_(
"increasing ALQ and gas is limited -> aborting iteration");
break;
}
}
*/
auto gradient = state.calcEcoGradient(
oil_rate, new_oil_rate, gas_rate, new_gas_rate);
if (this->debug_)
debugCheckNegativeGradient_(
gradient, cur_alq, temp_alq, oil_rate, new_oil_rate,
gas_rate, new_gas_rate, increase);
if (state.checkEcoGradient(gradient)) break;
cur_alq = temp_alq;
success = true;
delta_oil = new_oil_rate - oil_rate;
delta_gas = new_gas_rate - gas_rate;
oil_rate = new_oil_rate;
gas_rate = new_gas_rate;
oil_is_limited = new_oil_is_limited;
gas_is_limited = new_gas_is_limited;
state.updateGroupRates(delta_oil, delta_gas, delta_alq);
}
if (state.it > this->max_iterations_) {
warnMaxIterationsExceeded_();
}
std::optional<bool> increase_opt;
if (success) {
this->well_state_.gliftUpdateAlqIncreaseCount(this->well_name_, increase);
increase_opt = increase;
}
else {
increase_opt = std::nullopt;
}
ret_value = std::make_unique<GasLiftWellState>(oil_rate, oil_is_limited,
gas_rate, gas_is_limited, cur_alq, alq_is_limited, increase_opt);
return ret_value;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimize1_()
{
std::unique_ptr<GasLiftWellState> state;
auto inc_count = this->well_state_.gliftGetAlqIncreaseCount(this->well_name_);
auto dec_count = this->well_state_.gliftGetAlqDecreaseCount(this->well_name_);
if (dec_count == 0 && inc_count == 0) {
state = tryIncreaseLiftGas_();
if (!state && !state->alqChanged()) {
state = tryDecreaseLiftGas_();
}
}
else if (dec_count == 0) {
assert(inc_count > 0);
state = tryIncreaseLiftGas_();
}
else if (inc_count == 0) {
assert(dec_count > 0);
state = tryDecreaseLiftGas_();
}
return state;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimize2_()
{
std::unique_ptr<GasLiftWellState> state;
state = tryIncreaseLiftGas_();
if (!state || !(state->alqChanged())) {
state = tryDecreaseLiftGas_();
}
return state;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
tryDecreaseLiftGas_()
{
return runOptimizeLoop_(/*increase=*/ false);
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
tryIncreaseLiftGas_()
{
return runOptimizeLoop_(/*increase=*/ true);
}
void
GasLiftSingleWellGeneric::
setAlqMinRate_(const GasLiftOpt::Well& well)
{
// NOTE: According to WLIFTOPT item 5 :
// if min_rate() is negative, it means: allocate at least enough lift gas
// to enable the well to flow
// NOTE: "to enable the well to flow" : How to interpret this?
// We choose to interpret it to mean a positive oil rate as returned from
//
// computeWellRates_(bhp, cur_potentials);
//
// So even if the well is producing gas, if the oil rate is zero
// we say that the "well is not flowing".
//
// Note that if WECON item 2 is set, the well can be shut off
// before the flow rate reaches zero. Also,
// if bhp drops below the bhp lower limit, the well might switch to bhp
// control before the oil rate becomes zero.
this->min_alq_ = well.min_rate();
if (this->min_alq_ > 0) {
if (this->min_alq_ >= this->max_alq_) {
// NOTE: We reset the value to a negative value.
// negative value means: Allocate at least enough lift gas
// to allow the well to flow.
// TODO: Consider other options for resetting the value..
this->min_alq_ = -1;
displayWarning_("Minimum ALQ value is larger than maximum ALQ value!"
" Resetting value.");
}
}
}
// Called when we should use a fixed ALQ value
void
GasLiftSingleWellGeneric::
updateWellStateAlqFixedValue_(const GasLiftOpt::Well& well)
{
auto& max_alq_optional = well.max_rate();
if (max_alq_optional) {
// According to WLIFTOPT, item 3:
// If item 2 is NO, then item 3 is regarded as the fixed
// lift gas injection rate for the well.
auto new_alq = *max_alq_optional;
this->well_state_.setALQ(this->well_name_, new_alq);
}
// else {
// // If item 3 is defaulted, the lift gas rate remains
// // unchanged at its current value.
//}
}
// Determine if we should use a fixed ALQ value.
//
// From the manual for WLIFTOPT, item 2:
// Is the well's lift gas injection rate to be calculated by the
// optimization facility?
// - YES : The well's lift gas injection rate is calculated by the
// optimization facility.
// - NO : The well's lift gas injection rate remains fixed at a
// value that can be set either in Item 3 of this keyword, or in
// Item 12 of keyword WCONPROD, or with keyword WELTARG.
bool
GasLiftSingleWellGeneric::
useFixedAlq_(const GasLiftOpt::Well& well)
{
auto wliftopt_item2 = well.use_glo();
if (wliftopt_item2) {
return false;
}
else {
// auto& max_alq_optional = well.max_rate();
// if (max_alq_optional) {
// According to WLIFTOPT, item 3:
// If item 2 is NO, then item 3 is regarded as the fixed
// lift gas injection rate for the well.
// }
// else {
// If item 3 is defaulted, the lift gas rate remains
// unchanged at its current value.
// }
return true;
}
}
void
GasLiftSingleWellGeneric::
warnMaxIterationsExceeded_()
@ -702,6 +1092,70 @@ checkAlqOutsideLimits(double alq, [[maybe_unused]] double oil_rate)
return result;
}
bool
GasLiftSingleWellGeneric::OptimizeState::
checkGroupALQrateExceeded(double delta_alq)
{
const auto &pairs =
this->parent.group_info_.getWellGroups(this->parent.well_name_);
for (const auto &[group_name, efficiency] : pairs) {
auto max_alq_opt = this->parent.group_info_.maxAlq(group_name);
if (max_alq_opt) {
double alq =
this->parent.group_info_.alqRate(group_name) + efficiency * delta_alq;
if (alq > *max_alq_opt) {
if (this->parent.debug_) {
const std::string msg = fmt::format(
"Group {} : alq {} exceeds max_alq {}. Stopping iteration",
group_name, alq, *max_alq_opt);
this->parent.displayDebugMessage_(msg);
}
return true;
}
}
}
return false;
}
bool
GasLiftSingleWellGeneric::OptimizeState::
checkGroupTargetsViolated(double delta_oil, double delta_gas)
{
const auto &pairs =
this->parent.group_info_.getWellGroups(this->parent.well_name_);
for (const auto &[group_name, efficiency] : pairs) {
auto oil_target_opt = this->parent.group_info_.oilTarget(group_name);
if (oil_target_opt) {
double oil_rate =
this->parent.group_info_.oilRate(group_name) + efficiency * delta_oil;
if (oil_rate > *oil_target_opt) {
if (this->parent.debug_) {
const std::string msg = fmt::format(
"Group {} : oil rate {} exceeds oil target {}. Stopping iteration",
group_name, oil_rate, *oil_target_opt);
this->parent.displayDebugMessage_(msg);
}
return true;
}
}
auto gas_target_opt = this->parent.group_info_.gasTarget(group_name);
if (gas_target_opt) {
double gas_rate =
this->parent.group_info_.gasRate(group_name) + efficiency * delta_gas;
if (gas_rate > *gas_target_opt) {
if (this->parent.debug_) {
const std::string msg = fmt::format(
"Group {} : gas rate {} exceeds gas target {}. Stopping iteration",
group_name, gas_rate, *gas_target_opt);
this->parent.displayDebugMessage_(msg);
}
return true;
}
}
}
return false;
}
bool
GasLiftSingleWellGeneric::OptimizeState::
checkNegativeOilRate(double oil_rate)
@ -840,340 +1294,19 @@ getBhpWithLimit()
return new_bhp;
}
std::tuple<double,double,bool,bool,double>
GasLiftSingleWellGeneric::
reduceALQtoOilTarget_(double alq,
double oil_rate,
double gas_rate,
bool oil_is_limited,
bool gas_is_limited,
std::vector<double>& potentials)
{
displayDebugMessage_("Reducing ALQ to meet oil target before iteration starts..");
double orig_oil_rate = oil_rate;
double orig_alq = alq;
// NOTE: This method should only be called if oil_is_limited, and hence
// we know that it has oil rate control
assert(this->controls_.hasControl(Well::ProducerCMode::ORAT));
auto target = this->controls_.oil_rate;
bool stop_iteration = false;
double temp_alq = alq;
while(!stop_iteration) {
temp_alq -= this->increment_;
if (temp_alq <= 0) break;
auto bhp_opt = computeBhpAtThpLimit_(temp_alq);
if (!bhp_opt) break;
alq = temp_alq;
auto [bhp, bhp_is_limited] = getBhpWithLimit_(*bhp_opt);
computeWellRates_(bhp, potentials);
oil_rate = -potentials[this->oil_pos_];
if (oil_rate < target) {
break;
}
}
std::tie(oil_rate, oil_is_limited) = getOilRateWithLimit_(potentials);
std::tie(gas_rate, gas_is_limited) = getGasRateWithLimit_(potentials);
if (this->debug_) {
assert( alq <= orig_alq );
if (alq < orig_alq) {
// NOTE: ALQ may drop below zero before we are able to meet the target
const std::string msg = fmt::format(
"Reduced (oil_rate, alq) from ({}, {}) to ({}, {}) to meet target "
"at {}. ", orig_oil_rate, orig_alq, oil_rate, alq, target);
displayDebugMessage_(msg);
}
else if (alq == orig_alq) {
// We might not be able to reduce ALQ, for example if ALQ starts out at zero.
const std::string msg = fmt::format("Not able to reduce ALQ {} further. "
"Oil rate is {} and oil target is {}", orig_alq, oil_rate, target);
displayDebugMessage_(msg);
}
}
return std::make_tuple(oil_rate, gas_rate, oil_is_limited, gas_is_limited, alq);
}
// INPUT:
// - increase (boolean) :
// - true : try increase the lift gas supply,
// - false : try decrease lift gas supply.
//
// OUTPUT:
//
// - return value: a new GLiftWellState or nullptr
//
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimizeLoop_(bool increase)
{
std::vector<double> potentials(this->num_phases_, 0.0);
std::unique_ptr<GasLiftWellState> ret_value; // nullptr initially
if (!computeInitialWellRates_(potentials)) return ret_value;
bool alq_is_limited = false;
bool oil_is_limited = false;
bool gas_is_limited = false;
double oil_rate, gas_rate;
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited) =
getInitialRatesWithLimit_(potentials);
if (this->debug_) debugShowBhpAlqTable_();
if (this->debug_) debugShowAlqIncreaseDecreaseCounts_();
if (this->debug_) debugShowTargets_();
bool success = false; // did we succeed to increase alq?
auto cur_alq = this->orig_alq_;
auto temp_alq = cur_alq;
if (!increase && oil_is_limited) {
// NOTE: Try to decrease ALQ down to a value where the oil target is
// not exceeded.
// NOTE: This may reduce ALQ below the minimum value set in WLIFTOPT
// item 5. However, this is OK since the rate target is met and there
// is no point in using a higher ALQ value then.
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited, temp_alq) =
reduceALQtoOilTarget_(
temp_alq, oil_rate, gas_rate, oil_is_limited, gas_is_limited, potentials);
}
else {
if (increase && oil_rate < 0) {
// NOTE: Try to increase ALQ up to a value where oil_rate is positive
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited, temp_alq) =
increaseALQtoPositiveOilRate_(temp_alq, oil_rate,
gas_rate, oil_is_limited, gas_is_limited, potentials);
}
if (increase && (this->min_alq_> 0) && (temp_alq < this->min_alq_)) {
// NOTE: Try to increase ALQ up to the minimum limit without checking
// the economic gradient..
std::tie(oil_rate, gas_rate, oil_is_limited, gas_is_limited, temp_alq) =
increaseALQtoMinALQ_(temp_alq, oil_rate, gas_rate,
oil_is_limited, gas_is_limited, potentials);
}
}
if (checkInitialALQmodified_(temp_alq, cur_alq)) {
cur_alq = temp_alq;
success = true;
}
OptimizeState state {*this, increase};
if (this->debug_) debugShowStartIteration_(temp_alq, increase, oil_rate);
while (!state.stop_iteration && (++state.it <= this->max_iterations_)) {
if (!increase && state.checkNegativeOilRate(oil_rate)) break;
if (state.checkWellRatesViolated(potentials)) break;
if (state.checkAlqOutsideLimits(temp_alq, oil_rate)) break;
std::optional<double> alq_opt;
std::tie(alq_opt, alq_is_limited)
= state.addOrSubtractAlqIncrement(temp_alq);
if (!alq_opt) break;
temp_alq = *alq_opt;
if (this->debug_) state.debugShowIterationInfo(temp_alq);
if (!state.computeBhpAtThpLimit(temp_alq)) break;
// NOTE: if BHP is below limit, we set state.stop_iteration = true
auto bhp = state.getBhpWithLimit();
computeWellRates_(bhp, potentials);
auto [new_oil_rate, new_oil_is_limited] = getOilRateWithLimit_(potentials);
/* if (this->debug_abort_if_decrease_and_oil_is_limited_) {
if (oil_is_limited && !increase) {
// if oil is limited we do not want to decrease
displayDebugMessage_(
"decreasing ALQ and oil is limited -> aborting iteration");
break;
}
}
*/
auto [new_gas_rate, new_gas_is_limited] = getGasRateWithLimit_(potentials);
/* if (this->debug_abort_if_increase_and_gas_is_limited_) {
if (gas_is_limited && increase) {
// if gas is limited we do not want to increase
displayDebugMessage_(
"increasing ALQ and gas is limited -> aborting iteration");
break;
}
}
*/
auto gradient = state.calcEcoGradient(
oil_rate, new_oil_rate, gas_rate, new_gas_rate);
if (this->debug_)
debugCheckNegativeGradient_(
gradient, cur_alq, temp_alq, oil_rate, new_oil_rate,
gas_rate, new_gas_rate, increase);
if (state.checkEcoGradient(gradient)) break;
cur_alq = temp_alq;
success = true;
oil_rate = new_oil_rate;
gas_rate = new_gas_rate;
oil_is_limited = new_oil_is_limited;
gas_is_limited = new_gas_is_limited;
}
if (state.it > this->max_iterations_) {
warnMaxIterationsExceeded_();
}
std::optional<bool> increase_opt;
if (success) {
this->well_state_.gliftUpdateAlqIncreaseCount(this->well_name_, increase);
increase_opt = increase;
}
else {
increase_opt = std::nullopt;
}
ret_value = std::make_unique<GasLiftWellState>(oil_rate, oil_is_limited,
gas_rate, gas_is_limited, cur_alq, alq_is_limited, increase_opt);
return ret_value;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimize(const int iteration_idx)
{
std::unique_ptr<GasLiftWellState> state;
if (this->optimize_) {
if (this->debug_limit_increase_decrease_) {
state = runOptimize1_();
}
else {
state = runOptimize2_();
}
if (state) {
if (state->increase()) {
double alq = state->alq();
if (this->debug_)
logSuccess_(alq, iteration_idx);
this->well_state_.setALQ(this->well_name_, alq);
}
}
}
return state;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimize1_()
{
std::unique_ptr<GasLiftWellState> state;
auto inc_count = this->well_state_.gliftGetAlqIncreaseCount(this->well_name_);
auto dec_count = this->well_state_.gliftGetAlqDecreaseCount(this->well_name_);
if (dec_count == 0 && inc_count == 0) {
state = tryIncreaseLiftGas_();
if (!state && !state->alqChanged()) {
state = tryDecreaseLiftGas_();
}
}
else if (dec_count == 0) {
assert(inc_count > 0);
state = tryIncreaseLiftGas_();
}
else if (inc_count == 0) {
assert(dec_count > 0);
state = tryDecreaseLiftGas_();
}
return state;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
runOptimize2_()
{
std::unique_ptr<GasLiftWellState> state;
state = tryIncreaseLiftGas_();
if (!state || !(state->alqChanged())) {
state = tryDecreaseLiftGas_();
}
return state;
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
tryDecreaseLiftGas_()
{
return runOptimizeLoop_(/*increase=*/ false);
}
std::unique_ptr<GasLiftWellState>
GasLiftSingleWellGeneric::
tryIncreaseLiftGas_()
{
return runOptimizeLoop_(/*increase=*/ true);
}
void
GasLiftSingleWellGeneric::
setAlqMinRate_(const GasLiftOpt::Well& well)
GasLiftSingleWellGeneric::OptimizeState::
updateGroupRates(double delta_oil, double delta_gas, double delta_alq)
{
// NOTE: According to WLIFTOPT item 5 :
// if min_rate() is negative, it means: allocate at least enough lift gas
// to enable the well to flow
// NOTE: "to enable the well to flow" : How to interpret this?
// We choose to interpret it to mean a positive oil rate as returned from
//
// computeWellRates_(bhp, cur_potentials);
//
// So even if the well is producing gas, if the oil rate is zero
// we say that the "well is not flowing".
//
// Note that if WECON item 2 is set, the well can be shut off
// before the flow rate reaches zero. Also,
// if bhp drops below the bhp lower limit, the well might switch to bhp
// control before the oil rate becomes zero.
this->min_alq_ = well.min_rate();
if (this->min_alq_ > 0) {
if (this->min_alq_ >= this->max_alq_) {
// NOTE: We reset the value to a negative value.
// negative value means: Allocate at least enough lift gas
// to allow the well to flow.
// TODO: Consider other options for resetting the value..
this->min_alq_ = -1;
displayWarning_("Minimum ALQ value is larger than maximum ALQ value!"
" Resetting value.");
}
const auto &pairs =
this->parent.group_info_.getWellGroups(this->parent.well_name_);
for (const auto &[group_name, efficiency] : pairs) {
int idx = this->parent.group_info_.getGroupIdx(group_name);
this->parent.sync_groups_.insert(idx);
this->parent.group_info_.update(group_name,
efficiency * delta_oil, efficiency * delta_gas, efficiency * delta_alq);
}
}
// Called when we should use a fixed ALQ value
void
GasLiftSingleWellGeneric::
updateWellStateAlqFixedValue_(const GasLiftOpt::Well& well)
{
auto& max_alq_optional = well.max_rate();
if (max_alq_optional) {
// According to WLIFTOPT, item 3:
// If item 2 is NO, then item 3 is regarded as the fixed
// lift gas injection rate for the well.
auto new_alq = *max_alq_optional;
this->well_state_.setALQ(this->well_name_, new_alq);
}
// else {
// // If item 3 is defaulted, the lift gas rate remains
// // unchanged at its current value.
//}
}
// Determine if we should use a fixed ALQ value.
//
// From the manual for WLIFTOPT, item 2:
// Is the well's lift gas injection rate to be calculated by the
// optimization facility?
// - YES : The well's lift gas injection rate is calculated by the
// optimization facility.
// - NO : The well's lift gas injection rate remains fixed at a
// value that can be set either in Item 3 of this keyword, or in
// Item 12 of keyword WCONPROD, or with keyword WELTARG.
bool
GasLiftSingleWellGeneric::
useFixedAlq_(const GasLiftOpt::Well& well)
{
auto wliftopt_item2 = well.use_glo();
if (wliftopt_item2) {
return false;
}
else {
// auto& max_alq_optional = well.max_rate();
// if (max_alq_optional) {
// According to WLIFTOPT, item 3:
// If item 2 is NO, then item 3 is regarded as the fixed
// lift gas injection rate for the well.
// }
// else {
// If item 3 is defaulted, the lift gas rate remains
// unchanged at its current value.
// }
return true;
}
}
} // namespace Opm

View File

@ -20,10 +20,14 @@
#ifndef OPM_GASLIFT_SINGLE_WELL_GENERIC_HEADER_INCLUDED
#define OPM_GASLIFT_SINGLE_WELL_GENERIC_HEADER_INCLUDED
#include <dune/common/version.hh>
#include <dune/common/parallel/mpihelper.hh>
#include <opm/core/props/BlackoilPhases.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/GasLiftOpt.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <functional>
#include <optional>
@ -51,6 +55,7 @@ protected:
static constexpr double ALQ_EPSILON = 1e-8;
public:
using GLiftSyncGroups = std::set<int>;
struct GradInfo
{
GradInfo() { }
@ -86,12 +91,16 @@ public:
virtual const WellInterfaceGeneric& getStdWell() const = 0;
protected:
GasLiftSingleWellGeneric(DeferredLogger &deferred_logger,
WellState &well_state,
const Well& ecl_well,
const SummaryState& summary_state,
const Schedule& schedule,
const int report_step_idx);
GasLiftSingleWellGeneric(
DeferredLogger &deferred_logger,
WellState &well_state,
const Well& ecl_well,
const SummaryState& summary_state,
GasLiftGroupInfo &group_info,
const Schedule& schedule,
const int report_step_idx,
GLiftSyncGroups &sync_groups
);
struct OptimizeState
{
@ -114,6 +123,8 @@ protected:
double gas_rate, double new_gas_rate);
bool checkAlqOutsideLimits(double alq, double oil_rate);
bool checkEcoGradient(double gradient);
bool checkGroupALQrateExceeded(double delta_alq);
bool checkGroupTargetsViolated(double delta_oil, double delta_gas);
bool checkNegativeOilRate(double oil_rate);
bool checkOilRateExceedsTarget(double oil_rate);
bool checkRate(double rate, double limit, const std::string &rate_str) const;
@ -121,6 +132,7 @@ protected:
bool computeBhpAtThpLimit(double alq);
void debugShowIterationInfo(double alq);
double getBhpWithLimit();
void updateGroupRates(double delta_oil, double delta_gas, double delta_alq);
void warn_(std::string msg) {parent.displayWarning_(msg);}
};
@ -183,15 +195,13 @@ protected:
void logSuccess_(double alq,
const int iteration_idx);
std::tuple<double,double,double,bool,bool>
maybeAdjustALQbeforeOptimizeLoop_(
bool increase, double alq, double oil_rate, double gas_rate,
bool oil_is_limited, bool gas_is_limited, std::vector<double> &potentials);
std::tuple<double,double,bool,bool,double>
reduceALQtoOilTarget_(double alq,
double oil_rate,
double gas_rate,
bool oil_is_limited,
bool gas_is_limited,
std::vector<double>& potentials);
reduceALQtoOilTarget_(double alq, double oil_rate, double gas_rate,
bool oil_is_limited, bool gas_is_limited, std::vector<double> &potentials);
std::unique_ptr<GasLiftWellState> runOptimize1_();
std::unique_ptr<GasLiftWellState> runOptimize2_();
@ -212,7 +222,8 @@ protected:
WellState& well_state_;
const Well& ecl_well_;
const SummaryState& summary_state_;
GasLiftGroupInfo& group_info_;
GLiftSyncGroups& sync_groups_;
const Well::ProductionControls controls_;
double increment_;

View File

@ -18,16 +18,6 @@
*/
namespace Opm {
#include <opm/simulators/wells/StandardWell.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Schedule.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/Well/Well.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/SummaryState.hpp>
#include <opm/simulators/wells/WellState.hpp>
#include <opm/parser/eclipse/EclipseState/Schedule/GasLiftOpt.hpp>
#include <optional>
#include <string>
template<typename TypeTag>
GasLiftSingleWell<TypeTag>::
@ -35,13 +25,22 @@ GasLiftSingleWell(const StdWell &std_well,
const Simulator &ebos_simulator,
const SummaryState &summary_state,
DeferredLogger &deferred_logger,
WellState &well_state)
: GasLiftSingleWellGeneric(deferred_logger,
well_state,
std_well.wellEcl(),
summary_state,
ebos_simulator.vanguard().schedule(),
ebos_simulator.episodeIndex())
WellState &well_state,
GasLiftGroupInfo &group_info,
GLiftSyncGroups &sync_groups
)
// The parent class GasLiftSingleWellGeneric contains all stuff
// that is not dependent on TypeTag
: GasLiftSingleWellGeneric(
deferred_logger,
well_state,
std_well.wellEcl(),
summary_state,
group_info,
ebos_simulator.vanguard().schedule(),
ebos_simulator.episodeIndex(),
sync_groups
)
, ebos_simulator_{ebos_simulator}
, std_well_{std_well}
{

View File

@ -458,7 +458,7 @@ mpiSyncGlobalGradVector_(std::vector<GradPair> &grads_global) const
std::vector<GradPair> grads_local;
for (auto itr = grads_global.begin(); itr != grads_global.end(); itr++) {
if (well_state_map_.count(itr->first) > 0) {
if (this->well_state_map_.count(itr->first) > 0) {
grads_local.push_back(*itr);
}
}
@ -640,15 +640,18 @@ redistributeALQ_(std::vector<GasLiftSingleWell *> &wells, const Group &group,
std::vector<GradPair> &inc_grads, std::vector<GradPair> &dec_grads)
{
OptimizeState state {*this, group};
// NOTE: 'inc_grads' and 'dec_grads' can never grow larger than wells.size()
// By reserving space here, we can ensure that any push_back() on these
// will never reallocate memory and invalidate any iterators.
inc_grads.reserve(wells.size());
dec_grads.reserve(wells.size());
if (this->comm_.size() == 1) {
// NOTE: 'inc_grads' and 'dec_grads' can never grow larger than wells.size()
// By reserving space here, we can ensure that any push_back() on these
// will never reallocate memory and invalidate any iterators.
inc_grads.reserve(wells.size());
dec_grads.reserve(wells.size());
state.calculateEcoGradients(wells, inc_grads, dec_grads);
}
else {
auto max_size = this->comm_.sum(wells.size());
inc_grads.reserve(max_size);
dec_grads.reserve(max_size);
std::vector<GradPair> inc_grads_local;
std::vector<GradPair> dec_grads_local;
inc_grads_local.reserve(wells.size());
@ -662,7 +665,7 @@ redistributeALQ_(std::vector<GasLiftSingleWell *> &wells, const Group &group,
if (!state.checkAtLeastTwoWells(wells)) {
// NOTE: Even though we here in redistributeALQ_() do not use the
// economic gradient if there is only a single well, we still
// need to calculate it since inc_grads and dec_grads are returned
// need to calculate it (see above) since inc_grads and dec_grads are returned
// and will be used by removeSurplusALQ_() later.
return;
}

View File

@ -45,168 +45,172 @@ class Schedule;
class WellInterfaceGeneric;
class WellState;
class GasLiftStage2 {
using GasLiftSingleWell = GasLiftSingleWellGeneric;
using GLiftOptWells = std::map<std::string,std::unique_ptr<GasLiftSingleWell>>;
using GLiftProdWells = std::map<std::string,const WellInterfaceGeneric*>;
using GLiftWellStateMap = std::map<std::string,std::unique_ptr<GasLiftWellState>>;
using GradPair = std::pair<std::string, double>;
using GradPairItr = std::vector<GradPair>::iterator;
using GradInfo = typename GasLiftSingleWellGeneric::GradInfo;
using GradMap = std::map<std::string, GradInfo>;
using MPIComm = typename Dune::MPIHelper::MPICommunicator;
class GasLiftStage2 {
using GasLiftSingleWell = GasLiftSingleWellGeneric;
using GLiftOptWells = std::map<std::string,std::unique_ptr<GasLiftSingleWell>>;
using GLiftProdWells = std::map<std::string,const WellInterfaceGeneric*>;
using GLiftWellStateMap = std::map<std::string,std::unique_ptr<GasLiftWellState>>;
using GradPair = std::pair<std::string, double>;
using GradPairItr = std::vector<GradPair>::iterator;
using GradInfo = typename GasLiftSingleWellGeneric::GradInfo;
using GradMap = std::map<std::string, GradInfo>;
using MPIComm = typename Dune::MPIHelper::MPICommunicator;
#if DUNE_VERSION_NEWER(DUNE_COMMON, 2, 7)
using Communication = Dune::Communication<MPIComm>;
using Communication = Dune::Communication<MPIComm>;
#else
using Communication = Dune::CollectiveCommunication<MPIComm>;
using Communication = Dune::CollectiveCommunication<MPIComm>;
#endif
static constexpr int Water = BlackoilPhases::Aqua;
static constexpr int Oil = BlackoilPhases::Liquid;
static constexpr int Gas = BlackoilPhases::Vapour;
public:
GasLiftStage2(
const int report_step_idx,
const Communication& comm,
const Schedule& schedule,
const SummaryState& summary_state,
DeferredLogger &deferred_logger,
WellState &well_state,
GLiftProdWells &prod_wells,
GLiftOptWells &glift_wells,
GLiftWellStateMap &state_map
);
void runOptimize();
private:
void addOrRemoveALQincrement_(
GradMap &grad_map, const std::string& well_name, bool add);
std::optional<GradInfo> calcIncOrDecGrad_(
const std::string name, const GasLiftSingleWell &gs_well, bool increase);
bool checkRateAlreadyLimited_(GasLiftWellState &state, bool increase);
GradInfo deleteDecGradItem_(const std::string &name);
GradInfo deleteIncGradItem_(const std::string &name);
GradInfo deleteGrad_(const std::string &name, bool increase);
void displayDebugMessage_(const std::string &msg);
void displayDebugMessage2B_(const std::string &msg);
void displayDebugMessage_(const std::string &msg, const std::string &group_name);
void displayWarning_(const std::string &msg, const std::string &group_name);
void displayWarning_(const std::string &msg);
std::tuple<double, double, double> getCurrentGroupRates_(
const Group &group);
std::array<double,3> getCurrentGroupRatesRecursive_(
const Group &group);
std::tuple<double, double, double> getCurrentWellRates_(
const std::string &well_name, const std::string &group_name);
std::vector<GasLiftSingleWell *> getGroupGliftWells_(
const Group &group);
void getGroupGliftWellsRecursive_(
const Group &group, std::vector<GasLiftSingleWell *> &wells);
std::pair<double, double> getStdWellRates_(const WellInterfaceGeneric &well);
void optimizeGroup_(const Group &group);
void optimizeGroupsRecursive_(const Group &group);
void recalculateGradientAndUpdateData_(
GradPairItr &grad_itr, bool increase,
std::vector<GradPair> &grads, std::vector<GradPair> &other_grads);
void redistributeALQ_(
std::vector<GasLiftSingleWell *> &wells, const Group &group,
std::vector<GradPair> &inc_grads, std::vector<GradPair> &dec_grads);
void removeSurplusALQ_(
const Group &group,
std::vector<GradPair> &inc_grads, std::vector<GradPair> &dec_grads);
void saveGrad_(GradMap &map, const std::string &name, GradInfo &grad);
void saveDecGrad_(const std::string &name, GradInfo &grad);
void saveIncGrad_(const std::string &name, GradInfo &grad);
void sortGradients_(std::vector<GradPair> &grads);
std::optional<GradInfo> updateGrad_(
const std::string &name, GradInfo &grad, bool increase);
void updateGradVector_(
const std::string &name, std::vector<GradPair> &grads, double grad);
void mpiSyncGlobalGradVector_(std::vector<GradPair> &grads_global) const;
void mpiSyncLocalToGlobalGradVector_(
const std::vector<GradPair> &grads_local,
std::vector<GradPair> &grads_global) const;
static const int Water = BlackoilPhases::Aqua;
static const int Oil = BlackoilPhases::Liquid;
static const int Gas = BlackoilPhases::Vapour;
public:
GasLiftStage2(
const int report_step_idx,
const Communication& comm,
const Schedule& schedule,
const SummaryState& summary_state,
DeferredLogger& deferred_logger,
WellState& well_state,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GLiftWellStateMap& state_map
);
void runOptimize();
private:
void addOrRemoveALQincrement_(
GradMap& grad_map, const std::string& well_name, bool add);
std::optional<GradInfo> calcIncOrDecGrad_(
const std::string name, const GasLiftSingleWell& gs_well, bool increase);
bool checkRateAlreadyLimited_(GasLiftWellState& state, bool increase);
GradInfo deleteDecGradItem_(const std::string& name);
GradInfo deleteIncGradItem_(const std::string& name);
GradInfo deleteGrad_(const std::string& name, bool increase);
void displayDebugMessage_(const std::string& msg);
void displayDebugMessage2B_(const std::string& msg);
void displayDebugMessage_(const std::string& msg, const std::string& group_name);
void displayWarning_(const std::string& msg, const std::string& group_name);
void displayWarning_(const std::string& msg);
std::tuple<double, double, double> getCurrentGroupRates_(
const Group& group);
std::array<double,3> getCurrentGroupRatesRecursive_(
const Group& group);
std::tuple<double, double, double> getCurrentWellRates_(
const std::string& well_name, const std::string& group_name);
std::vector<GasLiftSingleWell *> getGroupGliftWells_(
const Group& group);
void getGroupGliftWellsRecursive_(
const Group& group, std::vector<GasLiftSingleWell *>& wells);
std::pair<double, double> getStdWellRates_(const WellInterfaceGeneric& well);
void optimizeGroup_(const Group& group);
void optimizeGroupsRecursive_(const Group& group);
void recalculateGradientAndUpdateData_(
GradPairItr& grad_itr, bool increase,
std::vector<GradPair>& grads, std::vector<GradPair>& other_grads);
void redistributeALQ_(
std::vector<GasLiftSingleWell *>& wells, const Group& group,
std::vector<GradPair>& inc_grads, std::vector<GradPair>& dec_grads);
void removeSurplusALQ_(
const Group& group,
std::vector<GradPair>& inc_grads, std::vector<GradPair>& dec_grads);
void saveGrad_(GradMap& map, const std::string& name, GradInfo& grad);
void saveDecGrad_(const std::string& name, GradInfo& grad);
void saveIncGrad_(const std::string& name, GradInfo& grad);
void sortGradients_(std::vector<GradPair>& grads);
std::optional<GradInfo> updateGrad_(
const std::string& name, GradInfo& grad, bool increase);
void updateGradVector_(
const std::string& name, std::vector<GradPair>& grads, double grad);
void mpiSyncGlobalGradVector_(std::vector<GradPair>& grads_global) const;
void mpiSyncLocalToGlobalGradVector_(
const std::vector<GradPair>& grads_local,
std::vector<GradPair>& grads_global) const;
DeferredLogger &deferred_logger_;
WellState &well_state_;
GLiftProdWells &prod_wells_;
GLiftOptWells &stage1_wells_;
GLiftWellStateMap &well_state_map_;
DeferredLogger& deferred_logger_;
WellState& well_state_;
GLiftProdWells& prod_wells_;
GLiftOptWells& stage1_wells_;
GLiftWellStateMap& well_state_map_;
int report_step_idx_;
const SummaryState &summary_state_;
const Schedule &schedule_;
const GasLiftOpt& glo_;
const Communication &comm_;
GradMap inc_grads_;
GradMap dec_grads_;
bool debug_;
static constexpr int max_iterations_ = 1000;
//int time_step_idx_;
int report_step_idx_;
const SummaryState& summary_state_;
const Schedule& schedule_;
const GasLiftOpt& glo_;
const Communication& comm_;
GradMap inc_grads_;
GradMap dec_grads_;
bool debug_;
int max_iterations_ = 1000;
//int time_step_idx_;
struct OptimizeState {
OptimizeState( GasLiftStage2 &parent_, const Group &group_ ) :
parent{parent_},
group{group_},
it{0}
{}
GasLiftStage2 &parent;
const Group &group;
int it;
struct OptimizeState {
OptimizeState(GasLiftStage2& parent_, const Group& group_ ) :
parent{parent_},
group{group_},
it{0}
{}
GasLiftStage2& parent;
const Group& group;
int it;
void calculateEcoGradients(std::vector<GasLiftSingleWell *> &wells,
std::vector<GradPair> &inc_grads, std::vector<GradPair> &dec_grads);
bool checkAtLeastTwoWells(std::vector<GasLiftSingleWell *> &wells);
void debugShowIterationInfo();
std::pair<std::optional<GradPairItr>,std::optional<GradPairItr>>
getEcoGradients(
std::vector<GradPair> &inc_grads, std::vector<GradPair> &dec_grads);
void recalculateGradients(
std::vector<GradPair> &inc_grads, std::vector<GradPair> &dec_grads,
GradPairItr &min_dec_grad_itr, GradPairItr &max_inc_grad_itr);
void redistributeALQ( GradPairItr &min_dec_grad, GradPairItr &max_inc_grad);
using GradInfo = typename GasLiftStage2::GradInfo;
using GradPair = typename GasLiftStage2::GradPair;
using GradPairItr = typename GasLiftStage2::GradPairItr;
using GradMap = typename GasLiftStage2::GradMap;
void calculateEcoGradients(std::vector<GasLiftSingleWell *>& wells,
std::vector<GradPair>& inc_grads, std::vector<GradPair>& dec_grads);
bool checkAtLeastTwoWells(std::vector<GasLiftSingleWell *>& wells);
void debugShowIterationInfo();
std::pair<std::optional<GradPairItr>,std::optional<GradPairItr>>
getEcoGradients(
std::vector<GradPair>& inc_grads, std::vector<GradPair>& dec_grads);
void recalculateGradients(
std::vector<GradPair>& inc_grads, std::vector<GradPair>& dec_grads,
GradPairItr& min_dec_grad_itr, GradPairItr &max_inc_grad_itr);
void redistributeALQ( GradPairItr& min_dec_grad, GradPairItr& max_inc_grad);
private:
void displayDebugMessage_(const std::string &msg);
void displayWarning_(const std::string &msg);
private:
void displayDebugMessage_(const std::string& msg);
void displayWarning_(const std::string& msg);
};
};
struct SurplusState {
SurplusState( GasLiftStage2 &parent_, const Group &group_,
double oil_rate_, double gas_rate_, double alq_, double min_eco_grad_,
double oil_target_, double gas_target_,
std::optional<double> max_glift_) :
parent{parent_},
group{group_},
oil_rate{oil_rate_},
gas_rate{gas_rate_},
alq{alq_},
min_eco_grad{min_eco_grad_},
oil_target{oil_target_},
gas_target{gas_target_},
max_glift{max_glift_},
it{0}
{}
GasLiftStage2 &parent;
const Group &group;
double oil_rate;
double gas_rate;
double alq;
const double min_eco_grad;
const double oil_target;
const double gas_target;
std::optional<double> max_glift;
int it;
struct SurplusState {
SurplusState( GasLiftStage2& parent_, const Group& group_,
double oil_rate_, double gas_rate_, double alq_, double min_eco_grad_,
double oil_target_, double gas_target_,
std::optional<double> max_glift_) :
parent{parent_},
group{group_},
oil_rate{oil_rate_},
gas_rate{gas_rate_},
alq{alq_},
min_eco_grad{min_eco_grad_},
oil_target{oil_target_},
gas_target{gas_target_},
max_glift{max_glift_},
it{0}
{}
GasLiftStage2 &parent;
const Group &group;
double oil_rate;
double gas_rate;
double alq;
const double min_eco_grad;
const double oil_target;
const double gas_target;
std::optional<double> max_glift;
int it;
void addOrRemoveALQincrement(
GradMap &grad_map, const std::string& well_name, bool add);
bool checkALQlimit();
bool checkEcoGradient(const std::string &well_name, double eco_grad);
bool checkGasTarget();
bool checkOilTarget();
void updateRates(const std::string &name);
};
};
void addOrRemoveALQincrement(
GradMap &grad_map, const std::string& well_name, bool add);
bool checkALQlimit();
bool checkEcoGradient(const std::string& well_name, double eco_grad);
bool checkGasTarget();
bool checkOilTarget();
void updateRates(const std::string& name);
};
};
} // namespace Opm

View File

@ -56,6 +56,7 @@ namespace Opm
using typename Base::GLiftProdWells;
using typename Base::GLiftOptWells;
using typename Base::GLiftWellStateMap;
using typename Base::GLiftSyncGroups;
/// the number of reservior equations
using Base::numEq;
@ -108,7 +109,9 @@ namespace Opm
DeferredLogger&,
GLiftProdWells &,
GLiftOptWells &,
GLiftWellStateMap &
GLiftWellStateMap &,
GasLiftGroupInfo &,
GLiftSyncGroups &
) const override {
// Not implemented yet
}

View File

@ -32,6 +32,7 @@
#include <opm/simulators/wells/WellProdIndexCalculator.hpp>
#include <opm/simulators/wells/ParallelWellInfo.hpp>
#include <opm/simulators/wells/GasLiftSingleWell.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <opm/models/blackoil/blackoilpolymermodules.hh>
#include <opm/models/blackoil/blackoilsolventmodules.hh>
@ -85,6 +86,7 @@ namespace Opm
using typename Base::GLiftOptWells;
using typename Base::GLiftProdWells;
using typename Base::GLiftWellStateMap;
using typename Base::GLiftSyncGroups;
using Base::numEq;
using Base::numPhases;
@ -230,7 +232,9 @@ namespace Opm
DeferredLogger& deferred_logger,
GLiftProdWells &prod_wells,
GLiftOptWells &glift_wells,
GLiftWellStateMap &state_map
GLiftWellStateMap &state_map,
GasLiftGroupInfo &group_info,
GLiftSyncGroups &sync_groups
) const override;
/* returns BHP */

View File

@ -1499,34 +1499,24 @@ namespace Opm
DeferredLogger& deferred_logger,
GLiftProdWells &prod_wells,
GLiftOptWells &glift_wells,
GLiftWellStateMap &glift_state_map
//std::map<std::string, WellInterface *> &prod_wells
GLiftWellStateMap &glift_state_map,
GasLiftGroupInfo &group_info,
GLiftSyncGroups &sync_groups
) const
{
const auto& well = well_ecl_;
if (well.isProducer()) {
const auto& summary_state = ebos_simulator.vanguard().summaryState();
if ( this->Base::wellHasTHPConstraints(summary_state) ) {
const int iteration_idx = ebos_simulator.model().newtonMethod().numIterations();
if (this->doGasLiftOptimize(well_state,
ebos_simulator.episodeIndex(),
iteration_idx,
ebos_simulator.vanguard().schedule(),
deferred_logger)) {
std::unique_ptr<GasLiftSingleWell> glift
= std::make_unique<GasLiftSingleWell>(
*this, ebos_simulator, summary_state,
deferred_logger, well_state);
auto state = glift->runOptimize(iteration_idx);
if (state) {
glift_state_map.insert({this->name(), std::move(state)});
glift_wells.insert({this->name(), std::move(glift)});
return;
}
}
}
prod_wells.insert({this->name(), this});
const auto& summary_state = ebos_simulator.vanguard().summaryState();
std::unique_ptr<GasLiftSingleWell> glift
= std::make_unique<GasLiftSingleWell>(
*this, ebos_simulator, summary_state,
deferred_logger, well_state, group_info, sync_groups);
auto state = glift->runOptimize(
ebos_simulator.model().newtonMethod().numIterations());
if (state) {
glift_state_map.insert({this->name(), std::move(state)});
glift_wells.insert({this->name(), std::move(glift)});
return;
}
prod_wells.insert({this->name(), this});
}
template<typename TypeTag>

View File

@ -41,8 +41,11 @@
namespace Opm {
template<typename TypeTag> class GasLiftSingleWell;
template<typename TypeTag> class BlackoilWellModel;
class GasLiftGroupInfo;
}
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <opm/simulators/wells/GasLiftSingleWell.hpp>
#include <opm/simulators/wells/GasLiftSingleWellGeneric.hpp>
#include <opm/simulators/wells/BlackoilWellModel.hpp>
#include <opm/simulators/flow/BlackoilModelParametersEbos.hpp>
@ -84,6 +87,7 @@ public:
using GLiftProdWells = typename BlackoilWellModel<TypeTag>::GLiftProdWells;
using GLiftWellStateMap =
typename BlackoilWellModel<TypeTag>::GLiftWellStateMap;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups;
static const int numEq = Indices::numEq;
static const int numPhases = Indices::numPhases;
@ -167,7 +171,9 @@ public:
DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GLiftWellStateMap& state_map
GLiftWellStateMap& state_map,
GasLiftGroupInfo &group_info,
GLiftSyncGroups &sync_groups
) const = 0;
/// using the solution x to recover the solution xw for wells and applying

View File

@ -250,6 +250,8 @@ void WellState::init(const std::vector<double>& cellPressures,
const int nw = wells_ecl.size();
do_glift_optimization_ = true;
if( nw == 0 ) return ;
// Initialize perfphaserates_, which must be done here.
@ -437,7 +439,6 @@ void WellState::init(const std::vector<double>& cellPressures,
updateWellsDefaultALQ(wells_ecl);
do_glift_optimization_ = true;
}
void WellState::resize(const std::vector<Well>& wells_ecl,

View File

@ -256,15 +256,6 @@ public:
void gliftTimeStepInit() {
this->alq_state.reset_count();
disableGliftOptimization();
}
void disableGliftOptimization() {
do_glift_optimization_ = false;
}
void enableGliftOptimization() {
do_glift_optimization_ = true;
}
int wellNameToGlobalIdx(const std::string &name) {

View File

@ -38,6 +38,8 @@
#include <opm/simulators/wells/BlackoilWellModel.hpp>
#include <opm/simulators/wells/StandardWell.hpp>
#include <opm/simulators/wells/GasLiftSingleWell.hpp>
#include <opm/simulators/wells/GasLiftSingleWellGeneric.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
//#include <opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp>
//#include <flow/flow_ebos_blackoil.hpp>
#include <opm/simulators/wells/WellState.hpp>
@ -122,7 +124,11 @@ BOOST_AUTO_TEST_CASE(G1)
using WellState = Opm::WellState;
using StdWell = Opm::StandardWell<TypeTag>;
using GasLiftSingleWell = Opm::GasLiftSingleWell<TypeTag>;
using GasLiftGroupInfo = Opm::GasLiftGroupInfo;
using GasLiftSingleWellGeneric = Opm::GasLiftSingleWellGeneric;
using GLiftEclWells = typename GasLiftGroupInfo::GLiftEclWells;
const std::string filename = "GLIFT1.DATA";
using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups;
auto simulator = initSimulator<TypeTag>(filename.data());
@ -161,9 +167,23 @@ BOOST_AUTO_TEST_CASE(G1)
BOOST_CHECK_EQUAL( well.name(), "B-1H");
const auto& summary_state = simulator->vanguard().summaryState();
WellState &well_state = well_model.wellState();
GLiftEclWells ecl_well_map;
well_model.initGliftEclWellMap(ecl_well_map);
const int iteration_idx = simulator->model().newtonMethod().numIterations();
GasLiftGroupInfo group_info {
ecl_well_map,
schedule,
summary_state,
simulator->episodeIndex(),
iteration_idx,
well_model.phaseUsage(),
deferred_logger,
well_state
};
GLiftSyncGroups sync_groups;
GasLiftSingleWell glift {*std_well, *(simulator.get()), summary_state,
deferred_logger, well_state};
auto state = glift.runOptimize(simulator->model().newtonMethod().numIterations());
deferred_logger, well_state, group_info, sync_groups};
auto state = glift.runOptimize(iteration_idx);
BOOST_CHECK_CLOSE(state->oilRate(), 0.01736111111111111, 1e-8);
BOOST_CHECK_CLOSE(state->gasRate(), 1.6464646999768586, 1e-8);
BOOST_CHECK(state->oilIsLimited());
@ -172,3 +192,4 @@ BOOST_AUTO_TEST_CASE(G1)
BOOST_CHECK_CLOSE(state->alq(), 0.0, 1e-8);
BOOST_CHECK(!state->increase().has_value());
}