opm-simulators
BlackoilWellModelNetwork_impl.hpp
1 /*
2  Copyright 2016 - 2019 SINTEF Digital, Mathematics & Cybernetics.
3  Copyright 2016 - 2018 Equinor ASA.
4  Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
5  Copyright 2016 - 2018 Norce AS
6 
7  This file is part of the Open Porous Media project (OPM).
8 
9  OPM is free software: you can redistribute it and/or modify
10  it under the terms of the GNU General Public License as published by
11  the Free Software Foundation, either version 3 of the License, or
12  (at your option) any later version.
13 
14  OPM is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  GNU General Public License for more details.
18 
19  You should have received a copy of the GNU General Public License
20  along with OPM. If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #ifndef OPM_BLACKOILWELLMODEL_NETWORK_IMPL_HEADER_INCLUDED
24 #define OPM_BLACKOILWELLMODEL_NETWORK_IMPL_HEADER_INCLUDED
25 
26 // Improve IDE experience
27 #ifndef OPM_BLACKOILWELLMODEL_NETWORK_HEADER_INCLUDED
28 #include <config.h>
29 #include <opm/simulators/wells/BlackoilWellModelNetwork.hpp>
30 #endif
31 
32 #include <opm/common/TimingMacros.hpp>
33 #include <opm/common/utility/numeric/RootFinders.hpp>
34 
35 #include <opm/input/eclipse/Units/Units.hpp>
36 
37 #include <opm/simulators/utils/DeferredLoggingErrorHelpers.hpp>
38 #include <opm/simulators/wells/BlackoilWellModel.hpp>
39 #include <opm/simulators/wells/TargetCalculator.hpp>
40 #include <opm/simulators/wells/WellBhpThpCalculator.hpp>
41 
42 #include <fmt/format.h>
43 
44 namespace Opm {
45 
46 template<typename TypeTag>
47 BlackoilWellModelNetwork<TypeTag>::
48 BlackoilWellModelNetwork(BlackoilWellModel<TypeTag>& well_model)
49  : BaseType(well_model)
50  , well_model_(well_model)
51 {}
52 
53 template<typename TypeTag>
54 void
55 BlackoilWellModelNetwork<TypeTag>::
56 doPreStepRebalance(DeferredLogger& deferred_logger)
57 {
58  OPM_TIMEFUNCTION();
59  const double dt = well_model_.simulator().timeStepSize();
60  // TODO: should we also have the group and network backed-up
61  // here in case the solution did not get converged?
62  auto& well_state = well_model_.wellState();
63 
64  const bool changed_well_group =
65  well_model_.updateWellControlsAndNetwork(/*mandatory_network_balance=*/true,
66  dt,
67  deferred_logger);
68  well_model_.assembleWellEqWithoutIteration(dt);
69  const bool converged =
70  well_model_.getWellConvergence(well_model_.B_avg(), true).converged() &&
71  !changed_well_group;
72 
73  OPM_BEGIN_PARALLEL_TRY_CATCH();
74  for (auto& well : this->well_model_) {
75  well->solveEqAndUpdateWellState(well_model_.simulator(),
76  well_model_.groupStateHelper(),
77  well_state);
78  }
79  OPM_END_PARALLEL_TRY_CATCH("BlackoilWellModelNetwork::doPreStepRebalance() failed: ",
80  well_model_.simulator().vanguard().grid().comm());
81 
82  if (!converged) {
83  deferred_logger.warning("Initial (pre-step) network balance did not converge.");
84  }
85 }
86 
87 template<typename TypeTag>
88 std::tuple<bool, typename BlackoilWellModelNetwork<TypeTag>::Scalar>
89 BlackoilWellModelNetwork<TypeTag>::
90 update(const bool mandatory_network_balance,
91  DeferredLogger& deferred_logger,
92  const bool relax_network_tolerance)
93 {
94  OPM_TIMEFUNCTION();
95  const int episodeIdx = well_model_.simulator().episodeIndex();
96  const auto& network = well_model_.schedule()[episodeIdx].network();
97  if (!well_model_.wellsActive() && !network.active()) {
98  return {/*more_network_update=*/false, /*network_imbalance=*/0.0};
99  }
100 
101  const auto& comm = well_model_.simulator().vanguard().grid().comm();
102 
103  // network related
104  Scalar network_imbalance = 0.0;
105  bool more_network_update = false;
106  if (this->shouldBalance(episodeIdx) || mandatory_network_balance) {
107  OPM_TIMEBLOCK(BalanceNetwork);
108  const double dt = well_model_.simulator().timeStepSize();
109  // Calculate common THP for subsea manifold well group (item 3 of NODEPROP set to YES)
110  const bool well_group_thp_updated = computeWellGroupThp(dt, deferred_logger);
111  const int max_number_of_sub_iterations =
112  well_model_.param().network_max_sub_iterations_;
113  const Scalar network_pressure_update_damping_factor =
114  well_model_.param().network_pressure_update_damping_factor_;
115  const Scalar network_max_pressure_update =
116  well_model_.param().network_max_pressure_update_in_bars_ * unit::barsa;
117  bool more_network_sub_update = false;
118  for (int i = 0; i < max_number_of_sub_iterations; i++) {
119  const auto local_network_imbalance =
120  this->updatePressures(episodeIdx,
121  network_pressure_update_damping_factor,
122  network_max_pressure_update);
123  network_imbalance = comm.max(local_network_imbalance);
124  const auto& balance = well_model_.schedule()[episodeIdx].network_balance();
125  constexpr Scalar relaxation_factor = 10.0;
126  const Scalar tolerance =
127  relax_network_tolerance ? relaxation_factor * balance.pressure_tolerance()
128  : balance.pressure_tolerance();
129  more_network_sub_update = this->active() && network_imbalance > tolerance;
130 #ifdef RESERVOIR_COUPLING_ENABLED
131  if (well_model_.isReservoirCouplingMaster()) {
132  // Tight reservoir-coupling: exchange node pressures and slave rates with the
133  // slaves per inner sub-iteration. Must run before the break below so the converged
134  // sub-iteration still feeds back the slaves' rates.
135  well_model_.rescoupHelper().maybeExchangeNetworkSubIterationWithSlaves();
136  }
137 #endif
138  if (!more_network_sub_update) {
139  break;
140  }
141 
142  for (const auto& well : well_model_) {
143  if (well->isInjector() || !well->wellEcl().predictionMode()) {
144  continue;
145  }
146 
147  const auto it = this->node_pressures_.find(well->wellEcl().groupName());
148  if (it != this->node_pressures_.end()) {
149  well->prepareWellBeforeAssembling(well_model_.simulator(),
150  dt,
151  well_model_.groupStateHelper(),
152  well_model_.wellState());
153  }
154  }
155  well_model_.updateAndCommunicateGroupData(episodeIdx, /*update_wellgrouptarget*/ true);
156  }
157  more_network_update = more_network_sub_update || well_group_thp_updated;
158  }
159  return { more_network_update, network_imbalance };
160 }
161 
162 template <typename TypeTag>
163 bool
165 computeWellGroupThp(const double dt, DeferredLogger& local_deferredLogger)
166 {
167  OPM_TIMEFUNCTION();
168  const int reportStepIdx = well_model_.simulator().episodeIndex();
169  const auto& network = well_model_.schedule()[reportStepIdx].network();
170  const auto& balance = well_model_.schedule()[reportStepIdx].network_balance();
171  const Scalar thp_tolerance = balance.thp_tolerance();
172 
173  if (!network.active()) {
174  return false;
175  }
176 
177  auto& well_state = well_model_.wellState();
178  auto& group_state = well_model_.groupState();
179 
180  bool well_group_thp_updated = false;
181  for (const std::string& nodeName : network.node_names()) {
182  const bool has_choke = network.node(nodeName).as_choke();
183  if (has_choke) {
184  const auto& summary_state = well_model_.simulator().vanguard().summaryState();
185  const Group& group = well_model_.schedule().getGroup(nodeName, reportStepIdx);
186 
187  //TODO: Auto choke combined with RESV control is not supported
188  std::vector<Scalar> resv_coeff(Indices::numPhases, 1.0);
189 
190  const auto ctrl = group.productionControls(summary_state);
191  auto cmode_tmp = ctrl.cmode;
192  Scalar target_tmp{0.0};
193  bool fld_none = false;
194  if (cmode_tmp == Group::ProductionCMode::FLD || cmode_tmp == Group::ProductionCMode::NONE) {
195  fld_none = true;
196  // Target is set for an ancestor group. Target for autochoke group to be
197  // derived via group guide rates
198  const Scalar efficiencyFactor = 1.0;
199  const Group& parentGroup = well_model_.schedule().getGroup(group.parent(), reportStepIdx);
200  auto target = well_model_.groupStateHelper().
201  getAutoChokeGroupProductionTargetRate(group,
202  parentGroup,
203  resv_coeff,
204  efficiencyFactor);
205  target_tmp = target.first;
206  cmode_tmp = target.second;
207  }
209  TargetCalculatorType tcalc{well_model_.groupStateHelper(), resv_coeff, group};
210  if (!fld_none)
211  {
212  // Target is set for the autochoke group itself
213  target_tmp = well_model_.groupStateHelper().getProductionGroupTarget(group);
214  }
215 
216  const Scalar orig_target = target_tmp;
217 
218  auto mismatch = [&] (auto group_thp) {
219  Scalar group_rate(0.0);
220  Scalar rate(0.0);
221  for (auto& well : well_model_) {
222  std::string well_name = well->name();
223  auto& ws = well_state.well(well_name);
224  if (group.hasWell(well_name)) {
225  well->setDynamicThpLimit(group_thp);
226  const Well& well_ecl = well_model_.eclWells()[well->indexOfWell()];
227  const auto inj_controls = Well::InjectionControls(0);
228  const auto prod_controls = well_ecl.productionControls(summary_state);
229  well->iterateWellEqWithSwitching(well_model_.simulator(),
230  dt,
231  inj_controls,
232  prod_controls,
233  well_model_.groupStateHelper(),
234  well_state,
235  /*fixed_control=*/false,
236  /*fixed_status=*/false,
237  /*solving_with_zero_rate=*/false);
238  rate = -tcalc.calcModeRateFromRates(ws.surface_rates);
239  group_rate += rate;
240  }
241  }
242  return (group_rate - orig_target)/orig_target;
243  };
244 
245  const auto upbranch = network.uptree_branch(nodeName);
246  const auto it = this->node_pressures_.find((*upbranch).uptree_node());
247  const Scalar nodal_pressure = it->second;
248  Scalar well_group_thp = nodal_pressure;
249 
250  std::optional<Scalar> autochoke_thp;
251  if (auto iter = this->well_group_thp_calc_.find(nodeName);
252  iter != this->well_group_thp_calc_.end())
253  {
254  autochoke_thp = this->well_group_thp_calc_.at(nodeName);
255  }
256 
257  using WellBhpThpCalculatorType = WellBhpThpCalculator<Scalar, IndexTraits>;
258  //Find an initial bracket
259  std::array<Scalar, 2> range_initial;
260  if (!autochoke_thp.has_value()){
261  Scalar min_thp, max_thp;
262  // Retrieve the terminal pressure of the associated root of the manifold group
263  std::string node_name = nodeName;
264  while (!network.node(node_name).terminal_pressure().has_value()) {
265  auto branch = network.uptree_branch(node_name).value();
266  node_name = branch.uptree_node();
267  }
268  min_thp = network.node(node_name).terminal_pressure().value();
269  WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch, min_thp, max_thp);
270  // Narrow down the bracket
271  Scalar low1, high1;
272  std::array<Scalar, 2> range = {Scalar{0.9}*min_thp, Scalar{1.1}*max_thp};
273  std::optional<Scalar> appr_sol;
274  WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch,
275  range,
276  low1,
277  high1,
278  appr_sol,
279  0.0,
280  local_deferredLogger);
281  min_thp = low1;
282  max_thp = high1;
283  range_initial = {min_thp, max_thp};
284  }
285 
286  if (!autochoke_thp.has_value() || autochoke_thp.value() > nodal_pressure) {
287  // The bracket is based on the initial bracket or
288  // on a range based on a previous calculated group thp
289  std::array<Scalar, 2> range = autochoke_thp.has_value() ?
290  std::array<Scalar, 2>{Scalar{0.9} * autochoke_thp.value(),
291  Scalar{1.1} * autochoke_thp.value()} : range_initial;
292  Scalar low, high;
293  std::optional<Scalar> approximate_solution;
294  const Scalar tolerance1 = thp_tolerance;
295  local_deferredLogger.debug("Using brute force search to bracket the group THP");
296  const bool finding_bracket = WellBhpThpCalculatorType::
297  bruteForceBracketCommonTHP(mismatch,
298  range,
299  low,
300  high,
301  approximate_solution,
302  tolerance1,
303  local_deferredLogger);
304 
305  if (approximate_solution.has_value()) {
306  autochoke_thp = *approximate_solution;
307  local_deferredLogger.debug("Approximate group THP value found: " +
308  std::to_string(autochoke_thp.value()));
309  } else if (finding_bracket) {
310  const Scalar tolerance2 = thp_tolerance;
311  const int max_iteration_solve = 100;
312  int iteration = 0;
313  autochoke_thp = RegulaFalsiBisection<ThrowOnError>::
314  solve(mismatch,
315  low,
316  high,
317  max_iteration_solve,
318  tolerance2,
319  iteration);
320  local_deferredLogger.debug(" bracket = [" + std::to_string(low) + ", " +
321  std::to_string(high) + "], " +
322  "iteration = " + std::to_string(iteration));
323  local_deferredLogger.debug("Group THP value = " + std::to_string(autochoke_thp.value()));
324  } else {
325  autochoke_thp.reset();
326  local_deferredLogger.debug("Group THP solve failed due to bracketing failure");
327  }
328  }
329  if (autochoke_thp.has_value()) {
330  well_group_thp_calc_[nodeName] = autochoke_thp.value();
331  // Note: The node pressure of the auto-choke node is set
332  // to well_group_thp in computeNetworkPressures()
333  // and must be larger or equal to the pressure of the uptree node of its branch.
334  well_group_thp = std::max(autochoke_thp.value(), nodal_pressure);
335  }
336 
337  for (auto& well : well_model_) {
338  std::string well_name = well->name();
339 
340  if (well->isInjector() || !well->wellEcl().predictionMode())
341  continue;
342 
343  if (group.hasWell(well_name)) {
344  well->setDynamicThpLimit(well_group_thp);
345  }
346  const auto& ws = well_model_.wellState().well(well->indexOfWell());
347  const bool thp_is_limit = ws.production_cmode == Well::ProducerCMode::THP;
348  if (thp_is_limit) {
349  well->prepareWellBeforeAssembling(well_model_.simulator(),
350  dt,
351  well_model_.groupStateHelper(),
352  well_model_.wellState());
353  }
354  }
355 
356  // Use the group THP in computeNetworkPressures().
357  const auto& current_well_group_thp = group_state.is_autochoke_group(nodeName)
358  ? group_state.well_group_thp(nodeName)
359  : 1e30;
360  if (std::abs(current_well_group_thp - well_group_thp) > balance.pressure_tolerance()) {
361  well_group_thp_updated = true;
362  group_state.update_well_group_thp(nodeName, well_group_thp);
363  }
364  }
365  }
366  return well_group_thp_updated;
367 }
368 
369 } // namespace Opm
370 
371 #endif // OPM_BLACKOILWELLMODEL_NETWORK_IMPL_HEADER_INCLUDED
bool computeWellGroupThp(const double dt, DeferredLogger &local_deferredLogger)
This function is to be used for well groups in an extended network that act as a subsea manifold The ...
Definition: BlackoilWellModelNetwork_impl.hpp:165
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
Definition: DeferredLogger.hpp:56
Based on a group control mode, extract or calculate rates, and provide other conveniences.
Definition: TargetCalculator.hpp:38
Class for computing BHP limits.
Definition: WellBhpThpCalculator.hpp:41