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(true, dt, deferred_logger);
66  well_model_.assembleWellEqWithoutIteration(dt);
67  const bool converged =
68  well_model_.getWellConvergence(well_model_.B_avg(), true).converged() &&
69  !changed_well_group;
70 
71  OPM_BEGIN_PARALLEL_TRY_CATCH();
72  for (auto& well : this->well_model_) {
73  well->solveEqAndUpdateWellState(well_model_.simulator(),
74  well_model_.groupStateHelper(),
75  well_state);
76  }
77  OPM_END_PARALLEL_TRY_CATCH("BlackoilWellModelNetwork::doPreStepRebalance() failed: ",
78  well_model_.simulator().vanguard().grid().comm());
79 
80  if (!converged) {
81  deferred_logger.warning("Initial (pre-step) network balance did not converge.");
82  }
83 }
84 
85 template<typename TypeTag>
86 std::tuple<bool, typename BlackoilWellModelNetwork<TypeTag>::Scalar>
87 BlackoilWellModelNetwork<TypeTag>::
88 update(const bool mandatory_network_balance,
89  DeferredLogger& deferred_logger,
90  const bool relax_network_tolerance)
91 {
92  OPM_TIMEFUNCTION();
93  const int episodeIdx = well_model_.simulator().episodeIndex();
94  const auto& network = well_model_.schedule()[episodeIdx].network();
95  if (!well_model_.wellsActive() && !network.active()) {
96  return {false, 0.0};
97  }
98 
99  const auto& iterCtx = well_model_.simulator().problem().iterationContext();
100  const auto& comm = well_model_.simulator().vanguard().grid().comm();
101 
102  // network related
103  Scalar network_imbalance = 0.0;
104  bool more_network_update = false;
105  if (this->shouldBalance(episodeIdx, iterCtx) || mandatory_network_balance) {
106  OPM_TIMEBLOCK(BalanceNetwork);
107  const double dt = well_model_.simulator().timeStepSize();
108  // Calculate common THP for subsea manifold well group (item 3 of NODEPROP set to YES)
109  const bool well_group_thp_updated = computeWellGroupThp(dt, deferred_logger);
110  const int max_number_of_sub_iterations =
111  well_model_.param().network_max_sub_iterations_;
112  const Scalar network_pressure_update_damping_factor =
113  well_model_.param().network_pressure_update_damping_factor_;
114  const Scalar network_max_pressure_update =
115  well_model_.param().network_max_pressure_update_in_bars_ * unit::barsa;
116  bool more_network_sub_update = false;
117  for (int i = 0; i < max_number_of_sub_iterations; i++) {
118  const auto local_network_imbalance =
119  this->updatePressures(episodeIdx,
120  network_pressure_update_damping_factor,
121  network_max_pressure_update);
122  network_imbalance = comm.max(local_network_imbalance);
123  const auto& balance = well_model_.schedule()[episodeIdx].network_balance();
124  constexpr Scalar relaxation_factor = 10.0;
125  const Scalar tolerance =
126  relax_network_tolerance ? relaxation_factor * balance.pressure_tolerance()
127  : balance.pressure_tolerance();
128  more_network_sub_update = this->active() && network_imbalance > tolerance;
129  if (!more_network_sub_update) {
130  break;
131  }
132 
133  for (const auto& well : well_model_) {
134  if (well->isInjector() || !well->wellEcl().predictionMode()) {
135  continue;
136  }
137 
138  const auto it = this->node_pressures_.find(well->wellEcl().groupName());
139  if (it != this->node_pressures_.end()) {
140  well->prepareWellBeforeAssembling(well_model_.simulator(),
141  dt,
142  well_model_.groupStateHelper(),
143  well_model_.wellState());
144  }
145  }
146  well_model_.updateAndCommunicateGroupData(episodeIdx,
147  iterCtx,
148  well_model_.param().nupcol_group_rate_tolerance_,
149  /*update_wellgrouptarget*/ true);
150  }
151  more_network_update = more_network_sub_update || well_group_thp_updated;
152  }
153  return { more_network_update, network_imbalance };
154 }
155 
156 template <typename TypeTag>
157 bool
159 computeWellGroupThp(const double dt, DeferredLogger& local_deferredLogger)
160 {
161  OPM_TIMEFUNCTION();
162  const int reportStepIdx = well_model_.simulator().episodeIndex();
163  const auto& network = well_model_.schedule()[reportStepIdx].network();
164  const auto& balance = well_model_.schedule()[reportStepIdx].network_balance();
165  const Scalar thp_tolerance = balance.thp_tolerance();
166 
167  if (!network.active()) {
168  return false;
169  }
170 
171  auto& well_state = well_model_.wellState();
172  auto& group_state = well_model_.groupState();
173 
174  bool well_group_thp_updated = false;
175  for (const std::string& nodeName : network.node_names()) {
176  const bool has_choke = network.node(nodeName).as_choke();
177  if (has_choke) {
178  const auto& summary_state = well_model_.simulator().vanguard().summaryState();
179  const Group& group = well_model_.schedule().getGroup(nodeName, reportStepIdx);
180 
181  //TODO: Auto choke combined with RESV control is not supported
182  std::vector<Scalar> resv_coeff(Indices::numPhases, 1.0);
183 
184  const auto ctrl = group.productionControls(summary_state);
185  auto cmode_tmp = ctrl.cmode;
186  Scalar target_tmp{0.0};
187  bool fld_none = false;
188  if (cmode_tmp == Group::ProductionCMode::FLD || cmode_tmp == Group::ProductionCMode::NONE) {
189  fld_none = true;
190  // Target is set for an ancestor group. Target for autochoke group to be
191  // derived via group guide rates
192  const Scalar efficiencyFactor = 1.0;
193  const Group& parentGroup = well_model_.schedule().getGroup(group.parent(), reportStepIdx);
194  auto target = well_model_.groupStateHelper().
195  getAutoChokeGroupProductionTargetRate(group,
196  parentGroup,
197  resv_coeff,
198  efficiencyFactor);
199  target_tmp = target.first;
200  cmode_tmp = target.second;
201  }
203  TargetCalculatorType tcalc{well_model_.groupStateHelper(), resv_coeff, group};
204  if (!fld_none)
205  {
206  // Target is set for the autochoke group itself
207  target_tmp = well_model_.groupStateHelper().getProductionGroupTarget(group);
208  }
209 
210  const Scalar orig_target = target_tmp;
211 
212  auto mismatch = [&] (auto group_thp) {
213  Scalar group_rate(0.0);
214  Scalar rate(0.0);
215  for (auto& well : well_model_) {
216  std::string well_name = well->name();
217  auto& ws = well_state.well(well_name);
218  if (group.hasWell(well_name)) {
219  well->setDynamicThpLimit(group_thp);
220  const Well& well_ecl = well_model_.eclWells()[well->indexOfWell()];
221  const auto inj_controls = Well::InjectionControls(0);
222  const auto prod_controls = well_ecl.productionControls(summary_state);
223  well->iterateWellEqWithSwitching(well_model_.simulator(),
224  dt,
225  inj_controls,
226  prod_controls,
227  well_model_.groupStateHelper(),
228  well_state,
229  /*fixed_control=*/false,
230  /*fixed_status=*/false,
231  /*solving_with_zero_rate=*/false);
232  rate = -tcalc.calcModeRateFromRates(ws.surface_rates);
233  group_rate += rate;
234  }
235  }
236  return (group_rate - orig_target)/orig_target;
237  };
238 
239  const auto upbranch = network.uptree_branch(nodeName);
240  const auto it = this->node_pressures_.find((*upbranch).uptree_node());
241  const Scalar nodal_pressure = it->second;
242  Scalar well_group_thp = nodal_pressure;
243 
244  std::optional<Scalar> autochoke_thp;
245  if (auto iter = this->well_group_thp_calc_.find(nodeName);
246  iter != this->well_group_thp_calc_.end())
247  {
248  autochoke_thp = this->well_group_thp_calc_.at(nodeName);
249  }
250 
251  using WellBhpThpCalculatorType = WellBhpThpCalculator<Scalar, IndexTraits>;
252  //Find an initial bracket
253  std::array<Scalar, 2> range_initial;
254  if (!autochoke_thp.has_value()){
255  Scalar min_thp, max_thp;
256  // Retrieve the terminal pressure of the associated root of the manifold group
257  std::string node_name = nodeName;
258  while (!network.node(node_name).terminal_pressure().has_value()) {
259  auto branch = network.uptree_branch(node_name).value();
260  node_name = branch.uptree_node();
261  }
262  min_thp = network.node(node_name).terminal_pressure().value();
263  WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch, min_thp, max_thp);
264  // Narrow down the bracket
265  Scalar low1, high1;
266  std::array<Scalar, 2> range = {Scalar{0.9}*min_thp, Scalar{1.1}*max_thp};
267  std::optional<Scalar> appr_sol;
268  WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch,
269  range,
270  low1,
271  high1,
272  appr_sol,
273  0.0,
274  local_deferredLogger);
275  min_thp = low1;
276  max_thp = high1;
277  range_initial = {min_thp, max_thp};
278  }
279 
280  if (!autochoke_thp.has_value() || autochoke_thp.value() > nodal_pressure) {
281  // The bracket is based on the initial bracket or
282  // on a range based on a previous calculated group thp
283  std::array<Scalar, 2> range = autochoke_thp.has_value() ?
284  std::array<Scalar, 2>{Scalar{0.9} * autochoke_thp.value(),
285  Scalar{1.1} * autochoke_thp.value()} : range_initial;
286  Scalar low, high;
287  std::optional<Scalar> approximate_solution;
288  const Scalar tolerance1 = thp_tolerance;
289  local_deferredLogger.debug("Using brute force search to bracket the group THP");
290  const bool finding_bracket = WellBhpThpCalculatorType::
291  bruteForceBracketCommonTHP(mismatch,
292  range,
293  low,
294  high,
295  approximate_solution,
296  tolerance1,
297  local_deferredLogger);
298 
299  if (approximate_solution.has_value()) {
300  autochoke_thp = *approximate_solution;
301  local_deferredLogger.debug("Approximate group THP value found: " +
302  std::to_string(autochoke_thp.value()));
303  } else if (finding_bracket) {
304  const Scalar tolerance2 = thp_tolerance;
305  const int max_iteration_solve = 100;
306  int iteration = 0;
307  autochoke_thp = RegulaFalsiBisection<ThrowOnError>::
308  solve(mismatch,
309  low,
310  high,
311  max_iteration_solve,
312  tolerance2,
313  iteration);
314  local_deferredLogger.debug(" bracket = [" + std::to_string(low) + ", " +
315  std::to_string(high) + "], " +
316  "iteration = " + std::to_string(iteration));
317  local_deferredLogger.debug("Group THP value = " + std::to_string(autochoke_thp.value()));
318  } else {
319  autochoke_thp.reset();
320  local_deferredLogger.debug("Group THP solve failed due to bracketing failure");
321  }
322  }
323  if (autochoke_thp.has_value()) {
324  well_group_thp_calc_[nodeName] = autochoke_thp.value();
325  // Note: The node pressure of the auto-choke node is set
326  // to well_group_thp in computeNetworkPressures()
327  // and must be larger or equal to the pressure of the uptree node of its branch.
328  well_group_thp = std::max(autochoke_thp.value(), nodal_pressure);
329  }
330 
331  for (auto& well : well_model_) {
332  std::string well_name = well->name();
333 
334  if (well->isInjector() || !well->wellEcl().predictionMode())
335  continue;
336 
337  if (group.hasWell(well_name)) {
338  well->setDynamicThpLimit(well_group_thp);
339  }
340  const auto& ws = well_model_.wellState().well(well->indexOfWell());
341  const bool thp_is_limit = ws.production_cmode == Well::ProducerCMode::THP;
342  if (thp_is_limit) {
343  well->prepareWellBeforeAssembling(well_model_.simulator(),
344  dt,
345  well_model_.groupStateHelper(),
346  well_model_.wellState());
347  }
348  }
349 
350  // Use the group THP in computeNetworkPressures().
351  const auto& current_well_group_thp = group_state.is_autochoke_group(nodeName)
352  ? group_state.well_group_thp(nodeName)
353  : 1e30;
354  if (std::abs(current_well_group_thp - well_group_thp) > balance.pressure_tolerance()) {
355  well_group_thp_updated = true;
356  group_state.update_well_group_thp(nodeName, well_group_thp);
357  }
358  }
359  }
360  return well_group_thp_updated;
361 }
362 
363 } // namespace Opm
364 
365 #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:159
This file contains a set of helper functions used by VFPProd / VFPInj.
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