Summary
SPE10-MOD02-01.DATA, which ships in opm-tests, cannot be run by flow 2026.04. Initialisation aborts because the critical oil-in-gas saturation for SATNUM 1 evaluates to -5.551115e-17 — one ULP of floating-point round-off below zero — and the saturation function consistency check compares it against exactly 0.
A tolerance-free comparison against a bound is the underlying problem: the end-point is computed as a difference of nearly equal quantities, so a result a few ULPs outside [0,1] is arithmetic noise rather than inconsistent input.
Environment
- flow 2026.04, built from source at
release/2026.04/final
(opm-common 0ea62974, opm-simulators b82f21d)
- Linux Mint 22.3 (Ubuntu 24.04 base), gcc 13.3.0, cmake 3.28.3, DUNE 2.11.0 (OPM PPA)
RelWithDebInfo, -march=native -fno-omit-frame-pointer, BUILD_TESTING=OFF
Reproduction
cd opm-tests/spe10
mpirun -np 4 flow SPE10-MOD02-01.DATA --parsing-strictness=low --output-dir=/tmp/spe10
(--parsing-strictness=low is needed for unrelated unsupported keywords in this deck —
OLDTRAN, NSTACK, ECHO/NOECHO, and item 10 of MESSAGES. The abort below happens
regardless, and also occurs in serial.)
Output, after the grid is built and partitioned successfully:
Processing grid
Total number of active cells: 1094421 / total pore volume: 13636601 RB
ZOLTAN Load balancing method = 9 (GRAPH)
...
Error: Saturation Function End-point Consistency Failures
Consistency Problem:
Non-negative critical oil saturation in G/O system
0 <= SOGCR < 1
Total Violations: 4
List of Violations
+--------+---------------+
| SATNUM | SOGCR |
+--------+---------------+
| 1 | -5.551115e-17 |
| 1 | -5.551115e-17 |
| 1 | -5.551115e-17 |
| 1 | -5.551115e-17 |
+--------+---------------+
Simulation aborted as program threw an unexpected exception: Could not initialize the
problem: Saturation function end-points do not meet requisite consistency conditions
Analysis
Where the value comes from — opm-common, opm/input/eclipse/EclipseState/Grid/SatfuncPropertyInitializers.cpp:750, in findCriticalOilGas():
return critical_oil_gas(sgofTables.getTable<Opm::SgofTable>(i), tolcrit) - swco[i];
For this deck the two operands are equal to within representation error, so the subtraction
yields -5.551115e-17 rather than 0.
Where it becomes fatal — opm-simulators, opm/simulators/utils/satfunc/OilPhaseConsistencyChecks.cpp:43-44, in SOcr_GO::testImpl():
const auto low = this->sogcr_ < Scalar{0};
const auto high = ! (this->sogcr_ < Scalar{1});
if (low || high) {
this->setViolated();
this->setCritical();
}
The comparison is exact, and setCritical() makes the violation fatal.
This is not specific to the oil phase: Oil, Gas and Water PhaseConsistencyChecks contain
17 comparisons of end-points against Scalar{0} / Scalar{1} in the same style, each written
out in its own testImpl.
Both the computation and the check are unchanged on master as of 2026-09-18.
Suggested fix
Rather than adding a tolerance to each of the 17 comparisons, the attached patch sanitises the
end-points once, where they enter EclEpsScalingPointsInfo — opm-common,
opm/material/fluidmatrixinteractions/EclEpsScalingPoints.cpp, extractUnscaled() (line 83).
One helper defines the tolerance; a loop applies it to Swl, Sgl, Swcr, Sgcr, Sowcr,
Sogcr, Swu, Sgu.
/// Snap a saturation end-point onto the unit interval when it lies
/// outside by no more than a few rounding errors.
template <typename Scalar>
Scalar snapToUnitInterval(const Scalar s)
{
const auto tol = 10 * std::numeric_limits<Scalar>::epsilon();
if ((s < Scalar{0}) && (s > -tol)) { return Scalar{0}; }
if ((s > Scalar{1}) && (s < Scalar{1} + tol)) { return Scalar{1}; }
return s;
}
The intent is explicitly not to clamp: end-points outside [0,1] by more than the tolerance
are returned unchanged, so genuinely inconsistent input is still detected and reported exactly
as before. Verified against -5.551115e-17 and -2.3e-16 (snapped to 0) versus -1e-14,
-0.3 and 1.3 (unchanged, still reported).
The patch applies cleanly to both release/2026.04/final and current master.
Result
With the patch applied, SPE10 model 2 (1,094,421 active cells) initialises and runs to
completion — all 33 report steps, 2000 days — on 4 MPI ranks:
Number of MPI processes: 4
Threads per MPI process: 1
Setup time: 45.96 s
Number of timesteps: 85
Simulation time: 2060.81 s
Assembly time: 253.57 s
Linear solve time: 1693.18 s
Overall Newton Iterations: 327
Overall Linear Iterations: 2447
Points for maintainers
Offered as a starting point rather than a finished solution — three things we were unsure of:
- Tolerance magnitude.
10 * epsilon (~2e-15 for doubles) is a guess with no basis in your
conventions. A relative tolerance, or one derived from tolcrit, may suit better.
- Where to sanitise. Correcting the stored end-point affects every downstream consumer, not
only the consistency checks. If you would rather the checks tolerate noise while the stored
value stays untouched, the same helper could be applied in PhaseCheckBase instead — at the
cost of touching all 17 comparison sites, or refactoring them through a shared predicate.
- Regression data. This changes a computed value, so stored reference results may shift in
their last digits.
An alternative fix, avoiding the tolerance question entirely, would be to make the subtraction in
findCriticalOilGas() not produce sub-zero noise in the first place — though that addresses only
this one end-point, and leaves the exact comparisons in place for others to trip over.
snap-endpoints.patch
Summary
SPE10-MOD02-01.DATA, which ships inopm-tests, cannot be run byflow2026.04. Initialisation aborts because the critical oil-in-gas saturation for SATNUM 1 evaluates to-5.551115e-17— one ULP of floating-point round-off below zero — and the saturation function consistency check compares it against exactly0.A tolerance-free comparison against a bound is the underlying problem: the end-point is computed as a difference of nearly equal quantities, so a result a few ULPs outside
[0,1]is arithmetic noise rather than inconsistent input.Environment
release/2026.04/final(opm-common
0ea62974, opm-simulatorsb82f21d)RelWithDebInfo,-march=native -fno-omit-frame-pointer,BUILD_TESTING=OFFReproduction
(
--parsing-strictness=lowis needed for unrelated unsupported keywords in this deck —OLDTRAN,NSTACK,ECHO/NOECHO, and item 10 ofMESSAGES. The abort below happensregardless, and also occurs in serial.)
Output, after the grid is built and partitioned successfully:
Analysis
Where the value comes from —
opm-common,opm/input/eclipse/EclipseState/Grid/SatfuncPropertyInitializers.cpp:750, infindCriticalOilGas():return critical_oil_gas(sgofTables.getTable<Opm::SgofTable>(i), tolcrit) - swco[i];For this deck the two operands are equal to within representation error, so the subtraction
yields
-5.551115e-17rather than0.Where it becomes fatal —
opm-simulators,opm/simulators/utils/satfunc/OilPhaseConsistencyChecks.cpp:43-44, inSOcr_GO::testImpl():The comparison is exact, and
setCritical()makes the violation fatal.This is not specific to the oil phase:
Oil,GasandWaterPhaseConsistencyChecks contain17 comparisons of end-points against
Scalar{0}/Scalar{1}in the same style, each writtenout in its own
testImpl.Both the computation and the check are unchanged on master as of 2026-09-18.
Suggested fix
Rather than adding a tolerance to each of the 17 comparisons, the attached patch sanitises the
end-points once, where they enter
EclEpsScalingPointsInfo—opm-common,opm/material/fluidmatrixinteractions/EclEpsScalingPoints.cpp,extractUnscaled()(line 83).One helper defines the tolerance; a loop applies it to
Swl,Sgl,Swcr,Sgcr,Sowcr,Sogcr,Swu,Sgu.The intent is explicitly not to clamp: end-points outside
[0,1]by more than the toleranceare returned unchanged, so genuinely inconsistent input is still detected and reported exactly
as before. Verified against
-5.551115e-17and-2.3e-16(snapped to 0) versus-1e-14,-0.3and1.3(unchanged, still reported).The patch applies cleanly to both
release/2026.04/finaland current master.Result
With the patch applied, SPE10 model 2 (1,094,421 active cells) initialises and runs to
completion — all 33 report steps, 2000 days — on 4 MPI ranks:
Points for maintainers
Offered as a starting point rather than a finished solution — three things we were unsure of:
10 * epsilon(~2e-15 for doubles) is a guess with no basis in yourconventions. A relative tolerance, or one derived from
tolcrit, may suit better.only the consistency checks. If you would rather the checks tolerate noise while the stored
value stays untouched, the same helper could be applied in
PhaseCheckBaseinstead — at thecost of touching all 17 comparison sites, or refactoring them through a shared predicate.
their last digits.
An alternative fix, avoiding the tolerance question entirely, would be to make the subtraction in
findCriticalOilGas()not produce sub-zero noise in the first place — though that addresses onlythis one end-point, and leaves the exact comparisons in place for others to trip over.
snap-endpoints.patch