31#ifndef OPM_INIT_STATE_EQUIL_COMP_HPP
32#define OPM_INIT_STATE_EQUIL_COMP_HPP
34#include <opm/common/ErrorMacros.hpp>
35#include <opm/common/OpmLog/OpmLog.hpp>
37#include <opm/material/common/Tabulated1DFunction.hpp>
38#include <opm/material/constraintsolvers/SaturationPressure.hpp>
39#include <opm/material/fluidstates/CompositionalFluidState.hpp>
41#include <opm/input/eclipse/EclipseState/Compositional/CompositionalConfig.hpp>
42#include <opm/input/eclipse/EclipseState/EclipseState.hpp>
43#include <opm/input/eclipse/EclipseState/InitConfig/Equil.hpp>
44#include <opm/input/eclipse/EclipseState/Tables/CompvdTable.hpp>
45#include <opm/input/eclipse/EclipseState/Tables/RtempvdTable.hpp>
46#include <opm/input/eclipse/EclipseState/Tables/SwfnTable.hpp>
47#include <opm/input/eclipse/EclipseState/Tables/TableContainer.hpp>
48#include <opm/input/eclipse/EclipseState/Tables/TableManager.hpp>
49#include <opm/input/eclipse/EclipseState/Tables/ZmfvdTable.hpp>
50#include <opm/input/eclipse/Units/Units.hpp>
56#include <fmt/format.h>
81template <
class Scalar>
82Scalar
evalDepthTable(
const Tabulated1DFunction<Scalar>& table,
const Scalar depth)
84 return table.eval(std::clamp(depth, table.xMin(), table.xMax()));
91template <
class Flu
idSystem>
95 using Scalar =
typename FluidSystem::Scalar;
96 using CompVec = std::array<Scalar, FluidSystem::numComponents>;
102 const unsigned phaseIdx,
103 const CompositionalConfig::EOSType eosType,
105 : composition_(std::move(composition))
106 , tempVdTable_(tempVdTable)
107 , phaseIdx_(phaseIdx)
115 const CompVec z = composition_(depth);
118 CompositionalFluidState<Scalar, FluidSystem> fs;
119 fs.setTemperature(temp);
120 fs.setPressure(FluidSystem::oilPhaseIdx, press);
121 fs.setPressure(FluidSystem::gasPhaseIdx, press);
122 for (
unsigned compIdx = 0; compIdx < FluidSystem::numComponents; ++compIdx) {
123 fs.setMoleFraction(phaseIdx_, compIdx, z[compIdx]);
126 typename FluidSystem::template ParameterCache<Scalar> paramCache(eosType_);
127 paramCache.updatePhase(fs, phaseIdx_);
129 return FluidSystem::density(fs, paramCache, phaseIdx_) * g_;
136 CompositionalConfig::EOSType eosType_;
142template <
class Flu
idSystem>
146 using Scalar =
typename FluidSystem::Scalar;
150 const CompositionalConfig::EOSType eosType,
152 : tempVdTable_(tempVdTable)
160 CompositionalFluidState<Scalar, FluidSystem> fs;
162 fs.setPressure(FluidSystem::waterPhaseIdx, press);
164 typename FluidSystem::template ParameterCache<Scalar> paramCache(eosType_);
166 return FluidSystem::density(fs, paramCache, FluidSystem::waterPhaseIdx) * g_;
171 CompositionalConfig::EOSType eosType_;
210template <
class Flu
idSystem>
214 using Scalar =
typename FluidSystem::Scalar;
215 using FluidState = CompositionalFluidState<Scalar, FluidSystem>;
230 const CompositionalConfig::EOSType eosType,
232 const std::vector<int>& eqlnum,
235 const int numSamplePoints,
236 const std::vector<Scalar>& connateWater = {},
237 const std::vector<Scalar>& maxWater = {})
239 , connateWater_(connateWater)
240 , maxWater_(maxWater)
242 const auto& records = inputState.getInitConfig().getEquil();
243 const auto& tables = inputState.getTableManager();
245 if (!tables.hasTables(
"ZMFVD") && !tables.hasTables(
"COMPVD")) {
246 OPM_THROW(std::runtime_error,
247 "Equilibration of a compositional model requires the composition "
248 "versus depth from the ZMFVD or the COMPVD keyword.");
253 OPM_THROW(std::runtime_error,
254 fmt::format(
"EQLNUM contains {} entries for {} cell depths.",
257 for (std::size_t cell = 0; cell < eqlnum.size(); ++cell) {
258 const auto region = eqlnum[cell];
259 if (region < 0 || std::cmp_greater_equal(region, records.size())) {
260 OPM_THROW(std::runtime_error,
261 fmt::format(
"Cell {} has EQLNUM {} outside the {} "
262 "equilibration regions.",
263 cell, region + 1, records.size()));
268 for (
const auto& [name, limits] : {std::pair{
"connate water", std::cref(connateWater)},
269 std::pair{
"maximum water", std::cref(maxWater)}}) {
270 if (!limits.get().empty() &&
272 OPM_THROW(std::runtime_error,
273 fmt::format(
"The {} saturation has {} entries for {} cell depths.",
279 std::vector<Region> regions;
280 regions.reserve(records.size());
281 for (std::size_t r = 0; r < records.size(); ++r) {
282 regions.push_back(setupRegion(records.getRecord(r), tables,
cellCenterDepth,
283 eqlnum, comm, gravity, numSamplePoints, r));
289 referencePressures_[cell] =
290 assignCell(fluidStates_[cell], regions[eqlnum[cell]],
cellCenterDepth[cell], cell);
295 {
return fluidStates_; }
298 {
return fluidStates_; }
304 {
return referencePressures_; }
307 using CompVec = std::array<Scalar, FluidSystem::numComponents>;
308 using TabulatedFunction = Tabulated1DFunction<Scalar>;
314 static constexpr int numComponents = FluidSystem::numComponents;
319 static constexpr Scalar constantTableSpan{1.0};
323 static constexpr Scalar minimumSpanExtent{1.0};
330 std::string_view compositionKeyword;
332 std::optional<unsigned> statedPhaseIdx{};
334 unsigned nominalPhaseIdx{FluidSystem::oilPhaseIdx};
339 std::vector<TabulatedFunction> vaporVdTable;
341 CompVec vaporComposition{};
344 std::vector<TabulatedFunction> compositionVdTable;
345 TabulatedFunction tempVdTable;
346 std::optional<PressFunc> oilPressure;
347 std::optional<PressFunc> gasPressure;
353 bool anchoredAtWaterContact{
false};
354 std::optional<WaterPressFunc> waterPressure;
359 static std::array<Scalar, 2> waterContactSpan(
const Region& reg,
360 const std::array<Scalar, 2>& span)
362 if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) {
363 return {std::min(span[0], reg.zwoc), std::max(span[1], reg.zwoc)};
369 static CompVec vaporComposition(
const Region& reg,
const Scalar depth)
373 for (
int c = 0; c < numComponents; ++c) {
378 OPM_THROW(std::runtime_error,
379 fmt::format(
"The COMPVD vapour composition vanishes at depth {} m.", depth));
381 std::ranges::transform(z, z.begin(), [sum](
const Scalar zc) { return zc / sum; });
385 static CompVec composition(
const Region& reg,
const Scalar depth)
389 for (
int c = 0; c < numComponents; ++c) {
394 OPM_THROW(std::runtime_error,
395 fmt::format(
"The composition vanishes at depth {} m.", depth));
397 std::ranges::transform(z, z.begin(), [sum](
const Scalar zc) { return zc / sum; });
403 template <
class Table>
404 static void setupComposition(std::vector<TabulatedFunction>& out,
406 const std::vector<std::size_t>& rows)
408 const auto& depthCol = table.getDepthColumn();
410 std::vector<Scalar> depths;
411 depths.reserve(rows.size() + 1);
412 for (
const auto row : rows) {
413 depths.push_back(depthCol[row]);
417 const bool constantComposition = (depths.size() == 1);
418 if (constantComposition) {
419 depths.push_back(depths.front() + constantTableSpan);
422 out.resize(numComponents);
423 for (
int c = 0; c < numComponents; ++c) {
424 const auto& col = table.getMoleFractionColumn(c);
425 std::vector<Scalar> values;
426 values.reserve(depths.size());
427 for (
const auto row : rows) {
428 values.push_back(col[row]);
430 if (constantComposition) {
431 values.push_back(values.front());
433 out[c].setXYContainers(depths, values);
439 static std::optional<std::size_t> sourceTable(
const TableContainer& container,
440 const std::size_t regionIdx)
442 const auto& byIndex = container.tables();
443 const auto after = byIndex.upper_bound(regionIdx);
444 if (after == byIndex.begin()) {
447 return std::prev(after)->first;
451 static std::vector<std::size_t> allRows(
const std::size_t count)
453 std::vector<std::size_t> rows(count);
454 std::iota(rows.begin(), rows.end(), std::size_t{0});
460 static void checkZonesStraddleContact(
const CompvdTable& compvd,
461 const std::vector<std::size_t>& vaporRows,
462 const std::vector<std::size_t>& liquidRows,
464 const std::size_t regionIdx)
466 if (vaporRows.empty() || liquidRows.empty()) {
467 OPM_THROW(std::runtime_error,
468 fmt::format(
"The COMPVD table of region {} names both phases but has "
469 "no row for one of them.", regionIdx + 1));
472 const auto& depth = compvd.getDepthColumn();
473 if ((depth[vaporRows.back()] > zgoc) || (depth[liquidRows.front()] < zgoc)) {
474 OPM_THROW(std::runtime_error,
475 fmt::format(
"The COMPVD table of region {} puts its vapour rows down to "
476 "{} m and its liquid rows from {} m, which do not meet at "
477 "the gas-oil contact at {} m.",
478 regionIdx + 1, depth[vaporRows.back()],
479 depth[liquidRows.front()], zgoc));
484 static std::vector<std::size_t> rowsOfPhase(
const CompvdTable& compvd,
487 const auto& flags = compvd.phaseFlags();
488 std::vector<std::size_t> rows;
489 for (std::size_t r = 0; r < flags.size(); ++r) {
490 if (flags[r] == phase) {
499 static std::optional<unsigned> statedPhase(
const CompvdTable& compvd,
500 const std::size_t regionIdx)
502 const auto& flags = compvd.phaseFlags();
507 const auto first = flags.front();
508 if (std::ranges::any_of(flags, [first](
const auto f) {
return f != first; })) {
509 OpmLog::info(fmt::format(
"Equilibration region {}: COMPVD names both phases, "
510 "so the composition of each depth decides the phase.",
515 return (first == CompvdTable::Phase::Vapor)
516 ? FluidSystem::gasPhaseIdx : FluidSystem::oilPhaseIdx;
520 static bool compositionVariesBetween(
const Region& reg,
526 constexpr Scalar sameComposition{1.0e-10};
528 const CompVec a = composition(reg, depthA);
529 const CompVec b = composition(reg, depthB);
530 return !std::ranges::equal(a, b, [](
const Scalar x,
const Scalar y) {
531 return std::abs(x - y) <= sameComposition;
535 Region setupRegion(
const EquilRecord& record,
536 const TableManager& tables,
538 const std::vector<int>& eqlnum,
541 const int numSamplePoints,
542 const std::size_t regionIdx)
const
546 reg.initType = record.compositionalInitType();
547 if (reg.initType != 1 && reg.initType != 3) {
548 OPM_THROW(std::runtime_error,
549 fmt::format(
"Compositional initialization type {} (EQUIL item 10) is "
550 "not supported for region {}; only type 1 (total "
551 "composition) and type 3 (liquid composition) are.",
552 reg.initType, regionIdx + 1));
555 if (record.gasOilContactCapillaryPressure() != 0.0) {
556 OPM_THROW(std::runtime_error,
557 fmt::format(
"Compositional equilibration only supports zero gas-oil "
558 "contact capillary pressure (EQUIL item 6); region {} "
561 unit::convert::to(record.gasOilContactCapillaryPressure(),
565 if (
const auto accuracy = record.initializationTargetAccuracy(); accuracy != 0) {
566 OPM_THROW(std::runtime_error,
567 fmt::format(
"Compositional equilibration only supports cell-centre "
568 "initialization (EQUIL item 9 = 0); region {} specifies {}.",
569 regionIdx + 1, accuracy));
572 reg.zgoc = record.gasOilContactDepth();
577 const bool deckHasZmfvd = tables.hasTables(
"ZMFVD");
578 const bool deckHasCompvd = tables.hasTables(
"COMPVD");
579 const bool statesZmfvd = deckHasZmfvd &&
580 tables.getZmfvdTables().hasTable(regionIdx);
581 const bool statesCompvd = deckHasCompvd &&
582 tables.getCompvdTables().hasTable(regionIdx);
584 if (statesZmfvd && statesCompvd) {
585 OPM_THROW(std::runtime_error,
586 fmt::format(
"Region {} has both a ZMFVD and a COMPVD composition "
587 "versus depth; give only one of them.", regionIdx + 1));
590 std::optional<std::size_t> zmfvdAt;
591 std::optional<std::size_t> compvdAt;
592 if (deckHasZmfvd && deckHasCompvd) {
593 if (!statesZmfvd && !statesCompvd) {
594 OPM_THROW(std::runtime_error,
595 fmt::format(
"Region {} has neither a ZMFVD nor a COMPVD "
596 "composition versus depth. A deck using both "
597 "keywords has to give every region a record of "
598 "its own, as neither can be inherited.",
601 (statesZmfvd ? zmfvdAt : compvdAt) = regionIdx;
603 else if (deckHasZmfvd) {
604 zmfvdAt = sourceTable(tables.getZmfvdTables(), regionIdx);
606 else if (deckHasCompvd) {
607 compvdAt = sourceTable(tables.getCompvdTables(), regionIdx);
610 if (!zmfvdAt.has_value() && !compvdAt.has_value()) {
611 OPM_THROW(std::runtime_error,
612 fmt::format(
"Region {} has neither a ZMFVD nor a COMPVD composition "
613 "versus depth.", regionIdx + 1));
616 if (zmfvdAt.has_value()) {
617 reg.compositionKeyword =
"ZMFVD";
619 tables.getZmfvdTables().template getTable<ZmfvdTable>(*zmfvdAt);
620 setupComposition(reg.compositionVdTable, zmfvd,
621 allRows(zmfvd.getDepthColumn().size()));
624 reg.compositionKeyword =
"COMPVD";
626 tables.getCompvdTables().template getTable<CompvdTable>(*compvdAt);
627 reg.statedPhaseIdx = statedPhase(compvd, regionIdx);
629 if (reg.statedPhaseIdx.has_value()) {
632 if ((reg.initType == 3) &&
633 (*reg.statedPhaseIdx == FluidSystem::gasPhaseIdx)) {
634 OPM_THROW(std::runtime_error,
635 fmt::format(
"Region {} states a vapour composition in COMPVD "
636 "while EQUIL item 10 is 3, which takes the liquid "
637 "composition at the gas-oil contact.",
640 setupComposition(reg.compositionVdTable, compvd,
641 allRows(compvd.getDepthColumn().size()));
646 const auto vaporRows = rowsOfPhase(compvd, CompvdTable::Phase::Vapor);
647 const auto liquidRows = rowsOfPhase(compvd, CompvdTable::Phase::Liquid);
648 checkZonesStraddleContact(compvd, vaporRows, liquidRows,
649 record.gasOilContactDepth(), regionIdx);
651 setupComposition(reg.vaporVdTable, compvd, vaporRows);
652 setupComposition(reg.compositionVdTable, compvd, liquidRows);
656 if (tables.hasTables(
"RTEMPVD")) {
657 const auto& rtempvd =
658 tables.getRtempvdTables().template getTable<RtempvdTable>(regionIdx);
659 std::vector<Scalar> tempDepths(rtempvd.getDepthColumn().begin(),
660 rtempvd.getDepthColumn().end());
661 const auto& tempCol = rtempvd.getTemperatureColumn();
662 std::vector<Scalar> temps(tempCol.begin(), tempCol.end());
665 if (tempDepths.size() == 1) {
666 tempDepths.push_back(tempDepths.front() + constantTableSpan);
667 temps.push_back(temps.front());
669 reg.tempVdTable.setXYContainers(tempDepths, temps);
672 const std::vector<Scalar> tempDepths{
Scalar{0}, constantTableSpan};
673 const std::vector<Scalar> temps(tempDepths.size(), tables.rtemp());
674 reg.tempVdTable.setXYContainers(tempDepths, temps);
678 auto span = std::array{std::numeric_limits<Scalar>::max(),
679 std::numeric_limits<Scalar>::lowest()};
681 if (std::cmp_equal(eqlnum[cell], regionIdx)) {
686 span[0] = comm.min(span[0]);
687 span[1] = comm.max(span[1]);
688 if (span[0] > span[1]) {
692 if (span[1] - span[0] < minimumSpanExtent) {
693 span = {span[0] - minimumSpanExtent, span[1] + minimumSpanExtent};
696 const bool waterActive = FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx);
698 reg.zwoc = record.waterOilContactDepth();
706 Scalar hcDatum = record.datumDepth();
707 Scalar hcPressure = record.datumDepthPressure();
708 const bool datumInWater = waterActive && (record.datumDepth() > reg.zwoc);
712 reg.anchoredAtWaterContact = waterActive && (record.datumDepth() >= reg.zwoc);
715 integrateWaterPressure(reg, span, gravity, numSamplePoints,
716 record.datumDepth(), record.datumDepthPressure());
718 hcPressure = reg.waterPressure->value(reg.zwoc)
719 + record.waterOilContactCapillaryPressure();
720 OpmLog::info(fmt::format(
"Equilibration region {}: the datum at {} m lies below the "
721 "water-oil contact at {} m, so it gives the water pressure; "
722 "the hydrocarbon pressure at the contact is {:.5} bar.",
723 regionIdx + 1, record.datumDepth(), reg.zwoc,
724 hcPressure / 1.0e5));
727 if (reg.initType == 1) {
728 setupSinglePhaseRegion(reg, span, gravity, numSamplePoints, regionIdx,
729 hcDatum, hcPressure);
737 OPM_THROW(std::runtime_error,
738 fmt::format(
"Compositional equilibration of region {} places the "
739 "datum at {} m, below the water-oil contact at {} m, "
740 "while EQUIL item 10 is 3. Put the datum in the "
741 "hydrocarbon column or use item 10 = 1.",
742 regionIdx + 1, record.datumDepth(), reg.zwoc));
744 setupTwoPhaseRegion(reg, record, span, gravity, numSamplePoints, regionIdx);
748 if (waterActive && !datumInWater) {
749 setupWaterZone(reg, record, span, gravity, numSamplePoints, regionIdx);
751 else if (waterActive) {
752 OpmLog::info(fmt::format(
"Equilibration region {}: the water-oil contact "
753 "is at {} m.", regionIdx + 1, reg.zwoc));
765 void setupWaterZone(Region& reg,
766 const EquilRecord& record,
767 const std::array<Scalar, 2>& span,
769 const int numSamplePoints,
770 const std::size_t regionIdx)
const
774 if (!reg.oilPressure.has_value() && !reg.gasPressure.has_value()) {
777 const auto& hcPressure = reg.oilPressure.has_value() ? reg.oilPressure : reg.gasPressure;
778 const Scalar pcow = record.waterOilContactCapillaryPressure();
779 const Scalar pContact = hcPressure->value(reg.zwoc) - pcow;
781 integrateWaterPressure(reg, span, gravity, numSamplePoints, reg.zwoc, pContact);
783 OpmLog::info(fmt::format(
"Equilibration region {}: the water-oil contact "
784 "is at {} m.", regionIdx + 1, reg.zwoc));
789 void integrateWaterPressure(Region& reg,
790 const std::array<Scalar, 2>& span,
792 const int numSamplePoints,
794 const Scalar pressure)
const
796 const WaterODE ode(reg.tempVdTable, eosType_, gravity);
797 reg.waterPressure.emplace(ode,
798 typename WaterPressFunc::InitCond{depth, pressure},
799 numSamplePoints, waterContactSpan(reg, span));
804 static Scalar waterLimit(
const std::vector<Scalar>& limits,
805 const std::size_t cell,
808 return limits.empty() ? fallback : limits[cell];
825 void setupSinglePhaseRegion(Region& reg,
826 const std::array<Scalar, 2>& span,
828 const int numSamplePoints,
829 const std::size_t regionIdx,
831 const Scalar datumPressure)
const
834 setupTwoZoneRegion(reg, span, gravity, numSamplePoints, regionIdx,
835 datum, datumPressure);
844 const bool gasAtDatum = reg.anchoredAtWaterContact ? (datum <= reg.zgoc)
845 : (datum < reg.zgoc);
846 const auto phaseIdx = reg.statedPhaseIdx.value_or(
847 gasAtDatum ? FluidSystem::gasPhaseIdx : FluidSystem::oilPhaseIdx);
851 if ((reg.zgoc > span[0]) && (reg.zgoc < span[1]) &&
852 !compositionVariesBetween(reg, span[0], reg.zgoc) &&
853 !compositionVariesBetween(reg, reg.zgoc, span[1])) {
854 OpmLog::warning(fmt::format(
"Equilibration region {}: the gas-oil contact "
855 "at {} m lies inside a type-1 region, but the "
856 "composition does not vary across the contact. "
857 "Compositional variation is required for proper "
858 "phase labeling.", regionIdx + 1, reg.zgoc));
861 const ODE ode([®](
const Scalar depth) {
return composition(reg, depth); },
862 reg.tempVdTable, phaseIdx, eosType_, gravity);
863 reg.oilPressure.emplace(ode,
864 typename PressFunc::InitCond{datum, datumPressure},
865 numSamplePoints, waterContactSpan(reg, span));
866 reg.nominalPhaseIdx = phaseIdx;
868 OpmLog::info(fmt::format(
"Equilibration region {}: pressure integrated with one "
869 "EOS root and the total composition from {} "
870 "(EQUIL item 10 = 1).",
871 regionIdx + 1, reg.compositionKeyword));
877 void setupTwoZoneRegion(Region& reg,
878 const std::array<Scalar, 2>& span,
880 const int numSamplePoints,
881 const std::size_t regionIdx,
883 const Scalar datumPressure)
const
885 const ODE liquidOde([®](
const Scalar depth) {
return composition(reg, depth); },
886 reg.tempVdTable, FluidSystem::oilPhaseIdx, eosType_, gravity);
887 const ODE gasOde([®](
const Scalar depth) {
return vaporComposition(reg, depth); },
888 reg.tempVdTable, FluidSystem::gasPhaseIdx, eosType_, gravity);
893 const auto pressureSpan = waterContactSpan(reg, span);
894 const std::array<Scalar, 2> datumSpan{std::min(pressureSpan[0], reg.zgoc),
895 std::max(pressureSpan[1], reg.zgoc)};
896 if ((reg.zgoc < span[0]) || (reg.zgoc > span[1])) {
897 OpmLog::warning(fmt::format(
"Equilibration region {}: the gas-oil contact at {} m "
898 "lies outside the cells of the region, so the COMPVD "
899 "rows of one phase describe no cell.",
900 regionIdx + 1, reg.zgoc));
903 if (datum < reg.zgoc) {
904 reg.gasPressure.emplace(gasOde,
905 typename PressFunc::InitCond{datum, datumPressure},
906 numSamplePoints, datumSpan);
907 reg.oilPressure.emplace(liquidOde,
908 typename PressFunc::InitCond{
909 reg.zgoc, reg.gasPressure->value(reg.zgoc)},
910 numSamplePoints, pressureSpan);
913 reg.oilPressure.emplace(liquidOde,
914 typename PressFunc::InitCond{datum, datumPressure},
915 numSamplePoints, datumSpan);
916 reg.gasPressure.emplace(gasOde,
917 typename PressFunc::InitCond{
918 reg.zgoc, reg.oilPressure->value(reg.zgoc)},
919 numSamplePoints, pressureSpan);
922 OpmLog::info(fmt::format(
"Equilibration region {}: COMPVD gives a gas zone above the "
923 "contact at {} m and a liquid one below it "
924 "(EQUIL item 10 = 1).", regionIdx + 1, reg.zgoc));
934 void setupTwoPhaseRegion(Region& reg,
935 const EquilRecord& record,
936 const std::array<Scalar, 2>& span,
938 const int numSamplePoints,
939 const std::size_t regionIdx)
const
941 const Scalar inputReferenceDepth = record.datumDepth();
942 if (inputReferenceDepth != reg.zgoc) {
943 OpmLog::warning(fmt::format(
"Equilibration region {}: the reference depth {} m "
944 "does not coincide with the gas-oil contact when "
945 "EQUIL item 10 is 3; resetting it to the contact "
947 regionIdx + 1, inputReferenceDepth, reg.zgoc));
950 const CompVec liquid = composition(reg, reg.zgoc);
954 if (!SaturationPressure<Scalar, FluidSystem>::bubblePressure(liquid, temp, eosType_,
956 OPM_THROW(std::runtime_error,
957 fmt::format(
"The saturation pressure calculation at the gas-oil "
958 "contact of region {} did not converge.", regionIdx + 1));
960 reg.vaporComposition = vapor;
967 constexpr Scalar oneAtmosphere = unit::atm;
968 const Scalar inputPressure = record.datumDepthPressure();
969 const bool resetToPsat = record.setToSaturationPressure()
970 && (std::abs(inputPressure - psat) >= oneAtmosphere);
971 const Scalar referencePressure = resetToPsat ? psat : inputPressure;
973 OpmLog::info(fmt::format(
"Equilibration region {}: two phases, liquid composition "
974 "specified (EQUIL item 10 is 3). The saturation pressure "
975 "at the gas-oil contact ({} m) is {:.6g} bar.",
976 regionIdx + 1, reg.zgoc,
977 unit::convert::to(psat, unit::barsa)));
980 OpmLog::warning(fmt::format(
"Equilibration region {}: the datum pressure {:.6g} bar "
981 "differs from the saturation pressure {:.6g} bar at the "
982 "gas-oil contact by one atmosphere or more; the "
983 "saturation pressure is used instead.",
985 unit::convert::to(inputPressure, unit::barsa),
986 unit::convert::to(psat, unit::barsa)));
989 const ODE oilOde([®](
const Scalar depth) {
return composition(reg, depth); },
990 reg.tempVdTable, FluidSystem::oilPhaseIdx, eosType_, gravity);
991 reg.oilPressure.emplace(oilOde,
992 typename PressFunc::InitCond{reg.zgoc, referencePressure},
993 numSamplePoints, waterContactSpan(reg, span));
1000 gasComposition = [®](
const Scalar depth) {
return vaporComposition(reg, depth); };
1003 gasComposition = [vapor](
const Scalar) {
return vapor; };
1005 const ODE gasOde(gasComposition,
1006 reg.tempVdTable, FluidSystem::gasPhaseIdx, eosType_, gravity);
1007 reg.gasPressure.emplace(gasOde,
1008 typename PressFunc::InitCond{reg.zgoc, referencePressure},
1009 numSamplePoints, waterContactSpan(reg, span));
1013 const std::size_t cell)
const
1015 const bool inGasZone = ((reg.initType == 3) || reg.twoZone) && (depth < reg.zgoc);
1017 const CompVec z = [®, depth, inGasZone]() {
1019 return composition(reg, depth);
1023 return reg.twoZone ? vaporComposition(reg, depth) : reg.vaporComposition;
1025 const auto& pressFunc = inGasZone ? reg.gasPressure : reg.oilPressure;
1026 if (!pressFunc.has_value()) {
1027 OPM_THROW(std::runtime_error,
1028 "Evaluating the equilibrated pressure of a region without cells.");
1035 const Scalar hydrocarbonPressure = pressFunc->value(depth);
1036 const bool inWaterZone = (depth > reg.zwoc) && reg.waterPressure.has_value();
1037 const Scalar press = inWaterZone ? reg.waterPressure->value(depth)
1038 : hydrocarbonPressure;
1041 for (
unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) {
1042 if (FluidSystem::phaseIsActive(phaseIdx)) {
1043 fs.setPressure(phaseIdx, hydrocarbonPressure);
1044 fs.setSaturation(phaseIdx, 0.0);
1051 if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) {
1055 sWat = (depth > reg.zwoc) ? waterLimit(maxWater_, cell,
Scalar{1})
1056 : waterLimit(connateWater_, cell,
Scalar{0});
1057 fs.setSaturation(FluidSystem::waterPhaseIdx, sWat);
1058 if (reg.waterPressure.has_value()) {
1059 fs.setPressure(FluidSystem::waterPhaseIdx, reg.waterPressure->value(depth));
1066 fs.setSaturation(inGasZone ? FluidSystem::gasPhaseIdx : reg.nominalPhaseIdx,
1069 for (
int c = 0; c < numComponents; ++c) {
1070 fs.setMoleFraction(c, z[c]);
1075 CompositionalConfig::EOSType eosType_;
1079 std::vector<Scalar> connateWater_;
1080 std::vector<Scalar> maxWater_;
1081 std::vector<FluidState> fluidStates_;
1082 std::vector<Scalar> referencePressures_;
#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
The ODE integrator and phase-pressure function used to solve the hydrostatic equilibrium problem,...
Definition: InitStateEquilComp.hpp:93
Tabulated1DFunction< Scalar > TabulatedFunction
Definition: InitStateEquilComp.hpp:98
Scalar operator()(const Scalar depth, const Scalar press) const
Definition: InitStateEquilComp.hpp:112
typename FluidSystem::Scalar Scalar
Definition: InitStateEquilComp.hpp:95
std::array< Scalar, FluidSystem::numComponents > CompVec
Definition: InitStateEquilComp.hpp:96
std::function< CompVec(Scalar)> CompositionFunction
Definition: InitStateEquilComp.hpp:97
EosDensityODE(CompositionFunction composition, const TabulatedFunction &tempVdTable, const unsigned phaseIdx, const CompositionalConfig::EOSType eosType, const Scalar normGrav)
Definition: InitStateEquilComp.hpp:100
Definition: InitStateEquilComp.hpp:144
Scalar operator()(const Scalar depth, const Scalar press) const
Definition: InitStateEquilComp.hpp:157
WaterDensityODE(const TabulatedFunction &tempVdTable, const CompositionalConfig::EOSType eosType, const Scalar normGrav)
Definition: InitStateEquilComp.hpp:149
Tabulated1DFunction< Scalar > TabulatedFunction
Definition: InitStateEquilComp.hpp:147
typename FluidSystem::Scalar Scalar
Definition: InitStateEquilComp.hpp:146
Computes the initial state of a compositional model from hydrostatic equilibrium (the EQUIL and ZMFVD...
Definition: InitStateEquilComp.hpp:212
CompositionalFluidState< Scalar, FluidSystem > FluidState
Definition: InitStateEquilComp.hpp:215
typename FluidSystem::Scalar Scalar
Definition: InitStateEquilComp.hpp:214
const std::vector< Scalar > & referencePressures() const
Definition: InitStateEquilComp.hpp:303
const std::vector< FluidState > & fluidStates() const
Definition: InitStateEquilComp.hpp:297
InitialStateComputer(const EclipseState &inputState, const CompositionalConfig::EOSType eosType, const std::vector< Scalar > &cellCenterDepth, const std::vector< int > &eqlnum, const Parallel::Communication &comm, const Scalar gravity, const int numSamplePoints, const std::vector< Scalar > &connateWater={}, const std::vector< Scalar > &maxWater={})
Definition: InitStateEquilComp.hpp:229
std::vector< FluidState > & fluidStates()
Definition: InitStateEquilComp.hpp:294
Definition: PressureFunction.hpp:128
Scalar evalDepthTable(const Tabulated1DFunction< Scalar > &table, const Scalar depth)
Definition: InitStateEquilComp.hpp:82
Definition: InitStateEquilComp.hpp:74
Scalar cellCenterDepth(const Element &element)
Definition: InitStateEquil_impl.hpp:134
Dune::Communication< MPIComm > Communication
Definition: ParallelCommunication.hpp:30
Phase
Phase indices for reservoir coupling, we currently only support black-oil phases (oil,...
Definition: ReservoirCoupling.hpp:183