InitStateEquil_impl.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 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 Consult the COPYING file in the top-level source directory of this
20 module for the precise wording of the license and the list of
21 copyright holders.
22*/
23#ifndef OPM_INIT_STATE_EQUIL_IMPL_HPP
24#define OPM_INIT_STATE_EQUIL_IMPL_HPP
25
26#include <dune/grid/common/mcmgmapper.hh>
27
28#include <opm/common/OpmLog/OpmLog.hpp>
29
30#include <opm/grid/utility/RegionMapping.hpp>
31#include <opm/grid/LookUpData.hh>
32
33#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
34#include <opm/input/eclipse/EclipseState/Tables/PbvdTable.hpp>
35#include <opm/input/eclipse/EclipseState/Tables/PdvdTable.hpp>
36#include <opm/input/eclipse/EclipseState/Tables/RsconstTable.hpp>
37#include <opm/input/eclipse/EclipseState/Tables/RsvdTable.hpp>
38#include <opm/input/eclipse/EclipseState/Tables/RtempvdTable.hpp>
39#include <opm/input/eclipse/EclipseState/Tables/RvvdTable.hpp>
40#include <opm/input/eclipse/EclipseState/Tables/RvwvdTable.hpp>
41#include <opm/input/eclipse/EclipseState/Tables/SaltvdTable.hpp>
42#include <opm/input/eclipse/EclipseState/Tables/SaltpvdTable.hpp>
43
44#include <opm/input/eclipse/Units/UnitSystem.hpp>
45
46#include <opm/material/fluidmatrixinteractions/EclMaterialLawManager.hpp>
47#include <opm/material/fluidsystems/BlackOilFluidSystem.hpp>
48
51
53
54#include <fmt/format.h>
55
56#include <algorithm>
57#include <cassert>
58#include <cmath>
59#include <cstddef>
60#include <limits>
61#include <numbers>
62#include <stdexcept>
63
64namespace Opm {
65namespace EQUIL {
66
67namespace Details {
68
69template <typename CellRange, class Scalar>
70void verticalExtent(const CellRange& cells,
71 const std::vector<std::pair<Scalar, Scalar>>& cellZMinMax,
72 const Parallel::Communication& comm,
73 std::array<Scalar,2>& span)
74{
75 span[0] = std::numeric_limits<Scalar>::max();
76 span[1] = std::numeric_limits<Scalar>::lowest();
77
78 // Define vertical span as
79 //
80 // [minimum(node depth(cells)), maximum(node depth(cells))]
81 //
82 // Note: The implementation of 'RK4IVP<>' implicitly
83 // imposes the requirement that cell centroids are all
84 // within this vertical span. That requirement is not
85 // checked.
86 for (const auto& cell : cells) {
87 if (cellZMinMax[cell].first < span[0]) { span[0] = cellZMinMax[cell].first; }
88 if (cellZMinMax[cell].second > span[1]) { span[1] = cellZMinMax[cell].second; }
89 }
90 span[0] = comm.min(span[0]);
91 span[1] = comm.max(span[1]);
92}
93
94template<class Scalar>
95void subdivisionCentrePoints(const Scalar left,
96 const Scalar right,
97 const int numIntervals,
98 std::vector<std::pair<Scalar, Scalar>>& subdiv)
99{
100 const auto h = (right - left) / numIntervals;
101
102 auto end = left;
103 for (auto i = 0*numIntervals; i < numIntervals; ++i) {
104 const auto start = end;
105 end = left + (i + 1)*h;
106
107 subdiv.emplace_back((start + end) / 2, h);
108 }
109}
110
111template <typename CellID, typename Scalar>
112std::vector<std::pair<Scalar, Scalar>>
113horizontalSubdivision(const CellID cell,
114 const std::pair<Scalar, Scalar> topbot,
115 const int numIntervals)
116{
117 auto subdiv = std::vector<std::pair<Scalar, Scalar>>{};
118 subdiv.reserve(2 * numIntervals);
119
120 if (topbot.first > topbot.second) {
121 throw std::out_of_range {
122 "Negative thickness (inverted top/bottom faces) in cell "
123 + std::to_string(cell)
124 };
125 }
126
127 subdivisionCentrePoints(topbot.first, topbot.second,
128 2*numIntervals, subdiv);
129
130 return subdiv;
131}
132
133template <class Scalar, class Element>
134Scalar cellCenterDepth(const Element& element)
135{
136 typedef typename Element::Geometry Geometry;
137 static constexpr int zCoord = Element::dimension - 1;
138 Scalar zz = 0.0;
139
140 const Geometry& geometry = element.geometry();
141 const int corners = geometry.corners();
142 for (int i=0; i < corners; ++i)
143 zz += geometry.corner(i)[zCoord];
144
145 return zz/corners;
146}
147
148template <class Scalar, class Element>
149std::pair<Scalar,Scalar> cellCenterXY(const Element& element)
150{
151 typedef typename Element::Geometry Geometry;
152 static constexpr int xCoord = Element::dimension - 3;
153 static constexpr int yCoord = Element::dimension - 2;
154 Scalar yy = 0.0;
155 Scalar xx = 0.0;
156
157
158 const Geometry& geometry = element.geometry();
159 const int corners = geometry.corners();
160 for (int i=0; i < corners; ++i) {
161 xx += geometry.corner(i)[xCoord];
162 yy += geometry.corner(i)[yCoord];
163 }
164 return std::make_pair(xx/corners, yy/corners);
165}
166
167template <class Scalar, class Element>
168std::pair<Scalar,Scalar> cellZSpan(const Element& element)
169{
170 typedef typename Element::Geometry Geometry;
171 static constexpr int zCoord = Element::dimension - 1;
172 Scalar bot = 0.0;
173 Scalar top = 0.0;
174
175 const Geometry& geometry = element.geometry();
176 const int corners = geometry.corners();
177 assert(corners == 8);
178 for (int i=0; i < 4; ++i)
179 bot += geometry.corner(i)[zCoord];
180 for (int i=4; i < corners; ++i)
181 top += geometry.corner(i)[zCoord];
182
183 return std::make_pair(bot/4, top/4);
184}
185
186template <class Scalar, class Element>
187std::pair<Scalar,Scalar> cellZMinMax(const Element& element)
188{
189 typedef typename Element::Geometry Geometry;
190 static constexpr int zCoord = Element::dimension - 1;
191 const Geometry& geometry = element.geometry();
192 const int corners = geometry.corners();
193 assert(corners == 8);
194 auto min = std::numeric_limits<Scalar>::max();
195 auto max = std::numeric_limits<Scalar>::lowest();
196
197
198 for (int i=0; i < corners; ++i) {
199 min = std::min(min, static_cast<Scalar>(geometry.corner(i)[zCoord]));
200 max = std::max(max, static_cast<Scalar>(geometry.corner(i)[zCoord]));
201 }
202 return std::make_pair(min, max);
203}
204
205template<class Scalar>
207 Scalar& dipAngle, Scalar& dipAzimuth)
208{
209 const auto& Xc = cellCorners.X;
210 const auto& Yc = cellCorners.Y;
211 const auto& Zc = cellCorners.Z;
212
213 Scalar v1x = Xc[1] - Xc[0];
214 Scalar v1y = Yc[1] - Yc[0];
215 Scalar v1z = Zc[1] - Zc[0];
216
217 Scalar v2x = Xc[2] - Xc[0];
218 Scalar v2y = Yc[2] - Yc[0];
219 Scalar v2z = Zc[2] - Zc[0];
220
221 // Cross product to get normal vector
222 Scalar nx = v1y * v2z - v1z * v2y;
223 Scalar ny = v1z * v2x - v1x * v2z;
224 Scalar nz = v1x * v2y - v1y * v2x;
225
226 // Normalize the normal vector
227 Scalar norm = std::hypot(nx, ny, nz);
228
229 if (norm > 1e-10) {
230 nx /= norm;
231 ny /= norm;
232 nz /= norm;
233
234 // Dip angle is the angle between normal and vertical (0,0,1)
235 dipAngle = std::acos(std::abs(nz));
236
237 // Dip azimuth (direction of dip)
238 if (std::abs(nx) > 1e-10 || std::abs(ny) > 1e-10) {
239 dipAzimuth = std::atan2(ny, nx);
240 // Convert to 0-2π range
241 dipAzimuth = std::fmod(dipAzimuth + 2*std::numbers::pi_v<Scalar>, 2*std::numbers::pi_v<Scalar>);
242 } else {
243 dipAzimuth = 0.0; // Vertical cell
244 }
245
246 // Clamp dip angle to reasonable values
247 const Scalar maxDip = std::numbers::pi_v<Scalar>/2 - static_cast<Scalar>(1e-6);
248 dipAngle = std::min(dipAngle, maxDip);
249 } else {
250 // Degenerate cell - assume horizontal
251 dipAngle = 0.0;
252 dipAzimuth = 0.0;
253 }
254}
255
256template <class Scalar, class Element>
258{
259 typedef typename Element::Geometry Geometry;
260 const Geometry& geometry = element.geometry();
261 static constexpr int zCoord = Element::dimension - 1;
262 static constexpr int yCoord = Element::dimension - 2;
263 static constexpr int xCoord = Element::dimension - 3;
264 const int corners = geometry.corners();
265 assert(corners == 8);
266 std::array<Scalar, 8> X {};
267 std::array<Scalar, 8> Y {};
268 std::array<Scalar, 8> Z {};
269 // Get all 8 corners of the hexahedral cell (maybe expensive)
270 for (int i = 0; i < corners; ++i) {
271 auto corner = geometry.corner(i);
272 X[i] = corner[xCoord];
273 Y[i] = corner[yCoord];
274 Z[i] = corner[zCoord];
275 }
276
277 return CellCornerData<Scalar>{X, Y, Z};
278}
279
280template<class Scalar>
281Scalar calculateTrueVerticalDepth(Scalar z, Scalar x, Scalar y,
282 Scalar dipAngle, Scalar dipAzimuth,
283 const std::array<Scalar, 3>& referencePoint)
284{
285 // For True Vertical Depth calculation:
286 // TVD = reference_depth + (z - reference_z) * cos(dipAngle)
287 // + lateral_distance * sin(dipAngle) * cos(azimuth_difference)
288
289 // Calculate lateral displacement from reference point
290 Scalar dx = x - referencePoint[0];
291 Scalar dy = y - referencePoint[1];
292 Scalar dz = z - referencePoint[2];
293
294 // If no dip, TVD is simply the depth
295 if (std::abs(dipAngle) < 1e-10) {
296 return referencePoint[2] + dz;
297 }
298
299 // Calculate the direction from reference point to current point
300 Scalar pointAzimuth = std::atan2(dy, dx);
301
302 // Calculate the angle between dip direction and point direction
303 Scalar azimuthDiff = pointAzimuth - dipAzimuth;
304
305 // Calculate lateral distance
306 Scalar lateralDist = std::hypot(dx, dy);
307
308 // Project lateral distance onto dip direction
309 Scalar lateralInDipDir = lateralDist * std::cos(azimuthDiff);
310
311 // True Vertical Depth calculation
312 // TVD increases with depth (more negative z means deeper)
313 // For a dipping plane: TVD = vertical_component + dip_component
314 Scalar tvd = referencePoint[2] + dz * std::cos(dipAngle) + lateralInDipDir * std::sin(dipAngle);
315
316 return tvd;
317}
318
319namespace PhasePressODE {
320
321template<class FluidSystem>
323Water(const TabulatedFunction& tempVdTable,
324 const TabulatedFunction& saltVdTable,
325 const int pvtRegionIdx,
326 const Scalar normGrav)
327 : tempVdTable_(tempVdTable)
328 , saltVdTable_(saltVdTable)
329 , pvtRegionIdx_(pvtRegionIdx)
330 , g_(normGrav)
331{
332}
333
334template<class FluidSystem>
335typename Water<FluidSystem>::Scalar
337operator()(const Scalar depth,
338 const Scalar press) const
339{
340 return this->density(depth, press) * g_;
341}
342
343template<class FluidSystem>
344typename Water<FluidSystem>::Scalar
346density(const Scalar depth,
347 const Scalar press) const
348{
349 // The initializing algorithm can give depths outside the range due to numerical noise i.e. we extrapolate
350 Scalar saltConcentration = saltVdTable_.eval(depth, /*extrapolate=*/true);
351 Scalar temp = tempVdTable_.eval(depth, /*extrapolate=*/true);
352 Scalar rho = FluidSystem::waterPvt().inverseFormationVolumeFactor(pvtRegionIdx_,
353 temp,
354 press,
355 Scalar{0.0} /*=Rsw*/,
356 saltConcentration);
357 rho *= FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, pvtRegionIdx_);
358 return rho;
359}
360
361template<class FluidSystem, class RS>
363Oil(const TabulatedFunction& tempVdTable,
364 const RS& rs,
365 const int pvtRegionIdx,
366 const Scalar normGrav)
367 : tempVdTable_(tempVdTable)
368 , rs_(rs)
369 , pvtRegionIdx_(pvtRegionIdx)
370 , g_(normGrav)
371{
372}
373
374template<class FluidSystem, class RS>
375typename Oil<FluidSystem,RS>::Scalar
377operator()(const Scalar depth,
378 const Scalar press) const
379{
380 return this->density(depth, press) * g_;
381}
382
383template<class FluidSystem, class RS>
384typename Oil<FluidSystem,RS>::Scalar
386density(const Scalar depth,
387 const Scalar press) const
388{
389 const Scalar temp = tempVdTable_.eval(depth, /*extrapolate=*/true);
390 Scalar rs = 0.0;
391 if (FluidSystem::enableDissolvedGas() || FluidSystem::enableConstantRs())
392 rs = rs_(depth, press, temp);
393
394 Scalar bOil = 0.0;
395 if (rs >= FluidSystem::oilPvt().saturatedGasDissolutionFactor(pvtRegionIdx_, temp, press)) {
396 bOil = FluidSystem::oilPvt().saturatedInverseFormationVolumeFactor(pvtRegionIdx_, temp, press);
397 }
398 else {
399 bOil = FluidSystem::oilPvt().inverseFormationVolumeFactor(pvtRegionIdx_, temp, press, rs);
400 }
401 Scalar rho = bOil * FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx_);
402 if (FluidSystem::enableDissolvedGas() || FluidSystem::enableConstantRs()) {
403 rho += rs * bOil * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
404 }
405
406 return rho;
407}
408
409template<class FluidSystem, class RV, class RVW>
411Gas(const TabulatedFunction& tempVdTable,
412 const RV& rv,
413 const RVW& rvw,
414 const int pvtRegionIdx,
415 const Scalar normGrav)
416 : tempVdTable_(tempVdTable)
417 , rv_(rv)
418 , rvw_(rvw)
419 , pvtRegionIdx_(pvtRegionIdx)
420 , g_(normGrav)
421{
422}
423
424template<class FluidSystem, class RV, class RVW>
425typename Gas<FluidSystem,RV,RVW>::Scalar
427operator()(const Scalar depth,
428 const Scalar press) const
429{
430 return this->density(depth, press) * g_;
431}
432
433template<class FluidSystem, class RV, class RVW>
434typename Gas<FluidSystem,RV,RVW>::Scalar
436density(const Scalar depth,
437 const Scalar press) const
438{
439 const Scalar temp = tempVdTable_.eval(depth, /*extrapolate=*/true);
440 Scalar rv = 0.0;
441 if (FluidSystem::enableVaporizedOil())
442 rv = rv_(depth, press, temp);
443
444 Scalar rvw = 0.0;
445 if (FluidSystem::enableVaporizedWater())
446 rvw = rvw_(depth, press, temp);
447
448 Scalar bGas = 0.0;
449
450 if (FluidSystem::enableVaporizedOil() && FluidSystem::enableVaporizedWater()) {
451 if (rv >= FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, temp, press)
452 && rvw >= FluidSystem::gasPvt().saturatedWaterVaporizationFactor(pvtRegionIdx_, temp, press))
453 {
454 bGas = FluidSystem::gasPvt().saturatedInverseFormationVolumeFactor(pvtRegionIdx_, temp, press);
455 } else {
456 bGas = FluidSystem::gasPvt().inverseFormationVolumeFactor(pvtRegionIdx_, temp, press, rv, rvw);
457 }
458 Scalar rho = bGas * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
459 rho += rv * bGas * FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx_)
460 + rvw * bGas * FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, pvtRegionIdx_);
461 return rho;
462 }
463
464 if (FluidSystem::enableVaporizedOil()){
465 if (rv >= FluidSystem::gasPvt().saturatedOilVaporizationFactor(pvtRegionIdx_, temp, press)) {
466 bGas = FluidSystem::gasPvt().saturatedInverseFormationVolumeFactor(pvtRegionIdx_, temp, press);
467 } else {
468 bGas = FluidSystem::gasPvt().inverseFormationVolumeFactor(pvtRegionIdx_,
469 temp,
470 press,
471 rv,
472 Scalar{0.0}/*=rvw*/);
473 }
474 Scalar rho = bGas * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
475 rho += rv * bGas * FluidSystem::referenceDensity(FluidSystem::oilPhaseIdx, pvtRegionIdx_);
476 return rho;
477 }
478
479 if (FluidSystem::enableVaporizedWater()){
480 if (rvw >= FluidSystem::gasPvt().saturatedWaterVaporizationFactor(pvtRegionIdx_, temp, press)) {
481 bGas = FluidSystem::gasPvt().saturatedInverseFormationVolumeFactor(pvtRegionIdx_, temp, press);
482 }
483 else {
484 bGas = FluidSystem::gasPvt().inverseFormationVolumeFactor(pvtRegionIdx_,
485 temp,
486 press,
487 Scalar{0.0} /*=rv*/,
488 rvw);
489 }
490 Scalar rho = bGas * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
491 rho += rvw * bGas * FluidSystem::referenceDensity(FluidSystem::waterPhaseIdx, pvtRegionIdx_);
492 return rho;
493 }
494
495 // immiscible gas
496 bGas = FluidSystem::gasPvt().inverseFormationVolumeFactor(pvtRegionIdx_, temp,
497 press,
498 Scalar{0.0} /*=rv*/,
499 Scalar{0.0} /*=rvw*/);
500 Scalar rho = bGas * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, pvtRegionIdx_);
501
502 return rho;
503}
504
505}
506
507template<class FluidSystem, class Region>
508template<typename PressFunc>
509void PressureTable<FluidSystem,Region>::
510checkPtr(const PressFunc* phasePress,
511 const std::string& phaseName) const
512{
513 if (phasePress != nullptr) { return; }
514
515 throw std::invalid_argument {
516 "Phase pressure function for \"" + phaseName
517 + "\" most not be null"
518 };
519}
520
521template<class FluidSystem, class Region>
522typename PressureTable<FluidSystem,Region>::Strategy
523PressureTable<FluidSystem,Region>::
524selectEquilibrationStrategy(const Region& reg) const
525{
526 if (!this->oilActive()) {
527 if (reg.datum() > reg.zwoc()) { // Datum in water zone
528 return &PressureTable::equil_WOG;
529 }
530 return &PressureTable::equil_GOW;
531 }
532
533 if (reg.datum() > reg.zwoc()) { // Datum in water zone
534 return &PressureTable::equil_WOG;
535 }
536 else if (reg.datum() < reg.zgoc()) { // Datum in gas zone
537 return &PressureTable::equil_GOW;
538 }
539 else { // Datum in oil zone
540 return &PressureTable::equil_OWG;
541 }
542}
543
544template<class FluidSystem, class Region>
545void PressureTable<FluidSystem,Region>::
546copyInPointers(const PressureTable& rhs)
547{
548 if (rhs.oil_ != nullptr) {
549 this->oil_ = std::make_unique<OPress>(*rhs.oil_);
550 }
551
552 if (rhs.gas_ != nullptr) {
553 this->gas_ = std::make_unique<GPress>(*rhs.gas_);
554 }
555
556 if (rhs.wat_ != nullptr) {
557 this->wat_ = std::make_unique<WPress>(*rhs.wat_);
558 }
559}
560
561template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
563PhaseSaturations(MaterialLawManager& matLawMgr,
564 const std::vector<Scalar>& swatInit)
565 : matLawMgr_(matLawMgr)
566 , swatInit_ (swatInit)
567{
568}
569
570template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
573 : matLawMgr_(rhs.matLawMgr_)
574 , swatInit_ (rhs.swatInit_)
575 , sat_ (rhs.sat_)
576 , press_ (rhs.press_)
577{
578 // Note: We don't need to do anything to the 'fluidState_' here.
579 this->setEvaluationPoint(*rhs.evalPt_.position,
580 *rhs.evalPt_.region,
581 *rhs.evalPt_.ptable);
582}
583
584template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
588 const Region& reg,
589 const PTable& ptable)
590{
591 this->setEvaluationPoint(x, reg, ptable);
592 this->initializePhaseQuantities();
593
594 if (ptable.gasActive()) { this->deriveGasSat(); }
595
596 if (ptable.waterActive()) { this->deriveWaterSat(); }
597
598
599 if (this->isOverlappingTransition()) {
600 this->fixUnphysicalTransition();
601 }
602
603 if (ptable.oilActive()) { this->deriveOilSat(); }
604
605 this->accountForScaledSaturations();
606
607 return this->sat_;
608}
609
610template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
612setEvaluationPoint(const Position& x,
613 const Region& reg,
614 const PTable& ptable)
615{
616 this->evalPt_.position = &x;
617 this->evalPt_.region = &reg;
618 this->evalPt_.ptable = &ptable;
619}
620
621template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
622void PhaseSaturations<MaterialLawManager,FluidSystem,Region,CellID>::
623initializePhaseQuantities()
624{
625 this->sat_.reset();
626 this->press_.reset();
627
628 const auto depth = this->evalPt_.position->depth;
629 const auto& ptable = *this->evalPt_.ptable;
630
631 if (ptable.oilActive()) {
632 this->press_.oil = ptable.oil(depth);
633 }
634
635 if (ptable.gasActive()) {
636 this->press_.gas = ptable.gas(depth);
637 }
638
639 if (ptable.waterActive()) {
640 this->press_.water = ptable.water(depth);
641 }
642}
643
644template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
645void PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::deriveOilSat()
646{
647 this->sat_.oil = 1.0 - this->sat_.water - this->sat_.gas;
648}
649
650template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
651void PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::deriveGasSat()
652{
653 auto& sg = this->sat_.gas;
654
655 const auto isIncr = true; // dPcgo/dSg >= 0 for all Sg.
656 const auto oilActive = this->evalPt_.ptable->oilActive();
657
658 if (this->isConstCapPress(this->gasPos())) {
659 // Sharp interface between phases. Can derive phase saturation
660 // directly from knowing where 'depth' of evaluation point is
661 // relative to depth of O/G contact.
662 const auto gas_contact = oilActive? this->evalPt_.region->zgoc() : this->evalPt_.region->zwoc();
663 sg = this->fromDepthTable(gas_contact,
664 this->gasPos(), isIncr);
665 }
666 else {
667 // Capillary pressure curve is non-constant, meaning there is a
668 // transition zone between the gas and oil phases. Invert capillary
669 // pressure relation
670 //
671 // Pcgo(Sg) = Pg - Po
672 //
673 // Note that Pcgo is defined to be (Pg - Po), not (Po - Pg).
674 const auto pw = oilActive? this->press_.oil : this->press_.water;
675 const auto pcgo = this->press_.gas - pw;
676 sg = this->invertCapPress(pcgo, this->gasPos(), isIncr);
677 }
678}
679
680template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
681void PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::deriveWaterSat()
682{
683 auto& sw = this->sat_.water;
684
685 const auto oilActive = this->evalPt_.ptable->oilActive();
686 if (!oilActive) {
687 // for 2p gas+water we set the water saturation to 1.0 - sg
688 sw = 1.0 - this->sat_.gas;
689
690 // Honour SWATINIT by scaling the gas/water capillary pressure curve,
691 // as is done for oil/water below. Not applicable for a sharp
692 // gas/water interface (constant Pc).
693 if (! this->swatInit_.empty() && ! this->isConstCapPress(this->gasPos())) {
694 const auto pcgw = this->press_.gas - this->press_.water;
695
696 auto [swout, newSwatInit] = this->applySwatInit(pcgw);
697 if (newSwatInit) {
698 // Curve possibly changed (e.g., PPCWMAX): re-invert.
699 const auto isIncr = true; // dPcgw/dSg >= 0 for all Sg.
700 this->sat_.gas = this->invertCapPress(pcgw, this->gasPos(), isIncr);
701 sw = 1.0 - this->sat_.gas;
702 }
703 else {
704 sw = swout;
705 this->sat_.gas = 1.0 - sw;
706 }
707 }
708 }
709 else {
710 const auto isIncr = false; // dPcow/dSw <= 0 for all Sw.
711
712 if (this->isConstCapPress(this->waterPos())) {
713 // Sharp interface between phases. Can derive phase saturation
714 // directly from knowing where 'depth' of evaluation point is
715 // relative to depth of O/W contact.
716 sw = this->fromDepthTable(this->evalPt_.region->zwoc(),
717 this->waterPos(), isIncr);
718 }
719 else {
720 // Capillary pressure curve is non-constant, meaning there is a
721 // transition zone between the oil and water phases. Invert
722 // capillary pressure relation
723 //
724 // Pcow(Sw) = Po - Pw
725 //
726 // unless the model uses "SWATINIT". In the latter case, pick the
727 // saturation directly from the SWATINIT array of the pertinent
728 // cell.
729 const auto pcow = this->press_.oil - this->press_.water;
730
731 if (this->swatInit_.empty()) {
732 sw = this->invertCapPress(pcow, this->waterPos(), isIncr);
733 }
734 else {
735 auto [swout, newSwatInit] = this->applySwatInit(pcow);
736 if (newSwatInit)
737 sw = this->invertCapPress(pcow, this->waterPos(), isIncr);
738 else {
739 sw = swout;
740 }
741 }
742 }
743 }
744}
745
746template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
747void PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
748fixUnphysicalTransition()
749{
750 auto& sg = this->sat_.gas;
751 auto& sw = this->sat_.water;
752
753 // Overlapping gas/oil and oil/water transition zones can lead to
754 // unphysical phase saturations when individual saturations are derived
755 // directly from inverting O/G and O/W capillary pressure curves.
756 //
757 // Recalculate phase saturations using the implied gas/water capillary
758 // pressure: Pg - Pw.
759 const auto pcgw = this->press_.gas - this->press_.water;
760 if (! this->swatInit_.empty()) {
761 // Re-scale Pc to reflect imposed sw for vanishing oil phase. This
762 // seems consistent with ECLIPSE, but fails to honour SWATINIT in
763 // case of non-trivial gas/oil capillary pressure.
764 auto [swout, newSwatInit] = this->applySwatInit(pcgw, sw);
765 if (newSwatInit){
766 const auto isIncr = false; // dPcow/dSw <= 0 for all Sw.
767 sw = this->invertCapPress(pcgw, this->waterPos(), isIncr);
768 }
769 else {
770 sw = swout;
771 }
772 }
773
774 sw = satFromSumOfPcs<FluidSystem>
775 (this->matLawMgr_, this->waterPos(), this->gasPos(),
776 this->evalPt_.position->cell, pcgw);
777 sg = 1.0 - sw;
778
779 this->fluidState_.setSaturation(this->oilPos(), 1.0 - sw - sg);
780 this->fluidState_.setSaturation(this->gasPos(), sg);
781 this->fluidState_.setSaturation(this->waterPos(), this->evalPt_
782 .ptable->waterActive() ? sw : 0.0);
783
784 // Pcgo = Pg - Po => Po = Pg - Pcgo
785 this->computeMaterialLawCapPress();
786 this->press_.oil = this->press_.gas - this->materialLawCapPressGasOil();
787}
788
789template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
790void PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
791accountForScaledSaturations()
792{
793 const auto gasActive = this->evalPt_.ptable->gasActive();
794 const auto watActive = this->evalPt_.ptable->waterActive();
795 const auto oilActive = this->evalPt_.ptable->oilActive();
796
797 auto sg = gasActive? this->sat_.gas : 0.0;
798 auto sw = watActive? this->sat_.water : 0.0;
799 auto so = oilActive? this->sat_.oil : 0.0;
800
801 this->fluidState_.setSaturation(this->waterPos(), sw);
802 this->fluidState_.setSaturation(this->oilPos(), so);
803 this->fluidState_.setSaturation(this->gasPos(), sg);
804
805 const auto& scaledDrainageInfo = this->matLawMgr_
806 .oilWaterScaledEpsInfoDrainage(this->evalPt_.position->cell);
807
808 const auto thresholdSat = 1.0e-6;
809 if (watActive && ((sw + thresholdSat) > scaledDrainageInfo.Swu)) {
810 // Water saturation exceeds maximum possible value. Reset oil phase
811 // pressure to that which corresponds to maximum possible water
812 // saturation value.
813 this->fluidState_.setSaturation(this->waterPos(), scaledDrainageInfo.Swu);
814 if (oilActive) {
815 this->fluidState_.setSaturation(this->oilPos(), so + sw - scaledDrainageInfo.Swu);
816 } else if (gasActive) {
817 this->fluidState_.setSaturation(this->gasPos(), sg + sw - scaledDrainageInfo.Swu);
818 }
819 sw = scaledDrainageInfo.Swu;
820 this->computeMaterialLawCapPress();
821
822 if (oilActive) {
823 // Pcow = Po - Pw => Po = Pw + Pcow
824 this->press_.oil = this->press_.water + this->materialLawCapPressOilWater();
825 } else {
826 // Pcgw = Pg - Pw => Pg = Pw + Pcgw
827 this->press_.gas = this->press_.water + this->materialLawCapPressGasWater();
828 }
829
830 }
831 if (gasActive && ((sg + thresholdSat) > scaledDrainageInfo.Sgu)) {
832 // Gas saturation exceeds maximum possible value. Reset oil phase
833 // pressure to that which corresponds to maximum possible gas
834 // saturation value.
835 this->fluidState_.setSaturation(this->gasPos(), scaledDrainageInfo.Sgu);
836 if (oilActive) {
837 this->fluidState_.setSaturation(this->oilPos(), so + sg - scaledDrainageInfo.Sgu);
838 } else if (watActive) {
839 this->fluidState_.setSaturation(this->waterPos(), sw + sg - scaledDrainageInfo.Sgu);
840 }
841 sg = scaledDrainageInfo.Sgu;
842 this->computeMaterialLawCapPress();
843
844 if (oilActive) {
845 // Pcgo = Pg - Po => Po = Pg - Pcgo
846 this->press_.oil = this->press_.gas - this->materialLawCapPressGasOil();
847 } else {
848 // Pcgw = Pg - Pw => Pw = Pg - Pcgw
849 this->press_.water = this->press_.gas - this->materialLawCapPressGasWater();
850 }
851 }
852
853 if (watActive && ((sw - thresholdSat) < scaledDrainageInfo.Swl)) {
854 // Water saturation less than minimum possible value in cell. Reset
855 // water phase pressure to that which corresponds to minimum
856 // possible water saturation value.
857 this->fluidState_.setSaturation(this->waterPos(), scaledDrainageInfo.Swl);
858 if (oilActive) {
859 this->fluidState_.setSaturation(this->oilPos(), so + sw - scaledDrainageInfo.Swl);
860 } else if (gasActive) {
861 this->fluidState_.setSaturation(this->gasPos(), sg + sw - scaledDrainageInfo.Swl);
862 }
863 sw = scaledDrainageInfo.Swl;
864 this->computeMaterialLawCapPress();
865
866 if (oilActive) {
867 // Pcwo = Po - Pw => Pw = Po - Pcow
868 this->press_.water = this->press_.oil - this->materialLawCapPressOilWater();
869 } else {
870 // Pcgw = Pg - Pw => Pw = Pg - Pcgw
871 this->press_.water = this->press_.gas - this->materialLawCapPressGasWater();
872 }
873 }
874
875 if (gasActive && ((sg - thresholdSat) < scaledDrainageInfo.Sgl)) {
876 // Gas saturation less than minimum possible value in cell. Reset
877 // gas phase pressure to that which corresponds to minimum possible
878 // gas saturation.
879 this->fluidState_.setSaturation(this->gasPos(), scaledDrainageInfo.Sgl);
880 if (oilActive) {
881 this->fluidState_.setSaturation(this->oilPos(), so + sg - scaledDrainageInfo.Sgl);
882 } else if (watActive) {
883 this->fluidState_.setSaturation(this->waterPos(), sw + sg - scaledDrainageInfo.Sgl);
884 }
885 sg = scaledDrainageInfo.Sgl;
886 this->computeMaterialLawCapPress();
887
888 if (oilActive) {
889 // Pcgo = Pg - Po => Pg = Po + Pcgo
890 this->press_.gas = this->press_.oil + this->materialLawCapPressGasOil();
891 } else {
892 // Pcgw = Pg - Pw => Pg = Pw + Pcgw
893 this->press_.gas = this->press_.water + this->materialLawCapPressGasWater();
894 }
895 }
896}
897
898template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
899std::pair<typename FluidSystem::Scalar, bool>
900PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
901applySwatInit(const Scalar pcow)
902{
903 return this->applySwatInit(pcow, this->swatInit_[this->evalPt_.position->cell]);
904}
905
906template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
907std::pair<typename FluidSystem::Scalar, bool>
908PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
909applySwatInit(const Scalar pcow, const Scalar sw)
910{
911 return this->matLawMgr_.applySwatinit(this->evalPt_.position->cell, pcow, sw);
912}
913
914template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
915void PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
916computeMaterialLawCapPress()
917{
918 const auto& matParams = this->matLawMgr_
919 .materialLawParams(this->evalPt_.position->cell);
920
921 this->matLawCapPress_.fill(0.0);
922 MaterialLaw::capillaryPressures(this->matLawCapPress_,
923 matParams, this->fluidState_);
924}
925
926template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
927typename FluidSystem::Scalar
928PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
929materialLawCapPressGasOil() const
930{
931 return this->matLawCapPress_[this->oilPos()]
932 + this->matLawCapPress_[this->gasPos()];
933}
934
935template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
936typename FluidSystem::Scalar
937PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
938materialLawCapPressOilWater() const
939{
940 return this->matLawCapPress_[this->oilPos()]
941 - this->matLawCapPress_[this->waterPos()];
942}
943
944template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
945typename FluidSystem::Scalar
946PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
947materialLawCapPressGasWater() const
948{
949 return this->matLawCapPress_[this->gasPos()]
950 - this->matLawCapPress_[this->waterPos()];
951}
952
953template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
954bool PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
955isConstCapPress(const PhaseIdx phaseIdx) const
956{
957 return isConstPc<FluidSystem>
958 (this->matLawMgr_, phaseIdx, this->evalPt_.position->cell);
959}
960
961template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
962bool PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
963isOverlappingTransition() const
964{
965 return this->evalPt_.ptable->gasActive()
966 && this->evalPt_.ptable->waterActive()
967 && ((this->sat_.gas + this->sat_.water) > 1.0);
968}
969
970template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
971typename FluidSystem::Scalar
972PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
973fromDepthTable(const Scalar contactdepth,
974 const PhaseIdx phasePos,
975 const bool isincr) const
976{
977 return satFromDepth<FluidSystem>
978 (this->matLawMgr_, this->evalPt_.position->depth,
979 contactdepth, static_cast<int>(phasePos),
980 this->evalPt_.position->cell, isincr);
981}
982
983template <class MaterialLawManager, class FluidSystem, class Region, typename CellID>
984typename FluidSystem::Scalar
985PhaseSaturations<MaterialLawManager, FluidSystem, Region, CellID>::
986invertCapPress(const Scalar pc,
987 const PhaseIdx phasePos,
988 const bool isincr) const
989{
990 return satFromPc<FluidSystem>
991 (this->matLawMgr_, static_cast<int>(phasePos),
992 this->evalPt_.position->cell, pc, isincr);
993}
994
995template<class FluidSystem, class Region>
997PressureTable(const Scalar gravity,
998 const int samplePoints)
999 : gravity_(gravity)
1000 , nsample_(samplePoints)
1001{
1002}
1003
1004template <class FluidSystem, class Region>
1007 : gravity_(rhs.gravity_)
1008 , nsample_(rhs.nsample_)
1009{
1010 this->copyInPointers(rhs);
1011}
1012
1013template <class FluidSystem, class Region>
1016 : gravity_(rhs.gravity_)
1017 , nsample_(rhs.nsample_)
1018 , oil_ (std::move(rhs.oil_))
1019 , gas_ (std::move(rhs.gas_))
1020 , wat_ (std::move(rhs.wat_))
1021{
1022}
1023
1024template <class FluidSystem, class Region>
1028{
1029 this->gravity_ = rhs.gravity_;
1030 this->nsample_ = rhs.nsample_;
1031 this->copyInPointers(rhs);
1032
1033 return *this;
1034}
1035
1036template <class FluidSystem, class Region>
1040{
1041 this->gravity_ = rhs.gravity_;
1042 this->nsample_ = rhs.nsample_;
1043
1044 this->oil_ = std::move(rhs.oil_);
1045 this->gas_ = std::move(rhs.gas_);
1046 this->wat_ = std::move(rhs.wat_);
1047
1048 return *this;
1049}
1050
1051template <class FluidSystem, class Region>
1053equilibrate(const Region& reg,
1054 const VSpan& span)
1055{
1056 // One of the PressureTable::equil_*() member functions.
1057 auto equil = this->selectEquilibrationStrategy(reg);
1058
1059 (this->*equil)(reg, span);
1060}
1061
1062template <class FluidSystem, class Region>
1064oilActive() const
1065{
1066 return FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
1067}
1068
1069template <class FluidSystem, class Region>
1071gasActive() const
1072{
1073 return FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx);
1074}
1075
1076template <class FluidSystem, class Region>
1078waterActive() const
1079{
1080 return FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx);
1081}
1082
1083template <class FluidSystem, class Region>
1084typename FluidSystem::Scalar
1086oil(const Scalar depth) const
1087{
1088 this->checkPtr(this->oil_.get(), "OIL");
1089
1090 return this->oil_->value(depth);
1091}
1092
1093template <class FluidSystem, class Region>
1094typename FluidSystem::Scalar
1096gas(const Scalar depth) const
1097{
1098 this->checkPtr(this->gas_.get(), "GAS");
1099
1100 return this->gas_->value(depth);
1101}
1102
1103
1104template <class FluidSystem, class Region>
1105typename FluidSystem::Scalar
1107water(const Scalar depth) const
1108{
1109 this->checkPtr(this->wat_.get(), "WATER");
1110
1111 return this->wat_->value(depth);
1112}
1113
1114template <class FluidSystem, class Region>
1116equil_WOG(const Region& reg, const VSpan& span)
1117{
1118 // Datum depth in water zone. Calculate phase pressure for water first,
1119 // followed by oil and gas if applicable.
1120
1121 if (! this->waterActive()) {
1122 throw std::invalid_argument {
1123 "Don't know how to interpret EQUIL datum depth in "
1124 "WATER zone in model without active water phase"
1125 };
1126 }
1127
1128 {
1129 const auto ic = typename WPress::InitCond {
1130 reg.datum(), reg.pressure()
1131 };
1132
1133 this->makeWatPressure(ic, reg, span);
1134 }
1135
1136 if (this->oilActive()) {
1137 // Pcow = Po - Pw => Po = Pw + Pcow
1138 const auto ic = typename OPress::InitCond {
1139 reg.zwoc(),
1140 this->water(reg.zwoc()) + reg.pcowWoc()
1141 };
1142
1143 this->makeOilPressure(ic, reg, span);
1144 }
1145
1146 if (this->gasActive() && this->oilActive()) {
1147 // Pcgo = Pg - Po => Pg = Po + Pcgo
1148 const auto ic = typename GPress::InitCond {
1149 reg.zgoc(),
1150 this->oil(reg.zgoc()) + reg.pcgoGoc()
1151 };
1152
1153 this->makeGasPressure(ic, reg, span);
1154 } else if (this->gasActive() && !this->oilActive()) {
1155 // No oil phase set Pg = Pw + Pcgw
1156 const auto ic = typename GPress::InitCond {
1157 reg.zwoc(), // The WOC is really the GWC for gas/water cases
1158 this->water(reg.zwoc()) + reg.pcowWoc() // Pcow(WOC) is really Pcgw(GWC) for gas/water cases
1159 };
1160 this->makeGasPressure(ic, reg, span);
1161 }
1162}
1163
1164template <class FluidSystem, class Region>
1165void PressureTable<FluidSystem, Region>::
1166equil_GOW(const Region& reg, const VSpan& span)
1167{
1168 // Datum depth in gas zone. Calculate phase pressure for gas first,
1169 // followed by oil and water if applicable.
1170
1171 if (! this->gasActive()) {
1172 throw std::invalid_argument {
1173 "Don't know how to interpret EQUIL datum depth in "
1174 "GAS zone in model without active gas phase"
1175 };
1176 }
1177
1178 {
1179 const auto ic = typename GPress::InitCond {
1180 reg.datum(), reg.pressure()
1181 };
1182
1183 this->makeGasPressure(ic, reg, span);
1184 }
1185
1186 if (this->oilActive()) {
1187 // Pcgo = Pg - Po => Po = Pg - Pcgo
1188 const auto ic = typename OPress::InitCond {
1189 reg.zgoc(),
1190 this->gas(reg.zgoc()) - reg.pcgoGoc()
1191 };
1192 this->makeOilPressure(ic, reg, span);
1193 }
1194
1195 if (this->waterActive() && this->oilActive()) {
1196 // Pcow = Po - Pw => Pw = Po - Pcow
1197 const auto ic = typename WPress::InitCond {
1198 reg.zwoc(),
1199 this->oil(reg.zwoc()) - reg.pcowWoc()
1200 };
1201
1202 this->makeWatPressure(ic, reg, span);
1203 } else if (this->waterActive() && !this->oilActive()) {
1204 // No oil phase set Pw = Pg - Pcgw
1205 const auto ic = typename WPress::InitCond {
1206 reg.zwoc(), // The WOC is really the GWC for gas/water cases
1207 this->gas(reg.zwoc()) - reg.pcowWoc() // Pcow(WOC) is really Pcgw(GWC) for gas/water cases
1208 };
1209 this->makeWatPressure(ic, reg, span);
1210 }
1211}
1212
1213template <class FluidSystem, class Region>
1214void PressureTable<FluidSystem, Region>::
1215equil_OWG(const Region& reg, const VSpan& span)
1216{
1217 // Datum depth in oil zone. Calculate phase pressure for oil first,
1218 // followed by gas and water if applicable.
1219
1220 if (! this->oilActive()) {
1221 throw std::invalid_argument {
1222 "Don't know how to interpret EQUIL datum depth in "
1223 "OIL zone in model without active oil phase"
1224 };
1225 }
1226
1227 {
1228 const auto ic = typename OPress::InitCond {
1229 reg.datum(), reg.pressure()
1230 };
1231
1232 this->makeOilPressure(ic, reg, span);
1233 }
1234
1235 if (this->waterActive()) {
1236 // Pcow = Po - Pw => Pw = Po - Pcow
1237 const auto ic = typename WPress::InitCond {
1238 reg.zwoc(),
1239 this->oil(reg.zwoc()) - reg.pcowWoc()
1240 };
1241
1242 this->makeWatPressure(ic, reg, span);
1243 }
1244
1245 if (this->gasActive()) {
1246 // Pcgo = Pg - Po => Pg = Po + Pcgo
1247 const auto ic = typename GPress::InitCond {
1248 reg.zgoc(),
1249 this->oil(reg.zgoc()) + reg.pcgoGoc()
1250 };
1251 this->makeGasPressure(ic, reg, span);
1252 }
1253}
1254
1255template <class FluidSystem, class Region>
1256void PressureTable<FluidSystem, Region>::
1257makeOilPressure(const typename OPress::InitCond& ic,
1258 const Region& reg,
1259 const VSpan& span)
1260{
1261 const auto drho = OilPressODE {
1262 reg.tempVdTable(), reg.dissolutionCalculator(),
1263 reg.pvtIdx(), this->gravity_
1264 };
1265
1266 this->oil_ = std::make_unique<OPress>(drho, ic, this->nsample_, span);
1267}
1268
1269template <class FluidSystem, class Region>
1270void PressureTable<FluidSystem, Region>::
1271makeGasPressure(const typename GPress::InitCond& ic,
1272 const Region& reg,
1273 const VSpan& span)
1274{
1275 const auto drho = GasPressODE {
1276 reg.tempVdTable(), reg.evaporationCalculator(), reg.waterEvaporationCalculator(),
1277 reg.pvtIdx(), this->gravity_
1278 };
1279
1280 this->gas_ = std::make_unique<GPress>(drho, ic, this->nsample_, span);
1281}
1282
1283template <class FluidSystem, class Region>
1284void PressureTable<FluidSystem, Region>::
1285makeWatPressure(const typename WPress::InitCond& ic,
1286 const Region& reg,
1287 const VSpan& span)
1288{
1289 const auto drho = WatPressODE {
1290 reg.tempVdTable(), reg.saltVdTable(), reg.pvtIdx(), this->gravity_
1291 };
1292
1293 this->wat_ = std::make_unique<WPress>(drho, ic, this->nsample_, span);
1294}
1295
1296}
1297
1298namespace DeckDependent {
1299
1300std::vector<EquilRecord>
1301getEquil(const EclipseState& state)
1302{
1303 const auto& init = state.getInitConfig();
1304
1305 if(!init.hasEquil()) {
1306 throw std::domain_error("Deck does not provide equilibration data.");
1307 }
1308
1309 const auto& equil = init.getEquil();
1310 return { equil.begin(), equil.end() };
1311}
1312
1313template<class GridView>
1314std::vector<int>
1315equilnum(const EclipseState& eclipseState,
1316 const GridView& gridview)
1317{
1318 std::vector<int> eqlnum(gridview.size(0), 0);
1319
1320 if (eclipseState.fieldProps().has_int("EQLNUM")) {
1321 // EQLNUM is given on the (unrefined) input grid, but the equilibration
1322 // works on the leaf grid. With LGRs the leaf has more cells than the
1323 // input grid and a different ordering, so copying the input array
1324 // directly into a leaf-sized vector misaligns it (and leaves refined
1325 // cells at region 1). LookUpData maps each leaf cell to its input-grid
1326 // origin - a refined cell inherits its parent cell's EQLNUM - which for
1327 // an unrefined grid reduces to the identity, so non-LGR cases are
1328 // unchanged. needsTranslation == true applies the 1-based -> 0-based
1329 // shift previously done by the transform.
1330 const LookUpData<typename GridView::Grid, GridView> lookUpData(gridview);
1331 eqlnum = lookUpData.template assignFieldPropsIntOnLeaf<int>(
1332 eclipseState.fieldProps(), "EQLNUM", /*needsTranslation=*/true);
1333 }
1335 const int num_regions = eclipseState.getTableManager().getEqldims().getNumEquilRegions();
1336 if (std::ranges::any_of(eqlnum, [num_regions](int n){return n >= num_regions;})) {
1337 throw std::runtime_error("Values larger than maximum Equil regions " +
1338 std::to_string(num_regions) + " provided in EQLNUM");
1339 }
1340 if (std::ranges::any_of(eqlnum, [](int n){return n < 0;})) {
1341 throw std::runtime_error("zero or negative values provided in EQLNUM");
1342 }
1343 OPM_END_PARALLEL_TRY_CATCH("Invalied EQLNUM numbers: ", gridview.comm());
1344
1345 return eqlnum;
1346}
1347
1348template<class FluidSystem,
1349 class Grid,
1350 class GridView,
1351 class ElementMapper,
1352 class CartesianIndexMapper>
1353template<class MaterialLawManager>
1354InitialStateComputer<FluidSystem,
1355 Grid,
1356 GridView,
1357 ElementMapper,
1358 CartesianIndexMapper>::
1359InitialStateComputer(MaterialLawManager& materialLawManager,
1360 const EclipseState& eclipseState,
1361 const Grid& grid,
1362 const GridView& gridView,
1363 const CartesianIndexMapper& cartMapper,
1364 const Scalar grav,
1365 const int num_pressure_points,
1366 const bool applySwatInit)
1367 : temperature_(grid.size(/*codim=*/0), eclipseState.getTableManager().rtemp()),
1368 saltConcentration_(grid.size(/*codim=*/0)),
1369 saltSaturation_(grid.size(/*codim=*/0)),
1370 pp_(FluidSystem::numPhases,
1371 std::vector<Scalar>(grid.size(/*codim=*/0))),
1372 sat_(FluidSystem::numPhases,
1373 std::vector<Scalar>(grid.size(/*codim=*/0))),
1374 rs_(grid.size(/*codim=*/0)),
1375 rv_(grid.size(/*codim=*/0)),
1376 rvw_(grid.size(/*codim=*/0)),
1377 cartesianIndexMapper_(cartMapper),
1378 num_pressure_points_(num_pressure_points)
1379{
1380 //Check for presence of kw SWATINIT
1381 if (applySwatInit) {
1382 if (eclipseState.fieldProps().has_double("SWATINIT")) {
1383 // SWATINIT is given on the (unrefined) input grid but is consumed per
1384 // leaf cell; with LGRs the leaf is larger and reordered, so map it
1385 // onto the leaf via LookUpData (a refined cell inherits its parent
1386 // cell's value; identity without LGRs).
1387 const LookUpData<Grid, GridView> lookUpData(gridView);
1388 auto input =
1389 lookUpData.assignFieldPropsDoubleOnLeaf(eclipseState.fieldProps(), "SWATINIT");
1390 if constexpr (std::is_same_v<Scalar, double>) {
1391 swatInit_ = std::move(input);
1392 } else {
1393 swatInit_.assign(input.begin(), input.end());
1394 }
1395 }
1396 }
1397
1398 // Querry cell depth, cell top-bottom.
1399 // numerical aquifer cells might be specified with different depths.
1400 const auto& num_aquifers = eclipseState.aquifer().numericalAquifers();
1401 updateCellProps_(gridView, num_aquifers);
1402
1403 // Get the equilibration records.
1404 const std::vector<EquilRecord> rec = getEquil(eclipseState);
1405 const auto& tables = eclipseState.getTableManager();
1406 // Create (inverse) region mapping.
1407 const RegionMapping<> eqlmap(equilnum(eclipseState, gridView));
1408 const int invalidRegion = -1;
1409 regionPvtIdx_.resize(rec.size(), invalidRegion);
1410 setRegionPvtIdx(eclipseState, gridView, eqlmap);
1411
1412 // Create Rs functions.
1413 rsFunc_.reserve(rec.size());
1414
1415 auto getArray = [](const std::vector<double>& input)
1416 {
1417 if constexpr (std::is_same_v<Scalar,double>) {
1418 return input;
1419 } else {
1420 std::vector<Scalar> output;
1421 output.resize(input.size());
1422 std::ranges::copy(input, output.begin());
1423 return output;
1424 }
1425 };
1426
1427 if (FluidSystem::enableDissolvedGas()) {
1428 for (std::size_t i = 0; i < rec.size(); ++i) {
1429 if (eqlmap.cells(i).empty()) {
1430 rsFunc_.push_back(std::shared_ptr<Miscibility::RsVD<FluidSystem>>());
1431 continue;
1432 }
1433 const int pvtIdx = regionPvtIdx_[i];
1434 if (!rec[i].liveOilInitConstantRs()) {
1435 const TableContainer& rsvdTables = tables.getRsvdTables();
1436 const TableContainer& pbvdTables = tables.getPbvdTables();
1437 if (rsvdTables.size() > 0) {
1438 const RsvdTable& rsvdTable = rsvdTables.getTable<RsvdTable>(i);
1439 auto depthColumn = getArray(rsvdTable.getColumn("DEPTH").vectorCopy());
1440 auto rsColumn = getArray(rsvdTable.getColumn("RS").vectorCopy());
1441 rsFunc_.push_back(std::make_shared<Miscibility::RsVD<FluidSystem>>(pvtIdx,
1442 depthColumn, rsColumn));
1443 } else if (pbvdTables.size() > 0) {
1444 const PbvdTable& pbvdTable = pbvdTables.getTable<PbvdTable>(i);
1445 auto depthColumn = getArray(pbvdTable.getColumn("DEPTH").vectorCopy());
1446 auto pbubColumn = getArray(pbvdTable.getColumn("PBUB").vectorCopy());
1447 rsFunc_.push_back(std::make_shared<Miscibility::PBVD<FluidSystem>>(pvtIdx,
1448 depthColumn, pbubColumn));
1449
1450 } else {
1451 throw std::runtime_error("Cannot initialise: RSVD or PBVD table not available.");
1452 }
1453
1454 }
1455 else {
1456 if (rec[i].gasOilContactDepth() != rec[i].datumDepth()) {
1457 throw std::runtime_error("Cannot initialise: when no explicit RSVD table is given, \n"
1458 "datum depth must be at the gas-oil-contact. "
1459 "In EQUIL region "+std::to_string(i + 1)+" (counting from 1), this does not hold.");
1460 }
1461 const Scalar pContact = rec[i].datumDepthPressure();
1462 const Scalar TContact = 273.15 + 20; // standard temperature for now
1463 rsFunc_.push_back(std::make_shared<Miscibility::RsSatAtContact<FluidSystem>>(pvtIdx, pContact, TContact));
1464 }
1465 }
1466 }
1467 else if (FluidSystem::enableConstantRs() && tables.hasTables("RSCONST")) {
1468 const auto& rsconstTables = tables.getRsconstTables();
1469
1470 if (rsconstTables.empty()) {
1471 for (std::size_t i = 0; i < rec.size(); ++i) {
1472 // Normal dead oil (no dissolved gas and rsconst)
1473 rsFunc_.push_back(std::make_shared<Miscibility::NoMixing<Scalar>>());
1474 }
1475 }
1476 else {
1477 const auto& rsconstTable = rsconstTables.getTable<RsconstTable>(0);
1478
1479 const auto rsConst = rsconstTable.getRsColumn().front();
1480 const auto pBub = rsconstTable.getPbubColumn().front();
1481
1482 const auto& units = eclipseState.getUnits();
1483
1484 OpmLog::info(fmt::format("Using RSCONST keyword: Rs = {:.2} [{}], Pb = {:.2} [{}]",
1485 units.from_si(UnitSystem::measure::gas_oil_ratio, rsConst),
1486 units.name (UnitSystem::measure::gas_oil_ratio),
1487 units.from_si(UnitSystem::measure::pressure, pBub),
1488 units.name (UnitSystem::measure::pressure)));
1489
1490 for (std::size_t i = 0; i < rec.size(); ++i) {
1491 rsFunc_.push_back(std::make_shared<Miscibility::RsConst<FluidSystem>>(rsConst, pBub));
1492 }
1493 }
1494 }
1495 else {
1496 for (std::size_t i = 0; i < rec.size(); ++i) {
1497 // Normal dead oil (no dissolved gas and rsconst)
1498 rsFunc_.push_back(std::make_shared<Miscibility::NoMixing<Scalar>>());
1499 }
1500 }
1501
1502 rvFunc_.reserve(rec.size());
1503 if (FluidSystem::enableVaporizedOil()) {
1504 for (std::size_t i = 0; i < rec.size(); ++i) {
1505 if (eqlmap.cells(i).empty()) {
1506 rvFunc_.push_back(std::shared_ptr<Miscibility::RvVD<FluidSystem>>());
1507 continue;
1508 }
1509 const int pvtIdx = regionPvtIdx_[i];
1510 if (!rec[i].wetGasInitConstantRv()) {
1511 const TableContainer& rvvdTables = tables.getRvvdTables();
1512 const TableContainer& pdvdTables = tables.getPdvdTables();
1513
1514 if (rvvdTables.size() > 0) {
1515 const RvvdTable& rvvdTable = rvvdTables.getTable<RvvdTable>(i);
1516 auto depthColumn = getArray(rvvdTable.getColumn("DEPTH").vectorCopy());
1517 auto rvColumn = getArray(rvvdTable.getColumn("RV").vectorCopy());
1518 rvFunc_.push_back(std::make_shared<Miscibility::RvVD<FluidSystem>>(pvtIdx,
1519 depthColumn, rvColumn));
1520 } else if (pdvdTables.size() > 0) {
1521 const PdvdTable& pdvdTable = pdvdTables.getTable<PdvdTable>(i);
1522 auto depthColumn = getArray(pdvdTable.getColumn("DEPTH").vectorCopy());
1523 auto pdewColumn = getArray(pdvdTable.getColumn("PDEW").vectorCopy());
1524 rvFunc_.push_back(std::make_shared<Miscibility::PDVD<FluidSystem>>(pvtIdx,
1525 depthColumn, pdewColumn));
1526 } else {
1527 throw std::runtime_error("Cannot initialise: RVVD or PDCD table not available.");
1528 }
1529 }
1530 else {
1531 if (rec[i].gasOilContactDepth() != rec[i].datumDepth()) {
1532 throw std::runtime_error(
1533 "Cannot initialise: when no explicit RVVD table is given, \n"
1534 "datum depth must be at the gas-oil-contact. "
1535 "In EQUIL region "+std::to_string(i + 1)+" (counting from 1), this does not hold.");
1536 }
1537 const Scalar pContact = rec[i].datumDepthPressure() + rec[i].gasOilContactCapillaryPressure();
1538 const Scalar TContact = 273.15 + 20; // standard temperature for now
1539 rvFunc_.push_back(std::make_shared<Miscibility::RvSatAtContact<FluidSystem>>(pvtIdx,pContact, TContact));
1540 }
1541 }
1542 }
1543 else {
1544 for (std::size_t i = 0; i < rec.size(); ++i) {
1545 rvFunc_.push_back(std::make_shared<Miscibility::NoMixing<Scalar>>());
1546 }
1547 }
1548
1549 rvwFunc_.reserve(rec.size());
1550 if (FluidSystem::enableVaporizedWater()) {
1551 for (std::size_t i = 0; i < rec.size(); ++i) {
1552 if (eqlmap.cells(i).empty()) {
1553 rvwFunc_.push_back(std::shared_ptr<Miscibility::RvwVD<FluidSystem>>());
1554 continue;
1555 }
1556 const int pvtIdx = regionPvtIdx_[i];
1557 if (!rec[i].humidGasInitConstantRvw()) {
1558 const TableContainer& rvwvdTables = tables.getRvwvdTables();
1559
1560 if (rvwvdTables.size() > 0) {
1561 const RvwvdTable& rvwvdTable = rvwvdTables.getTable<RvwvdTable>(i);
1562 auto depthColumn = getArray(rvwvdTable.getColumn("DEPTH").vectorCopy());
1563 auto rvwvdColumn = getArray(rvwvdTable.getColumn("RVWVD").vectorCopy());
1564 rvwFunc_.push_back(std::make_shared<Miscibility::RvwVD<FluidSystem>>(pvtIdx,
1565 depthColumn, rvwvdColumn));
1566 } else {
1567 throw std::runtime_error("Cannot initialise: RVWVD table not available.");
1568 }
1569 }
1570 else {
1571 const auto oilActive = FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
1572 if (oilActive) {
1573 if (rec[i].gasOilContactDepth() != rec[i].datumDepth()) {
1574 rvwFunc_.push_back(std::make_shared<Miscibility::NoMixing<Scalar>>());
1575 const auto msg = "No explicit RVWVD table is given for EQUIL region " + std::to_string(i + 1) +". \n"
1576 "and datum depth is not at the gas-oil-contact. \n"
1577 "Rvw is set to 0.0 in all cells. \n";
1578 OpmLog::warning(msg);
1579 } else {
1580 // pg = po + Pcgo = po + (pg - po)
1581 // for gas-condensate with initial no oil zone: water-oil contact depth (OWC) equal gas-oil contact depth (GOC)
1582 const Scalar pContact = rec[i].datumDepthPressure() + rec[i].gasOilContactCapillaryPressure();
1583 const Scalar TContact = 273.15 + 20; // standard temperature for now
1584 rvwFunc_.push_back(std::make_shared<Miscibility::RvwSatAtContact<FluidSystem>>(pvtIdx,pContact, TContact));
1585 }
1586 }
1587 else {
1588 // two-phase gas-water sytem: water-oil contact depth is taken equal to gas-water contact depth (GWC)
1589 // and water-oil capillary pressure (Pcwo) is taken equal to gas-water capillary pressure (Pcgw) at GWC
1590 if (rec[i].waterOilContactDepth() != rec[i].datumDepth()) {
1591 rvwFunc_.push_back(std::make_shared<Miscibility::NoMixing<Scalar>>());
1592 const auto msg = "No explicit RVWVD table is given for EQUIL region " + std::to_string(i + 1) +". \n"
1593 "and datum depth is not at the gas-water-contact. \n"
1594 "Rvw is set to 0.0 in all cells. \n";
1595 OpmLog::warning(msg);
1596 } else {
1597 // pg = pw + Pcgw = pw + (pg - pw)
1598 const Scalar pContact = rec[i].datumDepthPressure() + rec[i].waterOilContactCapillaryPressure();
1599 const Scalar TContact = 273.15 + 20; // standard temperature for now
1600 rvwFunc_.push_back(std::make_shared<Miscibility::RvwSatAtContact<FluidSystem>>(pvtIdx,pContact, TContact));
1601 }
1602 }
1603 }
1604 }
1605 }
1606 else {
1607 for (std::size_t i = 0; i < rec.size(); ++i) {
1608 rvwFunc_.push_back(std::make_shared<Miscibility::NoMixing<Scalar>>());
1609 }
1610 }
1611
1612 // EXTRACT the initial temperature
1613 updateInitialTemperature_(eclipseState, eqlmap);
1614
1615 // EXTRACT the initial salt concentration
1616 updateInitialSaltConcentration_(eclipseState, eqlmap);
1617
1618 // EXTRACT the initial salt saturation
1619 updateInitialSaltSaturation_(eclipseState, eqlmap);
1620
1621 // Compute pressures, saturations, rs and rv factors.
1622 const auto& comm = grid.comm();
1623 calcPressSatRsRv(eqlmap, rec, materialLawManager, gridView, comm, grav);
1624
1625 // modify the pressure and saturation for numerical aquifer cells
1626 applyNumericalAquifers_(gridView, num_aquifers,
1627 eclipseState.runspec().co2Storage() ||
1628 eclipseState.runspec().h2Storage());
1629
1630 // Modify oil pressure in no-oil regions so that the pressures of present phases can
1631 // be recovered from the oil pressure and capillary relations.
1632}
1633
1634template<class FluidSystem,
1635 class Grid,
1636 class GridView,
1637 class ElementMapper,
1638 class CartesianIndexMapper>
1639template<class RMap>
1640void InitialStateComputer<FluidSystem,
1641 Grid,
1642 GridView,
1643 ElementMapper,
1644 CartesianIndexMapper>::
1645updateInitialTemperature_(const EclipseState& eclState, const RMap& reg)
1646{
1647 const int numEquilReg = rsFunc_.size();
1648 tempVdTable_.resize(numEquilReg);
1649 const auto& tables = eclState.getTableManager();
1650 if (!tables.hasTables("RTEMPVD")) {
1651 std::vector<Scalar> x = {0.0,1.0};
1652 std::vector<Scalar> y = {static_cast<Scalar>(tables.rtemp()),
1653 static_cast<Scalar>(tables.rtemp())};
1654 for (auto& table : this->tempVdTable_) {
1655 table.setXYContainers(x, y);
1656 }
1657 } else {
1658 const TableContainer& tempvdTables = tables.getRtempvdTables();
1659 for (std::size_t i = 0; i < tempvdTables.size(); ++i) {
1660 const RtempvdTable& tempvdTable = tempvdTables.getTable<RtempvdTable>(i);
1661 tempVdTable_[i].setXYContainers(tempvdTable.getDepthColumn(), tempvdTable.getTemperatureColumn());
1662 const auto& cells = reg.cells(i);
1663 for (const auto& cell : cells) {
1664 const Scalar depth = cellCenterDepth_[cell];
1665 this->temperature_[cell] = tempVdTable_[i].eval(depth, /*extrapolate=*/true);
1666 }
1667 }
1668 }
1669}
1670
1671template<class FluidSystem,
1672 class Grid,
1673 class GridView,
1674 class ElementMapper,
1675 class CartesianIndexMapper>
1676template<class RMap>
1677void InitialStateComputer<FluidSystem,
1678 Grid,
1679 GridView,
1680 ElementMapper,
1681 CartesianIndexMapper>::
1682updateInitialSaltConcentration_(const EclipseState& eclState, const RMap& reg)
1683{
1684 const int numEquilReg = rsFunc_.size();
1685 saltVdTable_.resize(numEquilReg);
1686 const auto& tables = eclState.getTableManager();
1687 const TableContainer& saltvdTables = tables.getSaltvdTables();
1688
1689 // If no saltvd table is given, we create a trivial table for the density calculations
1690 if (saltvdTables.empty()) {
1691 std::vector<Scalar> x = {0.0,1.0};
1692 std::vector<Scalar> y = {0.0,0.0};
1693 for (auto& table : this->saltVdTable_) {
1694 table.setXYContainers(x, y);
1695 }
1696 } else {
1697 for (std::size_t i = 0; i < saltvdTables.size(); ++i) {
1698 const SaltvdTable& saltvdTable = saltvdTables.getTable<SaltvdTable>(i);
1699 saltVdTable_[i].setXYContainers(saltvdTable.getDepthColumn(), saltvdTable.getSaltColumn());
1700
1701 const auto& cells = reg.cells(i);
1702 for (const auto& cell : cells) {
1703 const Scalar depth = cellCenterDepth_[cell];
1704 this->saltConcentration_[cell] = saltVdTable_[i].eval(depth, /*extrapolate=*/true);
1705 }
1706 }
1707 }
1708}
1709
1710template<class FluidSystem,
1711 class Grid,
1712 class GridView,
1713 class ElementMapper,
1714 class CartesianIndexMapper>
1715template<class RMap>
1716void InitialStateComputer<FluidSystem,
1717 Grid,
1718 GridView,
1719 ElementMapper,
1720 CartesianIndexMapper>::
1721updateInitialSaltSaturation_(const EclipseState& eclState, const RMap& reg)
1722{
1723 const int numEquilReg = rsFunc_.size();
1724 saltpVdTable_.resize(numEquilReg);
1725 const auto& tables = eclState.getTableManager();
1726 const TableContainer& saltpvdTables = tables.getSaltpvdTables();
1727
1728 for (std::size_t i = 0; i < saltpvdTables.size(); ++i) {
1729 const SaltpvdTable& saltpvdTable = saltpvdTables.getTable<SaltpvdTable>(i);
1730 saltpVdTable_[i].setXYContainers(saltpvdTable.getDepthColumn(), saltpvdTable.getSaltpColumn());
1731
1732 const auto& cells = reg.cells(i);
1733 for (const auto& cell : cells) {
1734 const Scalar depth = cellCenterDepth_[cell];
1735 this->saltSaturation_[cell] = saltpVdTable_[i].eval(depth, /*extrapolate=*/true);
1736 }
1737 }
1738}
1739
1740template<class FluidSystem,
1741 class Grid,
1742 class GridView,
1743 class ElementMapper,
1744 class CartesianIndexMapper>
1745void InitialStateComputer<FluidSystem,
1746 Grid,
1747 GridView,
1748 ElementMapper,
1749 CartesianIndexMapper>::
1750updateCellProps_(const GridView& gridView,
1751 const NumericalAquifers& aquifer)
1752{
1753 ElementMapper elemMapper(gridView, Dune::mcmgElementLayout());
1754 int numElements = gridView.size(/*codim=*/0);
1755 cellCenterDepth_.resize(numElements);
1756 cellCenterXY_.resize(numElements);
1757 cellCorners_.resize(numElements);
1758 cellZSpan_.resize(numElements);
1759 cellZMinMax_.resize(numElements);
1760
1761 auto elemIt = gridView.template begin</*codim=*/0>();
1762 const auto& elemEndIt = gridView.template end</*codim=*/0>();
1763 const auto num_aqu_cells = aquifer.allAquiferCells();
1764 for (; elemIt != elemEndIt; ++elemIt) {
1765 const Element& element = *elemIt;
1766 const unsigned int elemIdx = elemMapper.index(element);
1767 cellCenterDepth_[elemIdx] = Details::cellCenterDepth<Scalar>(element);
1768 cellCenterXY_[elemIdx] = Details::cellCenterXY<Scalar>(element);
1769 cellCorners_[elemIdx] = Details::getCellCornerXY<Scalar>(element);
1770 const auto cartIx = cartesianIndexMapper_.cartesianIndex(elemIdx);
1771 cellZSpan_[elemIdx] = Details::cellZSpan<Scalar>(element);
1772 cellZMinMax_[elemIdx] = Details::cellZMinMax<Scalar>(element);
1773 if (!num_aqu_cells.empty()) {
1774 const auto search = num_aqu_cells.find(cartIx);
1775 if (search != num_aqu_cells.end()) {
1776 const auto* aqu_cell = num_aqu_cells.at(cartIx);
1777 const Scalar depth_change_num_aqu = aqu_cell->depth - cellCenterDepth_[elemIdx];
1778 cellCenterDepth_[elemIdx] += depth_change_num_aqu;
1779 cellZSpan_[elemIdx].first += depth_change_num_aqu;
1780 cellZSpan_[elemIdx].second += depth_change_num_aqu;
1781 cellZMinMax_[elemIdx].first += depth_change_num_aqu;
1782 cellZMinMax_[elemIdx].second += depth_change_num_aqu;
1783 }
1784 }
1785 }
1786}
1787
1788template<class FluidSystem,
1789 class Grid,
1790 class GridView,
1791 class ElementMapper,
1792 class CartesianIndexMapper>
1793void InitialStateComputer<FluidSystem,
1794 Grid,
1795 GridView,
1796 ElementMapper,
1797 CartesianIndexMapper>::
1798applyNumericalAquifers_(const GridView& gridView,
1799 const NumericalAquifers& aquifer,
1800 const bool co2store_or_h2store)
1801{
1802 const auto num_aqu_cells = aquifer.allAquiferCells();
1803 if (num_aqu_cells.empty()) return;
1804
1805 // Check if water phase is active, or in the case of CO2STORE and H2STORE, water is modelled as oil phase
1806 bool oil_as_brine = co2store_or_h2store && FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx);
1807 const auto watPos = oil_as_brine? FluidSystem::oilPhaseIdx : FluidSystem::waterPhaseIdx;
1808 if (!FluidSystem::phaseIsActive(watPos)){
1809 throw std::logic_error { "Water phase has to be active for numerical aquifer case" };
1810 }
1811
1812 ElementMapper elemMapper(gridView, Dune::mcmgElementLayout());
1813 auto elemIt = gridView.template begin</*codim=*/0>();
1814 const auto& elemEndIt = gridView.template end</*codim=*/0>();
1815 const auto oilPos = FluidSystem::oilPhaseIdx;
1816 const auto gasPos = FluidSystem::gasPhaseIdx;
1817 for (; elemIt != elemEndIt; ++elemIt) {
1818 const Element& element = *elemIt;
1819 const unsigned int elemIdx = elemMapper.index(element);
1820 const auto cartIx = cartesianIndexMapper_.cartesianIndex(elemIdx);
1821 const auto search = num_aqu_cells.find(cartIx);
1822 if (search != num_aqu_cells.end()) {
1823 // numerical aquifer cells are filled with water initially
1824 this->sat_[watPos][elemIdx] = 1.;
1825
1826 if (!co2store_or_h2store && FluidSystem::phaseIsActive(oilPos)) {
1827 this->sat_[oilPos][elemIdx] = 0.;
1828 }
1829
1830 if (FluidSystem::phaseIsActive(gasPos)) {
1831 this->sat_[gasPos][elemIdx] = 0.;
1832 }
1833 const auto* aqu_cell = num_aqu_cells.at(cartIx);
1834 const auto msg = fmt::format("FOR AQUIFER CELL AT ({}, {}, {}) OF NUMERICAL "
1835 "AQUIFER {}, WATER SATURATION IS SET TO BE UNITY",
1836 aqu_cell->I+1, aqu_cell->J+1, aqu_cell->K+1, aqu_cell->aquifer_id);
1837 OpmLog::info(msg);
1838
1839 // if pressure is specified for numerical aquifers, we use these pressure values
1840 // for numerical aquifer cells
1841 if (aqu_cell->init_pressure) {
1842 const Scalar pres = *(aqu_cell->init_pressure);
1843 this->pp_[watPos][elemIdx] = pres;
1844 if (FluidSystem::phaseIsActive(gasPos)) {
1845 this->pp_[gasPos][elemIdx] = pres;
1846 }
1847 if (FluidSystem::phaseIsActive(oilPos)) {
1848 this->pp_[oilPos][elemIdx] = pres;
1849 }
1850 }
1851 }
1852 }
1853}
1854
1855template<class FluidSystem,
1856 class Grid,
1857 class GridView,
1858 class ElementMapper,
1859 class CartesianIndexMapper>
1860template<class RMap>
1861void InitialStateComputer<FluidSystem,
1862 Grid,
1863 GridView,
1864 ElementMapper,
1865 CartesianIndexMapper>::
1866setRegionPvtIdx(const EclipseState& eclState, const GridView& gridView, const RMap& reg)
1867{
1868 // PVTNUM is given on the (unrefined) input grid, but reg.cells(r) are leaf
1869 // cell indices. With LGRs the leaf has more cells (and a different ordering)
1870 // than the input grid, so indexing the input PVTNUM array by a leaf index is
1871 // wrong (and out of bounds for refined cells). Map PVTNUM onto the leaf via
1872 // LookUpData - a refined cell inherits its parent cell's PVTNUM - which is
1873 // the identity without LGRs. needsTranslation == true applies the 1-based ->
1874 // 0-based shift previously done explicitly.
1875 const LookUpData<typename GridView::Grid, GridView> lookUpData(gridView);
1876 const auto pvtnumData = lookUpData.template assignFieldPropsIntOnLeaf<int>(
1877 eclState.fieldProps(), "PVTNUM", /*needsTranslation=*/true);
1878
1879 for (const auto& r : reg.activeRegions()) {
1880 const auto& cells = reg.cells(r);
1881 regionPvtIdx_[r] = pvtnumData[*cells.begin()];
1882 }
1883}
1884
1885template<class FluidSystem,
1886 class Grid,
1887 class GridView,
1888 class ElementMapper,
1889 class CartesianIndexMapper>
1890template<class RMap, class MaterialLawManager, class Comm>
1891void InitialStateComputer<FluidSystem,
1892 Grid,
1893 GridView,
1894 ElementMapper,
1895 CartesianIndexMapper>::
1896calcPressSatRsRv(const RMap& reg,
1897 const std::vector<EquilRecord>& rec,
1898 MaterialLawManager& materialLawManager,
1899 const GridView& gridView,
1900 const Comm& comm,
1901 const Scalar grav)
1902{
1903 using PhaseSat = Details::PhaseSaturations<
1904 MaterialLawManager, FluidSystem, EquilReg<Scalar>, typename RMap::CellId
1905 >;
1906
1907 auto ptable = Details::PressureTable<FluidSystem, EquilReg<Scalar>>{ grav, this->num_pressure_points_ };
1908 auto psat = PhaseSat { materialLawManager, this->swatInit_ };
1909 auto vspan = std::array<Scalar, 2>{};
1910
1911 std::vector<int> regionIsEmpty(rec.size(), 0);
1912 for (std::size_t r = 0; r < rec.size(); ++r) {
1913 const auto& cells = reg.cells(r);
1914
1915 Details::verticalExtent(cells, cellZMinMax_, comm, vspan);
1916
1917 const auto acc = rec[r].initializationTargetAccuracy();
1918 if (acc > 0) {
1919 // The grid blocks are treated as being tilted
1920 // First check if the region has cells
1921 if (cells.empty()) {
1922 regionIsEmpty[r] = 1;
1923 continue;
1924 }
1925 const auto eqreg = EquilReg {
1926 rec[r], this->rsFunc_[r], this->rvFunc_[r], this->rvwFunc_[r],
1927 this->tempVdTable_[r], this->saltVdTable_[r], this->regionPvtIdx_[r]
1928 };
1929 // Ensure contacts are within the span
1930 vspan[0] = std::min(vspan[0], std::min(eqreg.zgoc(), eqreg.zwoc()));
1931 vspan[1] = std::max(vspan[1], std::max(eqreg.zgoc(), eqreg.zwoc()));
1932 ptable.equilibrate(eqreg, vspan);
1933 // For titled blocks, we can use a simple weightening based on title of the grid
1934 // this->equilibrateTiltedFaultBlockSimple(cells, eqreg, gridView, acc, ptable, psat);
1935 this->equilibrateTiltedFaultBlock(cells, eqreg, gridView, acc, ptable, psat);
1936 }
1937 else if (acc == 0) {
1938 if (cells.empty()) {
1939 regionIsEmpty[r] = 1;
1940 continue;
1941 }
1942 const auto eqreg = EquilReg {
1943 rec[r], this->rsFunc_[r], this->rvFunc_[r], this->rvwFunc_[r],
1944 this->tempVdTable_[r], this->saltVdTable_[r], this->regionPvtIdx_[r]
1945 };
1946 vspan[0] = std::min(vspan[0], std::min(eqreg.zgoc(), eqreg.zwoc()));
1947 vspan[1] = std::max(vspan[1], std::max(eqreg.zgoc(), eqreg.zwoc()));
1948 ptable.equilibrate(eqreg, vspan);
1949 // Centre-point method
1950 this->equilibrateCellCentres(cells, eqreg, ptable, psat);
1951 }
1952 else if (acc < 0) {
1953 if (cells.empty()) {
1954 regionIsEmpty[r] = 1;
1955 continue;
1956 }
1957 const auto eqreg = EquilReg {
1958 rec[r], this->rsFunc_[r], this->rvFunc_[r], this->rvwFunc_[r],
1959 this->tempVdTable_[r], this->saltVdTable_[r], this->regionPvtIdx_[r]
1960 };
1961 vspan[0] = std::min(vspan[0], std::min(eqreg.zgoc(), eqreg.zwoc()));
1962 vspan[1] = std::max(vspan[1], std::max(eqreg.zgoc(), eqreg.zwoc()));
1963 ptable.equilibrate(eqreg, vspan);
1964 // Horizontal subdivision
1965 this->equilibrateHorizontal(cells, eqreg, -acc, ptable, psat);
1966 }
1967 }
1968 comm.min(regionIsEmpty.data(),regionIsEmpty.size());
1969 if (comm.rank() == 0) {
1970 for (std::size_t r = 0; r < rec.size(); ++r) {
1971 if (regionIsEmpty[r]) //region is empty on all partitions
1972 OpmLog::warning("Equilibration region " + std::to_string(r + 1)
1973 + " has no active cells");
1974 }
1975 }
1976}
1977
1978template<class FluidSystem,
1979 class Grid,
1980 class GridView,
1981 class ElementMapper,
1982 class CartesianIndexMapper>
1983template<class CellRange, class EquilibrationMethod>
1984void InitialStateComputer<FluidSystem,
1985 Grid,
1986 GridView,
1987 ElementMapper,
1988 CartesianIndexMapper>::
1989cellLoop(const CellRange& cells,
1990 EquilibrationMethod&& eqmethod)
1991{
1992 const auto oilPos = FluidSystem::oilPhaseIdx;
1993 const auto gasPos = FluidSystem::gasPhaseIdx;
1994 const auto watPos = FluidSystem::waterPhaseIdx;
1995
1996 const auto oilActive = FluidSystem::phaseIsActive(oilPos);
1997 const auto gasActive = FluidSystem::phaseIsActive(gasPos);
1998 const auto watActive = FluidSystem::phaseIsActive(watPos);
1999
2000 auto pressures = Details::PhaseQuantityValue<Scalar>{};
2001 auto saturations = Details::PhaseQuantityValue<Scalar>{};
2002 Scalar Rs = 0.0;
2003 Scalar Rv = 0.0;
2004 Scalar Rvw = 0.0;
2005
2006 for (const auto& cell : cells) {
2007 eqmethod(cell, pressures, saturations, Rs, Rv, Rvw);
2008
2009 if (oilActive) {
2010 this->pp_ [oilPos][cell] = pressures.oil;
2011 this->sat_[oilPos][cell] = saturations.oil;
2012 }
2013
2014 if (gasActive) {
2015 this->pp_ [gasPos][cell] = pressures.gas;
2016 this->sat_[gasPos][cell] = saturations.gas;
2017 }
2018
2019 if (watActive) {
2020 this->pp_ [watPos][cell] = pressures.water;
2021 this->sat_[watPos][cell] = saturations.water;
2022 }
2023
2024 if (oilActive && gasActive) {
2025 this->rs_[cell] = Rs;
2026 this->rv_[cell] = Rv;
2027 }
2028
2029 if (watActive && gasActive) {
2030 this->rvw_[cell] = Rvw;
2031 }
2032 }
2033}
2034
2035template<class FluidSystem,
2036 class Grid,
2037 class GridView,
2038 class ElementMapper,
2039 class CartesianIndexMapper>
2040template<class CellRange, class PressTable, class PhaseSat>
2041void InitialStateComputer<FluidSystem,
2042 Grid,
2043 GridView,
2044 ElementMapper,
2045 CartesianIndexMapper>::
2046equilibrateCellCentres(const CellRange& cells,
2047 const EquilReg<Scalar>& eqreg,
2048 const PressTable& ptable,
2049 PhaseSat& psat)
2050{
2051 using CellPos = typename PhaseSat::Position;
2052 using CellID = std::remove_cv_t<std::remove_reference_t<
2053 decltype(std::declval<CellPos>().cell)>>;
2054 this->cellLoop(cells, [this, &eqreg, &ptable, &psat]
2055 (const CellID cell,
2056 Details::PhaseQuantityValue<Scalar>& pressures,
2057 Details::PhaseQuantityValue<Scalar>& saturations,
2058 Scalar& Rs,
2059 Scalar& Rv,
2060 Scalar& Rvw) -> void
2061 {
2062 const auto pos = CellPos {
2063 cell, cellCenterDepth_[cell]
2064 };
2065
2066 saturations = psat.deriveSaturations(pos, eqreg, ptable);
2067 pressures = psat.correctedPhasePressures();
2068
2069 const auto temp = this->temperature_[cell];
2070
2071 Rs = eqreg.dissolutionCalculator()
2072 (pos.depth, pressures.oil, temp, saturations.gas);
2073
2074 Rv = eqreg.evaporationCalculator()
2075 (pos.depth, pressures.gas, temp, saturations.oil);
2076
2077 Rvw = eqreg.waterEvaporationCalculator()
2078 (pos.depth, pressures.gas, temp, saturations.water);
2079 });
2080}
2081
2082template<class FluidSystem,
2083 class Grid,
2084 class GridView,
2085 class ElementMapper,
2086 class CartesianIndexMapper>
2087template<class CellRange, class PressTable, class PhaseSat>
2088void InitialStateComputer<FluidSystem,
2089 Grid,
2090 GridView,
2091 ElementMapper,
2092 CartesianIndexMapper>::
2093equilibrateHorizontal(const CellRange& cells,
2094 const EquilReg<Scalar>& eqreg,
2095 const int acc,
2096 const PressTable& ptable,
2097 PhaseSat& psat)
2098{
2099 using CellPos = typename PhaseSat::Position;
2100 using CellID = std::remove_cv_t<std::remove_reference_t<
2101 decltype(std::declval<CellPos>().cell)>>;
2102
2103 this->cellLoop(cells, [this, acc, &eqreg, &ptable, &psat]
2104 (const CellID cell,
2105 Details::PhaseQuantityValue<Scalar>& pressures,
2106 Details::PhaseQuantityValue<Scalar>& saturations,
2107 Scalar& Rs,
2108 Scalar& Rv,
2109 Scalar& Rvw) -> void
2110 {
2111 pressures .reset();
2112 saturations.reset();
2113
2114 Scalar totfrac = 0.0;
2115 for (const auto& [depth, frac] : Details::horizontalSubdivision(cell, cellZSpan_[cell], acc)) {
2116 const auto pos = CellPos { cell, depth };
2117
2118 saturations.axpy(psat.deriveSaturations(pos, eqreg, ptable), frac);
2119 pressures .axpy(psat.correctedPhasePressures(), frac);
2120
2121 totfrac += frac;
2122 }
2123
2124 if (totfrac > 0.) {
2125 saturations /= totfrac;
2126 pressures /= totfrac;
2127 } else {
2128 // Fall back to centre point method for zero-thickness cells.
2129 const auto pos = CellPos {
2130 cell, cellCenterDepth_[cell]
2131 };
2132
2133 saturations = psat.deriveSaturations(pos, eqreg, ptable);
2134 pressures = psat.correctedPhasePressures();
2135 }
2136
2137 const auto temp = this->temperature_[cell];
2138 const auto cz = cellCenterDepth_[cell];
2139
2140 Rs = eqreg.dissolutionCalculator()
2141 (cz, pressures.oil, temp, saturations.gas);
2142
2143 Rv = eqreg.evaporationCalculator()
2144 (cz, pressures.gas, temp, saturations.oil);
2145
2146 Rvw = eqreg.waterEvaporationCalculator()
2147 (cz, pressures.gas, temp, saturations.water);
2148 });
2149}
2150
2151template<class FluidSystem, class Grid, class GridView, class ElementMapper, class CartesianIndexMapper>
2152template<class CellRange, class PressTable, class PhaseSat>
2153void InitialStateComputer<FluidSystem, Grid, GridView, ElementMapper, CartesianIndexMapper>::
2154equilibrateTiltedFaultBlockSimple(const CellRange& cells,
2155 const EquilReg<Scalar>& eqreg,
2156 const GridView& gridView,
2157 const int acc,
2158 const PressTable& ptable,
2159 PhaseSat& psat)
2160{
2161 using CellPos = typename PhaseSat::Position;
2162 using CellID = std::remove_cv_t<std::remove_reference_t<
2163 decltype(std::declval<CellPos>().cell)>>;
2164
2165 this->cellLoop(cells, [this, acc, &eqreg, &ptable, &psat, &gridView]
2166 (const CellID cell,
2167 Details::PhaseQuantityValue<Scalar>& pressures,
2168 Details::PhaseQuantityValue<Scalar>& saturations,
2169 Scalar& Rs,
2170 Scalar& Rv,
2171 Scalar& Rvw) -> void
2172 {
2173 pressures.reset();
2174 saturations.reset();
2175 Scalar totalWeight = 0.0;
2176
2177 // We assume grid blocks are treated as being tilted
2178 const auto& [zmin, zmax] = cellZMinMax_[cell];
2179 const Scalar cellThickness = zmax - zmin;
2180 const Scalar halfThickness = cellThickness / 2.0;
2181
2182 // Calculate dip parameters from corner point geometry
2183 Scalar dipAngle, dipAzimuth;
2184 Details::computeBlockDip(cellCorners_[cell], dipAngle, dipAzimuth);
2185
2186 // Reference point for TVD calculations
2187 std::array<Scalar, 3> referencePoint = {
2188 cellCenterXY_[cell].first,
2189 cellCenterXY_[cell].second,
2190 cellCenterDepth_[cell]
2191 };
2192
2193 // We have acc levels within each half (upper and lower) of the block
2194 const int numLevelsPerHalf = std::min(20, acc);
2195
2196 // Create subdivisions for upper and lower halves with cross-section weighting
2197 std::vector<std::pair<Scalar, Scalar>> levels;
2198
2199 // Subdivide upper and lower halves separately
2200 for (int side = 0; side < 2; ++side) {
2201 Scalar halfStart = (side == 0) ? zmin : zmin + halfThickness;
2202
2203 for (int i = 0; i < numLevelsPerHalf; ++i) {
2204 // Calculate depth at the center of this subdivision
2205 Scalar depth = halfStart + (i + 0.5) * (halfThickness / numLevelsPerHalf);
2206
2207 // A simple way: we can estimate cross-section weight based on dip angle
2208 // For horizontal cells: weight = 1.0, for tilted cells: weight decreases with dip
2209 Scalar crossSectionWeight = (halfThickness / numLevelsPerHalf);
2210
2211 // Apply dip correction to weight (cross-section area decreases with dip)
2212 if (std::abs(dipAngle) > 1e-10) {
2213 crossSectionWeight /= std::cos(dipAngle);
2214 }
2215
2216 levels.emplace_back(depth, crossSectionWeight);
2217 }
2218 }
2219
2220 for (const auto& [depth, weight] : levels) {
2221 // Convert measured depth to True Vertical Depth for tilted blocks
2222 const auto& [x, y] = cellCenterXY_[cell];
2224 depth, x, y, dipAngle, dipAzimuth, referencePoint);
2225
2226 const auto pos = CellPos{cell, tvd};
2227
2228 auto localSaturations = psat.deriveSaturations(pos, eqreg, ptable);
2229 auto localPressures = psat.correctedPhasePressures();
2230
2231 // Apply cross-section weighted averaging
2232 saturations.axpy(localSaturations, weight);
2233 pressures.axpy(localPressures, weight);
2234 totalWeight += weight;
2235 }
2236
2237 // Normalize results
2238 if (totalWeight > 1e-10) {
2239 saturations /= totalWeight;
2240 pressures /= totalWeight;
2241 } else {
2242 // Fallback to center point method using TVD
2243 const auto& [x, y] = cellCenterXY_[cell];
2244 Scalar tvdCenter = Details::calculateTrueVerticalDepth(
2245 cellCenterDepth_[cell], x, y, dipAngle, dipAzimuth, referencePoint);
2246 const auto pos = CellPos{cell, tvdCenter};
2247 saturations = psat.deriveSaturations(pos, eqreg, ptable);
2248 pressures = psat.correctedPhasePressures();
2249 }
2250
2251 // Compute solution ratios at cell center TVD
2252 const auto temp = this->temperature_[cell];
2253 const auto& [x, y] = cellCenterXY_[cell];
2254 Scalar tvdCenter = Details::calculateTrueVerticalDepth(
2255 cellCenterDepth_[cell], x, y, dipAngle, dipAzimuth, referencePoint);
2256
2257 Rs = eqreg.dissolutionCalculator()(tvdCenter, pressures.oil, temp, saturations.gas);
2258 Rv = eqreg.evaporationCalculator()(tvdCenter, pressures.gas, temp, saturations.oil);
2259 Rvw = eqreg.waterEvaporationCalculator()(tvdCenter, pressures.gas, temp, saturations.water);
2260 });
2261}
2262
2263template<class FluidSystem, class Grid, class GridView, class ElementMapper, class CartesianIndexMapper>
2264template<class CellRange, class PressTable, class PhaseSat>
2265void InitialStateComputer<FluidSystem, Grid, GridView, ElementMapper, CartesianIndexMapper>::
2266equilibrateTiltedFaultBlock(const CellRange& cells,
2267 const EquilReg<Scalar>& eqreg,
2268 const GridView& gridView,
2269 const int acc,
2270 const PressTable& ptable,
2271 PhaseSat& psat)
2272{
2273 using CellPos = typename PhaseSat::Position;
2274 using CellID = std::remove_cv_t<std::remove_reference_t<
2275 decltype(std::declval<CellPos>().cell)>>;
2276
2277 std::vector<typename GridView::template Codim<0>::Entity> entityMap(gridView.size(0));
2278 for (const auto& entity : entities(gridView, Dune::Codim<0>())) {
2279 CellID idx = gridView.indexSet().index(entity);
2280 entityMap[idx] = entity;
2281 }
2282
2283 // Face Area Calculation
2284 auto polygonArea = [](const std::vector<std::array<Scalar, 2>>& pts) {
2285 if (pts.size() < 3) return Scalar(0);
2286 Scalar area = 0;
2287 for (size_t i = 0; i < pts.size(); ++i) {
2288 size_t j = (i + 1) % pts.size();
2289 area += pts[i][0] * pts[j][1] - pts[j][0] * pts[i][1];
2290 }
2291 return std::abs(area) * Scalar(0.5);
2292 };
2293
2294 // Compute horizontal cross-section at given depth
2295 auto computeCrossSectionArea = [&](const CellID cell, Scalar depth) -> Scalar {
2296 try {
2297 const auto& entity = entityMap[cell];
2298 const auto& geometry = entity.geometry();
2299 const int numCorners = geometry.corners();
2300
2301 std::vector<std::array<Scalar, 3>> corners(numCorners);
2302 for (int i = 0; i < numCorners; ++i) {
2303 const auto& corner = geometry.corner(i);
2304 corners[i] = {static_cast<Scalar>(corner[0]), static_cast<Scalar>(corner[1]), static_cast<Scalar>(corner[2])};
2305 }
2306
2307 // Find all intersections between horizontal plane and cell edges
2308 std::vector<std::array<Scalar, 2>> intersectionPoints;
2309 const Scalar tol = 1e-10;
2310
2311 // Check all edges between corners (could be optimized further)
2312 for (size_t i = 0; i < corners.size(); ++i) {
2313 for (size_t j = i + 1; j < corners.size(); ++j) {
2314 Scalar za = corners[i][2];
2315 Scalar zb = corners[j][2];
2316
2317 if ((za - depth) * (zb - depth) <= 0.0 && std::abs(za - zb) > tol) {
2318 // Edge crosses the horizontal plane
2319 Scalar t = (depth - za) / (zb - za);
2320 Scalar x = corners[i][0] + t * (corners[j][0] - corners[i][0]);
2321 Scalar y = corners[i][1] + t * (corners[j][1] - corners[i][1]);
2322 intersectionPoints.push_back({x, y});
2323 }
2324 }
2325 }
2326
2327 // Remove duplicates
2328 if (intersectionPoints.size() > 1) {
2329 auto pointsEqual = [tol](const std::array<Scalar, 2>& a, const std::array<Scalar, 2>& b) {
2330 return std::abs(a[0] - b[0]) < tol && std::abs(a[1] - b[1]) < tol;
2331 };
2332
2333 intersectionPoints.erase(
2334 std::unique(intersectionPoints.begin(), intersectionPoints.end(), pointsEqual),
2335 intersectionPoints.end()
2336 );
2337 }
2338
2339 if (intersectionPoints.size() < 3) {
2340 // No valid grid found, use fallback
2341 return 0.0;
2342 }
2343
2344 // Order points counter-clockwise around centroid
2345 Scalar cx = 0, cy = 0;
2346 for (const auto& p : intersectionPoints) {
2347 cx += p[0]; cy += p[1];
2348 }
2349 cx /= intersectionPoints.size();
2350 cy /= intersectionPoints.size();
2351
2352 // Sorting
2353 auto angleCompare = [cx, cy](const std::array<Scalar, 2>& a, const std::array<Scalar, 2>& b) {
2354 return std::atan2(a[1] - cy, a[0] - cx) < std::atan2(b[1] - cy, b[0] - cx);
2355 };
2356
2357 std::ranges::sort(intersectionPoints, angleCompare);
2358
2359 return polygonArea(intersectionPoints);
2360
2361 } catch (const std::exception& e) {
2362 return 0.0;
2363 }
2364 };
2365
2366 auto cellProcessor = [this, acc, &eqreg, &ptable, &psat, &computeCrossSectionArea]
2367 (const CellID cell,
2368 Details::PhaseQuantityValue<Scalar>& pressures,
2369 Details::PhaseQuantityValue<Scalar>& saturations,
2370 Scalar& Rs,
2371 Scalar& Rv,
2372 Scalar& Rvw) -> void
2373 {
2374 pressures.reset();
2375 saturations.reset();
2376 Scalar totalWeight = 0.0;
2377
2378 const auto& zmin = this->cellZMinMax_[cell].first;
2379 const auto& zmax = this->cellZMinMax_[cell].second;
2380 const Scalar cellThickness = zmax - zmin;
2381 const Scalar halfThickness = cellThickness / 2.0;
2382
2383 // Calculate dip parameters from corner point geometry
2384 Scalar dipAngle, dipAzimuth;
2385 Details::computeBlockDip(this->cellCorners_[cell], dipAngle, dipAzimuth);
2386
2387 // Reference point for TVD calculations
2388 std::array<Scalar, 3> referencePoint = {
2389 this->cellCenterXY_[cell].first,
2390 this->cellCenterXY_[cell].second,
2391 cellCenterDepth_[cell]
2392 };
2393
2394 // We have acc levels within each half (upper and lower) of the block
2395 const int numLevelsPerHalf = std::min(20, acc);
2396
2397 // Create subdivisions for upper and lower halves with cross-section weighting
2398 std::vector<std::pair<Scalar, Scalar>> levels;
2399
2400 // Subdivide upper and lower halves separately
2401 for (int side = 0; side < 2; ++side) {
2402 Scalar halfStart = (side == 0) ? zmin : zmin + halfThickness;
2403
2404 for (int i = 0; i < numLevelsPerHalf; ++i) {
2405 // Calculate depth at the center of this subdivision
2406 Scalar depth = halfStart + (i + 0.5) * (halfThickness / numLevelsPerHalf);
2407
2408 // Compute cross-section area at this depth
2409 Scalar crossSectionArea = computeCrossSectionArea(cell, depth);
2410
2411 // Weight is proportional to: area × Δz (volume element)
2412 Scalar weight = crossSectionArea * (halfThickness / numLevelsPerHalf);
2413
2414 levels.emplace_back(depth, weight);
2415 }
2416 }
2417
2418 // Maybe not necessary (for debug)
2419 bool hasValidAreas = false;
2420 for (const auto& level : levels) {
2421 if (level.second > 1e-10) {
2422 hasValidAreas = true;
2423 break;
2424 }
2425 }
2426
2427 if (!hasValidAreas) {
2428 // Fallback to dip-based weighting as used in equilibrateTiltedFaultBlockSimple
2429 levels.clear();
2430 for (int side = 0; side < 2; ++side) {
2431 Scalar halfStart = (side == 0) ? zmin : zmin + halfThickness;
2432 for (int i = 0; i < numLevelsPerHalf; ++i) {
2433 Scalar depth = halfStart + (i + 0.5) * (halfThickness / numLevelsPerHalf);
2434 Scalar weight = (halfThickness / numLevelsPerHalf);
2435 if (std::abs(dipAngle) > 1e-10) {
2436 weight /= std::cos(dipAngle);
2437 }
2438 levels.emplace_back(depth, weight);
2439 }
2440 }
2441 }
2442
2443 for (const auto& level : levels) {
2444 Scalar depth = level.first;
2445 Scalar weight = level.second;
2446
2447 // Convert measured depth to True Vertical Depth for tilted blocks
2448 const auto& xy = this->cellCenterXY_[cell];
2450 depth, xy.first, xy.second, dipAngle, dipAzimuth, referencePoint);
2451
2452 const auto pos = CellPos{cell, tvd};
2453
2454 auto localSaturations = psat.deriveSaturations(pos, eqreg, ptable);
2455 auto localPressures = psat.correctedPhasePressures();
2456
2457 // Apply cross-section weighted averaging
2458 saturations.axpy(localSaturations, weight);
2459 pressures.axpy(localPressures, weight);
2460 totalWeight += weight;
2461 }
2462
2463 if (totalWeight > 1e-10) {
2464 saturations /= totalWeight;
2465 pressures /= totalWeight;
2466 } else {
2467 // Fallback to center point method using TVD
2468 const auto& xy = this->cellCenterXY_[cell];
2469 Scalar tvdCenter = Details::calculateTrueVerticalDepth(
2470 this->cellCenterDepth_[cell], xy.first, xy.second, dipAngle, dipAzimuth, referencePoint);
2471 const auto pos = CellPos{cell, tvdCenter};
2472 saturations = psat.deriveSaturations(pos, eqreg, ptable);
2473 pressures = psat.correctedPhasePressures();
2474 }
2475
2476 // Compute solution ratios at cell center TVD
2477 const auto temp = this->temperature_[cell];
2478 const auto& xy = this->cellCenterXY_[cell];
2479 Scalar tvdCenter = Details::calculateTrueVerticalDepth(
2480 this->cellCenterDepth_[cell], xy.first, xy.second, dipAngle, dipAzimuth, referencePoint);
2481
2482 Rs = eqreg.dissolutionCalculator()(tvdCenter, pressures.oil, temp, saturations.gas);
2483 Rv = eqreg.evaporationCalculator()(tvdCenter, pressures.gas, temp, saturations.oil);
2484 Rvw = eqreg.waterEvaporationCalculator()(tvdCenter, pressures.gas, temp, saturations.water);
2485 };
2486
2487 this->cellLoop(cells, cellProcessor);
2488}
2489}
2490} // namespace EQUIL
2491} // namespace Opm
2492
2493#endif // OPM_INIT_STATE_EQUIL_IMPL_HPP
#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
Auxiliary routines that to solve the ODEs that emerge from the hydrostatic equilibrium problem.
Dune::OwnerOverlapCopyCommunication< int, int > Comm
Definition: FlexibleSolver_impl.hpp:394
Routines that actually solve the ODEs that emerge from the hydrostatic equilibrium problem.
Definition: InitStateEquil.hpp:655
Definition: InitStateEquil.hpp:133
Gas(const TabulatedFunction &tempVdTable, const RV &rv, const RVW &rvw, const int pvtRegionIdx, const Scalar normGrav)
Definition: InitStateEquil_impl.hpp:411
Scalar operator()(const Scalar depth, const Scalar press) const
Definition: InitStateEquil_impl.hpp:427
Definition: InitStateEquil.hpp:108
Oil(const TabulatedFunction &tempVdTable, const RS &rs, const int pvtRegionIdx, const Scalar normGrav)
Definition: InitStateEquil_impl.hpp:363
Scalar operator()(const Scalar depth, const Scalar press) const
Definition: InitStateEquil_impl.hpp:377
Definition: InitStateEquil.hpp:83
Scalar operator()(const Scalar depth, const Scalar press) const
Definition: InitStateEquil_impl.hpp:337
Water(const TabulatedFunction &tempVdTable, const TabulatedFunction &saltVdTable, const int pvtRegionIdx, const Scalar normGrav)
Definition: InitStateEquil_impl.hpp:323
Definition: InitStateEquil.hpp:350
const PhaseQuantityValue< Scalar > & deriveSaturations(const Position &x, const Region &reg, const PTable &ptable)
Definition: InitStateEquil_impl.hpp:587
PhaseSaturations(MaterialLawManager &matLawMgr, const std::vector< Scalar > &swatInit)
Definition: InitStateEquil_impl.hpp:563
Definition: InitStateEquil.hpp:162
PressureTable & operator=(const PressureTable &rhs)
Definition: InitStateEquil_impl.hpp:1027
Scalar water(const Scalar depth) const
Definition: InitStateEquil_impl.hpp:1107
Scalar gas(const Scalar depth) const
Definition: InitStateEquil_impl.hpp:1096
bool waterActive() const
Predicate for whether or not water is an active phase.
Definition: InitStateEquil_impl.hpp:1078
bool gasActive() const
Predicate for whether or not gas is an active phase.
Definition: InitStateEquil_impl.hpp:1071
Scalar oil(const Scalar depth) const
Definition: InitStateEquil_impl.hpp:1086
std::array< Scalar, 2 > VSpan
Definition: InitStateEquil.hpp:165
bool oilActive() const
Predicate for whether or not oil is an active phase.
Definition: InitStateEquil_impl.hpp:1064
typename FluidSystem::Scalar Scalar
Definition: InitStateEquil.hpp:164
void equilibrate(const Region &reg, const VSpan &span)
Definition: InitStateEquil_impl.hpp:1053
PressureTable(const Scalar gravity, const int samplePoints=2000)
Definition: InitStateEquil_impl.hpp:997
Definition: EquilibrationHelpers.hpp:135
Definition: EquilibrationHelpers.hpp:216
Definition: EquilibrationHelpers.hpp:269
Definition: EquilibrationHelpers.hpp:612
Definition: EquilibrationHelpers.hpp:439
Definition: EquilibrationHelpers.hpp:162
Definition: EquilibrationHelpers.hpp:500
Definition: EquilibrationHelpers.hpp:322
Definition: EquilibrationHelpers.hpp:560
Definition: EquilibrationHelpers.hpp:376
Definition: FlowGenericProblem.hpp:51
std::vector< EquilRecord > getEquil(const EclipseState &state)
Definition: InitStateEquil_impl.hpp:1301
std::vector< int > equilnum(const EclipseState &eclipseState, const GridView &gridview)
Definition: InitStateEquil_impl.hpp:1315
std::pair< Scalar, Scalar > cellZMinMax(const Element &element)
Definition: InitStateEquil_impl.hpp:187
Scalar cellCenterDepth(const Element &element)
Definition: InitStateEquil_impl.hpp:134
std::pair< Scalar, Scalar > cellZSpan(const Element &element)
Definition: InitStateEquil_impl.hpp:168
CellCornerData< Scalar > getCellCornerXY(const Element &element)
Definition: InitStateEquil_impl.hpp:257
void verticalExtent(const CellRange &cells, const std::vector< std::pair< Scalar, Scalar > > &cellZMinMax, const Parallel::Communication &comm, std::array< Scalar, 2 > &span)
Definition: InitStateEquil_impl.hpp:70
std::pair< Scalar, Scalar > cellCenterXY(const Element &element)
Definition: InitStateEquil_impl.hpp:149
Scalar calculateTrueVerticalDepth(Scalar z, Scalar x, Scalar y, Scalar dipAngle, Scalar dipAzimuth, const std::array< Scalar, 3 > &referencePoint)
Definition: InitStateEquil_impl.hpp:281
void subdivisionCentrePoints(const Scalar left, const Scalar right, const int numIntervals, std::vector< std::pair< Scalar, Scalar > > &subdiv)
Definition: InitStateEquil_impl.hpp:95
std::vector< std::pair< Scalar, Scalar > > horizontalSubdivision(const CellID cell, const std::pair< Scalar, Scalar > topbot, const int numIntervals)
Definition: InitStateEquil_impl.hpp:113
void computeBlockDip(const CellCornerData< Scalar > &cellCorners, Scalar &dipAngle, Scalar &dipAzimuth)
Definition: InitStateEquil_impl.hpp:206
Dune::Communication< MPIComm > Communication
Definition: ParallelCommunication.hpp:30
Definition: blackoilbioeffectsmodules.hh:45
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Definition: InitStateEquil.hpp:62
std::array< Scalar, 8 > X
Definition: InitStateEquil.hpp:63
std::array< Scalar, 8 > Y
Definition: InitStateEquil.hpp:64
std::array< Scalar, 8 > Z
Definition: InitStateEquil.hpp:65
Simple set of per-phase (named by primary component) quantities.
Definition: InitStateEquil.hpp:302
Definition: InitStateEquil.hpp:356