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
44#include <optional>
45
46namespace Opm {
47
48template<typename TypeTag>
51 : BaseType(well_model)
52 , well_model_(well_model)
53{}
54
55template<typename TypeTag>
56void
58doPreStepRebalance(DeferredLogger& deferred_logger)
59{
60 OPM_TIMEFUNCTION();
61 const double dt = well_model_.simulator().timeStepSize();
62 // TODO: should we also have the group and network backed-up
63 // here in case the solution did not get converged?
64 auto& well_state = well_model_.wellState();
65
66 const bool changed_well_group =
67 well_model_.updateWellControlsAndNetwork(/*mandatory_network_balance=*/true,
68 dt,
69 deferred_logger);
70 well_model_.assembleWellEqWithoutIteration(dt);
71 const bool converged =
72 well_model_.getWellConvergence(well_model_.B_avg(), true).converged() &&
73 !changed_well_group;
74
76 for (auto& well : this->well_model_) {
77 well->solveEqAndUpdateWellState(well_model_.simulator(),
78 well_model_.groupStateHelper(),
79 well_state);
80 }
81 OPM_END_PARALLEL_TRY_CATCH("BlackoilWellModelNetwork::doPreStepRebalance() failed: ",
82 well_model_.simulator().vanguard().grid().comm());
83
84 if (!converged) {
85 deferred_logger.warning("Initial (pre-step) network balance did not converge.");
86 }
87}
88
89template<typename TypeTag>
90std::tuple<bool, typename BlackoilWellModelNetwork<TypeTag>::Scalar>
92update(const bool mandatory_network_balance,
93 DeferredLogger& deferred_logger,
94 const bool relax_network_tolerance)
95{
96 OPM_TIMEFUNCTION();
97 const int episodeIdx = well_model_.simulator().episodeIndex();
98 if (!well_model_.wellsActive() && !details::anyNetworkActive(well_model_.schedule(), episodeIdx)) {
99 return {/*more_network_update=*/false, /*network_imbalance=*/0.0};
100 }
101
102 const auto& comm = well_model_.simulator().vanguard().grid().comm();
103
104 // network related
105 Scalar network_imbalance = 0.0;
106 bool more_network_update = false;
107 if (this->shouldBalance(episodeIdx) || mandatory_network_balance) {
108 OPM_TIMEBLOCK(BalanceNetwork);
109 const double dt = well_model_.simulator().timeStepSize();
110 // Calculate common THP for subsea manifold well group (item 3 of NODEPROP set to YES)
111 const bool well_group_thp_updated = computeWellGroupThp(dt, deferred_logger);
112 const int max_number_of_sub_iterations =
113 well_model_.param().network_max_sub_iterations_;
114 const Scalar network_pressure_update_damping_factor =
115 well_model_.param().network_pressure_update_damping_factor_;
116 const Scalar network_max_pressure_update =
117 well_model_.param().network_max_pressure_update_in_bars_ * unit::barsa;
118 bool more_network_sub_update = false;
119 for (int i = 0; i < max_number_of_sub_iterations; i++) {
120 const auto local_network_imbalance =
121 this->updatePressures(episodeIdx,
122 network_pressure_update_damping_factor,
123 network_max_pressure_update);
124 network_imbalance = comm.max(local_network_imbalance);
125 const auto& balance = well_model_.schedule()[episodeIdx].network_balance();
126 constexpr Scalar relaxation_factor = 10.0;
127 const Scalar tolerance =
128 relax_network_tolerance ? relaxation_factor * balance.pressure_tolerance()
129 : balance.pressure_tolerance();
130 more_network_sub_update = this->active() && network_imbalance > tolerance;
131#ifdef RESERVOIR_COUPLING_ENABLED
132 if (well_model_.isReservoirCouplingMaster()) {
133 // Tight reservoir-coupling: exchange node pressures and slave rates with the
134 // slaves per inner sub-iteration. Must run before the break below so the converged
135 // sub-iteration still feeds back the slaves' rates.
136 well_model_.rescoupHelper().maybeExchangeNetworkSubIterationWithSlaves();
137 }
138#endif
139 if (!more_network_sub_update) {
140 break;
141 }
142
143 for (const auto& well : well_model_) {
144 if (!well->wellEcl().predictionMode()) {
145 continue;
146 }
147
148 const auto domain = details::domainForWell(*well);
149
150 if (!domain.has_value()) {
151 continue;
152 }
153
154 const auto it = this->nodePressures(*domain).find(well->wellEcl().groupName());
155 if (it != this->nodePressures(*domain).end()) {
156 well->prepareWellBeforeAssembling(well_model_.simulator(),
157 dt,
158 well_model_.groupStateHelper(),
159 well_model_.wellState());
160 // Option B: after re-solving at the current network THP, update
161 // ws.well_potentials for injection wells. The rate_less_than_potential
162 // check in WellConstraints::activeInjectionConstraint compares current
163 // injection rates against ws.well_potentials to decide whether switching
164 // to THP mode would increase or decrease injection. The potentials are
165 // normally computed once per timestep at the static WCONINJE THP and are
166 // stale during network iterations. Refreshing them here (from the rate
167 // the well just solved to under the current network THP) makes the check
168 // accurate for subsequent outer iterations.
169 if (well->isInjector()) {
170 auto& ws = well_model_.wellState().well(well->indexOfWell());
171 if (ws.injection_cmode == Well::InjectorCMode::THP) {
172 const int np = well_model_.numPhases();
173 for (int p = 0; p < np; ++p) {
174 ws.well_potentials[p] =
175 std::max(Scalar{0.0}, ws.surface_rates[p]);
176 }
177 }
178 }
179 }
180 }
181 well_model_.updateAndCommunicateGroupData(episodeIdx, /*update_wellgrouptarget*/ true);
182 }
183 more_network_update = more_network_sub_update || well_group_thp_updated;
184 }
185 return { more_network_update, network_imbalance };
186}
187
188template <typename TypeTag>
189bool
191computeWellGroupThp(const double dt, DeferredLogger& local_deferredLogger)
192{
193 OPM_TIMEFUNCTION();
194 const int reportStepIdx = well_model_.simulator().episodeIndex();
195 // This function is only relevant for auto-choke groups, and
196 // therefore as of now only relevant for the production network.
197 // \TODO: If we later also want to support auto-choke groups in the
198 // injection network, we should change this function also.
199 const auto& network = well_model_.schedule()[reportStepIdx].network();
200 const auto& balance = well_model_.schedule()[reportStepIdx].network_balance();
201 const Scalar thp_tolerance = balance.thp_tolerance();
202
203 if (!network.active()) {
204 return false;
205 }
206
207 auto& well_state = well_model_.wellState();
208 auto& group_state = well_model_.groupState();
209
210 bool well_group_thp_updated = false;
211 for (const std::string& nodeName : network.node_names()) {
212 const bool has_choke = network.node(nodeName).as_choke();
213 if (has_choke) {
214 const auto& summary_state = well_model_.simulator().vanguard().summaryState();
215 const Group& group = well_model_.schedule().getGroup(nodeName, reportStepIdx);
216
217 //TODO: Auto choke combined with RESV control is not supported
218 std::vector<Scalar> resv_coeff(Indices::numPhases, 1.0);
219
220 const auto ctrl = group.productionControls(summary_state);
221 auto cmode_tmp = ctrl.cmode;
222 Scalar target_tmp{0.0};
223 bool fld_none = false;
224 if (cmode_tmp == Group::ProductionCMode::FLD || cmode_tmp == Group::ProductionCMode::NONE) {
225 fld_none = true;
226 // Target is set for an ancestor group. Target for autochoke group to be
227 // derived via group guide rates
228 const Scalar efficiencyFactor = 1.0;
229 const Group& parentGroup = well_model_.schedule().getGroup(group.parent(), reportStepIdx);
230 auto target = well_model_.groupStateHelper().
231 getAutoChokeGroupProductionTargetRate(group,
232 parentGroup,
233 resv_coeff,
234 efficiencyFactor);
235 target_tmp = target.first;
236 cmode_tmp = target.second;
237 }
239 TargetCalculatorType tcalc{well_model_.groupStateHelper(), resv_coeff, group};
240 if (!fld_none)
241 {
242 // Target is set for the autochoke group itself
243 target_tmp = well_model_.groupStateHelper().getProductionGroupTarget(group);
244 }
245
246 const Scalar orig_target = target_tmp;
247
248 auto mismatch = [&] (auto group_thp) {
249 Scalar group_rate(0.0);
250 Scalar rate(0.0);
251 for (auto& well : well_model_) {
252 std::string well_name = well->name();
253 auto& ws = well_state.well(well_name);
254 if (group.hasWell(well_name)) {
255 // A well without a VFP table cannot be put under THP
256 // control, so the common THP is not imposed on it.
257 if (well->wellEcl().vfp_table_number() > 0) {
258 well->setDynamicThpLimit(group_thp);
259 }
260 const auto inj_controls = Well::InjectionControls(0);
261 // The well equations are solved here to find the group
262 // THP, so the controls must carry the drawdown limit;
263 // callers that pass controls explicitly are required to
264 // have applied it.
265 const auto prod_controls =
266 well->productionControlsWithWeldraw(summary_state, ws);
267 well->iterateWellEqWithSwitching(well_model_.simulator(),
268 dt,
269 inj_controls,
270 prod_controls,
271 well_model_.groupStateHelper(),
272 well_state,
273 /*fixed_control=*/false,
274 /*fixed_status=*/false,
275 /*solving_with_zero_rate=*/false);
276 rate = -tcalc.calcModeRateFromRates(ws.surface_rates);
277 group_rate += rate;
278 }
279 }
280 return (group_rate - orig_target)/orig_target;
281 };
282
283 const auto upbranch = network.uptree_branch(nodeName);
284 const auto it = this->node_pressures_.find((*upbranch).uptree_node());
285 const Scalar nodal_pressure = it->second;
286 Scalar well_group_thp = nodal_pressure;
287
288 std::optional<Scalar> autochoke_thp;
289 if (auto iter = this->well_group_thp_calc_.find(nodeName);
290 iter != this->well_group_thp_calc_.end())
291 {
292 autochoke_thp = this->well_group_thp_calc_.at(nodeName);
293 }
294
295 using WellBhpThpCalculatorType = WellBhpThpCalculator<Scalar, IndexTraits>;
296 //Find an initial bracket
297 std::array<Scalar, 2> range_initial;
298 if (!autochoke_thp.has_value()){
299 Scalar min_thp, max_thp;
300 // Retrieve the terminal pressure of the associated root of the manifold group
301 std::string node_name = nodeName;
302 while (!network.node(node_name).terminal_pressure().has_value()) {
303 auto branch = network.uptree_branch(node_name).value();
304 node_name = branch.uptree_node();
305 }
306 min_thp = network.node(node_name).terminal_pressure().value();
307 WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch, min_thp, max_thp);
308 // Narrow down the bracket
309 Scalar low1, high1;
310 std::array<Scalar, 2> range = {Scalar{0.9}*min_thp, Scalar{1.1}*max_thp};
311 std::optional<Scalar> appr_sol;
312 WellBhpThpCalculatorType::bruteForceBracketCommonTHP(mismatch,
313 range,
314 low1,
315 high1,
316 appr_sol,
317 0.0,
318 local_deferredLogger);
319 min_thp = low1;
320 max_thp = high1;
321 range_initial = {min_thp, max_thp};
322 }
323
324 if (!autochoke_thp.has_value() || autochoke_thp.value() > nodal_pressure) {
325 // The bracket is based on the initial bracket or
326 // on a range based on a previous calculated group thp
327 std::array<Scalar, 2> range = autochoke_thp.has_value() ?
328 std::array<Scalar, 2>{Scalar{0.9} * autochoke_thp.value(),
329 Scalar{1.1} * autochoke_thp.value()} : range_initial;
330 Scalar low, high;
331 std::optional<Scalar> approximate_solution;
332 const Scalar tolerance1 = thp_tolerance;
333 local_deferredLogger.debug("Using brute force search to bracket the group THP");
334 const bool finding_bracket = WellBhpThpCalculatorType::
335 bruteForceBracketCommonTHP(mismatch,
336 range,
337 low,
338 high,
339 approximate_solution,
340 tolerance1,
341 local_deferredLogger);
342
343 if (approximate_solution.has_value()) {
344 autochoke_thp = *approximate_solution;
345 local_deferredLogger.debug("Approximate group THP value found: " +
346 std::to_string(autochoke_thp.value()));
347 } else if (finding_bracket) {
348 const Scalar tolerance2 = thp_tolerance;
349 const int max_iteration_solve = 100;
350 int iteration = 0;
351 autochoke_thp = RegulaFalsiBisection<ThrowOnError>::
352 solve(mismatch,
353 low,
354 high,
355 max_iteration_solve,
356 tolerance2,
357 iteration);
358 local_deferredLogger.debug(" bracket = [" + std::to_string(low) + ", " +
359 std::to_string(high) + "], " +
360 "iteration = " + std::to_string(iteration));
361 local_deferredLogger.debug("Group THP value = " + std::to_string(autochoke_thp.value()));
362 } else {
363 autochoke_thp.reset();
364 local_deferredLogger.debug("Group THP solve failed due to bracketing failure");
365 }
366 }
367 if (autochoke_thp.has_value()) {
368 this->well_group_thp_calc_[nodeName] = autochoke_thp.value();
369 // Note: The node pressure of the auto-choke node is set
370 // to well_group_thp in computeNetworkPressures()
371 // and must be larger or equal to the pressure of the uptree node of its branch.
372 well_group_thp = std::max(autochoke_thp.value(), nodal_pressure);
373 }
374
375 for (auto& well : well_model_) {
376 std::string well_name = well->name();
377
378 if (well->isInjector() || !well->wellEcl().predictionMode())
379 continue;
380
381 // As above: a well without a VFP table cannot be put under THP
382 // control, so the auto-choke group THP is not imposed on it.
383 if (group.hasWell(well_name)
384 && well->wellEcl().vfp_table_number() > 0) {
385 well->setDynamicThpLimit(well_group_thp);
386 }
387 const auto& ws = well_model_.wellState().well(well->indexOfWell());
388 const bool thp_is_limit = ws.production_cmode == Well::ProducerCMode::THP;
389 if (thp_is_limit) {
390 well->prepareWellBeforeAssembling(well_model_.simulator(),
391 dt,
392 well_model_.groupStateHelper(),
393 well_model_.wellState());
394 }
395 }
396
397 // Use the group THP in computeNetworkPressures().
398 const auto& current_well_group_thp = group_state.is_autochoke_group(nodeName)
399 ? group_state.well_group_thp(nodeName)
400 : 1e30;
401 if (std::abs(current_well_group_thp - well_group_thp) > balance.pressure_tolerance()) {
402 well_group_thp_updated = true;
403 group_state.update_well_group_thp(nodeName, well_group_thp);
404 }
405 }
406 }
407 return well_group_thp_updated;
408}
409
410} // namespace Opm
411
412#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:107
bool computeWellGroupThp(const double dt, DeferredLogger &local_deferredLogger)
Definition: BlackoilWellModelNetwork_impl.hpp:191
std::tuple< bool, Scalar > update(const bool mandatory_network_balance, DeferredLogger &deferred_logger, const bool relax_network_tolerance=false)
Definition: BlackoilWellModelNetwork_impl.hpp:92
BlackoilWellModelNetwork(BlackoilWellModel< TypeTag > &well_model)
Definition: BlackoilWellModelNetwork_impl.hpp:50
void doPreStepRebalance(DeferredLogger &deferred_logger)
Definition: BlackoilWellModelNetwork_impl.hpp:58
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
std::optional< NetworkDomain > domainForWell(const Well &well)
Definition: BlackoilWellModelNetworkGeneric.hpp:73
bool anyNetworkActive(const Schedule &schedule, const int timeStepIdx)
Helper to check if any network (production, gas injection, water injection) is active at a given time...
Definition: blackoilbioeffectsmodules.hh:45
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)