diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 571550bca2..ba25c05fb7 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -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 diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index 0123e2d94a..c0c3e5adec 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -48,6 +47,7 @@ #include +#include #include #include #include @@ -80,12 +80,6 @@ #include -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 WellContributions; using RateVector = GetPropType; using GlobalEqVector = GetPropType; using SparseMatrixAdapter = GetPropType; - using GasLiftSingleWell = typename WellInterface::GasLiftSingleWell; - 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; - using ModelParameters = BlackoilModelParameters; constexpr static std::size_t pressureVarIndex = GetPropType::pressureSwitchIdx; @@ -362,8 +348,6 @@ template class WellContributions; void addWellPressureEquationsStruct(PressureMatrix& jacobian) const; - void initGliftEclWellMap(GLiftEclWells &ecl_well_map); - /// \brief Get list of local nonshut wells const std::vector& localNonshutWells() const { @@ -525,23 +509,6 @@ template 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& group_info, - GLiftWellStateMap& state_map); - - // cannot be const since it accesses the non-const WellState - void gasLiftOptimizationStage1SingleWell(WellInterface* well, - DeferredLogger& deferred_logger, - GLiftProdWells& prod_wells, - GLiftOptWells& glift_wells, - GasLiftGroupInfo& group_info, - GLiftWellStateMap& state_map, - GLiftSyncGroups& groups_to_sync); - void extractLegacyCellPvtRegionIndex_(); void extractLegacyDepth_(); @@ -569,6 +536,8 @@ template class WellContributions; private: BlackoilWellModel(Simulator& simulator, const PhaseUsage& pu); + BlackoilWellModelGasLift 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 diff --git a/opm/simulators/wells/BlackoilWellModelGasLift.cpp b/opm/simulators/wells/BlackoilWellModelGasLift.cpp new file mode 100644 index 0000000000..5ce467cc7a --- /dev/null +++ b/opm/simulators/wells/BlackoilWellModelGasLift.cpp @@ -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 . +*/ + +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Opm { + +template +void BlackoilWellModelGasLiftGeneric:: +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 +void BlackoilWellModelGasLiftGeneric:: +gliftDebugShowALQ(const std::vector*>& well_container, + const WellState& 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 +void BlackoilWellModelGasLiftGeneric:: +gasLiftOptimizationStage2(const Parallel::Communication& comm, + const Schedule& schedule, + const SummaryState& summaryState, + WellState& wellState, + GroupState& groupState, + GLiftProdWells& prod_wells, + GLiftOptWells& glift_wells, + GasLiftGroupInfo& 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; + +#if FLOW_INSTANTIATE_FLOAT +template class BlackoilWellModelGasLiftGeneric; +#endif + +} diff --git a/opm/simulators/wells/BlackoilWellModelGasLift.hpp b/opm/simulators/wells/BlackoilWellModelGasLift.hpp new file mode 100644 index 0000000000..bb1f425f51 --- /dev/null +++ b/opm/simulators/wells/BlackoilWellModelGasLift.hpp @@ -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 . +*/ +#ifndef OPM_BLACKOILWELLMODEL_GASLIFT_HEADER_INCLUDED +#define OPM_BLACKOILWELLMODEL_GASLIFT_HEADER_INCLUDED + +#include "opm/models/utils/basicproperties.hh" +#include + +#include +#include +#include + +namespace Opm { + +class DeferredLogger; +template class GroupState; +template class WellState; +template class WellInterface; + +template +class BlackoilWellModelGasLiftGeneric +{ +public: + using GLiftOptWells = std::map>>; + using GLiftProdWells = std::map*>; + using GLiftWellStateMap = std::map>>; + using GLiftEclWells = typename GasLiftGroupInfo::GLiftEclWells; + using GLiftSyncGroups = typename GasLiftSingleWellGeneric::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*>& well_container, + const WellState& wellState, + DeferredLogger& deferred_logger); + + void gasLiftOptimizationStage2(const Parallel::Communication& comm, + const Schedule& schedule, + const SummaryState& summaryState, + WellState& wellState, + GroupState& groupState, + GLiftProdWells& prod_wells, + GLiftOptWells& glift_wells, + GasLiftGroupInfo& 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 +class BlackoilWellModelGasLift : + public BlackoilWellModelGasLiftGeneric> +{ + using Base = BlackoilWellModelGasLiftGeneric>; + +public: + using Scalar = GetPropType; + using GLiftEclWells = typename GasLiftGroupInfo::GLiftEclWells; + using GLiftOptWells = typename Base::GLiftOptWells; + using GLiftProdWells = typename Base::GLiftProdWells; + using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups; + using GLiftWellStateMap = typename Base::GLiftWellStateMap; + using Simulator = GetPropType; + using WellInterfacePtr = std::shared_ptr>; + + BlackoilWellModelGasLift(bool terminal_output, + const PhaseUsage& phase_usage) + : Base(terminal_output) + , phase_usage_(phase_usage) + {} + + static void initGliftEclWellMap(const std::vector& well_container, + GLiftEclWells& ecl_well_map); + + bool maybeDoGasLiftOptimize(const Simulator& simulator, + const std::vector& well_container, + WellState& wellState, + GroupState& groupState, + DeferredLogger& deferred_logger); + +private: + void gasLiftOptimizationStage1(const Simulator& simulator, + const std::vector& well_container, + WellState& wellState, + GroupState& groupState, + GLiftProdWells& prod_wells, + GLiftOptWells& glift_wells, + GasLiftGroupInfo& group_info, + GLiftWellStateMap& state_map, + DeferredLogger& deferred_logger); + + // cannot be const since it accesses the non-const WellState + void gasLiftOptimizationStage1SingleWell(WellInterface* well, + const Simulator& simulator, + WellState& wellState, + GroupState& groupState, + GLiftProdWells& prod_wells, + GLiftOptWells& glift_wells, + GasLiftGroupInfo& group_info, + GLiftWellStateMap& state_map, + GLiftSyncGroups& groups_to_sync, + DeferredLogger& deferred_logger); + + const PhaseUsage& phase_usage_; +}; + +} // namespace Opm + +#include "BlackoilWellModelGasLift_impl.hpp" + +#endif diff --git a/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp new file mode 100644 index 0000000000..75e924f173 --- /dev/null +++ b/opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp @@ -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 . +*/ +#define OPM_BLACKOILWELLMODEL_GASLIFT_IMPL_HEADER_INCLUDED +#define OPM_BLACKOILWELLMODEL_GASLIFT_IMPL_HEADER_INCLUDED + +// Improve IDE experience +#ifndef OPM_BLACKOILWELLMODEL_GASLIFT_HEADER_INCLUDED +#include +#include +#endif + +#include + +#if HAVE_MPI +#include +#endif + +namespace Opm { + +template +bool +BlackoilWellModelGasLift:: +maybeDoGasLiftOptimize(const Simulator& simulator, + const std::vector& well_container, + WellState& wellState, + GroupState& 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) 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*> wc; + wc.reserve(well_container.size()); + for (const auto& w : well_container) { + wc.push_back(static_cast*>(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 +void +BlackoilWellModelGasLift:: +gasLiftOptimizationStage1(const Simulator& simulator, + const std::vector& well_container, + WellState& wellState, + GroupState& groupState, + GLiftProdWells& prod_wells, + GLiftOptWells &glift_wells, + GasLiftGroupInfo& 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 group_indexes; + group_indexes.reserve(num_rates_to_sync); + std::vector group_alq_rates; + group_alq_rates.reserve(num_rates_to_sync); + std::vector group_oil_rates; + group_oil_rates.reserve(num_rates_to_sync); + std::vector group_gas_rates; + group_gas_rates.reserve(num_rates_to_sync); + std::vector 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 +void +BlackoilWellModelGasLift:: +gasLiftOptimizationStage1SingleWell(WellInterface* well, + const Simulator& simulator, + WellState& wellState, + GroupState& groupState, + GLiftProdWells& prod_wells, + GLiftOptWells& glift_wells, + GasLiftGroupInfo& group_info, + GLiftWellStateMap& state_map, + GLiftSyncGroups& sync_groups, + DeferredLogger& deferred_logger) +{ + const auto& summary_state = simulator.vanguard().summaryState(); + auto glift = std::make_unique>(*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 +void +BlackoilWellModelGasLift:: +initGliftEclWellMap(const std::vector& 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 diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.cpp b/opm/simulators/wells/BlackoilWellModelGeneric.cpp index f4e0adff8d..abc9fab1d3 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.cpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.cpp @@ -49,6 +49,8 @@ #include +#include + #include #include #include @@ -93,6 +95,8 @@ BlackoilWellModelGeneric(Schedule& schedule, , eclState_(eclState) , comm_(comm) , phase_usage_(phase_usage) + , terminal_output_(comm_.rank() == 0 && + Parameters::Get()) , wbpCalculationService_ { eclState.gridDims(), comm_ } , guideRate_(schedule) , active_wgstate_(phase_usage) @@ -1613,64 +1617,6 @@ setRepRadiusPerfLength() } } -template -void BlackoilWellModelGeneric:: -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 -void BlackoilWellModelGeneric:: -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 -void BlackoilWellModelGeneric:: -gasLiftOptimizationStage2(DeferredLogger& deferred_logger, - GLiftProdWells& prod_wells, - GLiftOptWells& glift_wells, - GasLiftGroupInfo& 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 void BlackoilWellModelGeneric:: updateWellPotentials(const int reportStepIdx, diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.hpp b/opm/simulators/wells/BlackoilWellModelGeneric.hpp index ff867ab52b..80f083c256 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.hpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.hpp @@ -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 BlackoilWellModelGeneric { public: - // --------- Types --------- - using GLiftOptWells = std::map>>; - using GLiftProdWells = std::map*>; - using GLiftWellStateMap = std::map>>; - BlackoilWellModelGeneric(Schedule& schedule, const SummaryState& summaryState, const EclipseState& eclState, @@ -242,7 +243,6 @@ class BlackoilWellModelGeneric 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 @@ class BlackoilWellModelGeneric 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 @@ class BlackoilWellModelGeneric 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& group_info, - GLiftWellStateMap& map, - const int episodeIndex); - virtual void computePotentials(const std::size_t widx, const WellState& well_state_copy, std::string& exc_msg, @@ -628,10 +615,6 @@ class BlackoilWellModelGeneric WGState last_valid_wgstate_; WGState 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 diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 31ae29eb80..d00a399169 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -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(); - 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::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,14 +1353,19 @@ 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 + // update guide rates const int reportStepIdx = simulator_.episodeIndex(); if (alq_updated || BlackoilWellModelGuideRates(*this). guideRateUpdateIsNeeded(reportStepIdx)) { @@ -1591,212 +1607,6 @@ namespace Opm { } - template - bool - BlackoilWellModel:: - 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) 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 - void - BlackoilWellModel:: - gasLiftOptimizationStage1(DeferredLogger& deferred_logger, - GLiftProdWells& prod_wells, - GLiftOptWells &glift_wells, - GasLiftGroupInfo& 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 group_indexes; - group_indexes.reserve(num_rates_to_sync); - std::vector group_alq_rates; - group_alq_rates.reserve(num_rates_to_sync); - std::vector group_oil_rates; - group_oil_rates.reserve(num_rates_to_sync); - std::vector group_gas_rates; - group_gas_rates.reserve(num_rates_to_sync); - std::vector 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; jglift_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 - void - BlackoilWellModel:: - gasLiftOptimizationStage1SingleWell(WellInterface* well, - DeferredLogger& deferred_logger, - GLiftProdWells& prod_wells, - GLiftOptWells& glift_wells, - GasLiftGroupInfo& group_info, - GLiftWellStateMap& state_map, - GLiftSyncGroups& sync_groups) - { - const auto& summary_state = simulator_.vanguard().summaryState(); - std::unique_ptr glift - = std::make_unique( - *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 - void - BlackoilWellModel:: - initGliftEclWellMap(GLiftEclWells &ecl_well_map) - { - for ( const auto& well: well_container_ ) { - ecl_well_map.try_emplace( - well->name(), &(well->wellEcl()), well->indexOfWell()); - } - } - - template void BlackoilWellModel:: diff --git a/opm/simulators/wells/GasLiftSingleWell.hpp b/opm/simulators/wells/GasLiftSingleWell.hpp index d784ea1a2a..1db81c72ed 100644 --- a/opm/simulators/wells/GasLiftSingleWell.hpp +++ b/opm/simulators/wells/GasLiftSingleWell.hpp @@ -25,12 +25,13 @@ #include #include #include -#include #include namespace Opm { +template class WellInterface; + template class GasLiftSingleWell : public GasLiftSingleWellGeneric> { diff --git a/opm/simulators/wells/GasLiftSingleWell_impl.hpp b/opm/simulators/wells/GasLiftSingleWell_impl.hpp index 92da233b78..78a9d2ff8e 100644 --- a/opm/simulators/wells/GasLiftSingleWell_impl.hpp +++ b/opm/simulators/wells/GasLiftSingleWell_impl.hpp @@ -27,11 +27,13 @@ #endif #include -#include +#include #include #include +#include + namespace Opm { template diff --git a/opm/simulators/wells/MultisegmentWell.hpp b/opm/simulators/wells/MultisegmentWell.hpp index b61b48437f..3d861f11a6 100644 --- a/opm/simulators/wells/MultisegmentWell.hpp +++ b/opm/simulators/wells/MultisegmentWell.hpp @@ -22,6 +22,8 @@ #ifndef OPM_MULTISEGMENTWELL_HEADER_INCLUDED #define OPM_MULTISEGMENTWELL_HEADER_INCLUDED +#include + #include #include diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 8f6af4e61d..d6df2b7a68 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -37,6 +37,8 @@ namespace Opm { #include +#include + #include #include #include @@ -82,12 +84,7 @@ class WellInterface : public WellInterfaceIndices; using RateVector = GetPropType; using GasLiftSingleWell = ::Opm::GasLiftSingleWell; - using GLiftOptWells = typename BlackoilWellModel::GLiftOptWells; - using GLiftProdWells = typename BlackoilWellModel::GLiftProdWells; using GLiftEclWells = typename GasLiftGroupInfo::GLiftEclWells; - using GLiftWellStateMap = - typename BlackoilWellModel::GLiftWellStateMap; - using GLiftSyncGroups = typename GasLiftSingleWellGeneric::GLiftSyncGroups; using VectorBlockType = Dune::FieldVector; using MatrixBlockType = Dune::FieldMatrix; diff --git a/tests/test_RestartSerialization.cpp b/tests/test_RestartSerialization.cpp index 85e83986af..3dd32cad33 100644 --- a/tests/test_RestartSerialization.cpp +++ b/tests/test_RestartSerialization.cpp @@ -324,7 +324,6 @@ class BlackoilWellModelGenericTest : public BlackoilWellModelGeneric active_wgstate_ = WGState::serializationTestObject(dummy); last_valid_wgstate_ = WGState::serializationTestObject(dummy); nupcol_wgstate_ = WGState::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} }}; diff --git a/tests/test_glift1.cpp b/tests/test_glift1.cpp index 62868d67be..9fc2295020 100644 --- a/tests/test_glift1.cpp +++ b/tests/test_glift1.cpp @@ -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:: + 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 {