BlackoilWellModelNldd_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_NLDD_IMPL_HEADER_INCLUDED
24#define OPM_BLACKOILWELLMODEL_NLDD_IMPL_HEADER_INCLUDED
25
26// Improve IDE experience
27#ifndef OPM_BLACKOILWELLMODEL_NLDD_HEADER_INCLUDED
28#include <config.h>
30#endif
31
32#include <algorithm>
33
34namespace Opm {
35
36template<typename TypeTag>
37void
39assemble(const double dt,
40 const Domain& domain)
41{
42 OPM_TIMEBLOCK(assemble);
43 // NLDD domain well assembly for local solves.
44 //
45 // Call chain: BlackoilWellModelNldd::assemble()
46 // -> BlackoilWellModelNldd::assembleWellEq(dt, domain)
47 // -> WellInterface::assembleWellEq()
48 // -> WellInterface::prepareWellBeforeAssembling()
49 // -> WellInterface::assembleWellEqWithoutIteration()
50 //
51 // Key assumptions and design decisions:
52 //
53 // 1. Global well initialization (BlackoilWellModel::prepareTimeStep, including the
54 // optional initial well solve) has already been called via
55 // BlackoilWellModel::assemble() during the initial global linearization.
56 // This is ensured by NewtonIterationContext::needsTimestepInit().
57 //
58 // 2. Inner well iterations (BlackoilWellModel::iterateWellEquations) are skipped
59 // during local solves via NewtonIterationContext::shouldRunInnerWellIterations()
60 // (inLocalSolve=true).
61 // The NLDD local Newton loop already iterates wells to convergence.
62 //
63 // 3. No MPI collective operations (do_mpi_gather=false).
64 //
65 // 4. Only individual well constraints are checked (see
66 // BlackoilWellModelNldd::updateWellControls).
67 // Group controls, network balancing, gas lift, and NUPCOL/VREP updates
68 // are NOT performed during domain solves. This means:
69 // - Wells can switch between individual controls (BHP, THP, rate limits)
70 // to respect physical safety constraints.
71 // - Wells cannot be switched to/from GRUP control. Individual constraint
72 // checks (WellInterface::checkIndividualConstraints) never produce GRUP
73 // as a result; only WellInterface::checkGroupConstraints can do that,
74 // and it is not called here.
75 // - Group production targets and VREP/REIN injection targets use stale
76 // group state from the last global assembly.
77 // - Well control switches during domain solves are not logged to
78 // well_control_log_ (gated by inLocalSolve), preventing local
79 // oscillations from exhausting the global oscillation budget.
80 //
81 // The final Newton step after NLDD domain solves re-synchronizes everything
82 // via a global BlackoilWellModel::assemble() which performs the full control
83 // update pipeline: BlackoilWellModelGeneric::updateAndCommunicateGroupData,
84 // BlackoilWellModelGeneric::updateGroupControls,
85 // group+individual well constraint checks, network balancing, and gas lift.
86 // Importantly, the global step also checks individual constraints (after
87 // group constraints), so if a domain solve switched a well to e.g. BHP
88 // because of a genuine BHP limit violation, the global step will reach
89 // the same conclusion -- the two levels are consistent for individual
90 // constraints.
91
92 // Use do_mpi_gather=false to avoid MPI collective operations in domain solves.
93 auto loggerGuard = wellModel_.groupStateHelper().pushLogger(/*do_mpi_gather=*/false);
94
95 this->updateWellControls(domain);
96 this->assembleWellEq(dt, domain);
97
98 // Update cellRates_ with current contributions from wells in this domain for reservoir linearization
99 wellModel_.updateCellRatesForDomain(domain.index, this->well_domain());
100 // Wells of other domains may perforate this domain's overlap cells. Keep
101 // their current rates as fixed sources there, as for any other value
102 // outside the owned cells.
103 if (domain.hasOverlap()) {
104 wellModel_.addConstantCellRatesForOverlap(domain.index, this->well_domain(),
105 domain.cells, domain.interior);
106 }
107}
108
109template<typename TypeTag>
110void
112assembleWellEq(const double dt,
113 const Domain& domain)
114{
115 OPM_TIMEBLOCK(assembleWellEq);
116 for (const auto& well : wellModel_.localNonshutWells()) {
117 if (this->well_domain().at(well->name()) == domain.index) {
118 well->assembleWellEq(wellModel_.simulator(),
119 dt,
120 wellModel_.groupStateHelper(),
121 wellModel_.wellState());
122 }
123 }
124}
125
126template<typename TypeTag>
127void
130 const BVector& /*weights*/,
131 const bool /*use_well_weights*/,
132 const int /*domainIndex*/) const
133{
134 throw std::logic_error("CPRW is not yet implemented for NLDD subdomains");
135 // To fix this function, rdofs should be the size of the domain, and the nw should be the number of wells in the domain
136 // int nw = this->numLocalWellsEnd(); // should number of wells in the domain
137 // int rdofs = local_num_cells_; // should be the size of the domain
138 // for ( int i = 0; i < nw; i++ ) {
139 // int wdof = rdofs + i;
140 // jacobian[wdof][wdof] = 1.0;// better scaling ?
141 // }
142
143 // for ( const auto& well : well_container_ ) {
144 // if (well_domain_.at(well->name()) == domainIndex) {
145 // weights should be the size of the domain
146 // well->addWellPressureEquations(jacobian, weights, pressureVarIndex, use_well_weights, this->wellState());
147 // }
148 // }
149}
150
151template<typename TypeTag>
152void
155 const int domainIdx)
156{
157 // Note: no point in trying to do a parallel gathering
158 // try/catch here, as this function is not called in
159 // parallel but for each individual domain of each rank.
160 // Use do_mpi_gather=false to avoid MPI collective operations.
161 auto loggerGuard = wellModel_.groupStateHelper().pushLogger(/*do_mpi_gather=*/false);
162 for (const auto& well : wellModel_.localNonshutWells()) {
163 if (this->well_domain().at(well->name()) == domainIdx) {
164 const auto& cells = well->cells();
165 x_local_.resize(cells.size());
166
167 for (size_t i = 0; i < cells.size(); ++i) {
168 x_local_[i] = x[cells[i]];
169 }
170 well->recoverWellSolutionAndUpdateWellState(wellModel_.simulator(),
171 x_local_,
172 wellModel_.groupStateHelper(),
173 wellModel_.wellState());
174 }
175 }
176}
177
178template<typename TypeTag>
181getWellConvergence(const Domain& domain,
182 const std::vector<Scalar>& B_avg,
183 DeferredLogger& local_deferredLogger) const
184{
185 const auto& iterCtx = wellModel_.simulator().problem().iterationContext();
186 const bool relax_tolerance = iterCtx.shouldRelax(wellModel_.numStrictIterations() + 1);
187
188 ConvergenceReport report;
189 {
190 // Use do_mpi_gather=false to avoid MPI collective operations in domain solves.
191 auto loggerGuard = wellModel_.groupStateHelper().pushLogger(/*do_mpi_gather=*/false);
192
193 for (const auto& well : wellModel_.localNonshutWells()) {
194 if ((this->well_domain().at(well->name()) == domain.index)) {
195 if (well->isOperableAndSolvable() || well->wellIsStopped()) {
196 report += well->getWellConvergence(wellModel_.groupStateHelper(),
197 B_avg,
198 relax_tolerance);
199 } else {
200 ConvergenceReport xreport;
201 using CR = ConvergenceReport;
202 xreport.setWellFailed({CR::WellFailure::Type::Unsolvable,
203 CR::Severity::Normal, -1, well->name()});
204 report += xreport;
205 }
206 }
207 }
208 } // loggerGuard goes out of scope here, before the OpmLog::debug() calls below
209
210 // Log debug messages for NaN or too large residuals.
211 if (wellModel_.terminalOutput()) {
212 for (const auto& f : report.wellFailures()) {
213 if (f.severity() == ConvergenceReport::Severity::NotANumber) {
214 local_deferredLogger.debug("NaN residual found with phase " +
215 std::to_string(f.phase()) +
216 " for well " + f.wellName());
217 } else if (f.severity() == ConvergenceReport::Severity::TooLarge) {
218 local_deferredLogger.debug("Too large residual found with phase " +
219 std::to_string(f.phase()) +
220 " for well " + f.wellName());
221 }
222 }
223 }
224 return report;
225}
226
227template<typename TypeTag>
228void
230updateWellControls(const Domain& domain)
231{
232 OPM_TIMEBLOCK(updateWellControls);
233 if (!wellModel_.wellsActive()) {
234 return;
235 }
236
237 // Only individual well constraints (BHP, THP, rate limits) are checked.
238 // Group constraints, network balancing, and NUPCOL/VREP updates require
239 // MPI collectives and consistent global group state, which are not
240 // available during domain-local solves. The final global Newton step
241 // handles all group-level control decisions.
242 //
243 // Individual constraints are physical safety limits that are essential
244 // for well convergence (e.g., BHP minimum prevents infeasible operating
245 // points). Skipping them would cause domain convergence failures.
246 for (const auto& well : wellModel_.localNonshutWells()) {
247 if (this->well_domain().at(well->name()) == domain.index) {
249 well->updateWellControl(wellModel_.simulator(),
250 mode,
251 wellModel_.groupStateHelper(),
252 wellModel_.wellState());
253 }
254 }
255}
256
257template <typename TypeTag>
258void
260setupDomains(const std::vector<Domain>& domains)
261{
262 std::vector<const SubDomainIndices*> genDomains;
263 std::ranges::transform(domains, std::back_inserter(genDomains),
264 [](const auto& domain)
265 { return static_cast<const SubDomainIndices*>(&domain); });
266 this->calcDomains(genDomains);
267}
268
269} // namespace Opm
270
271#endif
Class for handling the blackoil well model in a NLDD solver.
Definition: BlackoilWellModelNldd.hpp:80
ConvergenceReport getWellConvergence(const Domain &domain, const std::vector< Scalar > &B_avg, DeferredLogger &local_deferredLogger) const
Definition: BlackoilWellModelNldd_impl.hpp:181
void recoverWellSolutionAndUpdateWellState(const BVector &x, const int domainIdx)
Definition: BlackoilWellModelNldd_impl.hpp:154
void assemble(const double dt, const Domain &domain)
Definition: BlackoilWellModelNldd_impl.hpp:39
typename BlackoilWellModel< TypeTag >::BVector BVector
Definition: BlackoilWellModelNldd.hpp:86
void setupDomains(const std::vector< Domain > &domains)
Definition: BlackoilWellModelNldd_impl.hpp:260
typename BlackoilWellModel< TypeTag >::PressureMatrix PressureMatrix
Definition: BlackoilWellModelNldd.hpp:85
void addWellPressureEquations(PressureMatrix &jacobian, const BVector &weights, const bool use_well_weights, const int domainIndex) const
Definition: BlackoilWellModelNldd_impl.hpp:129
void updateWellControls(const Domain &domain)
Definition: BlackoilWellModelNldd_impl.hpp:230
Definition: ConvergenceReport.hpp:38
void setWellFailed(const WellFailure &wf)
Definition: ConvergenceReport.hpp:296
const std::vector< WellFailure > & wellFailures() const
Definition: ConvergenceReport.hpp:459
Definition: DeferredLogger.hpp:57
void debug(const std::string &tag, const std::string &message)
Definition: WellInterface.hpp:79
bool updateWellControl(const Simulator &simulator, const IndividualOrGroup iog, const GroupStateHelperType &groupStateHelper, WellStateType &well_state)
Definition: WellInterface_impl.hpp:192
Definition: blackoilbioeffectsmodules.hh:45
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Definition: SubDomain.hpp:71
std::vector< bool > interior
Definition: SubDomain.hpp:84
std::vector< int > cells
Definition: SubDomain.hpp:77
int index
Definition: SubDomain.hpp:74
bool hasOverlap() const
Definition: SubDomain.hpp:114
Definition: SubDomain.hpp:124