Skip to content

Commit cc9084b

Browse files
committed
Test the PSAT phase selection and reduce its failure count over ranks
Move the choice between the cell pressure, the bubble point and the dew point into CompositionalContainer::cellSaturationPressure(), which takes plain values instead of a fluid state, and cover its four outcomes in the container test: both phases present, oil only, gas only, and a supercritical mixture with no saturation pressure. The output module only unpacks the fluid state and writes the result. Count the cells without a saturation pressure in validateLocalData(), which every rank reaches after the per-cell pass, and reduce it over the grid communicator before logging the total once from rank 0. The previous report at output time was the I/O rank's count alone. Say at both sites what zero stands for: the solver reports that it could not determine a saturation pressure, which a supercritical mixture also does; it does not assert that none exists.
1 parent 037f51d commit cc9084b

4 files changed

Lines changed: 149 additions & 36 deletions

File tree

opm/simulators/flow/CompositionalContainer.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
#include <config.h>
2424
#include <opm/simulators/flow/CompositionalContainer.hpp>
2525

26+
#include <opm/input/eclipse/EclipseState/Compositional/CompositionalConfig.hpp>
27+
28+
#include <opm/material/constraintsolvers/SaturationPressure.hpp>
2629
#include <opm/material/fluidsystems/GenericOilGasWaterFluidSystem.hpp>
2730

2831
#include <opm/output/data/Solution.hpp>
2932

3033
#include <algorithm>
34+
#include <optional>
3135
#include <tuple>
3236

3337
#include <fmt/format.h>
@@ -237,6 +241,36 @@ outputRestart(data::Solution& sol,
237241
this->allocated_ = false;
238242
}
239243

244+
template<class FluidSystem>
245+
std::optional<typename CompositionalContainer<FluidSystem>::Scalar>
246+
CompositionalContainer<FluidSystem>::
247+
cellSaturationPressure(const Scalar oilSaturation,
248+
const Scalar gasSaturation,
249+
const Scalar oilPressure,
250+
const std::array<Scalar, numComponents>& moleFractions,
251+
const Scalar temperature,
252+
const CompositionalConfig::EOSType eosType)
253+
{
254+
if (oilSaturation > 0.0 && gasSaturation > 0.0) {
255+
return oilPressure;
256+
}
257+
258+
// The zero-component instantiation exists only to register a parameter
259+
// and has no equation of state to solve.
260+
if constexpr (numComponents > 0) {
261+
using SatP = SaturationPressure<Scalar, FluidSystem>;
262+
typename SatP::CompVec incipient;
263+
Scalar psat = 0.0;
264+
const bool found = (gasSaturation <= 0.0)
265+
? SatP::bubblePressure(moleFractions, temperature, eosType, psat, incipient)
266+
: SatP::dewPressure(moleFractions, temperature, eosType, psat, incipient);
267+
if (found) {
268+
return psat;
269+
}
270+
}
271+
return std::nullopt;
272+
}
273+
240274
#define INSTANTIATE_COMP_THREEPHASE(NUM) \
241275
template<class T> using FS##NUM = GenericOilGasWaterFluidSystem<T, NUM, true>; \
242276
template class CompositionalContainer<FS##NUM<double>>;

opm/simulators/flow/CompositionalContainer.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
#ifndef OPM_COMPOSITIONAL_CONTAINER_HPP
2727
#define OPM_COMPOSITIONAL_CONTAINER_HPP
2828

29+
#include <opm/input/eclipse/EclipseState/Compositional/CompositionalConfig.hpp>
30+
2931
#include <array>
3032
#include <functional>
3133
#include <map>
34+
#include <optional>
3235
#include <string>
3336
#include <vector>
3437

@@ -72,6 +75,20 @@ class CompositionalContainer
7275
void assignSaturationPressure(const unsigned globalDofIdx,
7376
const Scalar psat);
7477

78+
/// The saturation pressure of a cell, or nothing when the solver could
79+
/// not determine one. A cell holding both hydrocarbon phases is at its
80+
/// saturation pressure; a single-phase cell has the bubble- (dew-) point
81+
/// pressure of its total composition. Takes plain values rather than a
82+
/// fluid state so the phase selection and the solver wiring can be
83+
/// exercised without a simulator.
84+
static std::optional<Scalar>
85+
cellSaturationPressure(const Scalar oilSaturation,
86+
const Scalar gasSaturation,
87+
const Scalar oilPressure,
88+
const std::array<Scalar, numComponents>& moleFractions,
89+
const Scalar temperature,
90+
const CompositionalConfig::EOSType eosType);
91+
7592
void assignVaporFraction(const unsigned globalDofIdx,
7693
const Scalar vmf);
7794

