Merge pull request #5832 from akva2/blackoilwellmodel_gaslift

changed: move GasLift code in BlackoilWellModel to separate class
This commit is contained in:
Bård Skaflestad 2025-01-06 16:00:53 +01:00 committed by GitHub
commit 09d7e8d8c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 609 additions and 348 deletions

View File

@ -161,6 +161,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/simulators/utils/satfunc/RelpermDiagnostics.cpp
opm/simulators/wells/ALQState.cpp
opm/simulators/wells/BlackoilWellModelConstraints.cpp
opm/simulators/wells/BlackoilWellModelGasLift.cpp
opm/simulators/wells/BlackoilWellModelGeneric.cpp
opm/simulators/wells/BlackoilWellModelGuideRates.cpp
opm/simulators/wells/BlackoilWellModelRestart.cpp
@ -959,6 +960,8 @@ list (APPEND PUBLIC_HEADER_FILES
opm/simulators/wells/BlackoilWellModel.hpp
opm/simulators/wells/BlackoilWellModel_impl.hpp
opm/simulators/wells/BlackoilWellModelConstraints.hpp
opm/simulators/wells/BlackoilWellModelGasLift.hpp
opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp
opm/simulators/wells/BlackoilWellModelGeneric.hpp
opm/simulators/wells/BlackoilWellModelGuideRates.hpp
opm/simulators/wells/BlackoilWellModelRestart.hpp

View File

@ -29,7 +29,6 @@
#include <cstddef>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -48,6 +47,7 @@
#include <opm/simulators/linalg/matrixblock.hh>
#include <opm/simulators/wells/BlackoilWellModelGasLift.hpp>
#include <opm/simulators/wells/BlackoilWellModelGeneric.hpp>
#include <opm/simulators/wells/BlackoilWellModelGuideRates.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
@ -80,12 +80,6 @@
#include <opm/simulators/utils/DeferredLogger.hpp>
namespace Opm::Parameters {
struct EnableTerminalOutput { static constexpr bool value = true; };
} // namespace Opm::Parameters
namespace Opm {
#if COMPILE_GPU_BRIDGE
@ -110,14 +104,6 @@ template<class Scalar> class WellContributions;
using RateVector = GetPropType<TypeTag, Properties::RateVector>;
using GlobalEqVector = GetPropType<TypeTag, Properties::GlobalEqVector>;
using SparseMatrixAdapter = GetPropType<TypeTag, Properties::SparseMatrixAdapter>;
using GasLiftSingleWell = typename WellInterface<TypeTag>::GasLiftSingleWell;
using GLiftOptWells = typename BlackoilWellModelGeneric<Scalar>::GLiftOptWells;
using GLiftProdWells = typename BlackoilWellModelGeneric<Scalar>::GLiftProdWells;
using GLiftWellStateMap =
typename BlackoilWellModelGeneric<Scalar>::GLiftWellStateMap;
using GLiftEclWells = typename GasLiftGroupInfo<Scalar>::GLiftEclWells;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric<Scalar>::GLiftSyncGroups;
using ModelParameters = BlackoilModelParameters<Scalar>;
constexpr static std::size_t pressureVarIndex = GetPropType<TypeTag, Properties::Indices>::pressureSwitchIdx;
@ -362,8 +348,6 @@ template<class Scalar> class WellContributions;
void addWellPressureEquationsStruct(PressureMatrix& jacobian) const;
void initGliftEclWellMap(GLiftEclWells &ecl_well_map);
/// \brief Get list of local nonshut wells
const std::vector<WellInterfacePtr>& localNonshutWells() const
{
@ -525,23 +509,6 @@ template<class Scalar> class WellContributions;
// TODO: finding a better naming
void assembleWellEqWithoutIteration(const double dt, DeferredLogger& deferred_logger);
bool maybeDoGasLiftOptimize(DeferredLogger& deferred_logger);
void gasLiftOptimizationStage1(DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map);
// cannot be const since it accesses the non-const WellState
void gasLiftOptimizationStage1SingleWell(WellInterface<TypeTag>* well,
DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map,
GLiftSyncGroups& groups_to_sync);
void extractLegacyCellPvtRegionIndex_();
void extractLegacyDepth_();
@ -569,6 +536,8 @@ template<class Scalar> class WellContributions;
private:
BlackoilWellModel(Simulator& simulator, const PhaseUsage& pu);
BlackoilWellModelGasLift<TypeTag> gaslift_;
// These members are used to avoid reallocation in specific functions
// instead of using local variables.
// Their state is not relevant between function calls, so they can

View File

@ -0,0 +1,110 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 - 2017 Statoil ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 IRIS AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <opm/simulators/wells/BlackoilWellModelGasLift.hpp>
#include <opm/simulators/wells/GasLiftStage2.hpp>
#include <opm/simulators/wells/WellInterfaceGeneric.hpp>
#include <opm/simulators/wells/WellState.hpp>
#include <fmt/format.h>
namespace Opm {
template<class Scalar>
void BlackoilWellModelGasLiftGeneric<Scalar>::
gliftDebug([[maybe_unused]] const std::string& msg,
[[maybe_unused]] DeferredLogger& deferred_logger) const
{
if constexpr (glift_debug) {
if (terminal_output_) {
const std::string message =
fmt::format(" GLIFT (DEBUG) : BlackoilWellModel : {}", msg);
deferred_logger.info(message);
}
}
}
template<class Scalar>
void BlackoilWellModelGasLiftGeneric<Scalar>::
gliftDebugShowALQ(const std::vector<WellInterfaceGeneric<Scalar>*>& well_container,
const WellState<Scalar>& wellState,
DeferredLogger& deferred_logger)
{
for (const auto& well : well_container) {
if (well->isProducer()) {
auto alq = wellState.getALQ(well->name());
const std::string msg = fmt::format("ALQ_REPORT : {} : {}",
well->name(), alq);
gliftDebug(msg, deferred_logger);
}
}
}
// If a group has any production rate constraints, and/or a limit
// on its total rate of lift gas supply, allocate lift gas
// preferentially to the wells that gain the most benefit from
// it. Lift gas increments are allocated in turn to the well that
// currently has the largest weighted incremental gradient. The
// procedure takes account of any limits on the group production
// rate or lift gas supply applied to any level of group.
template<class Scalar>
void BlackoilWellModelGasLiftGeneric<Scalar>::
gasLiftOptimizationStage2(const Parallel::Communication& comm,
const Schedule& schedule,
const SummaryState& summaryState,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& glift_well_state_map,
const int episodeIndex,
DeferredLogger& deferred_logger)
{
GasLiftStage2 glift {episodeIndex,
comm,
schedule,
summaryState,
deferred_logger,
wellState,
groupState,
prod_wells,
glift_wells,
group_info,
glift_well_state_map,
this->glift_debug
};
glift.runOptimize();
}
template class BlackoilWellModelGasLiftGeneric<double>;
#if FLOW_INSTANTIATE_FLOAT
template class BlackoilWellModelGasLiftGeneric<float>;
#endif
}

View File

@ -0,0 +1,143 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 - 2017 Statoil ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 IRIS AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILWELLMODEL_GASLIFT_HEADER_INCLUDED
#define OPM_BLACKOILWELLMODEL_GASLIFT_HEADER_INCLUDED
#include "opm/models/utils/basicproperties.hh"
#include <opm/simulators/wells/GasLiftSingleWellGeneric.hpp>
#include <memory>
#include <map>
#include <string>
namespace Opm {
class DeferredLogger;
template<class Scalar> class GroupState;
template<class Scalar> class WellState;
template<class TypeTag> class WellInterface;
template<class Scalar>
class BlackoilWellModelGasLiftGeneric
{
public:
using GLiftOptWells = std::map<std::string, std::unique_ptr<GasLiftSingleWellGeneric<Scalar>>>;
using GLiftProdWells = std::map<std::string, const WellInterfaceGeneric<Scalar>*>;
using GLiftWellStateMap = std::map<std::string, std::unique_ptr<GasLiftWellState<Scalar>>>;
using GLiftEclWells = typename GasLiftGroupInfo<Scalar>::GLiftEclWells;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric<Scalar>::GLiftSyncGroups;
explicit BlackoilWellModelGasLiftGeneric(bool terminal_output)
: terminal_output_(terminal_output)
{}
static constexpr bool glift_debug = false;
void gliftDebug(const std::string& msg,
DeferredLogger& deferred_logger) const;
bool terminalOutput() const { return terminal_output_; }
protected:
void gliftDebugShowALQ(const std::vector<WellInterfaceGeneric<Scalar>*>& well_container,
const WellState<Scalar>& wellState,
DeferredLogger& deferred_logger);
void gasLiftOptimizationStage2(const Parallel::Communication& comm,
const Schedule& schedule,
const SummaryState& summaryState,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& map,
const int episodeIndex,
DeferredLogger& deferred_logger);
bool terminal_output_;
double last_glift_opt_time_ = -1.0;
};
/// Class for handling the gaslift in the blackoil well model.
template<typename TypeTag>
class BlackoilWellModelGasLift :
public BlackoilWellModelGasLiftGeneric<GetPropType<TypeTag, Properties::Scalar>>
{
using Base = BlackoilWellModelGasLiftGeneric<GetPropType<TypeTag, Properties::Scalar>>;
public:
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using GLiftEclWells = typename GasLiftGroupInfo<Scalar>::GLiftEclWells;
using GLiftOptWells = typename Base::GLiftOptWells;
using GLiftProdWells = typename Base::GLiftProdWells;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric<Scalar>::GLiftSyncGroups;
using GLiftWellStateMap = typename Base::GLiftWellStateMap;
using Simulator = GetPropType<TypeTag, Properties::Simulator>;
using WellInterfacePtr = std::shared_ptr<WellInterface<TypeTag>>;
BlackoilWellModelGasLift(bool terminal_output,
const PhaseUsage& phase_usage)
: Base(terminal_output)
, phase_usage_(phase_usage)
{}
static void initGliftEclWellMap(const std::vector<WellInterfacePtr>& well_container,
GLiftEclWells& ecl_well_map);
bool maybeDoGasLiftOptimize(const Simulator& simulator,
const std::vector<WellInterfacePtr>& well_container,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
DeferredLogger& deferred_logger);
private:
void gasLiftOptimizationStage1(const Simulator& simulator,
const std::vector<WellInterfacePtr>& well_container,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map,
DeferredLogger& deferred_logger);
// cannot be const since it accesses the non-const WellState
void gasLiftOptimizationStage1SingleWell(WellInterface<TypeTag>* well,
const Simulator& simulator,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map,
GLiftSyncGroups& groups_to_sync,
DeferredLogger& deferred_logger);
const PhaseUsage& phase_usage_;
};
} // namespace Opm
#include "BlackoilWellModelGasLift_impl.hpp"
#endif

View File

@ -0,0 +1,295 @@
/*
Copyright 2016 - 2019 SINTEF Digital, Mathematics & Cybernetics.
Copyright 2016 - 2018 Equinor ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 Norce AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#define OPM_BLACKOILWELLMODEL_GASLIFT_IMPL_HEADER_INCLUDED
#define OPM_BLACKOILWELLMODEL_GASLIFT_IMPL_HEADER_INCLUDED
// Improve IDE experience
#ifndef OPM_BLACKOILWELLMODEL_GASLIFT_HEADER_INCLUDED
#include <config.h>
#include <opm/simulators/wells/BlackoilWellModelGasLift.hpp>
#endif
#include <opm/simulators/wells/GasLiftSingleWell.hpp>
#if HAVE_MPI
#include <opm/simulators/utils/MPISerializer.hpp>
#endif
namespace Opm {
template<typename TypeTag>
bool
BlackoilWellModelGasLift<TypeTag>::
maybeDoGasLiftOptimize(const Simulator& simulator,
const std::vector<WellInterfacePtr>& well_container,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
DeferredLogger& deferred_logger)
{
bool do_glift_optimization = false;
int num_wells_changed = 0;
const double simulation_time = simulator.time();
const Scalar min_wait = simulator.vanguard().schedule().glo(simulator.episodeIndex()).min_wait();
// We only optimize if a min_wait time has past.
// If all_newton is true we still want to optimize several times pr timestep
// i.e. we also optimize if check simulation_time == last_glift_opt_time_
// that is when the last_glift_opt_time is already updated with the current time step
if (simulation_time == this->last_glift_opt_time_ ||
simulation_time >= (this->last_glift_opt_time_ + min_wait))
{
do_glift_optimization = true;
this->last_glift_opt_time_ = simulation_time;
}
if (do_glift_optimization) {
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(well_container, ecl_well_map);
GasLiftGroupInfo group_info {
ecl_well_map,
simulator.vanguard().schedule(),
simulator.vanguard().summaryState(),
simulator.episodeIndex(),
simulator.model().newtonMethod().numIterations(),
phase_usage_,
deferred_logger,
wellState,
groupState,
simulator.vanguard().grid().comm(),
this->glift_debug
};
group_info.initialize();
gasLiftOptimizationStage1(simulator,
well_container,
wellState,
groupState,
prod_wells,
glift_wells,
group_info,
state_map,
deferred_logger);
this->gasLiftOptimizationStage2(simulator.vanguard().gridView().comm(),
simulator.vanguard().schedule(),
simulator.vanguard().summaryState(),
wellState,
groupState,
prod_wells,
glift_wells,
group_info,
state_map,
simulator.episodeIndex(),
deferred_logger);
if constexpr (this->glift_debug) {
std::vector<WellInterfaceGeneric<Scalar>*> wc;
wc.reserve(well_container.size());
for (const auto& w : well_container) {
wc.push_back(static_cast<WellInterfaceGeneric<Scalar>*>(w.get()));
}
this->gliftDebugShowALQ(wc,
wellState,
deferred_logger);
}
num_wells_changed = glift_wells.size();
}
num_wells_changed = simulator.vanguard().gridView().comm().sum(num_wells_changed);
return num_wells_changed > 0;
}
template<typename TypeTag>
void
BlackoilWellModelGasLift<TypeTag>::
gasLiftOptimizationStage1(const Simulator& simulator,
const std::vector<WellInterfacePtr>& well_container,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
GLiftProdWells& prod_wells,
GLiftOptWells &glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map,
DeferredLogger& deferred_logger)
{
auto comm = simulator.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())) {
gasLiftOptimizationStage1SingleWell(well.get(),
simulator,
wellState,
groupState,
prod_wells,
glift_wells,
group_info,
state_map,
groups_to_sync,
deferred_logger);
}
}
num_rates_to_sync = groups_to_sync.size();
}
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<Scalar> group_alq_rates;
group_alq_rates.reserve(num_rates_to_sync);
std::vector<Scalar> group_oil_rates;
group_oil_rates.reserve(num_rates_to_sync);
std::vector<Scalar> group_gas_rates;
group_gas_rates.reserve(num_rates_to_sync);
std::vector<Scalar> group_water_rates;
group_water_rates.reserve(num_rates_to_sync);
if (comm.rank() == i) {
for (auto idx : groups_to_sync) {
auto [oil_rate, gas_rate, water_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_water_rates.push_back(water_rate);
group_alq_rates.push_back(alq);
}
} else {
group_indexes.resize(num_rates_to_sync);
group_oil_rates.resize(num_rates_to_sync);
group_gas_rates.resize(num_rates_to_sync);
group_water_rates.resize(num_rates_to_sync);
group_alq_rates.resize(num_rates_to_sync);
}
#if HAVE_MPI
Parallel::MpiSerializer ser(comm);
ser.broadcast(i, group_indexes, group_oil_rates,
group_gas_rates, group_water_rates, group_alq_rates);
#endif
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_water_rates[j],
group_alq_rates[j]);
}
}
if (this->glift_debug) {
int counter = 0;
if (comm.rank() == i) {
counter = wellState.gliftGetDebugCounter();
}
counter = comm.sum(counter);
if (comm.rank() != i) {
wellState.gliftSetDebugCounter(counter);
}
}
}
}
}
// NOTE: this method cannot be const since it passes this->wellState()
// (see below) to the GasLiftSingleWell constructor which accepts WellState
// as a non-const reference..
template<typename TypeTag>
void
BlackoilWellModelGasLift<TypeTag>::
gasLiftOptimizationStage1SingleWell(WellInterface<TypeTag>* well,
const Simulator& simulator,
WellState<Scalar>& wellState,
GroupState<Scalar>& groupState,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map,
GLiftSyncGroups& sync_groups,
DeferredLogger& deferred_logger)
{
const auto& summary_state = simulator.vanguard().summaryState();
auto glift = std::make_unique<GasLiftSingleWell<TypeTag>>(*well,
simulator,
summary_state,
deferred_logger,
wellState,
groupState,
group_info,
sync_groups,
simulator.vanguard().gridView().comm(),
this->glift_debug);
auto state = glift->runOptimize(simulator.model().newtonMethod().numIterations());
if (state) {
state_map.emplace(well->name(), std::move(state));
glift_wells.emplace(well->name(), std::move(glift));
return;
}
prod_wells.insert({well->name(), well});
}
template<typename TypeTag>
void
BlackoilWellModelGasLift<TypeTag>::
initGliftEclWellMap(const std::vector<WellInterfacePtr>& well_container,
GLiftEclWells& ecl_well_map)
{
for (const auto& well : well_container) {
ecl_well_map.try_emplace(well->name(), &well->wellEcl(), well->indexOfWell());
}
}
} // namespace Opm

View File

@ -49,6 +49,8 @@
#include <opm/input/eclipse/Units/Units.hpp>
#include <opm/models/utils/parametersystem.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <opm/simulators/wells/BlackoilWellModelConstraints.hpp>
#include <opm/simulators/wells/BlackoilWellModelGuideRates.hpp>
@ -93,6 +95,8 @@ BlackoilWellModelGeneric(Schedule& schedule,
, eclState_(eclState)
, comm_(comm)
, phase_usage_(phase_usage)
, terminal_output_(comm_.rank() == 0 &&
Parameters::Get<Parameters::EnableTerminalOutput>())
, wbpCalculationService_ { eclState.gridDims(), comm_ }
, guideRate_(schedule)
, active_wgstate_(phase_usage)
@ -1613,64 +1617,6 @@ setRepRadiusPerfLength()
}
}
template<class Scalar>
void BlackoilWellModelGeneric<Scalar>::
gliftDebug(const std::string& msg,
DeferredLogger& deferred_logger) const
{
if (this->glift_debug && this->terminal_output_) {
const std::string message = fmt::format(
" GLIFT (DEBUG) : BlackoilWellModel : {}", msg);
deferred_logger.info(message);
}
}
template<class Scalar>
void BlackoilWellModelGeneric<Scalar>::
gliftDebugShowALQ(DeferredLogger& deferred_logger)
{
for (auto& well : this->well_container_generic_) {
if (well->isProducer()) {
auto alq = this->wellState().getALQ(well->name());
const std::string msg = fmt::format("ALQ_REPORT : {} : {}",
well->name(), alq);
gliftDebug(msg, deferred_logger);
}
}
}
// If a group has any production rate constraints, and/or a limit
// on its total rate of lift gas supply, allocate lift gas
// preferentially to the wells that gain the most benefit from
// it. Lift gas increments are allocated in turn to the well that
// currently has the largest weighted incremental gradient. The
// procedure takes account of any limits on the group production
// rate or lift gas supply applied to any level of group.
template<class Scalar>
void BlackoilWellModelGeneric<Scalar>::
gasLiftOptimizationStage2(DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& glift_well_state_map,
const int episodeIndex)
{
GasLiftStage2 glift {episodeIndex,
comm_,
schedule_,
summaryState_,
deferred_logger,
this->wellState(),
this->groupState(),
prod_wells,
glift_wells,
group_info,
glift_well_state_map,
this->glift_debug
};
glift.runOptimize();
}
template<class Scalar>
void BlackoilWellModelGeneric<Scalar>::
updateWellPotentials(const int reportStepIdx,

View File

@ -75,6 +75,12 @@ namespace Opm { namespace data {
struct NodeData;
}} // namespace Opm::data
namespace Opm::Parameters {
struct EnableTerminalOutput { static constexpr bool value = true; };
} // namespace Opm::Parameters
namespace Opm {
/// Class for handling the blackoil well model.
@ -82,11 +88,6 @@ template<class Scalar>
class BlackoilWellModelGeneric
{
public:
// --------- Types ---------
using GLiftOptWells = std::map<std::string, std::unique_ptr<GasLiftSingleWellGeneric<Scalar>>>;
using GLiftProdWells = std::map<std::string, const WellInterfaceGeneric<Scalar>*>;
using GLiftWellStateMap = std::map<std::string, std::unique_ptr<GasLiftWellState<Scalar>>>;
BlackoilWellModelGeneric(Schedule& schedule,
const SummaryState& summaryState,
const EclipseState& eclState,
@ -242,7 +243,6 @@ public:
serializer(active_wgstate_);
serializer(last_valid_wgstate_);
serializer(nupcol_wgstate_);
serializer(last_glift_opt_time_);
serializer(switched_prod_groups_);
serializer(switched_inj_groups_);
serializer(closed_offending_wells_);
@ -260,7 +260,6 @@ public:
this->active_wgstate_ == rhs.active_wgstate_ &&
this->last_valid_wgstate_ == rhs.last_valid_wgstate_ &&
this->nupcol_wgstate_ == rhs.nupcol_wgstate_ &&
this->last_glift_opt_time_ == rhs.last_glift_opt_time_ &&
this->switched_prod_groups_ == rhs.switched_prod_groups_ &&
this->switched_inj_groups_ == rhs.switched_inj_groups_ &&
this->closed_offending_wells_ == rhs.closed_offending_wells_;
@ -399,18 +398,6 @@ protected:
void setRepRadiusPerfLength();
void gliftDebug(const std::string& msg,
DeferredLogger& deferred_logger) const;
void gliftDebugShowALQ(DeferredLogger& deferred_logger);
void gasLiftOptimizationStage2(DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& map,
const int episodeIndex);
virtual void computePotentials(const std::size_t widx,
const WellState<Scalar>& well_state_copy,
std::string& exc_msg,
@ -628,10 +615,6 @@ protected:
WGState<Scalar> last_valid_wgstate_;
WGState<Scalar> nupcol_wgstate_;
bool glift_debug = false;
double last_glift_opt_time_ = -1.0;
bool wellStructureChangedDynamically_{false};
// Store maps of group name and new group controls for output

View File

@ -79,10 +79,8 @@ namespace Opm {
phase_usage,
simulator.gridView().comm())
, simulator_(simulator)
, gaslift_(this->terminal_output_, this->phase_usage_)
{
this->terminal_output_ = (simulator.gridView().comm().rank() == 0)
&& Parameters::Get<Parameters::EnableTerminalOutput>();
local_num_cells_ = simulator_.gridView().size(0);
// Number of cells the global grid view
@ -680,11 +678,18 @@ namespace Opm {
}
try {
using GLiftEclWells = typename GasLiftGroupInfo<Scalar>::GLiftEclWells;
GLiftEclWells ecl_well_map;
initGliftEclWellMap(ecl_well_map);
well->wellTesting(simulator_, simulationTime, this->wellState(),
this->groupState(), this->wellTestState(), this->phase_usage_,
ecl_well_map, this->well_open_times_, deferred_logger);
gaslift_.initGliftEclWellMap(well_container_, ecl_well_map);
well->wellTesting(simulator_,
simulationTime,
this->wellState(),
this->groupState(),
this->wellTestState(),
this->phase_usage_,
ecl_well_map,
this->well_open_times_,
deferred_logger);
} catch (const std::exception& e) {
const std::string msg = fmt::format("Exception during testing of well: {}. The well will not open.\n Exception message: {}", wellEcl.name(), e.what());
deferred_logger.warning("WELL_TESTING_FAILED", msg);
@ -844,8 +849,9 @@ namespace Opm {
{
rate = 0;
if (!is_cell_perforated_[elemIdx])
if (!is_cell_perforated_[elemIdx]) {
return;
}
for (const auto& well : well_container_)
well->addCellRates(rate, elemIdx);
@ -864,8 +870,9 @@ namespace Opm {
rate = 0;
int elemIdx = context.globalSpaceIndex(spaceIdx, timeIdx);
if (!is_cell_perforated_[elemIdx])
if (!is_cell_perforated_[elemIdx]) {
return;
}
for (const auto& well : well_container_)
well->addCellRates(rate, elemIdx);
@ -1233,12 +1240,14 @@ namespace Opm {
assemble(const int iterationIdx,
const double dt)
{
DeferredLogger local_deferredLogger;
if (this->glift_debug) {
const std::string msg = fmt::format(
"assemble() : iteration {}" , iterationIdx);
this->gliftDebug(msg, local_deferredLogger);
if constexpr (gaslift_.glift_debug) {
if (gaslift_.terminalOutput()) {
const std::string msg =
fmt::format("assemble() : iteration {}" , iterationIdx);
gaslift_.gliftDebug(msg, local_deferredLogger);
}
}
last_report_ = SimulatorReportSingle();
Dune::Timer perfTimer;
@ -1334,7 +1343,9 @@ namespace Opm {
DeferredLogger& local_deferredLogger)
{
auto [well_group_control_changed, more_network_update] =
updateWellControls(mandatory_network_balance, local_deferredLogger, relax_network_tolerance);
updateWellControls(mandatory_network_balance,
local_deferredLogger,
relax_network_tolerance);
bool alq_updated = false;
OPM_BEGIN_PARALLEL_TRY_CATCH();
@ -1342,11 +1353,16 @@ namespace Opm {
// Set the well primary variables based on the value of well solutions
initPrimaryVariablesEvaluation();
alq_updated = maybeDoGasLiftOptimize(local_deferredLogger);
alq_updated = gaslift_.maybeDoGasLiftOptimize(simulator_,
well_container_,
this->wellState(),
this->groupState(),
local_deferredLogger);
prepareWellsBeforeAssembling(dt, local_deferredLogger);
}
OPM_END_PARALLEL_TRY_CATCH_LOG(local_deferredLogger, "updateWellControlsAndNetworkIteration() failed: ",
OPM_END_PARALLEL_TRY_CATCH_LOG(local_deferredLogger,
"updateWellControlsAndNetworkIteration() failed: ",
this->terminal_output_, grid().comm());
// update guide rates
@ -1591,212 +1607,6 @@ namespace Opm {
}
template<typename TypeTag>
bool
BlackoilWellModel<TypeTag>::
maybeDoGasLiftOptimize(DeferredLogger& deferred_logger)
{
bool do_glift_optimization = false;
int num_wells_changed = 0;
const double simulation_time = simulator_.time();
const Scalar min_wait = simulator_.vanguard().schedule().glo(simulator_.episodeIndex()).min_wait();
// We only optimize if a min_wait time has past.
// If all_newton is true we still want to optimize several times pr timestep
// i.e. we also optimize if check simulation_time == last_glift_opt_time_
// that is when the last_glift_opt_time is already updated with the current time step
if ( simulation_time == this->last_glift_opt_time_ || simulation_time >= (this->last_glift_opt_time_ + min_wait)) {
do_glift_optimization = true;
this->last_glift_opt_time_ = simulation_time;
}
if (do_glift_optimization) {
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,
simulator_.vanguard().schedule(),
simulator_.vanguard().summaryState(),
simulator_.episodeIndex(),
simulator_.model().newtonMethod().numIterations(),
this->phase_usage_,
deferred_logger,
this->wellState(),
this->groupState(),
simulator_.vanguard().grid().comm(),
this->glift_debug
};
group_info.initialize();
gasLiftOptimizationStage1(deferred_logger, prod_wells, glift_wells,
group_info, state_map);
this->gasLiftOptimizationStage2(deferred_logger, prod_wells, glift_wells,
group_info, state_map, simulator_.episodeIndex());
if (this->glift_debug) {
this->gliftDebugShowALQ(deferred_logger);
}
num_wells_changed = glift_wells.size();
}
num_wells_changed = this->comm_.sum(num_wells_changed);
return num_wells_changed > 0;
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
gasLiftOptimizationStage1(DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells &glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map)
{
auto comm = simulator_.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())) {
gasLiftOptimizationStage1SingleWell(
well.get(), deferred_logger, prod_wells, glift_wells,
group_info, state_map, groups_to_sync
);
}
}
num_rates_to_sync = groups_to_sync.size();
}
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<Scalar> group_alq_rates;
group_alq_rates.reserve(num_rates_to_sync);
std::vector<Scalar> group_oil_rates;
group_oil_rates.reserve(num_rates_to_sync);
std::vector<Scalar> group_gas_rates;
group_gas_rates.reserve(num_rates_to_sync);
std::vector<Scalar> group_water_rates;
group_water_rates.reserve(num_rates_to_sync);
if (comm.rank() == i) {
for (auto idx : groups_to_sync) {
auto [oil_rate, gas_rate, water_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_water_rates.push_back(water_rate);
group_alq_rates.push_back(alq);
}
} else {
group_indexes.resize(num_rates_to_sync);
group_oil_rates.resize(num_rates_to_sync);
group_gas_rates.resize(num_rates_to_sync);
group_water_rates.resize(num_rates_to_sync);
group_alq_rates.resize(num_rates_to_sync);
}
#if HAVE_MPI
Parallel::MpiSerializer ser(comm);
ser.broadcast(i, group_indexes, group_oil_rates,
group_gas_rates, group_water_rates, group_alq_rates);
#endif
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_water_rates[j], group_alq_rates[j]);
}
}
if (this->glift_debug) {
int counter = 0;
if (comm.rank() == i) {
counter = this->wellState().gliftGetDebugCounter();
}
counter = comm.sum(counter);
if (comm.rank() != i) {
this->wellState().gliftSetDebugCounter(counter);
}
}
}
}
}
// NOTE: this method cannot be const since it passes this->wellState()
// (see below) to the GasLiftSingleWell constructor which accepts WellState
// as a non-const reference..
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
gasLiftOptimizationStage1SingleWell(WellInterface<TypeTag>* well,
DeferredLogger& deferred_logger,
GLiftProdWells& prod_wells,
GLiftOptWells& glift_wells,
GasLiftGroupInfo<Scalar>& group_info,
GLiftWellStateMap& state_map,
GLiftSyncGroups& sync_groups)
{
const auto& summary_state = simulator_.vanguard().summaryState();
std::unique_ptr<GasLiftSingleWell> glift
= std::make_unique<GasLiftSingleWell>(
*well, simulator_, summary_state,
deferred_logger, this->wellState(), this->groupState(),
group_info, sync_groups, this->comm_, this->glift_debug);
auto state = glift->runOptimize(
simulator_.model().newtonMethod().numIterations());
if (state) {
state_map.insert({well->name(), std::move(state)});
glift_wells.insert({well->name(), std::move(glift)});
return;
}
prod_wells.insert({well->name(), well});
}
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>
void
BlackoilWellModel<TypeTag>::

View File

@ -25,12 +25,13 @@
#include <opm/models/discretization/common/fvbaseproperties.hh>
#include <opm/simulators/wells/GasLiftSingleWellGeneric.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <opm/simulators/wells/WellInterface.hpp>
#include <optional>
namespace Opm {
template<class TypeTag> class WellInterface;
template<class TypeTag>
class GasLiftSingleWell : public GasLiftSingleWellGeneric<GetPropType<TypeTag, Properties::Scalar>>
{

View File

@ -27,11 +27,13 @@
#endif
#include <opm/input/eclipse/Schedule/GasLiftOpt.hpp>
#include <fmt/format.h>
#include <opm/input/eclipse/Schedule/Well/Well.hpp>
#include <string>
#include <vector>
#include <fmt/format.h>
namespace Opm {
template<typename TypeTag>

View File

@ -22,6 +22,8 @@
#ifndef OPM_MULTISEGMENTWELL_HEADER_INCLUDED
#define OPM_MULTISEGMENTWELL_HEADER_INCLUDED
#include <opm/models/common/multiphasebaseproperties.hh>
#include <opm/simulators/wells/WellInterface.hpp>
#include <opm/simulators/wells/MultisegmentWellEval.hpp>

View File

@ -37,6 +37,8 @@ namespace Opm {
#include <opm/input/eclipse/Schedule/Well/WellTestState.hpp>
#include <opm/material/fluidstates/BlackOilFluidState.hpp>
#include <opm/simulators/wells/BlackoilWellModel.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <opm/simulators/wells/GasLiftSingleWell.hpp>
@ -82,12 +84,7 @@ public:
using SparseMatrixAdapter = GetPropType<TypeTag, Properties::SparseMatrixAdapter>;
using RateVector = GetPropType<TypeTag, Properties::RateVector>;
using GasLiftSingleWell = ::Opm::GasLiftSingleWell<TypeTag>;
using GLiftOptWells = typename BlackoilWellModel<TypeTag>::GLiftOptWells;
using GLiftProdWells = typename BlackoilWellModel<TypeTag>::GLiftProdWells;
using GLiftEclWells = typename GasLiftGroupInfo<Scalar>::GLiftEclWells;
using GLiftWellStateMap =
typename BlackoilWellModel<TypeTag>::GLiftWellStateMap;
using GLiftSyncGroups = typename GasLiftSingleWellGeneric<Scalar>::GLiftSyncGroups;
using VectorBlockType = Dune::FieldVector<Scalar, Indices::numEq>;
using MatrixBlockType = Dune::FieldMatrix<Scalar, Indices::numEq, Indices::numEq>;

View File

@ -324,7 +324,6 @@ public:
active_wgstate_ = WGState<double>::serializationTestObject(dummy);
last_valid_wgstate_ = WGState<double>::serializationTestObject(dummy);
nupcol_wgstate_ = WGState<double>::serializationTestObject(dummy);
last_glift_opt_time_ = 5.0;
switched_prod_groups_ = {{"test4", {Group::ProductionCMode::NONE, Group::ProductionCMode::ORAT}}};
const auto controls = {Group::InjectionCMode::NONE, Group::InjectionCMode::RATE, Group::InjectionCMode::RATE };
switched_inj_groups_ = {{"test4", {controls, {}, controls} }};

View File

@ -175,7 +175,8 @@ BOOST_AUTO_TEST_CASE(G1)
WellState &well_state = well_model.wellState();
const auto &group_state = well_model.groupState();
GLiftEclWells ecl_well_map;
well_model.initGliftEclWellMap(ecl_well_map);
Opm::BlackoilWellModelGasLift<TypeTag>::
initGliftEclWellMap(well_model.localNonshutWells(), ecl_well_map);
const int iteration_idx = simulator->model().newtonMethod().numIterations();
const auto& comm = simulator->vanguard().grid().comm();
GasLiftGroupInfo group_info {