blackoilnewtonupdate.hpp
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3/*
4 Copyright 2026 Equinor ASA
5
6 This file is part of the Open Porous Media project (OPM).
7
8 OPM is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 2 of the License, or
11 (at your option) any later version.
12
13 OPM is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with OPM. If not, see <http://www.gnu.org/licenses/>.
20
21 Consult the COPYING file in the top-level source directory of this
22 module for the precise wording of the license and the list of
23 copyright holders.
24*/
28#ifndef OPM_BLACK_OIL_NEWTON_UPDATE_HPP
29#define OPM_BLACK_OIL_NEWTON_UPDATE_HPP
30
31#include <opm/common/utility/gpuDecorators.hpp>
35#include <opm/material/common/Valgrind.hpp>
36#include <algorithm>
37#include <cmath>
38#include <limits>
39
40namespace Opm {
41
42// Keep all Newton chopping, bounds and phase switches in one implementation.
43// The owning nonlinear method supplies the problem, fluid system and history;
44// this operation only writes one candidate cell and returns its switch history.
45template<class TypeTag>
47{
50 static constexpr unsigned numEq = getPropValue<TypeTag, Properties::NumEq>();
51 static constexpr bool enableBioeffects = getPropValue<TypeTag, Properties::EnableBioeffects>();
52 static constexpr bool enableSaltPrecipitation = getPropValue<TypeTag, Properties::EnableSaltPrecipitation>();
53
54 template<class Problem, class FluidSystem, class PrimaryVariables, class Update>
55 OPM_HOST_DEVICE static bool update(const Problem& problem,
56 const FluidSystem& fluidSystem,
57 unsigned globalDofIdx,
58 PrimaryVariables& nextValue,
59 const PrimaryVariables& currentValue,
60 const Update& update,
61 const BlackoilNewtonParams<Scalar>& params,
62 bool wasSwitched)
63 {
64 static constexpr bool enableSolvent =
65 Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max();
66 static constexpr bool enableExtbo =
67 Indices::zFractionIdx != std::numeric_limits<unsigned>::max();
68 static constexpr bool enablePolymer =
69 Indices::polymerConcentrationIdx != std::numeric_limits<unsigned>::max();
70 static constexpr bool enablePolymerWeight =
71 Indices::polymerMoleWeightIdx != std::numeric_limits<unsigned>::max();
72 static constexpr bool enableFullyImplicitThermal =
73 Indices::temperatureIdx != std::numeric_limits<unsigned>::max();
74 static constexpr bool enableFoam =
75 Indices::foamConcentrationIdx != std::numeric_limits<unsigned>::max();
76 static constexpr bool enableBrine =
77 Indices::saltConcentrationIdx != std::numeric_limits<unsigned>::max();
78 static constexpr bool enableMICP = Indices::enableMICP;
79
80 currentValue.checkDefined();
81 Valgrind::CheckDefined(update);
82
83 // saturation delta for each phase
84 Scalar deltaSw = 0.0;
85 Scalar deltaSo = 0.0;
86 Scalar deltaSg = 0.0;
87 Scalar deltaSs = 0.0;
88
89 if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
90 {
91 if constexpr (Indices::waterSwitchIdx != std::numeric_limits<unsigned>::max()) {
92 deltaSw = update[Indices::waterSwitchIdx];
93 deltaSo -= deltaSw;
94 }
95 }
96 if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
97 {
98 if constexpr (Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max()) {
99 deltaSg = update[Indices::compositionSwitchIdx];
100 deltaSo -= deltaSg;
101 }
102 }
103 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
104 if constexpr (Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max()) {
105 deltaSs = update[Indices::solventSaturationIdx];
106 deltaSo -= deltaSs;
107 }
108 }
109
110 // maximum saturation delta
111 Scalar maxSatDelta = std::max(std::abs(deltaSg), std::abs(deltaSo));
112 maxSatDelta = std::max(maxSatDelta, std::abs(deltaSw));
113 maxSatDelta = std::max(maxSatDelta, std::abs(deltaSs));
114
115 // scaling factor for saturation deltas to make sure that none of them exceeds
116 // the specified threshold value.
117 Scalar satAlpha = 1.0;
118 if (maxSatDelta > params.dsMax_) {
119 satAlpha = params.dsMax_ / maxSatDelta;
120 }
121
122 for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) {
123 // calculate the update of the current primary variable. For the black-oil
124 // model we limit the pressure delta relative to the pressure's current
125 // absolute value (Default: 30%) and saturation deltas to an absolute change
126 // (Default: 20%). Further, we ensure that the R factors, solvent
127 // "saturation" and polymer concentration do not become negative after the
128 // update.
129 Scalar delta = update[pvIdx];
130
131 // limit pressure delta
132 if (pvIdx == Indices::pressureSwitchIdx) {
133 if (std::abs(delta) > params.dpMaxRel_ * currentValue[pvIdx]) {
134 delta = ((Scalar{0} < delta) - (delta < Scalar{0})) * params.dpMaxRel_ * currentValue[pvIdx];
135 }
136 }
137 // water saturation delta
138 else if (pvIdx == Indices::waterSwitchIdx)
139 if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) {
140 delta *= satAlpha;
141 }
142 else {
143 //Ensure Rvw and Rsw factor does not become negative
144 if (delta > currentValue[ Indices::waterSwitchIdx]) {
145 delta = currentValue[ Indices::waterSwitchIdx];
146 }
147 }
148 else if (pvIdx == Indices::compositionSwitchIdx) {
149 // the switching primary variable for composition is tricky because the
150 // "reasonable" value ranges it exhibits vary widely depending on its
151 // interpretation since it can represent Sg, Rs or Rv. For now, we only
152 // limit saturation deltas and ensure that the R factors do not become
153 // negative.
154 if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg) {
155 delta *= satAlpha;
156 }
157 else {
158 // Ensure Rv and Rs factor does not become negative
159 if (delta > currentValue[Indices::compositionSwitchIdx]) {
160 delta = currentValue[Indices::compositionSwitchIdx];
161 }
162 }
163 }
164 else if (enableSolvent && pvIdx == Indices::solventSaturationIdx) {
165 // solvent saturation updates are also subject to the Appleyard chop
166 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
167 delta *= satAlpha;
168 }
169 else {
170 // Ensure Rssolw factor does not become negative
171 if (delta > currentValue[Indices::solventSaturationIdx]) {
172 delta = currentValue[Indices::solventSaturationIdx];
173 }
174 }
175 }
176 else if (enableExtbo && pvIdx == Indices::zFractionIdx) {
177 // z fraction updates are also subject to the Appleyard chop
178 const auto& curr = currentValue[Indices::zFractionIdx]; // or currentValue[pvIdx] given the block condition
179 delta = std::clamp(delta, curr - Scalar{1.0}, curr);
180 }
181 else if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) {
182 const double sign = delta >= 0. ? 1. : -1.;
183 // maximum change of polymer molecular weight, the unit is MDa.
184 // applying this limit to stabilize the simulation. The value itself is still experimental.
185 const Scalar maxMolarWeightChange = 100.0;
186 delta = sign * std::min(std::abs(delta), maxMolarWeightChange);
187 delta *= satAlpha;
188 }
189 else if (enableFullyImplicitThermal && pvIdx == Indices::temperatureIdx) {
190 const double sign = delta >= 0. ? 1. : -1.;
191 delta = sign * std::min(std::abs(delta), params.maxTempChange_);
192 }
193 else if (enableBrine && pvIdx == Indices::saltConcentrationIdx &&
195 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)
196 {
197 const Scalar maxSaltSaturationChange = 0.1;
198 const Scalar sign = delta >= 0. ? 1. : -1.;
199 delta = sign * std::min(std::abs(delta), maxSaltSaturationChange);
200 }
201
202 // do the actual update
203 nextValue[pvIdx] = currentValue[pvIdx] - delta;
204
205 // keep the solvent saturation between 0 and 1
206 if (enableSolvent && pvIdx == Indices::solventSaturationIdx) {
207 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
208 nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0});
209 }
210 }
211
212 // keep the z fraction between 0 and 1
213 if (enableExtbo && pvIdx == Indices::zFractionIdx) {
214 nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0});
215 }
216
217 // keep the polymer concentration above 0
218 if (enablePolymer && pvIdx == Indices::polymerConcentrationIdx) {
219 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
220 }
221
222 if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) {
223 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
224 const double polymerConcentration = nextValue[Indices::polymerConcentrationIdx];
225 if (polymerConcentration < 1.e-10) {
226 nextValue[pvIdx] = 0.0;
227 }
228 }
229
230 // keep the foam concentration above 0
231 if (enableFoam && pvIdx == Indices::foamConcentrationIdx) {
232 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
233 }
234
235 if (enableBrine && pvIdx == Indices::saltConcentrationIdx) {
236 // keep the salt concentration above 0
238 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Cs)
239 {
240 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
241 }
242 // keep the salt saturation below upperlimit
244 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)
245 {
246 nextValue[pvIdx] = std::min(nextValue[pvIdx], Scalar{1.0-1.e-8});
247 }
248 }
249
250 // keep the temperature within given values
251 if (enableFullyImplicitThermal && pvIdx == Indices::temperatureIdx) {
252 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], params.tempMin_, params.tempMax_);
253 }
254
255 if (pvIdx == Indices::pressureSwitchIdx) {
256 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], params.pressMin_, params.pressMax_);
257 }
258
259 // keep the values above 0
260 // for the biofilm and calcite, we set an upper limit equal to the initial porosity
261 // minus 1e-8. This prevents singularities (e.g., one of the calcite source term is
262 // evaluated at 1/(iniPoro - calcite)). The value 1e-8 is taken from the salt precipitation
263 // clapping above.
264 if constexpr (enableBioeffects) {
265 if (pvIdx == Indices::microbialConcentrationIdx) {
266 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
267 }
268 if (pvIdx == Indices::biofilmVolumeFractionIdx) {
269 nextValue[pvIdx] = std::clamp(nextValue[pvIdx],
270 Scalar{0.0},
271 problem.referencePorosity(globalDofIdx, 0) - 1e-8);
272 }
273 if constexpr (enableMICP) {
274 if (pvIdx == Indices::oxygenConcentrationIdx) {
275 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
276 }
277 if (pvIdx == Indices::ureaConcentrationIdx) {
278 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
279 }
280 if (pvIdx == Indices::calciteVolumeFractionIdx) {
281 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0},
282 problem.referencePorosity(globalDofIdx, 0) - 1e-8);
283 }
284 }
285 }
286 }
287
288 // switch the new primary variables to something which is physically meaningful.
289 // use a threshold value after a switch to make it harder to switch back
290 // immediately.
291 const bool switched = nextValue.adaptPrimaryVariables(
292 problem, fluidSystem, globalDofIdx, params.waterSaturationMax_,
293 params.waterOnlyThreshold_, wasSwitched ? params.priVarOscilationThreshold_ : Scalar{0});
294
295 if (params.projectSaturations_) {
296 nextValue.chopAndNormalizeSaturations();
297 }
298
299 nextValue.checkDefined();
300 return switched;
301 }
302};
303
304} // namespace Opm
305#endif
Defines a type tags and some fundamental properties all models.
Declares the properties required by the black oil model.
Definition: blackoilbioeffectsmodules.hh:45
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(....
Definition: propertysystem.hh:233
Definition: blackoilnewtonupdate.hpp:47
GetPropType< TypeTag, Properties::Indices > Indices
Definition: blackoilnewtonupdate.hpp:49
static OPM_HOST_DEVICE bool update(const Problem &problem, const FluidSystem &fluidSystem, unsigned globalDofIdx, PrimaryVariables &nextValue, const PrimaryVariables &currentValue, const Update &update, const BlackoilNewtonParams< Scalar > &params, bool wasSwitched)
Definition: blackoilnewtonupdate.hpp:55
static constexpr unsigned numEq
Definition: blackoilnewtonupdate.hpp:50
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: blackoilnewtonupdate.hpp:48
static constexpr bool enableSaltPrecipitation
Definition: blackoilnewtonupdate.hpp:52
static constexpr bool enableBioeffects
Definition: blackoilnewtonupdate.hpp:51
Struct holding the parameters for BlackoilNewtonMethod.
Definition: blackoilnewtonmethodparams.hpp:74
Scalar waterOnlyThreshold_
Definition: blackoilnewtonmethodparams.hpp:83
Scalar tempMin_
Definition: blackoilnewtonmethodparams.hpp:90
Scalar waterSaturationMax_
Definition: blackoilnewtonmethodparams.hpp:82
Scalar tempMax_
Definition: blackoilnewtonmethodparams.hpp:89
Scalar maxTempChange_
Definition: blackoilnewtonmethodparams.hpp:88
Scalar priVarOscilationThreshold_
Definition: blackoilnewtonmethodparams.hpp:81
Scalar dsMax_
Definition: blackoilnewtonmethodparams.hpp:86
Scalar dpMaxRel_
Definition: blackoilnewtonmethodparams.hpp:85
Scalar pressMin_
Definition: blackoilnewtonmethodparams.hpp:92
Scalar pressMax_
Definition: blackoilnewtonmethodparams.hpp:91
bool projectSaturations_
Definition: blackoilnewtonmethodparams.hpp:87