BlackoilWellModelRescoup_impl.hpp
Go to the documentation of this file.
1/*
2 Copyright 2025 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_BLACKOILWELLMODEL_RESCOUP_IMPL_HEADER_INCLUDED
21#define OPM_BLACKOILWELLMODEL_RESCOUP_IMPL_HEADER_INCLUDED
22
23// Improve IDE experience
24#ifndef OPM_BLACKOILWELLMODEL_RESCOUP_HEADER_INCLUDED
25#include <config.h>
27#endif
28
29#ifdef RESERVOIR_COUPLING_ENABLED
30
31#include <opm/common/TimingMacros.hpp>
32
33#include <opm/input/eclipse/EclipseState/Grid/RegionSetMatcher.hpp>
34#include <opm/input/eclipse/Schedule/ResCoup/ReservoirCouplingInfo.hpp>
35#include <opm/input/eclipse/Schedule/Schedule.hpp>
36#include <opm/input/eclipse/Schedule/SummaryState.hpp>
37#include <opm/input/eclipse/Schedule/UDQ/UDQConfig.hpp>
38#include <opm/input/eclipse/Schedule/UDQ/UDQDefine.hpp>
39#include <opm/input/eclipse/Schedule/UDQ/UDQEnums.hpp>
40#include <opm/input/eclipse/Schedule/Well/WellMatcher.hpp>
41#include <opm/input/eclipse/Units/UnitSystem.hpp>
42
49
50#include <algorithm>
51#include <array>
52#include <cassert>
53#include <cstddef>
54#include <memory>
55#include <optional>
56#include <string>
57#include <tuple>
58
59namespace Opm {
60
61// Constructor
62// -----------
63template<typename TypeTag>
64BlackoilWellModelRescoup<TypeTag>::
65BlackoilWellModelRescoup(BlackoilWellModel<TypeTag>& well_model)
66 : well_model_{well_model}
67 , network_{well_model.network()}
68 , simulator_{well_model.simulator()}
69 , param_{well_model.param()}
70{}
71
72// Public methods alphabetically
73// ------------------------------
74
75template<typename TypeTag>
76void
77BlackoilWellModelRescoup<TypeTag>::
78evalGroupAndFieldUDQs()
79{
80 const int reportStepIdx = this->groupStateHelper().reportStepIdx();
81 auto& deferred_logger = this->groupStateHelper().deferredLogger();
82 // UDQs are normally evaluated at the end of a time step. A slave UDQ
83 // that depends on a target imposed by the master would then lag one
84 // step behind, so re-evaluate the group and field level UDQs now that
85 // this step's targets have arrived. Well and segment level UDQs are
86 // left alone: their inputs are not available yet. So is any
87 // "UPDATE NEXT" DEFINE: it is a one-shot that the ordinary end-of-step
88 // evaluation is meant to consume, and this extra evaluation must not
89 // use it up early.
90 const auto select = [](const UDQDefine& def) {
91 const auto var_type = def.var_type();
92 return ((var_type == UDQVarType::GROUP_VAR) || (var_type == UDQVarType::FIELD_VAR))
93 && (def.status().first != UDQUpdate::NEXT);
94 };
95 const auto& schedule = this->schedule();
97 {
98 schedule[reportStepIdx].udq().eval(
99 reportStepIdx,
100 schedule.wellMatcher(reportStepIdx),
101 schedule[reportStepIdx].group_order(),
102 schedule.segmentMatcherFactory(reportStepIdx),
103 [es = std::cref(this->simulator_.vanguard().eclState())]() {
104 return std::make_unique<RegionSetMatcher>(es.get().fipRegionStatistics());
105 },
106 this->simulator_.vanguard().summaryState(),
107 this->simulator_.vanguard().udqState(),
108 select);
109 }
110 OPM_END_PARALLEL_TRY_CATCH_LOG(deferred_logger,
111 "Failed to evaluate UDQs for reservoir coupling slave: ",
112 this->well_model_.terminalOutput(),
113 this->simulator_.vanguard().grid().comm())
114}
115
116template<typename TypeTag>
117bool
118BlackoilWellModelRescoup<TypeTag>::
119masterIsInCoupledNetworkIteration() const
120{
121 return this->isReservoirCouplingMaster()
122 && this->reservoirCouplingMaster().isFirstSubstepOfSyncTimestep()
123 && this->masterNetworkHasMasterGroupLeaves()
124 && !this->lastSentMasterGroupNodePressuresIsFinal();
125}
126
127
128template<typename TypeTag>
129bool
130BlackoilWellModelRescoup<TypeTag>::
131masterNetworkHasMasterGroupLeaves() const
132{
133 // Query the parsed Schedule topology (`network.has_node(...)`) rather than the runtime
134 // `node_pressures_` map. The runtime map is empty on the very first beginTimeStep call
135 // (network_.update has not yet run for this substep).
136 if (!this->isReservoirCouplingMaster()) return false;
137 const auto& rcm = this->reservoirCouplingMaster();
138 const auto num_slaves = rcm.numSlaves();
139 for (std::size_t s = 0; s < num_slaves; ++s) {
140 if (!rcm.slaveIsCoupled(s)) continue;
141 if (this->masterNetworkHasMasterGroupLeavesForSlave_(s)) {
142 return true;
143 }
144 }
145 return false;
146}
147
148template<typename TypeTag>
149void
150BlackoilWellModelRescoup<TypeTag>::
151maybeExchangeNetworkOuterIterationWithSlaves(bool more_network_update)
152{
153 if (!this->masterIsInCoupledNetworkIteration()) {
154 return;
155 }
156 const bool is_final = !more_network_update;
157 // In tight coupling the inner sub-loop (maybeExchangeNetworkSubIterationWithSlaves)
158 // performs every non-final exchange, so here we only emit the single
159 // terminating (is_final) message -- the non-final case would just resend the
160 // same node pressures the last sub-iteration already shipped, and get the same
161 // rates back. The retained final send still covers the case where the network
162 // is not balanced this iteration (the inner loop never runs). In loose coupling
163 // this per-outer send is the sole master<->slave coupling and runs every outer
164 // iteration.
165 if (is_final || !this->well_model_.useTightRcNetworkCoupling()) {
166 // send the pressures freshly computed by network_.update() to all activated slaves
167 this->sendMasterGroupNodePressuresToSlaves(is_final);
168 if (!is_final) {
169 // receive slaves' updated network_surface_rates for the next outer iteration.
170 this->receiveSlaveGroupData();
171 this->refreshAndSendInjectionTargets_();
172 }
173 }
174}
175
176template<typename TypeTag>
177void
178BlackoilWellModelRescoup<TypeTag>::
179maybeExchangeNetworkSubIterationWithSlaves()
180{
181 // Called for rescoup master for the tight master-slave coupling (--rc-network-loose-coupling=false)
182 // mode at the sub-iteration level: Send node pressures and receive slave rates per master inner
183 // network sub-iteration.
184 // In the loose mode (--rc-network-loose-coupling=true) the exchange happens only once
185 // per master outer iteration (see updateWellControlsAndNetworkIteration), so
186 // the inner loop converges the node pressures against a frozen slave rate.
187 // This loose mode is faster but has been observed in some cases to overshoot to a value that
188 // incorrectly shuts the slave wells in.
189 // To avoid this, the tight coupling is therefore the default coupling.
190 //
191 // NOTE: This is always a non-final exchange. The inner sub-iteration loop cannot
192 // know whether the master's outer network loop will iterate again (the inner
193 // loop ending on max_sub_iter, or on a THP update, still leaves
194 // more_inner_network_update true), so it must not declare termination here.
195 // The single is_final = true send is owned by the per-outer send in
196 // updateWellControlsAndNetworkIteration() (when the outer loop converges)
197 // and by sendSlaveNetworkLoopTerminationSignal_() (when the outer loop hits
198 // max_iter).
199 if (!this->well_model_.useTightRcNetworkCoupling()) {
200 return;
201 }
202 if (!this->masterIsInCoupledNetworkIteration()) {
203 return;
204 }
205 this->sendMasterGroupNodePressuresToSlaves(/*is_final=*/false);
206 this->receiveSlaveGroupData();
207 this->refreshAndSendInjectionTargets_();
208}
209
210template<typename TypeTag>
211void
212BlackoilWellModelRescoup<TypeTag>::
213receiveCoupledNetworkActiveStatus()
214{
215 OPM_TIMEFUNCTION();
216 assert(this->isReservoirCouplingSlave());
217 this->reservoirCouplingSlave().receiveCoupledNetworkActiveStatusFromMaster();
218}
219
220template<typename TypeTag>
221void
222BlackoilWellModelRescoup<TypeTag>::
223receiveGroupConstraintsFromMaster()
224{
225 OPM_TIMEFUNCTION();
226 RescoupReceiveGroupConstraints<Scalar, IndexTraits> constraint_receiver{
227 this->well_model_.guideRateHandler(),
228 this->groupStateHelper()
229 };
230 constraint_receiver.receiveGroupConstraintsFromMaster();
231}
232
233template<typename TypeTag>
234void
235BlackoilWellModelRescoup<TypeTag>::
236receiveMasterGroupNodePressuresFromMaster()
237{
238 OPM_TIMEFUNCTION();
239 assert(this->isReservoirCouplingSlave());
240 auto& rescoup_slave = this->reservoirCouplingSlave();
241 const auto [num_pressures, _is_final] =
242 rescoup_slave.receiveNumMasterGroupNodePressuresFromMaster();
243 if (num_pressures > 0) {
244 rescoup_slave.receiveMasterGroupNodePressuresFromMaster(num_pressures);
245 }
246 // Apply pressures as dynamic THP limits on every producer whose group
247 // has a master-supplied pressure *and* whose group the slave's own deck
248 // placed in its own surface network as a fixed-pressure node. Wells in
249 // master groups that are not network leaves are not touched either (no
250 // entry in the map). Mirrors the standard local-network apply pattern in
251 // BlackoilWellModelNetworkGeneric::updatePressures(): when the well is
252 // currently THP-controlled, also write the new THP into the WellState
253 // directly, because setDynamicThpLimit() alone leaves the active
254 // control's THP value stale and the subsequent well-solve would
255 // converge against the old THP.
256 //
257 // The node-pressure exchange is a boundary condition between two
258 // networks: the master's network ends at the master groups, and the
259 // pressure it computes there is the fixed pressure of the corresponding
260 // slave group's node in the *slave's* network. A slave that declares no
261 // network has no such node, and its wells keep the THP limits from their
262 // own WCONPROD records -- so the messages are still received (the
263 // protocol is unchanged) but nothing is imposed.
264 //
265 // TODO (follow-up PR): when the slave group is a fixed-pressure node with
266 // branches below it, the pressure belongs at the top of the slave's own
267 // network solve rather than directly on the wells; the slave's branches
268 // and their pressure losses then decide the wells' THP limits. The
269 // plumbing exists -- the solver already reads
270 // Network::Node::terminal_pressure() when walking up branches -- but
271 // surfacing the master-sent value into the solver needs a runtime
272 // override path (e.g. a fixed-pressure override map on
273 // BlackoilWellModelNetwork) so we do not mutate the parsed Schedule
274 // network at runtime. Until then the direct write below is exact only
275 // for a slave group that is itself a well group, where there is no
276 // branch between the node and the wells.
277 const auto& pressures = rescoup_slave.masterGroupNodePressures();
278 if (pressures.empty()) return;
279 const auto& summary_state = this->well_model_.summaryState();
280 auto& well_state = this->wellState();
281 for (auto& well : this->wellContainer()) {
282 if (!well->isProducer() || !well->wellEcl().predictionMode()) continue;
283 const auto& group_name = well->wellEcl().groupName();
284 const auto it = pressures.find(group_name);
285 if (it == pressures.end()) continue;
286 if (!this->slaveGroupIsFixedPressureNodeInOwnNetwork_(group_name)) continue;
287 well->setDynamicThpLimit(it->second);
288 auto& ws = well_state[well->indexOfWell()];
289 if (ws.production_cmode == Well::ProducerCMode::THP) {
290 ws.thp = well->getTHPConstraint(summary_state);
291 }
292 }
293}
294
295template<typename TypeTag>
296void
297BlackoilWellModelRescoup<TypeTag>::
298receiveSlaveGroupData()
299{
300 OPM_TIMEFUNCTION();
301 assert(this->isReservoirCouplingMaster());
302 RescoupReceiveSlaveGroupData<Scalar, IndexTraits> slave_group_data_receiver{
303 this->groupStateHelper(),
304 };
305 slave_group_data_receiver.receiveSlaveGroupData();
306}
307
308template<typename TypeTag>
309void
310BlackoilWellModelRescoup<TypeTag>::
311refreshSlaveGroupInjectionTargets()
312{
313 // The deck's own GCONINJE limit may be a UDA, resolved from the summary
314 // state, and the UDQ evaluation below may change it. So prime, evaluate
315 // the selected UDQs, and work the targets in force out once more with
316 // the UDQs current, as the well control path will see them. The second
317 // pass repeats the loop over the slave groups, not the UDQ evaluation.
318 this->storeSlaveGroupInjectionTargets();
319 this->evalGroupAndFieldUDQs();
320 this->storeSlaveGroupInjectionTargets();
321}
322
323template<typename TypeTag>
324void
325BlackoilWellModelRescoup<TypeTag>::
326rescoupSyncSummaryData()
327{
328 // Reservoir coupling: exchange production data between slaves and master.
329 //
330 // Master side: after its first substep, the master blocks here until all
331 // slaves have completed the sync step and sent their production data.
332 // This ensures evalSummaryState() (called next in endTimeStep) and all
333 // subsequent master substeps have correct slave production rates.
334 //
335 // Slave side: on the last substep of the sync step, the slave sends its
336 // production data to the master. The master is already waiting at this
337 // point (blocked on MPI_Recv from its first substep's timeStepSucceeded).
338 if (this->isReservoirCouplingMaster()) {
339 if (this->reservoirCouplingMaster().needsSlaveDataReceive()) {
340 this->receiveSlaveGroupData();
341 this->reservoirCouplingMaster().setNeedsSlaveDataReceive(false);
342 }
343 }
344 if (this->isReservoirCouplingSlave()) {
345 if (this->reservoirCouplingSlave().isLastSubstepOfSyncTimestep()) {
346 this->sendSlaveGroupDataToMaster();
347 }
348 }
349}
350
351template<typename TypeTag>
352void
353BlackoilWellModelRescoup<TypeTag>::
354sendCoupledNetworkActiveStatus()
355{
356 OPM_TIMEFUNCTION();
357 assert(this->isReservoirCouplingMaster());
358 // Send to each activated slave a single bool: "are you connected to the
359 // master's cross-rescoup network this sync timestep?", i.e. does this
360 // slave have a master group that is a leaf in the master network. This
361 // is per-slave: a slave with no leaf in the master network does not
362 // participate even if other slaves do. Sent as a dedicated one-element
363 // MPI message; the per-iteration node-pressure sends inside
364 // updateWellControlsAndNetworkIteration() carry their own is_final flag
365 // in the NumMasterGroupNodePressures header.
366 auto& rescoup_master = this->reservoirCouplingMaster();
367 const auto num_slaves = rescoup_master.numSlaves();
368 bool any_connected = false;
369 for (std::size_t slave_idx = 0; slave_idx < num_slaves; ++slave_idx) {
370 if (rescoup_master.slaveIsCoupled(slave_idx)) {
371 const bool connected =
372 this->masterNetworkHasMasterGroupLeavesForSlave_(slave_idx);
373 any_connected = any_connected || connected;
374 rescoup_master.sendCoupledNetworkActiveStatusToSlave(slave_idx, connected);
375 }
376 }
377 // Mirror the *global* active state into the master's own is_final flag:
378 // the master iterates the exchange iff at least one slave is connected.
379 // (This must stay global -- a per-slave value would break the master's
380 // own updateWellControlsAndNetworkIteration() gate.)
381 this->last_sent_master_group_node_pressures_is_final_ = !any_connected;
382}
383
384template<typename TypeTag>
385void
386BlackoilWellModelRescoup<TypeTag>::
387sendMasterGroupConstraintsToSlaves()
388{
389 OPM_TIMEFUNCTION();
390 // This function is called by the master process to send the group
391 // constraints to the slaves. The "will the master iterate cross-rescoup?"
392 // flag is shipped separately by sendCoupledNetworkActiveStatus().
393 RescoupConstraintsCalculator<Scalar, IndexTraits> constraints_calculator{
394 this->well_model_.guideRateHandler(),
395 this->groupStateHelper()
396 };
397 constraints_calculator.calculateMasterGroupConstraintsAndSendToSlaves();
398}
399
400template<typename TypeTag>
401void
402BlackoilWellModelRescoup<TypeTag>::
403sendMasterGroupNodePressuresToSlaves(bool is_final)
404{
405 OPM_TIMEFUNCTION();
406 assert(this->isReservoirCouplingMaster());
407 auto& rescoup_master = this->reservoirCouplingMaster();
408 const auto& node_pressures = this->network_.nodePressures();
409 const auto num_slaves = rescoup_master.numSlaves();
410 for (std::size_t slave_idx = 0; slave_idx < num_slaves; ++slave_idx) {
411 if (!rescoup_master.slaveIsCoupled(slave_idx)) continue;
412 std::vector<typename ReservoirCoupling::MasterGroupNodePressure<Scalar>> pressures;
413 const auto& master_groups = rescoup_master.getMasterGroupNamesForSlave(slave_idx);
414 for (std::size_t i = 0; i < master_groups.size(); ++i) {
415 const auto it = node_pressures.find(master_groups[i]);
416 if (it != node_pressures.end()) {
417 pressures.push_back({i, it->second});
418 }
419 }
420 rescoup_master.sendNumMasterGroupNodePressuresToSlave(
421 slave_idx, pressures.size(), is_final);
422 if (!pressures.empty()) {
423 rescoup_master.sendMasterGroupNodePressuresToSlave(slave_idx, pressures);
424 }
425 }
426 this->last_sent_master_group_node_pressures_is_final_ = is_final;
427}
428
429template<typename TypeTag>
430void
431BlackoilWellModelRescoup<TypeTag>::
432sendSlaveGroupDataToMaster()
433{
434 OPM_TIMEFUNCTION();
435 assert(this->isReservoirCouplingSlave());
436 RescoupSendSlaveGroupData<Scalar, IndexTraits> slave_group_data_sender{
437 this->groupStateHelper()};
438 slave_group_data_sender.sendSlaveGroupDataToMaster();
439}
440
441// Automatically manages the lifecycle of the DeferredLogger pointer
442// in the reservoir coupling logger. Ensures the logger is properly
443// cleared when it goes out of scope, preventing dangling pointer issues:
444//
445// - The ScopedLoggerGuard constructor sets the logger pointer
446// - When the guard goes out of scope, the destructor clears the pointer
447// - Move semantics transfer ownership safely when returning from this function
448// - The moved-from guard is "nullified" and its destructor does nothing
449// - Only the final guard in the caller will clear the logger
450template<typename TypeTag>
451std::optional<ReservoirCoupling::ScopedLoggerGuard>
452BlackoilWellModelRescoup<TypeTag>::
453setupScopedLogger(DeferredLogger& local_logger)
454{
455 if (this->isReservoirCouplingMaster()) {
456 return ReservoirCoupling::ScopedLoggerGuard{
457 this->reservoirCouplingMaster().logger(),
458 &local_logger
459 };
460 } else if (this->isReservoirCouplingSlave()) {
461 return ReservoirCoupling::ScopedLoggerGuard{
462 this->reservoirCouplingSlave().logger(),
463 &local_logger
464 };
465 }
466 return std::nullopt;
467}
468
469template<typename TypeTag>
470void
471BlackoilWellModelRescoup<TypeTag>::
472storeSlaveGroupInjectionTargets()
473{
474 const int reportStepIdx = this->groupStateHelper().reportStepIdx();
475 // The injection target that applies to a slave group is not something
476 // the slave's schedule can know on its own: it depends on the target
477 // the master imposes at every synchronization step and on the group's
478 // GRUPSLAV filter flag, which says whether the master's limit, the
479 // deck's own GCONINJE limit, or the smaller of the two is in force.
480 // Work out that effective target and keep it on the slave, where the
481 // summary writer picks it up and reports it as GGIRT/GWIRT. Only
482 // surface rate targets map onto these keywords.
483 //
484 // A UDQ that refers to the target reads it from the summary state, so
485 // the summary state is primed with the same value, in output units,
486 // ahead of the UDQ evaluation that follows. When nothing is in force
487 // the slot is primed with the schedule's own target instead -- what the
488 // end-of-step summary evaluation will report -- so that a target in
489 // force on an earlier sync step cannot linger there.
490 using M = UnitSystem::measure;
491 auto& summary_state = this->simulator_.vanguard().summaryState();
492 const auto& units = this->simulator_.vanguard().eclState().getUnits();
493 auto& slave = this->reservoirCouplingSlave();
494 const auto& rescoup = this->schedule()[reportStepIdx].rescoup();
495 // The surface-rate group injection target vectors, which is all of them:
496 // there is no oil equivalent, so a master oil injection target reaches the
497 // control path but has nothing to be reported as; and GVIRT is a reservoir
498 // volume target, which cannot be derived from what the master sends, since
499 // it converts every injection mode to a surface rate before sending (see
500 // RescoupConstraintsCalculator::calculateSlaveGroupInjectionTargets_()).
501 // A slave UDQ naming GVIRT for a slave group therefore still reads the
502 // schedule, as it did before reservoir coupling could report a target at
503 // all.
504 const auto targets = std::array {
505 std::tuple { Phase::GAS, std::string{"GGIRT"}, M::gas_surface_rate },
506 std::tuple { Phase::WATER, std::string{"GWIRT"}, M::liquid_surface_rate },
507 };
508 auto& in_force = slave.effectiveInjectionTargets();
509 in_force.clear();
510 for (std::size_t i = 0; i < slave.numSlaveGroups(); ++i) {
511 const auto& gname = slave.slaveGroupIdxToGroupName(i);
512 for (const auto& [phase, keyword, unit] : targets) {
513 const auto effective = this->effectiveSlaveGroupInjectionTarget_(
514 gname, phase, reportStepIdx, rescoup, summary_state);
515 if (effective.has_value()) {
516 in_force[gname][phase] = *effective;
517 summary_state.update_group_var(gname, keyword, units.from_si(unit, *effective));
518 }
519 else {
520 summary_state.update_group_var(
521 gname, keyword,
522 units.from_si(unit, this->scheduleInjectionTarget_(gname, phase, reportStepIdx, summary_state)));
523 }
524 }
525 }
526}
527
528// Private methods alphabetically
529// ------------------------------
530
531template<typename TypeTag>
532std::optional<typename BlackoilWellModelRescoup<TypeTag>::Scalar>
533BlackoilWellModelRescoup<TypeTag>::
534effectiveSlaveGroupInjectionTarget_(const std::string& gname,
535 const Phase phase,
536 const int reportStepIdx,
537 const ReservoirCoupling::CouplingInfo& rescoup,
538 const SummaryState& summary_state) const
539{
540 using FilterFlag = ReservoirCoupling::GrupSlav::FilterFlag;
541 const auto& slave = this->reservoirCouplingSlave();
542 if (! slave.hasMasterInjectionTarget(gname, phase)) {
543 return std::nullopt;
544 }
545 const auto [master_target, cmode] = slave.masterInjectionTarget(gname, phase);
546 if (cmode != Group::InjectionCMode::RATE) {
547 return std::nullopt;
548 }
549
550 // Same fallback as GroupStateHelper::getInjectionFilterFlag_(): a slave
551 // group without a GRUPSLAV record follows the master.
552 auto filter = FilterFlag::MAST;
553 if (rescoup.hasGrupSlav(gname)) {
554 const auto& grup_slav = rescoup.grupSlav(gname);
555 filter = (phase == Phase::GAS) ? grup_slav.gasInjFlag()
556 : (phase == Phase::WATER) ? grup_slav.waterInjFlag()
557 : grup_slav.oilInjFlag();
558 }
559 if (filter == FilterFlag::SLAV) {
560 // The deck's own limit applies; the summary evaluator reads it from
561 // the schedule as for any other group.
562 return std::nullopt;
563 }
564 if (filter == FilterFlag::BOTH) {
565 const auto& group = this->schedule().getGroup(gname, reportStepIdx);
566 if (group.hasInjectionControl(phase)) {
567 return std::min(master_target,
568 this->scheduleInjectionTarget_(gname, phase, reportStepIdx, summary_state));
569 }
570 }
571 return master_target;
572}
573
574template<typename TypeTag>
575bool
576BlackoilWellModelRescoup<TypeTag>::
577masterNetworkHasMasterGroupLeavesForSlave_(std::size_t slave_idx) const
578{
579 // See masterNetworkHasMasterGroupLeaves() for why the parsed Schedule
580 // topology is queried rather than the runtime node_pressures_ map.
581 if (!this->isReservoirCouplingMaster()) return false;
582 const int episodeIdx = this->simulator_.episodeIndex();
583 const auto& network = this->schedule()[episodeIdx].network();
584 if (!network.active()) return false;
585 const auto& rcm = this->reservoirCouplingMaster();
586 for (const auto& mg : rcm.getMasterGroupNamesForSlave(slave_idx)) {
587 if (network.has_node(mg)) {
588 return true;
589 }
590 }
591 return false;
592}
593
594template<typename TypeTag>
595void
596BlackoilWellModelRescoup<TypeTag>::
597refreshAndSendInjectionTargets_()
598{
599 // Called right after a receiveSlaveGroupData() inside the network iteration.
600 // Fold the rates just received into the master's group state -- that is what
601 // recomputes the reinjection and voidage rates that a GCONINJE REIN, SALE
602 // or VREP target is built from -- and ship the resulting targets to the
603 // slaves, replacing the ones they are currently holding. Without this the
604 // targets stay at the values computed in beginTimeStep(), from slave
605 // production of the previous sync step.
606 const int report_step_idx = this->well_model_.simulator().episodeIndex();
607 this->well_model_.updateAndCommunicateGroupData(
608 report_step_idx, /*update_wellgrouptarget=*/false);
609 this->sendMasterGroupInjectionTargetsToSlaves_();
610}
611
612template<typename TypeTag>
613typename BlackoilWellModelRescoup<TypeTag>::Scalar
614BlackoilWellModelRescoup<TypeTag>::
615scheduleInjectionTarget_(const std::string& gname,
616 const Phase phase,
617 const int reportStepIdx,
618 const SummaryState& summary_state) const
619{
620 const auto& group = this->schedule().getGroup(gname, reportStepIdx);
621 if (! group.hasInjectionControl(phase)) {
622 return Scalar{0};
623 }
624 return static_cast<Scalar>(group.injectionControls(phase, summary_state).surface_max_rate);
625}
626
627template<typename TypeTag>
628void
629BlackoilWellModelRescoup<TypeTag>::
630sendMasterGroupInjectionTargetsToSlaves_()
631{
632 OPM_TIMEFUNCTION();
633 RescoupConstraintsCalculator<Scalar, IndexTraits> constraints_calculator{
634 this->well_model_.guideRateHandler(),
635 this->groupStateHelper()
636 };
637 constraints_calculator.recalculateInjectionTargetsAndSendToSlaves();
638}
639
640template<typename TypeTag>
641bool
642BlackoilWellModelRescoup<TypeTag>::
643slaveGroupIsFixedPressureNodeInOwnNetwork_(const std::string& group_name) const
644{
645 const int episodeIdx = this->simulator_.episodeIndex();
646 const auto& network = this->schedule()[episodeIdx].network();
647 if (!network.active()) return false;
648 if (!network.has_node(group_name)) return false;
649 return network.node(group_name).terminal_pressure().has_value();
650}
651
652
653} // namespace Opm
654
655#endif // RESERVOIR_COUPLING_ENABLED
656#endif // OPM_BLACKOILWELLMODEL_RESCOUP_IMPL_HEADER_INCLUDED
#define OPM_END_PARALLEL_TRY_CATCH_LOG(obptc_logger, obptc_prefix, obptc_output, comm)
Catch exception, log, and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:207
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
Phase
Phase indices for reservoir coupling, we currently only support black-oil phases (oil,...
Definition: ReservoirCoupling.hpp:183
Definition: blackoilbioeffectsmodules.hh:45