BlackoilWellModelNetwork_impl.hpp
Go to the documentation of this file.
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>
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
41
42#include <fmt/format.h>
43
44namespace Opm {
45
46template<typename TypeTag>
49 : BaseType(well_model)
50 , well_model_(well_model)
51{}
52
53template<typename TypeTag>
54void
56doPreStepRebalance(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
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
87template<typename TypeTag>
88std::tuple<bool, typename BlackoilWellModelNetwork<TypeTag>::Scalar>
90update(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
162template <typename TypeTag>
163bool
165computeWellGroupThp(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 // A well without a VFP table cannot be put under THP
226 // control, so the common THP is not imposed on it.
227 if (well->wellEcl().vfp_table_number() > 0) {
228 well->setDynamicThpLimit(group_thp);
229 }
230 const auto inj_controls = Well::InjectionControls(0);
231 // The well equations are solved here to find the group
232 // THP, so the controls must carry the drawdown limit;
233 // callers that pass controls explicitly are required to
234 // have applied it.
235 const auto prod_controls =
236 well->productionControlsWithWeldraw(summary_state, ws);
237 well->iterateWellEqWithSwitching(well_model_.simulator(),
238 dt,
239 inj_controls,
240 prod_controls,
241 well_model_.groupStateHelper(),
242 well_state,
243 /*fixed_control=*/false,
244 /*fixed_status=*/false,
245 /*solving_with_zero_rate=*/false);
246 rate = -tcalc.calcModeRateFromRates(ws.surface_rates);
247 group_rate += rate;
248 }
249 }
250 return (group_rate - orig_target)/orig_target;
251 };
252
253 const auto upbranch = network.uptree_branch(nodeName);
254 const auto it = this->node_pressures_.find((*upbranch).uptree_node());
255 const Scalar nodal_pressure = it->second;
256 Scalar well_group_thp = nodal_pressure;
257
258 std::optional<Scalar> autochoke_thp;
259 if (auto iter = this->well_group_thp_calc_.find(nodeName);
260 iter != this->well_group_thp_calc_.end())
261 {
262 autochoke_thp = this->well_group_thp_calc_.at(nodeName);
263 }
264
265 using WellBhpThpCalculatorType = WellBhpThpCalculator<Scalar, IndexTraits>;
266 //Find an initial bracket
267 std::array<Scalar, 2> range_initial;
268 if (!autochoke_thp.has_value()){
269 Scalar min_thp, max_thp;
270 // Retrieve the terminal pressure of the associated root of the manifold group
271 std::string node_name = nodeName;
272 while (!network.node(node_name).terminal_pressure().has_value()) {
273 auto branch = network.uptree_branch(node_name).value();
274 node_name = branch.uptree_node();
275 }
276 min_thp = network.node(node_name).terminal_pressure().value();
277 WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch, min_thp, max_thp);
278 // Narrow down the bracket
279 Scalar low1, high1;
280 std::array<Scalar, 2> range = {Scalar{0.9}*min_thp, Scalar{1.1}*max_thp};
281 std::optional<Scalar> appr_sol;
282 WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch,
283 range,
284 low1,
285 high1,
286 appr_sol,
287 0.0,
288 local_deferredLogger);
289 min_thp = low1;
290 max_thp = high1;
291 range_initial = {min_thp, max_thp};
292 }
293
294 if (!autochoke_thp.has_value() || autochoke_thp.value() > nodal_pressure) {
295 // The bracket is based on the initial bracket or
296 // on a range based on a previous calculated group thp
297 std::array<Scalar, 2> range = autochoke_thp.has_value() ?
298 std::array<Scalar, 2>{Scalar{0.9} * autochoke_thp.value(),
299 Scalar{1.1} * autochoke_thp.value()} : range_initial;
300 Scalar low, high;
301 std::optional<Scalar> approximate_solution;
302 const Scalar tolerance1 = thp_tolerance;
303 local_deferredLogger.debug("Using brute force search to bracket the group THP");
304 const bool finding_bracket = WellBhpThpCalculatorType::
305 bruteForceBracketCommonTHP(mismatch,
306 range,
307 low,
308 high,
309 approximate_solution,
310 tolerance1,
311 local_deferredLogger);
312
313 if (approximate_solution.has_value()) {
314 autochoke_thp = *approximate_solution;
315 local_deferredLogger.debug("Approximate group THP value found: " +
316 std::to_string(autochoke_thp.value()));
317 } else if (finding_bracket) {
318 const Scalar tolerance2 = thp_tolerance;
319 const int max_iteration_solve = 100;
320 int iteration = 0;
321 autochoke_thp = RegulaFalsiBisection<ThrowOnError>::
322 solve(mismatch,
323 low,
324 high,
325 max_iteration_solve,
326 tolerance2,
327 iteration);
328 local_deferredLogger.debug(" bracket = [" + std::to_string(low) + ", " +
329 std::to_string(high) + "], " +
330 "iteration = " + std::to_string(iteration));
331 local_deferredLogger.debug("Group THP value = " + std::to_string(autochoke_thp.value()));
332 } else {
333 autochoke_thp.reset();
334 local_deferredLogger.debug("Group THP solve failed due to bracketing failure");
335 }
336 }
337 if (autochoke_thp.has_value()) {
338 this->well_group_thp_calc_[nodeName] = autochoke_thp.value();
339 // Note: The node pressure of the auto-choke node is set
340 // to well_group_thp in computeNetworkPressures()
341 // and must be larger or equal to the pressure of the uptree node of its branch.
342 well_group_thp = std::max(autochoke_thp.value(), nodal_pressure);
343 }
344
345 for (auto& well : well_model_) {
346 std::string well_name = well->name();
347
348 if (well->isInjector() || !well->wellEcl().predictionMode())
349 continue;
350
351 // As above: a well without a VFP table cannot be put under THP
352 // control, so the auto-choke group THP is not imposed on it.
353 if (group.hasWell(well_name)
354 && well->wellEcl().vfp_table_number() > 0) {
355 well->setDynamicThpLimit(well_group_thp);
356 }
357 const auto& ws = well_model_.wellState().well(well->indexOfWell());
358 const bool thp_is_limit = ws.production_cmode == Well::ProducerCMode::THP;
359 if (thp_is_limit) {
360 well->prepareWellBeforeAssembling(well_model_.simulator(),
361 dt,
362 well_model_.groupStateHelper(),
363 well_model_.wellState());
364 }
365 }
366
367 // Use the group THP in computeNetworkPressures().
368 const auto& current_well_group_thp = group_state.is_autochoke_group(nodeName)
369 ? group_state.well_group_thp(nodeName)
370 : 1e30;
371 if (std::abs(current_well_group_thp - well_group_thp) > balance.pressure_tolerance()) {
372 well_group_thp_updated = true;
373 group_state.update_well_group_thp(nodeName, well_group_thp);
374 }
375 }
376 }
377 return well_group_thp_updated;
378}
379
380} // namespace Opm
381
382#endif // OPM_BLACKOILWELLMODEL_NETWORK_IMPL_HEADER_INCLUDED
#define OPM_END_PARALLEL_TRY_CATCH(prefix, comm)
Catch exception and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:197
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
Class for handling the blackoil well network model.
Definition: BlackoilWellModelNetworkGeneric.hpp:50
bool computeWellGroupThp(const double dt, DeferredLogger &local_deferredLogger)
Definition: BlackoilWellModelNetwork_impl.hpp:165
std::tuple< bool, Scalar > update(const bool mandatory_network_balance, DeferredLogger &deferred_logger, const bool relax_network_tolerance=false)
Definition: BlackoilWellModelNetwork_impl.hpp:90
BlackoilWellModelNetwork(BlackoilWellModel< TypeTag > &well_model)
Definition: BlackoilWellModelNetwork_impl.hpp:48
void doPreStepRebalance(DeferredLogger &deferred_logger)
Definition: BlackoilWellModelNetwork_impl.hpp:56
Class for handling the blackoil well model.
Definition: BlackoilWellModel.hpp:101
Definition: DeferredLogger.hpp:57
void warning(const std::string &tag, const std::string &message)
void debug(const std::string &tag, const std::string &message)
Definition: TargetCalculator.hpp:39
Class for computing BHP limits.
Definition: WellBhpThpCalculator.hpp:41
@ NONE
Definition: DeferredLogger.hpp:46
Definition: blackoilbioeffectsmodules.hh:45
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)