opm/simulators/flow/OutputCompositionalModule.hpp

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
#include <opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
4343

4444
#include <opm/material/common/Valgrind.hpp>
45-
#include <opm/material/constraintsolvers/SaturationPressure.hpp>
4645

4746
#include <opm/models/blackoil/blackoilproperties.hh>
4847
#include <opm/models/common/multiphasebaseproperties.hh>
@@ -202,15 +201,6 @@ class OutputCompositionalModule : public GenericOutputModule<GetPropType<TypeTag
202201

203202
void assignToSolution(data::Solution& sol) override
204203
{
205-
if (this->numFailedSaturationPressures_ > 0) {
206-
// The solver reports both an absent root and a failed search as
207-
// zero. This count covers the current local output snapshot.
208-
OpmLog::info(fmt::format("No saturation pressure for {} cells; "
209-
"PSAT is reported as zero there.",
210-
this->numFailedSaturationPressures_));
211-
this->numFailedSaturationPressures_ = 0;
212-
}
213-
214204
this->compC_.outputRestart(sol, this->saturation_[oilPhaseIdx]);
215205
BaseType::assignToSolution(sol);
216206

@@ -738,6 +728,22 @@ class OutputCompositionalModule : public GenericOutputModule<GetPropType<TypeTag
738728
this->assignSaturationPressure_(globalDofIdx, intQuants.fluidState());
739729
}
740730

731+
/// Runs on every rank once the per-cell pass is complete, so this is
732+
/// where the count of cells without a saturation pressure is reduced.
733+
/// Reporting it at output time would report the I/O rank's cells alone:
734+
/// the output is assembled there, while the count is gathered per rank.
735+
void validateLocalData()
736+
{
737+
const auto& comm = this->simulator_.gridView().comm();
738+
const auto total = comm.sum(this->numFailedSaturationPressures_);
739+
this->numFailedSaturationPressures_ = 0;
740+
if (total > 0 && comm.rank() == 0) {
741+
OpmLog::info(fmt::format("No saturation pressure could be determined for {} "
742+
"cells; PSAT is written as zero there.", total));
743+
}
744+
BaseType::validateLocalData();
745+
}
746+
741747
protected:
742748
//! \brief Allocate compositional relative-permeability buffers.
743749
void allocFormulationBuffers(std::map<std::string, int>& rstKeywords,
@@ -812,37 +818,29 @@ class OutputCompositionalModule : public GenericOutputModule<GetPropType<TypeTag
812818
return;
813819
}
814820

815-
const Scalar sOil = getValue(fs.saturation(oilPhaseIdx));
816-
const Scalar sGas = getValue(fs.saturation(gasPhaseIdx));
817-
818-
Scalar psat = 0.0;
819-
if (sOil > 0.0 && sGas > 0.0) {
820-
psat = getValue(fs.pressure(oilPhaseIdx));
821+
std::array<Scalar, numComponents> z;
822+
for (int c = 0; c < numComponents; ++c) {
823+
z[c] = getValue(fs.moleFraction(c));
821824
}
822-
else {
823-
using SatP = SaturationPressure<Scalar, FluidSystem>;
824-
typename SatP::CompVec z;
825-
typename SatP::CompVec incipient;
826-
for (int c = 0; c < numComponents; ++c) {
827-
z[c] = getValue(fs.moleFraction(c));
828-
}
829-
const Scalar temp = getValue(fs.temperature(oilPhaseIdx));
830-
const bool converged = (sGas <= 0.0)
831-
? SatP::bubblePressure(z, temp, eosType_, psat, incipient)
832-
: SatP::dewPressure(z, temp, eosType_, psat, incipient);
833-
if (!converged) {
834-
// A supercritical mixture genuinely has no saturation pressure,
835-
// and so does a search that gave up; both report zero. Count
836-
// them so that a systematically failing solve is not silent.
837-
psat = 0.0;
825+
const auto psat = CompositionalContainer<FluidSystem>::cellSaturationPressure(
826+
getValue(fs.saturation(oilPhaseIdx)),
827+
getValue(fs.saturation(gasPhaseIdx)),
828+
getValue(fs.pressure(oilPhaseIdx)),
829+
z,
830+
getValue(fs.temperature(oilPhaseIdx)),
831+
eosType_);
832+
if (!psat) {
833+
// The solver never asserts that no saturation pressure exists; it
834+
// reports that it could not determine one, which a supercritical
835+
// mixture, where none exists, also does. Zero stands for that,
836+
// and the cells are counted so a field-wide total is visible.
838837
#ifdef _OPENMP
839838
#pragma omp atomic
840839
#endif
841-
++this->numFailedSaturationPressures_;
842-
}
840+
++this->numFailedSaturationPressures_;
843841
}
844842

845-
this->compC_.assignSaturationPressure(globalDofIdx, psat);
843+
this->compC_.assignSaturationPressure(globalDofIdx, psat.value_or(Scalar{0}));
846844
}
847845

848846
const Simulator& simulator_;

tests/test_compositionalcontainer.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,37 @@
2424

2525
#include <opm/simulators/flow/CompositionalContainer.hpp>
2626

27+
#include <opm/input/eclipse/EclipseState/Compositional/CompositionalConfig.hpp>
28+
2729
#include <opm/material/fluidsystems/GenericOilGasWaterFluidSystem.hpp>
2830
#include <opm/output/data/Solution.hpp>
2931

32+
#include <array>
3033
#include <map>
34+
#include <optional>
3135
#include <string>
3236
#include <vector>
3337

3438
namespace {
3539
using FluidSystem = Opm::GenericOilGasWaterFluidSystem<double, 3, false>;
3640
using Container = Opm::CompositionalContainer<FluidSystem>;
37-
}
41+
42+
// The three-component CO2/methane/decane fluid of the saturation-pressure
43+
// solver's own tests, so their reference values can be reused here.
44+
struct Fixture
45+
{
46+
Fixture()
47+
{
48+
using CompParam = typename FluidSystem::ComponentParam;
49+
FluidSystem::init();
50+
FluidSystem::addComponent(CompParam{"CO2", 44.0, 304.128, 73.773e5, 0.09412, 0.22394});
51+
FluidSystem::addComponent(CompParam{"C1", 16.04, 190.564, 45.992e5, 0.09863, 0.01142});
52+
FluidSystem::addComponent(CompParam{"C10", 142.28, 617.7, 21.03e5, 0.60980, 0.4884});
53+
}
54+
};
55+
} // anonymous namespace
56+
57+
BOOST_GLOBAL_FIXTURE(Fixture);
3858

