opm-simulators
GasLiftSingleWell_impl.hpp
1 /*
2  Copyright 2020 Equinor ASA.
3 
4  This file is part of the Open Porous Media project (OPM).
5 
6  OPM is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  OPM is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OPM. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #ifndef OPM_GASLIFT_SINGLE_WELL_IMPL_HEADER_INCLUDED
21 #define OPM_GASLIFT_SINGLE_WELL_IMPL_HEADER_INCLUDED
22 
23 // Improve IDE experience
24 #ifndef OPM_GASLIFT_SINGLE_WELL_HEADER_INCLUDED
25 #include <config.h>
26 #include <opm/simulators/wells/GasLiftSingleWell.hpp>
27 #endif
28 
29 #include <opm/common/TimingMacros.hpp>
30 
31 #include <opm/input/eclipse/Schedule/GasLiftOpt.hpp>
32 #include <opm/input/eclipse/Schedule/Well/Well.hpp>
33 
34 #include <string>
35 #include <vector>
36 
37 #include <fmt/format.h>
38 
39 namespace Opm {
40 
41 template<typename TypeTag>
42 GasLiftSingleWell<TypeTag>::
43 GasLiftSingleWell(WellInterface<TypeTag>& well,
44  const Simulator& simulator,
45  const SummaryState& summary_state,
46  DeferredLogger& deferred_logger,
47  WellState<Scalar, IndexTraits>& well_state,
48  const GroupState<Scalar>& group_state,
49  GasLiftGroupInfo<Scalar, IndexTraits>& group_info,
50  GLiftSyncGroups &sync_groups,
51  const Parallel::Communication& comm,
52  bool glift_debug)
53  // The parent class GasLiftSingleWellGeneric contains all stuff
54  // that is not dependent on TypeTag
55  : GasLiftSingleWellGeneric<Scalar, IndexTraits>(deferred_logger,
56  well_state,
57  group_state,
58  well.wellEcl(),
59  summary_state,
60  group_info,
61  simulator.vanguard().schedule(),
62  simulator.episodeIndex(),
63  sync_groups,
64  comm,
65  glift_debug)
66  , simulator_{simulator}
67  , well_{well}
68 {
69  const auto& gl_well = *this->gl_well_;
70  if (this->useFixedAlq_(gl_well)) {
71  this->updateWellStateAlqFixedValue_(gl_well);
72  this->optimize_ = false; // lift gas supply is fixed
73  }
74  else {
75  setAlqMaxRate_(gl_well);
76  this->optimize_ = true;
77  }
78 
79  setupPhaseVariables_();
80  // get the alq value used for this well for the previous iteration (a
81  // nonlinear iteration in assemble() in BlackoilWellModel).
82  // If gas lift optimization has not been applied to this well yet, the
83  // default value is used.
84  this->orig_alq_ = this->well_state_.well(this->well_name_).alq_state.get();
85  if (this->optimize_) {
86  this->setAlqMinRate_(gl_well);
87  // NOTE: According to item 4 in WLIFTOPT, this value does not
88  // have to be positive.
89  // TODO: Does it make sense to have a negative value?
90  this->alpha_w_ = gl_well.weight_factor();
91  if (this->alpha_w_ <= 0 ) {
92  this->displayWarning_("Nonpositive value for alpha_w ignored");
93  this->alpha_w_ = 1.0;
94  }
95 
96  // NOTE: According to item 6 in WLIFTOPT:
97  // "If this value is greater than zero, the incremental gas rate will influence
98  // the calculation of the incremental gradient and may be used
99  // to discourage the allocation of lift gas to wells which produce more gas."
100  // TODO: Does this mean that we should ignore this value if it
101  // is negative?
102  this->alpha_g_ = gl_well.inc_weight_factor();
103 
104  // TODO: adhoc value.. Should we keep max_iterations_ as a safety measure
105  // or does it not make sense to have it?
106  this->max_iterations_ = 1000;
107  }
108 }
109 
110 /****************************************
111  * Private methods in alphabetical order
112  ****************************************/
113 
114 template<typename TypeTag>
115 typename GasLiftSingleWell<TypeTag>::RatesAndBhp
116 GasLiftSingleWell<TypeTag>::
117 computeWellRates_(Scalar bhp, bool bhp_is_limited, bool debug_output ) const
118 {
119  std::vector<Scalar> potentials(this->NUM_PHASES, 0.0);
120  this->well_.computeWellRatesWithBhp(this->simulator_,
121  bhp,
122  potentials,
123  this->deferred_logger_);
124  if (debug_output) {
125  const std::string msg = fmt::format("computed well potentials given bhp {}, "
126  "oil: {}, gas: {}, water: {}", bhp,
127  -potentials[this->oil_pos_], -potentials[this->gas_pos_],
128  -potentials[this->water_pos_]);
129  this->displayDebugMessage_(msg);
130  }
131 
132  std::ranges::transform(potentials, potentials.begin(),
133  [](const auto& potential)
134  { return std::min(Scalar{0}, potential); });
135  return {-potentials[this->oil_pos_],
136  -potentials[this->gas_pos_],
137  -potentials[this->water_pos_],
138  bhp,
139  bhp_is_limited
140  };
141 }
142 
143 template<typename TypeTag>
144 std::optional<typename GasLiftSingleWell<TypeTag>::Scalar>
145 GasLiftSingleWell<TypeTag>::
146 computeBhpAtThpLimit_(Scalar alq, Scalar current_bhp, bool debug_output) const
147 {
148  OPM_TIMEFUNCTION();
149  // we compute new bhp value based on the alq rate by finding the intersection
150  // between the vfp curve and the IPR. The IPR is computed using the current bhp
151  // value
152  auto bhp_at_thp_limit = this->well_.computeBhpAtThpLimitProdWithAlqUsingIPR(
153  this->simulator_,
154  this->well_state_,
155  current_bhp,
156  this->summary_state_,
157  alq);
158  if (bhp_at_thp_limit) {
159  if (*bhp_at_thp_limit < this->controls_.bhp_limit) {
160  if (debug_output && this->debug) {
161  const std::string msg = fmt::format(
162  "Computed bhp ({}) from thp limit is below bhp limit ({}), (ALQ = {})."
163  " Using bhp limit instead",
164  *bhp_at_thp_limit, this->controls_.bhp_limit, alq
165  );
166  this->displayDebugMessage_(msg);
167  }
168  bhp_at_thp_limit = this->controls_.bhp_limit;
169  }
170  //bhp_at_thp_limit = std::max(*bhp_at_thp_limit, this->controls_.bhp_limit);
171  }
172  else {
173  const std::string msg = fmt::format(
174  "Failed in getting converged bhp potential from thp limit (ALQ = {})", alq);
175  this->displayDebugMessage_(msg);
176  }
177  return bhp_at_thp_limit;
178 }
179 
180 template<typename TypeTag>
181 void
182 GasLiftSingleWell<TypeTag>::
183 setupPhaseVariables_()
184 {
185 #ifndef NDEBUG
186  bool num_phases_ok = (FluidSystem::numActivePhases()== 3);
187 #endif
188  if (FluidSystem::numActivePhases()== 2) {
189  // NOTE: We support two-phase oil-water flow, by setting the gas flow rate
190  // to zero. This is done by initializing the potential vector to zero:
191  //
192  // std::vector<double> potentials(NUM_PHASES, 0.0);
193  //
194  // see e.g. runOptimizeLoop_() in GasLiftSingleWellGeneric.cpp
195  // In addition the VFP calculations, e.g. to calculate BHP from THP
196  // has been adapted to the two-phase oil-water case, see the comment
197  // in WellInterfaceGeneric.cpp for the method adaptRatesForVFP() for
198  // more information.
199  if ( FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)
200  && FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)
201  && !FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) )
202  {
203 #ifndef NDEBUG
204  num_phases_ok = true; // two-phase oil-water is also supported
205 #endif
206  }
207  else {
208  throw std::logic_error("Two-phase gas lift optimization only supported"
209  " for oil and water");
210  }
211  }
212  assert(num_phases_ok);
213  this->oil_pos_ = FluidSystem::canonicalToActivePhaseIdx(FluidSystem::oilPhaseIdx);
214  this->gas_pos_ = FluidSystem::canonicalToActivePhaseIdx(FluidSystem::gasPhaseIdx);
215  this->water_pos_ = FluidSystem::canonicalToActivePhaseIdx(FluidSystem::waterPhaseIdx);
216 }
217 
218 template<typename TypeTag>
219 void
220 GasLiftSingleWell<TypeTag>::
221 setAlqMaxRate_(const GasLiftWell& well)
222 {
223  const auto& max_alq_optional = well.max_rate();
224  if (max_alq_optional) {
225  // NOTE: To prevent extrapolation of the VFP tables, any value
226  // entered here must not exceed the largest ALQ value in the well's VFP table.
227  this->max_alq_ = *max_alq_optional;
228  }
229  else { // i.e. WLIFTOPT, item 3 has been defaulted
230  // According to the manual for WLIFTOPT, item 3:
231  // The default value should be set to the largest ALQ
232  // value in the well's VFP table
233  const auto& table = well_.vfpProperties()->getProd()->getTable(
234  this->controls_.vfp_table_number);
235  const auto& alq_values = table.getALQAxis();
236  // Assume the alq_values are sorted in ascending order, so
237  // the last item should be the largest value:
238  this->max_alq_ = alq_values.back();
239  }
240 }
241 
242 template<typename TypeTag>
243 bool
244 GasLiftSingleWell<TypeTag>::
245 checkThpControl_() const
246 {
247  const int well_index = this->well_state_.index(this->well_name_).value();
248  const Well::ProducerCMode& control_mode =
249  this->well_state_.well(well_index).production_cmode;
250  bool thp_control = control_mode == Well::ProducerCMode::THP;
251  const auto& well = getWell();
252  thp_control = thp_control || well.thpLimitViolatedButNotSwitched();
253  if (this->debug) {
254  if (!thp_control) {
255  this->displayDebugMessage_("Well is not under THP control, skipping iteration..");
256  }
257  }
258  return thp_control;
259 }
260 
261 }
262 
263 #endif
This file contains a set of helper functions used by VFPProd / VFPInj.
Definition: blackoilbioeffectsmodules.hh:45