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