3959
BOOST_AUTO_TEST_CASE(SaturationPressureRequiresRestartOutput)
4060
{
@@ -85,3 +105,47 @@ BOOST_AUTO_TEST_CASE(DisabledSaturationPressureClearsPreviousRequest)
85105
container.allocate(2, keywords, /*isRestartOutput=*/true);
86106
BOOST_CHECK(!container.saturationPressureAllocated());
87107
}
108+
109+
BOOST_AUTO_TEST_CASE(CellSaturationPressureSelectsThePhaseAndTheBranch)
110+
{
111+
// 100 degC with Peng-Robinson, as in the solver's tests.
112+
constexpr double temperature = 373.15;
113+
constexpr auto eos = Opm::CompositionalConfig::EOSType::PR;
114+
using CompVec = std::array<double, 3>;
115+
116+
// Both phases present: the cell is at its saturation pressure and the
117+
// solver is not consulted.
118+
{
119+
const auto psat = Container::cellSaturationPressure(
120+
0.3, 0.7, 75.0e5, CompVec{0.0, 0.5, 0.5}, temperature, eos);
121+
BOOST_REQUIRE(psat.has_value());
122+
BOOST_CHECK_CLOSE(*psat, 75.0e5, 1.0e-12);
123+
}
124+
125+
// Oil only: the bubble point of the total composition. The reference
126+
// simulator puts this liquid at 160.5601 bar.
127+
{
128+
const auto psat = Container::cellSaturationPressure(
129+
1.0, 0.0, 150.0e5, CompVec{0.0, 0.5, 0.5}, temperature, eos);
130+
BOOST_REQUIRE(psat.has_value());
131+
BOOST_CHECK_CLOSE(*psat / 1.0e5, 160.56010, 1.0e-3);
132+
}
133+
134+
// Gas only: the retrograde dew point of that liquid's equilibrium vapour,
135+
// which recovers the same pressure. The vapour is known to single
136+
// precision, hence the tolerance.
137+
{
138+
const auto psat = Container::cellSaturationPressure(
139+
0.0, 1.0, 150.0e5, CompVec{0.0, 0.987784, 0.012216}, temperature, eos);
140+
BOOST_REQUIRE(psat.has_value());
141+
BOOST_CHECK_CLOSE(*psat / 1.0e5, 160.56010, 1.0e-2);
142+
}
143+
144+
// Pure methane far above its critical temperature has no saturation
145+
// pressure, and the cell pressure must not stand in for one.
146+
{
147+
const auto psat = Container::cellSaturationPressure(
148+
0.0, 1.0, 150.0e5, CompVec{0.0, 1.0, 0.0}, temperature, eos);
149+
BOOST_CHECK(!psat.has_value());
150+
}
151+
}

0 commit comments

Comments
 (0)