Merge pull request #3384 from hakonhagland/glift_sw_mpi5

Check group limits in gas lift stage 1
This commit is contained in:
Tor Harald Sandve
2021-06-17 11:52:01 +02:00
committed by GitHub
18 changed files with 1588 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();
int 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 (int 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,274 @@
/*
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);
// TODO: See comment below for initializeGroupRatesRecursive_() for why
// the implementation of initialize() is kept here in the header file instead
// of in the .cpp file...
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 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

@@ -42,7 +42,9 @@ namespace Opm {
template<typename TypeTag> class GasLiftSingleWell;
template<typename TypeTag> class BlackoilWellModel;
}
#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 +86,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 +170,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());
}