Merge pull request #5438 from bska/sfunc-consistency-checks

Add New Platform for Saturation Function Consistency Checks
This commit is contained in:
Bård Skaflestad 2024-06-26 13:22:21 +02:00 committed by GitHub
commit e9591e0a3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1891 additions and 1 deletions

View File

@ -183,6 +183,11 @@ list (APPEND MAIN_SOURCE_FILES
opm/simulators/wells/WGState.cpp
)
if (HAVE_ECL_INPUT)
list (APPEND MAIN_SOURCE_FILES
opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp
)
endif()
if (Damaris_FOUND AND MPI_FOUND AND USE_DAMARIS_LIB)
list (APPEND MAIN_SOURCE_FILES
@ -333,7 +338,10 @@ list (APPEND TEST_SOURCE_FILES
)
if (HAVE_ECL_INPUT)
list(APPEND TEST_SOURCE_FILES tests/test_nonnc.cpp)
list(APPEND TEST_SOURCE_FILES
tests/test_nonnc.cpp
tests/test_SatfuncConsistencyChecks.cpp
)
endif()
if(MPI_FOUND)
@ -675,6 +683,12 @@ if (USE_BDA_BRIDGE)
)
endif()
if (HAVE_ECL_INPUT)
list (APPEND PUBLIC_HEADER_FILES
opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp
)
endif()
if (Damaris_FOUND AND MPI_FOUND AND USE_DAMARIS_LIB)
list (APPEND PUBLIC_HEADER_FILES
opm/simulators/utils/DamarisKeywords.hpp

View File

@ -0,0 +1,560 @@
/*
Copyright 2024 Equinor AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp>
#include <opm/material/fluidmatrixinteractions/EclEpsScalingPoints.hpp>
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <tuple>
#include <utility>
#include <vector>
#include <fmt/format.h>
// ===========================================================================
// Public member functions for SatfuncConsistencyChecks Template
// ===========================================================================
template <typename Scalar>
Opm::SatfuncConsistencyChecks<Scalar>::
SatfuncConsistencyChecks(std::string_view pointName,
const std::size_t numSamplePoints)
: pointName_ { pointName }
, numSamplePoints_ { numSamplePoints }
, formatPointID_ { [](const std::size_t i) { return fmt::format("{}", i); } }
{}
template <typename Scalar>
Opm::SatfuncConsistencyChecks<Scalar>::
SatfuncConsistencyChecks(SatfuncConsistencyChecks&& rhs)
: pointName_ { std::move(rhs.pointName_) }
, numSamplePoints_ { rhs.numSamplePoints_ }
, formatPointID_ { std::move(rhs.formatPointID_) }
, startCheckValues_ { std::move(rhs.startCheckValues_) }
, violations_ { std::move(rhs.violations_) }
, battery_ { std::move(rhs.battery_) }
{}
template <typename Scalar>
Opm::SatfuncConsistencyChecks<Scalar>&
Opm::SatfuncConsistencyChecks<Scalar>::operator=(SatfuncConsistencyChecks&& rhs)
{
this->pointName_ = std::move(rhs.pointName_);
this->numSamplePoints_ = rhs.numSamplePoints_;
this->formatPointID_ = std::move(rhs.formatPointID_);
this->startCheckValues_ = std::move(rhs.startCheckValues_);
this->violations_ = std::move(rhs.violations_);
this->battery_ = std::move(rhs.battery_);
this->urbg_.reset();
return *this;
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::resetCheckSet()
{
this->startCheckValues_.clear();
this->startCheckValues_.push_back(0);
for (auto& violation : this->violations_) {
violation.clear();
}
this->battery_.clear();
this->urbg_.reset();
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::addCheck(std::unique_ptr<Check> check)
{
this->battery_.push_back(std::move(check));
const auto numCheckValues = this->battery_.back()->numExportedCheckValues();
this->startCheckValues_.push_back(this->numSamplePoints_ * numCheckValues);
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::finaliseCheckSet()
{
std::partial_sum(this->startCheckValues_.begin(),
this->startCheckValues_.end(),
this->startCheckValues_.begin());
for (auto& violation : this->violations_) {
this->buildStructure(violation);
}
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::
checkEndpoints(const std::size_t pointID,
const EclEpsScalingPointsInfo<Scalar>& endPoints)
{
this->checkLoop([pointID, &endPoints, this]
(Check* currentCheck, const std::size_t checkIx)
{
currentCheck->test(endPoints);
if (! currentCheck->isViolated()) {
// Check holds for this set of end-points. Nothing to do.
return;
}
// If we get here then the check does not hold for this set of
// end-points. Process the violation at the prescribed level of
// attention. Critical violations typically end the run whereas
// a standard level violation typically generates warnings only.
const auto level = currentCheck->isCritical()
? ViolationLevel::Critical
: ViolationLevel::Standard;
this->processViolation(level, checkIx, pointID);
});
}
template <typename Scalar>
bool Opm::SatfuncConsistencyChecks<Scalar>::anyFailedChecks() const
{
return this->anyFailedChecks(ViolationLevel::Standard);
}
template <typename Scalar>
bool Opm::SatfuncConsistencyChecks<Scalar>::anyFailedCriticalChecks() const
{
return this->anyFailedChecks(ViolationLevel::Critical);
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::
reportFailures(const ViolationLevel level,
const ReportRecordOutput& emitReportRecord) const
{
this->checkLoop([this,
&emitReportRecord,
nValueChar = fmt::formatted_size("{:> 8.6e}", 1.0),
&violation = this->violations_[this->index(level)]]
(const Check* currentCheck, const std::size_t checkIx)
{
if (violation.count[checkIx] == 0) {
return;
}
this->writeReportHeader(currentCheck,
violation.count[checkIx],
emitReportRecord);
this->writeTabulatedReportSample(nValueChar,
currentCheck,
violation,
checkIx,
emitReportRecord);
});
}
// ===========================================================================
// Private member functions for SatfuncConsistencyChecks Template
// ===========================================================================
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::ViolationSample::clear()
{
this->count.clear();
this->pointID.clear();
this->checkValues.clear();
}
// ---------------------------------------------------------------------------
template <typename Scalar>
void
Opm::SatfuncConsistencyChecks<Scalar>::
buildStructure(ViolationSample& violation)
{
violation.count.assign(this->battery_.size(), 0);
violation.pointID.resize(this->battery_.size() * this->numSamplePoints_,
static_cast<std::size_t>(0xdeadc0deUL));
violation.checkValues.resize(this->startCheckValues_.back());
if constexpr (std::numeric_limits<Scalar>::has_quiet_NaN) {
std::fill(violation.checkValues.begin(),
violation.checkValues.end(),
std::numeric_limits<Scalar>::quiet_NaN());
}
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::
processViolation(const ViolationLevel level,
const std::size_t checkIx,
const std::size_t pointID)
{
auto& violation = this->violations_[this->index(level)];
const auto nViol = ++violation.count[checkIx];
// Special case handling for number of violations not exceeding number
// of sample points. Needed in order to guarantee that the full table
// is populated before starting the random replacement stage.
const auto sampleIx = (nViol <= this->numSamplePoints_)
? (nViol - 1)
: this->getSampleIndex(nViol);
if (sampleIx >= this->numSamplePoints_) {
// Reservoir sampling algorithm
// (https://en.wikipedia.org/wiki/Reservoir_sampling) says that this
// particular set of end-points should *not* be included in the
// reported violations. No more work needed in this case.
return;
}
// If we get here, then this set of end-points should be included in the
// reported violations. Record the pointID and the corresponding check
// values in their appropriate locations.
violation.pointID[checkIx*this->numSamplePoints_ + sampleIx] = pointID;
auto* exportedCheckValues = violation.checkValues.data()
+ this->violationValueStart(checkIx, sampleIx);
this->battery_[checkIx]->exportCheckValues(exportedCheckValues);
}
namespace {
std::vector<std::string::size_type>
computeFieldWidths(const std::vector<std::string>& columnHeaders,
const std::string::size_type minColWidth)
{
auto fieldWidths = std::vector<std::size_t>(columnHeaders.size());
std::transform(columnHeaders.begin(), columnHeaders.end(),
fieldWidths.begin(),
[minColWidth](const std::string& header)
{ return std::max(minColWidth, header.size()); });
return fieldWidths;
}
std::string
createTableSeparator(const std::string::size_type fwPointID,
const std::vector<std::string::size_type>& fieldWidths)
{
using namespace fmt::literals;
// Note: "+2" for one blank space on each side of the string value.
auto separator = fmt::format("+{name:-<{width}}",
"name"_a = "",
"width"_a = fwPointID + 2);
for (const auto& fieldWidth : fieldWidths) {
separator += fmt::format("+{name:-<{width}}",
"name"_a = "",
"width"_a = fieldWidth + 2);
}
separator += '+';
return separator;
}
template <typename EmitRecord>
void writeTableHeader(const std::string_view::size_type fwPointID,
std::string_view pointName,
const std::vector<std::string::size_type>& fieldWidths,
const std::vector<std::string>& columnHeaders,
EmitRecord&& emitRecord)
{
using namespace fmt::literals;
auto tableHeader = fmt::format("| {name:<{width}} ",
"name"_a = pointName,
"width"_a = fwPointID);
for (auto colIx = 0*columnHeaders.size(); colIx < columnHeaders.size(); ++colIx) {
tableHeader += fmt::format("| {name:<{width}} ",
"name"_a = columnHeaders[colIx],
"width"_a = fieldWidths[colIx]);
}
emitRecord(tableHeader + '|');
}
template <typename Scalar, typename EmitRecord>
void writeTableRecord(const std::string_view::size_type fwPointID,
std::string_view pointID,
const std::vector<std::string::size_type>& fieldWidths,
const Scalar* checkValues,
EmitRecord&& emitRecord)
{
using namespace fmt::literals;
auto record = fmt::format("| {pointID:<{width}} ",
"width"_a = fwPointID,
"pointID"_a = pointID);
for (auto colIx = 0*fieldWidths.size(); colIx < fieldWidths.size(); ++colIx) {
record += fmt::format("| {checkValue:>{width}.6e} ",
"width"_a = fieldWidths[colIx],
"checkValue"_a = checkValues[colIx]);
}
emitRecord(record + '|');
}
} // Anonymous namespace
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::
writeReportHeader(const Check* currentCheck,
const std::size_t violationCount,
const ReportRecordOutput& emitReportRecord) const
{
const auto* sampleMsg = (violationCount > this->numSamplePoints_)
? "Sample Violations"
: "List of Violations";
emitReportRecord(fmt::format("Consistency Problem:\n"
" {}\n"
" {}\n"
" Total Violations: {}\n\n"
"{}",
currentCheck->description(),
currentCheck->condition(),
violationCount, sampleMsg));
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::
writeTabulatedReportSample(const std::size_t nValueChar,
const Check* currentCheck,
const ViolationSample& violation,
const std::size_t checkIx,
const ReportRecordOutput& emitReportRecord) const
{
const auto formattedPointIDs = this->formatPointIDs(violation, checkIx);
const auto fieldWidthPointID =
std::max(formattedPointIDs.second, this->pointName_.size());
const auto columnHeaders = this->collectColumnHeaders(currentCheck);
const auto fieldWidths = computeFieldWidths(columnHeaders, nValueChar);
const auto separator = createTableSeparator(fieldWidthPointID, fieldWidths);
// Output separator to start table output.
emitReportRecord(separator);
// Output column headers.
writeTableHeader(fieldWidthPointID, this->pointName_,
fieldWidths, columnHeaders,
emitReportRecord);
// Output separator to start table value output.
emitReportRecord(separator);
// Emit sampled check violations in order sorted on the pointID.
for (const auto& i : this->sortedPointIndices(violation, checkIx)) {
const auto* checkValues = violation.checkValues.data()
+ this->violationValueStart(checkIx, i);
writeTableRecord(fieldWidthPointID, formattedPointIDs.first[i],
fieldWidths, checkValues,
emitReportRecord);
}
// Output separator to end table output.
//
// Note: We emit two blank lines after final separator in order to
// generate some vertical space for the case of multiple failing checks.
emitReportRecord(fmt::format("{}\n\n", separator));
}
template <typename Scalar>
std::pair<std::vector<std::string>, std::string::size_type>
Opm::SatfuncConsistencyChecks<Scalar>::
formatPointIDs(const ViolationSample& violation,
const std::size_t checkIx) const
{
auto formattedPointIDs = std::pair
<std::vector<std::string>,
std::string::size_type>
{
std::piecewise_construct,
std::forward_as_tuple(),
std::forward_as_tuple(std::string::size_type{0})
};
const auto nPoints = this->numPoints(violation, checkIx);
formattedPointIDs.first.reserve(nPoints);
const auto* pointIDs = violation.pointID.data()
+ (checkIx * this->numSamplePoints_);
for (auto point = 0*nPoints; point < nPoints; ++point) {
formattedPointIDs.first.push_back
(this->formatPointID_(pointIDs[point]));
formattedPointIDs.second =
std::max(formattedPointIDs.second,
formattedPointIDs.first.back().size());
}
return formattedPointIDs;
}
template <typename Scalar>
std::vector<std::string>
Opm::SatfuncConsistencyChecks<Scalar>::
collectColumnHeaders(const Check* currentCheck) const
{
auto headers = std::vector<std::string>
(currentCheck->numExportedCheckValues());
currentCheck->columnNames(headers.data());
return headers;
}
template <typename Scalar>
std::vector<std::size_t>
Opm::SatfuncConsistencyChecks<Scalar>::
sortedPointIndices(const ViolationSample& violation,
const std::size_t checkIx) const
{
auto sortedIdxs = std::vector<std::size_t>
(this->numPoints(violation, checkIx));
std::iota(sortedIdxs.begin(), sortedIdxs.end(), std::size_t{0});
std::sort(sortedIdxs.begin(), sortedIdxs.end(),
[pointIDs = violation.pointID.data() + (checkIx * this->numSamplePoints_)]
(const std::size_t i1, const std::size_t i2)
{
return pointIDs[i1] < pointIDs[i2];
});
return sortedIdxs;
}
template <typename Scalar>
std::size_t
Opm::SatfuncConsistencyChecks<Scalar>::
numPoints(const ViolationSample& violation,
const std::size_t checkIx) const
{
return std::min(this->numSamplePoints_,
violation.count[checkIx]);
}
template <typename Scalar>
std::size_t
Opm::SatfuncConsistencyChecks<Scalar>::
getSampleIndex(const std::size_t sampleSize)
{
assert (sampleSize > 0);
this->ensureRandomBitGeneratorIsInitialised();
return std::uniform_int_distribution<std::size_t>
{ 0, sampleSize - 1 }(*this->urbg_);
}
template <typename Scalar>
void Opm::SatfuncConsistencyChecks<Scalar>::ensureRandomBitGeneratorIsInitialised()
{
if (this->urbg_ != nullptr) {
return;
}
const auto k = static_cast<std::size_t>
(std::log2(RandomBitGenerator::modulus) / 32) + 1;
auto state = std::vector<typename RandomBitGenerator::result_type>(k + 3);
std::random_device rd{};
std::generate(state.begin(), state.end(), std::ref(rd));
std::seed_seq seeds(state.begin(), state.end());
this->urbg_ = std::make_unique<RandomBitGenerator>(seeds);
}
template <typename Scalar>
typename std::vector<Scalar>::size_type
Opm::SatfuncConsistencyChecks<Scalar>::
violationValueStart(const std::size_t checkIx,
const std::size_t sampleIx) const
{
return this->startCheckValues_[checkIx]
+ (sampleIx * this->battery_[checkIx]->numExportedCheckValues());
}
template <typename Scalar>
bool
Opm::SatfuncConsistencyChecks<Scalar>::
anyFailedChecks(const ViolationLevel level) const
{
const auto& violation = this->violations_[this->index(level)];
return std::any_of(violation.count.begin(), violation.count.end(),
[](const std::size_t n) { return n > 0; });
}
template <typename Scalar>
template <typename Body>
void Opm::SatfuncConsistencyChecks<Scalar>::checkLoop(Body&& body)
{
const auto numChecks = this->battery_.size();
for (auto checkIx = 0*numChecks; checkIx < numChecks; ++checkIx) {
body(this->battery_[checkIx].get(), checkIx);
}
}
template <typename Scalar>
template <typename Body>
void Opm::SatfuncConsistencyChecks<Scalar>::checkLoop(Body&& body) const
{
const auto numChecks = this->battery_.size();
for (auto checkIx = 0*numChecks; checkIx < numChecks; ++checkIx) {
body(this->battery_[checkIx].get(), checkIx);
}
}
// ===========================================================================
// Explicit Specialisations of SatfuncConsistencyChecks Template
//
// No other code below this separator
// ===========================================================================
template class Opm::SatfuncConsistencyChecks<float>;
template class Opm::SatfuncConsistencyChecks<double>;

View File

@ -0,0 +1,505 @@
/*
Copyright 2024 Equinor AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP
#define OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP
#include <cstddef>
#include <functional>
#include <memory>
#include <random>
#include <string>
#include <string_view>
#include <vector>
namespace Opm {
template <typename Scalar>
struct EclEpsScalingPointsInfo;
} // namespace Opm
namespace Opm {
/// Platform for running sets of consistency checks against collection
/// of saturation function end-points
///
/// \tparam Scalar Element type. Typically \c float or \c double.
template <typename Scalar>
class SatfuncConsistencyChecks
{
public:
/// Call-back interface for an individual check.
///
/// Specific checks are expected to inherit from this base class.
class Check
{
public:
/// Run specific check against a set of saturation function end-points.
///
/// \param[in] endPoints Set of saturation function end-points.
/// Might for instance be the scaled end-points of the
/// drainage functions in a single grid block or the unscaled
/// end-points of the tabulated saturation functions in a
/// single saturation region.
virtual void test(const EclEpsScalingPointsInfo<Scalar>& endPoints) = 0;
/// Whether or not last set of end-points violated this particular check.
virtual bool isViolated() const = 0;
/// Whether or not this check is critical to the simulator's
/// ability to run the case.
///
/// Violating critical checks should typically stop the run.
virtual bool isCritical() const = 0;
/// Number of \c Scalar values involved in the check.
virtual std::size_t numExportedCheckValues() const = 0;
/// Get a linearised copy of the \c Sclar values involved in the check.
///
/// \param[in,out] exportedCheckValues Pointer to contiguous
/// sequence of at least numExportedCheckValues() \c Scalars.
/// It is the responsibility of exportCheckValues() to
/// populate this sequence with sensible values.
virtual void exportCheckValues(Scalar* exportedCheckValues) const = 0;
/// Descriptive textual summary of this check.
///
/// Might for instance be something along the lines of
/// Oil-phase scaled end-points
virtual std::string description() const = 0;
/// Textual representation of the consistency condition.
virtual std::string condition() const = 0;
/// Retrieve names of the exported check values.
///
/// Order should match that of exportCheckValues().
///
/// \param[in,out] headers Pointer to contiguous sequence of at
/// least numExportedCheckValues() strings. It is the
/// responsibility of columnNames() to populate this sequence
/// with sensible values.
virtual void columnNames(std::string* headers) const = 0;
};
/// Severity level for consistency condition violation.
enum class ViolationLevel : std::size_t {
/// Consistency condition violated, but we're able to continue
/// the run.
Standard,
/// Consistency condition violated and we're not able to
/// continue the run.
Critical,
/// Implementation helper. Must be last enumerator.
NumLevels,
};
/// Call-back function type for outputting a single record of a
/// consistency condition violation report.
using ReportRecordOutput = std::function<void(std::string_view)>;
/// Call-back function type for formatting a numeric end-point ID.
using PointIDFormatCallback = std::function<std::string(std::size_t)>;
/// Constructor
///
/// \param[in] pointName Name/category of the points in this set of
/// checks. Might for instance be "Grid block" or "Saturation
/// region". Will be used as a column header.
///
/// \param[in] numSamplePoints Upper bound on the number of
/// end-point check violations to preserve for reporting
/// purposes. Should normally be a small number like 5 or 10.
explicit SatfuncConsistencyChecks(std::string_view pointName,
const std::size_t numSamplePoints);
/// Destructor.
~SatfuncConsistencyChecks() = default;
/// Deleted copy constructor
SatfuncConsistencyChecks(const SatfuncConsistencyChecks& rhs) = delete;
/// Move-constructor.
///
/// \param[in,out] rhs Source object. Left in a "valid but
/// unspecified" state on exit.
SatfuncConsistencyChecks(SatfuncConsistencyChecks&& rhs);
/// Deleted assignment operator.
SatfuncConsistencyChecks&
operator=(const SatfuncConsistencyChecks& rhs) = delete;
/// Move-assignment operator.
///
/// \param[in,out] rhs Source object. Left in a "valid but
/// unspecified" state on exit.
///
/// \return \code *this \endcode.
SatfuncConsistencyChecks& operator=(SatfuncConsistencyChecks&& rhs);
/// Replace formatting function for end-point IDs.
///
/// The default formatting function is just the identity (\code
/// std::to_string() \endcode) which is useful for testing, but
/// which will for instance not capture Cartesian structure.
///
/// \param[in] formatPointID Call-back function type for formatting
/// a numeric end-point ID.
///
/// \return \code *this \endcode
SatfuncConsistencyChecks&
setPointIDFormatCallback(const PointIDFormatCallback& formatPointID)
{
this->formatPointID_ = formatPointID;
return *this;
}
/// Clear current set of end-point checks.
void resetCheckSet();
/// Add specific check to in-progress check set.
///
/// \param[in] check Particular end-point check.
void addCheck(std::unique_ptr<Check> check);
/// Commit current set of checks and build requisite internal
/// support structures.
void finaliseCheckSet();
/// Run current set of checks against a specific set of end-points.
///
/// \param[in] pointID Numeric identifier for this particular set of
/// end-points. Typically a saturation region or a cell ID.
///
/// \param[in] endPoints Set of saturation function end-points.
/// Might for instance be the scaled end-points of the drainage
/// functions in a single grid block or the unscaled end-points
/// of the tabulated saturation functions in a single saturation
/// region. Will be passed directly on to \code Check::test()
/// \endcode for each check in the current set.
void checkEndpoints(const std::size_t pointID,
const EclEpsScalingPointsInfo<Scalar>& endPoints);
/// Whether or not any checks failed at the \c Standard level.
bool anyFailedChecks() const;
/// Whether or not any checks failed at the \c Critical level.
bool anyFailedCriticalChecks() const;
/// Generate textual summary output of all failed consistency checks
/// at specific level.
///
/// Reports only those conditions/checks for which there is at least
/// one violation.
///
/// \param[in] level Report's severity level.
///
/// \param[in] emitReportRecord Call-back function for outputting a
/// single record/line of a violation report. Typically a
/// wrapper of \code OpmLog::info() \endcode. It is the
/// responsibility of emitReportRecord() to properly display the
/// text lines to end users.
void reportFailures(const ViolationLevel level,
const ReportRecordOutput& emitReportRecord) const;
private:
/// Uniform random bit generator for "reservoir sampling".
///
/// We don't need mt19937 for this problem. The linear congruential
/// engine has a smaller internal state than mt19937 and that aspect
/// is more important here.
using RandomBitGenerator = std::minstd_rand;
/// Sample of consistency check violations at single severity level.
struct ViolationSample
{
/// Number of consistency check violations.
///
/// Size equal to number of consistency checks.
std::vector<std::size_t> count{};
/// Sample of point IDs for violated consistency checks.
///
/// \c numSamplePoints_ allocated for each consistency check.
/// Number of valid entries for check \c i is minimum of \c
/// numSamplePoints_ and \code count[i] \endcode.
std::vector<std::size_t> pointID{};
/// Scalar values for each sampled point.
///
/// \c numSamplePoints_ allocated for each individual check, and
/// the number of values per check determined by \code
/// Check::numExportedCheckValues() \endcode. Number of valid
/// entries for check \c i is minimum of \c numSamplePoints_ and
/// \code count[i] \endcode.
std::vector<Scalar> checkValues{};
/// Clear contents of all data members.
void clear();
};
/// Collection of consistency check violations.
///
/// One set of violations for each severity level.
using ViolationCollection = std::array
<ViolationSample, static_cast<std::size_t>(ViolationLevel::NumLevels)>;
/// Name/category of the points in this set of checks.
///
/// Set by constructor. Might for instance be "Grid block" or
/// "Saturation region". Will be used as a column header.
std::string pointName_{};
/// Maximum number of points retained for reporting purposes.
std::size_t numSamplePoints_;
/// Formatting function for end-point IDs.
///
/// The default formatting function is just the identity (\code
/// std::to_string() \endcode) which is useful for testing, but
/// which will for instance not capture Cartesian structure.
PointIDFormatCallback formatPointID_{};
/// Start offsets into \code ViolationSample::checkValues \endcode
/// for each individual check.
std::vector<typename std::vector<Scalar>::size_type> startCheckValues_{};
/// Collection of sampled consistency check violations.
///
/// One collection for each severity level.
ViolationCollection violations_{};
/// Collection of checks to run against the saturation function
/// end-points.
std::vector<std::unique_ptr<Check>> battery_{};
/// Random bit generator for point sampling.
///
/// Represented as a pointer in order to avoid allocation and
/// initialisation when the facility is not needed. This is useful
/// when none of the consistency checks are violated which in turn
/// is a common case in production runs.
std::unique_ptr<RandomBitGenerator> urbg_{};
/// Allocate and initialise backing storage for a single set of
/// sampled consistency check violations.
///
/// \param[out] violation On return, zeroed or otherwise initialised
/// violation sample of proper size.
void buildStructure(ViolationSample& violation);
/// Internalise a single violation into internal data structures.
///
/// Counts the violation and uses "reservoir sampling"
/// (https://en.wikipedia.org/wiki/Reservoir_sampling) to determine
/// whether or not to include the specific point into the reporting
/// sample.
///
/// \param[in] level Violation severity level.
///
/// \param[in] checkIx Numerical check index in the range
/// [0..battery_.size()).
///
/// \param[in] pointID Numeric identifier for this particular set of
/// end-points. Typically a saturation region or a cell ID.
void processViolation(const ViolationLevel level,
const std::size_t checkIx,
const std::size_t pointID);
/// Generate random index in the sample size.
///
/// \param[in] sampleSize Total number of violations of a particular
/// check.
///
/// \return Uniformly distributed random integer in the range
/// 0..sampleSize-1, inclusive.
std::size_t getSampleIndex(const std::size_t sampleSize);
/// Ensure that random bit generator exists and is properly
/// initialised.
void ensureRandomBitGeneratorIsInitialised();
/// Compute start offset into ViolationSample::checkValues for
/// particular check and sample index.
///
/// \param[in] checkIx Numerical check index in the range
/// [0..battery_.size()).
///
/// \param[in] sampleIx Numerical index in the range
/// [0..min(count[checkIx], numSamplePoints_)).
///
/// \return Start offset into ViolationSample::checkValues.
typename std::vector<Scalar>::size_type
violationValueStart(const std::size_t checkIx,
const std::size_t sampleIx) const;
/// Emit violation report header.
///
/// Includes such information as the \code Check::description()
/// \endcode, the \code Check::condition() \endcode and the total
/// number of violations of this specific check.
///
/// \param[in] currentCheck Current check object. Needed for the
/// description and condition values.
///
/// \param[in] violationCount Total number of condition violations.
///
/// \param[in] emitReportRecord Call-back function for outputting a
/// single record/line of a violation report.
void writeReportHeader(const Check* currentCheck,
const std::size_t violationCount,
const ReportRecordOutput& emitReportRecord) const;
/// Emit tabulated sample of check violations.
///
/// \param[in] nValueChar Minimum number of characters (field width)
/// needed to display a single floating-point number ("checkValues").
///
/// \param[in] currentCheck Current check object. Needed for the
/// column headers in the tabulated output.
///
/// \param[in] violation Sample of consistency check violations.
///
/// \param[in] checkIx Numerical check index in the range
/// [0..battery_.size()).
///
/// \param[in] emitReportRecord Call-back function for outputting a
/// single record/line of a violation report.
void writeTabulatedReportSample(const std::size_t nValueChar,
const Check* currentCheck,
const ViolationSample& violation,
const std::size_t checkIx,
const ReportRecordOutput& emitReportRecord) const;
/// Generate textual representation of all sampled point IDs for a
/// single consistency check.
///
/// \param[in] violation Sample of consistency check violations.
///
/// \param[in] checkIx Numerical check index in the range
/// [0..battery_.size()).
///
/// \return Sequence of formatted point IDs and the minimum number
/// of characters needed to represent each one of these.
std::pair<std::vector<std::string>, std::string::size_type>
formatPointIDs(const ViolationSample& violation,
const std::size_t checkIx) const;
/// Generate sequence of table column headers for a single
/// consistency check.
///
/// \param[in] currentCheck Current check object. Needed for the
/// \code Check::columnNames() \endcode.
///
/// \return Copy of the check's column names.
std::vector<std::string>
collectColumnHeaders(const Check* currentCheck) const;
/// Generate sequence of sample indices, sorted ascendingly on the
/// corresponding point ID.
///
/// \param[in] currentCheck Current check object. Needed for the
/// \code Check::columnNames() \endcode.
///
/// \param[in] checkIx Numerical check index in the range
/// [0..battery_.size()).
///
/// \return Ascendingly sorted sample indices.
std::vector<std::size_t>
sortedPointIndices(const ViolationSample& violation,
const std::size_t checkIx) const;
/// Compute number of sample points for a single check's violations.
///
/// Effectively the minimum of the number of violations of that
/// check and the maximum number of sample points (\code
/// this->numSamplePoints_ \endcode).
///
/// \param[in] violation Sample of consistency check violations.
///
/// \param[in] checkIx Numerical check index in the range
/// [0..battery_.size()).
///
/// \return Number of active sample points.
std::size_t numPoints(const ViolationSample& violation,
const std::size_t checkIx) const;
/// Whether or not any checks failed at specified severity level.
///
/// \param[in] level Violation severity level.
///
/// \return Whether or not any checks failed at severity level \p
/// level.
bool anyFailedChecks(const ViolationLevel level) const;
/// Convert severity level into collection index.
///
/// \param[in] level Violation severity level.
///
/// \return Corresponding index into \code this->violations_
/// \endcode.
auto index(const ViolationLevel level) const
{
return static_cast<typename ViolationCollection::size_type>(level);
}
/// Run block of code for each registered consistency check.
///
/// Mutable version.
///
/// \tparam Body Call-back function type encapsulating block of code
/// to run for each registered consistency check. Expected to be a
/// callable type with a function call operator of the form
/// \code
/// void Body::operator()(Check* checkPtr, std::size_t i)
/// \endcode
/// in which the \c checkPtr points to a (mutable) \c Check object
/// and \c i is the sequence index in which the particular check was
/// registered in a call to addCheck().
///
/// \param[in,out] body Block of code to run for each registered
/// consistency check.
template <typename Body>
void checkLoop(Body&& body);
/// Run block of code for each registered consistency check.
///
/// Immutable version.
///
/// \tparam Body Call-back function type encapsulating block of code
/// to run for each registered consistency check. Expected to be a
/// callable type with a function call operator of the form
/// \code
/// void Body::operator()(const Check* checkPtr, std::size_t i) const
/// \endcode
/// in which the \c checkPtr points to an immutable \c Check object
/// and \c i is the sequence index in which the particular check was
/// registered in a call to addCheck().
///
/// \param[in] body Block of code to run for each registered
/// consistency check.
template <typename Body>
void checkLoop(Body&& body) const;
};
} // namespace Opm
#endif // OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP

View File

@ -0,0 +1,811 @@
/*
Copyright 2024 Equinor AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#define BOOST_TEST_MODULE TestSatfuncConsistencyChecks
#ifndef HAVE_MPI
// Suppress GCC diagnostics of the form
//
// warning: "HAVE_MPI" is not defined, evaluates to 0
//
// when compiling with "-Wundef".
#define HAVE_MPI 0
#endif // HAVE_MPI
#include <boost/test/unit_test.hpp>
#include <opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp>
#include <opm/material/fluidmatrixinteractions/EclEpsScalingPoints.hpp>
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <fmt/format.h>
BOOST_AUTO_TEST_SUITE(NoFailures)
namespace {
class Standard : public Opm::SatfuncConsistencyChecks<double>::Check
{
public:
void test(const Opm::EclEpsScalingPointsInfo<double>&) override {}
bool isViolated() const override { return false; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 1; }
void exportCheckValues(double* exportedCheckValues) const override
{
*exportedCheckValues = 17.29;
}
std::string description() const override
{
return "Water Phase End-Point";
}
std::string condition() const override
{
return "0 <= SWL < 1";
}
void columnNames(std::string* headers) const override
{
*headers = "SWL";
}
};
Opm::EclEpsScalingPointsInfo<double> makePoints() { return {}; }
} // Anonymous namespace
BOOST_AUTO_TEST_CASE(All_Good)
{
auto checker = Opm::SatfuncConsistencyChecks<double>{"Cell", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<Standard>());
checker.finaliseCheckSet();
checker.checkEndpoints(1234, makePoints());
BOOST_CHECK_MESSAGE(! checker.anyFailedChecks(),
"There must be no failed checks");
BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(),
"There must be no failed critical checks");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<double>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_MESSAGE(rpt.empty(), "There must be no output from reportFailures()");
}
BOOST_AUTO_TEST_SUITE_END()
// ===========================================================================
BOOST_AUTO_TEST_SUITE(Single_Exported_Value)
namespace {
class StandardViolation : public Opm::SatfuncConsistencyChecks<double>::Check
{
public:
void test(const Opm::EclEpsScalingPointsInfo<double>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 1; }
void exportCheckValues(double* exportedCheckValues) const override
{
*exportedCheckValues = 17.29;
}
std::string description() const override
{
return "Water Phase End-Point";
}
std::string condition() const override
{
return "0 <= SWL < 1";
}
void columnNames(std::string* headers) const override
{
*headers = "SWL";
}
};
class CriticalViolation : public Opm::SatfuncConsistencyChecks<double>::Check
{
public:
void test(const Opm::EclEpsScalingPointsInfo<double>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return true; }
std::size_t numExportedCheckValues() const override { return 1; }
void exportCheckValues(double* exportedCheckValues) const override
{
*exportedCheckValues = 314.15926;
}
std::string description() const override
{
return "Minimum Pressure";
}
std::string condition() const override
{
return "PRESS > 350";
}
void columnNames(std::string* headers) const override
{
*headers = "PRESS";
}
};
Opm::EclEpsScalingPointsInfo<double> makePoints() { return {}; }
} // Anonymous namespace
BOOST_AUTO_TEST_CASE(Standard_Violation)
{
auto checker = Opm::SatfuncConsistencyChecks<double>{"Cell", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<StandardViolation>());
checker.finaliseCheckSet();
checker.checkEndpoints(1234, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<double>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Water Phase End-Point
0 <= SWL < 1
Total Violations: 1
List of Violations
+------+---------------+
| Cell | SWL |
+------+---------------+
| 1234 | 1.729000e+01 |
+------+---------------+
)");
}
BOOST_AUTO_TEST_CASE(Standard_Violation_ReportIJK)
{
auto checker = Opm::SatfuncConsistencyChecks<double>{"Cell", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<StandardViolation>());
checker.finaliseCheckSet();
checker.setPointIDFormatCallback([](const std::size_t)
{
return std::string { "(11, 22, 33)" };
});
checker.checkEndpoints(1234, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<double>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Water Phase End-Point
0 <= SWL < 1
Total Violations: 1
List of Violations
+--------------+---------------+
| Cell | SWL |
+--------------+---------------+
| (11, 22, 33) | 1.729000e+01 |
+--------------+---------------+
)");
}
BOOST_AUTO_TEST_CASE(Critical_Violation)
{
auto checker = Opm::SatfuncConsistencyChecks<double>{"PVTNUM", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<CriticalViolation>());
checker.finaliseCheckSet();
checker.checkEndpoints(42, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedCriticalChecks(),
"There must be at least one failed Critical check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<double>::ViolationLevel::Critical,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Minimum Pressure
PRESS > 350
Total Violations: 1
List of Violations
+--------+---------------+
| PVTNUM | PRESS |
+--------+---------------+
| 42 | 3.141593e+02 |
+--------+---------------+
)");
}
BOOST_AUTO_TEST_SUITE_END() // Single_Exported_Value
// ===========================================================================
BOOST_AUTO_TEST_SUITE(Two_Exported_Values)
namespace {
class Violation : public Opm::SatfuncConsistencyChecks<float>::Check
{
void test(const Opm::EclEpsScalingPointsInfo<float>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 2; }
void exportCheckValues(float* exportedCheckValues) const override
{
exportedCheckValues[0] = 1.6f;
exportedCheckValues[1] = 1.6f + 0.5f;
}
std::string description() const override
{
return "Sum";
}
std::string condition() const override
{
return "a + 1/2 < 2";
}
void columnNames(std::string* headers) const override
{
headers[0] = "a";
headers[1] = "a + 1/2";
}
};
Opm::EclEpsScalingPointsInfo<float> makePoints() { return {}; }
} // Anonymous namespace
BOOST_AUTO_TEST_CASE(Standard)
{
auto checker = Opm::SatfuncConsistencyChecks<float>{"Bucket", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<Violation>());
checker.finaliseCheckSet();
checker.checkEndpoints(1234, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<float>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Sum
a + 1/2 < 2
Total Violations: 1
List of Violations
+--------+---------------+---------------+
| Bucket | a | a + 1/2 |
+--------+---------------+---------------+
| 1234 | 1.600000e+00 | 2.100000e+00 |
+--------+---------------+---------------+
)");
}
BOOST_AUTO_TEST_SUITE_END() // Two_Exported_Values
// ===========================================================================
BOOST_AUTO_TEST_SUITE(Five_Exported_Values)
namespace {
class Violation : public Opm::SatfuncConsistencyChecks<float>::Check
{
void test(const Opm::EclEpsScalingPointsInfo<float>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 5; }
void exportCheckValues(float* exportedCheckValues) const override
{
exportedCheckValues[0] = 0.1f;
exportedCheckValues[1] = 0.7f;
exportedCheckValues[2] = 0.3f;
exportedCheckValues[3] = 0.2f;
exportedCheckValues[4] = 0.88f;
}
std::string description() const override
{
return "Water Phase End-Point Displacing Saturation";
}
std::string condition() const override
{
return "SWCR < 1-SOWCR-SGL < SWU";
}
void columnNames(std::string* headers) const override
{
headers[0] = "SGL";
headers[1] = "SOWCR";
headers[2] = "SWCR";
headers[3] = "1-SOWCR-SGL";
headers[4] = "SWU";
}
};
Opm::EclEpsScalingPointsInfo<float> makePoints() { return {}; }
} // Anonymous namespace
BOOST_AUTO_TEST_CASE(Standard)
{
auto checker = Opm::SatfuncConsistencyChecks<float>{"Grid Block", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<Violation>());
checker.finaliseCheckSet();
checker.setPointIDFormatCallback([](const std::size_t)
{
return std::string { "(121, 323, 42)" };
});
checker.checkEndpoints(1234, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<float>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Water Phase End-Point Displacing Saturation
SWCR < 1-SOWCR-SGL < SWU
Total Violations: 1
List of Violations
+----------------+---------------+---------------+---------------+---------------+---------------+
| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU |
+----------------+---------------+---------------+---------------+---------------+---------------+
| (121, 323, 42) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 |
+----------------+---------------+---------------+---------------+---------------+---------------+
)");
}
BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points)
{
auto checker = Opm::SatfuncConsistencyChecks<float>{"Grid Block", 5};
checker.resetCheckSet();
checker.addCheck(std::make_unique<Violation>());
checker.finaliseCheckSet();
checker.checkEndpoints( 1234, makePoints());
checker.checkEndpoints( 1729, makePoints());
checker.checkEndpoints( 1618, makePoints());
checker.checkEndpoints(31415, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<float>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
// Note that grid blocks are reported in sorted order rather in the
// order of insertion.
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Water Phase End-Point Displacing Saturation
SWCR < 1-SOWCR-SGL < SWU
Total Violations: 4
List of Violations
+------------+---------------+---------------+---------------+---------------+---------------+
| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU |
+------------+---------------+---------------+---------------+---------------+---------------+
| 1234 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 |
| 1618 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 |
| 1729 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 |
| 31415 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 |
+------------+---------------+---------------+---------------+---------------+---------------+
)");
}
BOOST_AUTO_TEST_SUITE_END() // Five_Exported_Values
// ===========================================================================
BOOST_AUTO_TEST_SUITE(Multiple_Failing_Tests)
namespace {
class MonotoneWater : public Opm::SatfuncConsistencyChecks<float>::Check
{
void test(const Opm::EclEpsScalingPointsInfo<float>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 3; }
void exportCheckValues(float* exportedCheckValues) const override
{
exportedCheckValues[0] = 0.1f;
exportedCheckValues[1] = 0.3f;
exportedCheckValues[2] = 0.3f;
}
std::string description() const override
{
return "Water Phase End-Point Monotonicity";
}
std::string condition() const override
{
return "SWL <= SWCR < SWU";
}
void columnNames(std::string* headers) const override
{
headers[0] = "SWL";
headers[1] = "SWCR";
headers[2] = "SWU";
}
};
class MonotoneGas : public Opm::SatfuncConsistencyChecks<float>::Check
{
void test(const Opm::EclEpsScalingPointsInfo<float>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 3; }
void exportCheckValues(float* exportedCheckValues) const override
{
exportedCheckValues[0] = 0.0f;
exportedCheckValues[1] = -0.1f;
exportedCheckValues[2] = 0.8f;
}
std::string description() const override
{
return "Gas Phase End-Point Monotonicity";
}
std::string condition() const override
{
return "SGL <= SGCR < SGU";
}
void columnNames(std::string* headers) const override
{
headers[0] = "SGL";
headers[1] = "SGCR";
headers[2] = "SGU";
}
};
class NonNegOSAT_OW : public Opm::SatfuncConsistencyChecks<float>::Check
{
void test(const Opm::EclEpsScalingPointsInfo<float>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 3; }
void exportCheckValues(float* exportedCheckValues) const override
{
exportedCheckValues[0] = 0.1f;
exportedCheckValues[1] = 1.0f;
exportedCheckValues[2] = 1.1f;
}
std::string description() const override
{
return "Oil Phase Non-Negative Saturation (O/W)";
}
std::string condition() const override
{
return "SGL + SWU <= 1";
}
void columnNames(std::string* headers) const override
{
headers[0] = "SGL";
headers[1] = "SWU";
headers[2] = "SGL + SWU";
}
};
class NonNegOSAT_GO : public Opm::SatfuncConsistencyChecks<float>::Check
{
void test(const Opm::EclEpsScalingPointsInfo<float>&) override {}
bool isViolated() const override { return true; }
bool isCritical() const override { return false; }
std::size_t numExportedCheckValues() const override { return 3; }
void exportCheckValues(float* exportedCheckValues) const override
{
exportedCheckValues[0] = 0.25f;
exportedCheckValues[1] = 0.8f;
exportedCheckValues[2] = 1.05f;
}
std::string description() const override
{
return "Oil Phase Non-Negative Saturation (G/O)";
}
std::string condition() const override
{
return "SWL + SGU <= 1";
}
void columnNames(std::string* headers) const override
{
headers[0] = "SWL";
headers[1] = "SGU";
headers[2] = "SWL + SGU";
}
};
Opm::EclEpsScalingPointsInfo<float> makePoints() { return {}; }
} // Anonymous namespace
BOOST_AUTO_TEST_CASE(Standard)
{
auto checker = Opm::SatfuncConsistencyChecks<float>{"Grid Block", 1};
checker.resetCheckSet();
checker.addCheck(std::make_unique<MonotoneGas>());
checker.addCheck(std::make_unique<NonNegOSAT_GO>());
checker.addCheck(std::make_unique<MonotoneWater>());
checker.addCheck(std::make_unique<NonNegOSAT_OW>());
checker.finaliseCheckSet();
checker.setPointIDFormatCallback([](const std::size_t)
{
return std::string { "(121, 323, 42)" };
});
checker.checkEndpoints(1234, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<float>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Gas Phase End-Point Monotonicity
SGL <= SGCR < SGU
Total Violations: 1
List of Violations
+----------------+---------------+---------------+---------------+
| Grid Block | SGL | SGCR | SGU |
+----------------+---------------+---------------+---------------+
| (121, 323, 42) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 |
+----------------+---------------+---------------+---------------+
Consistency Problem:
Oil Phase Non-Negative Saturation (G/O)
SWL + SGU <= 1
Total Violations: 1
List of Violations
+----------------+---------------+---------------+---------------+
| Grid Block | SWL | SGU | SWL + SGU |
+----------------+---------------+---------------+---------------+
| (121, 323, 42) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 |
+----------------+---------------+---------------+---------------+
Consistency Problem:
Water Phase End-Point Monotonicity
SWL <= SWCR < SWU
Total Violations: 1
List of Violations
+----------------+---------------+---------------+---------------+
| Grid Block | SWL | SWCR | SWU |
+----------------+---------------+---------------+---------------+
| (121, 323, 42) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 |
+----------------+---------------+---------------+---------------+
Consistency Problem:
Oil Phase Non-Negative Saturation (O/W)
SGL + SWU <= 1
Total Violations: 1
List of Violations
+----------------+---------------+---------------+---------------+
| Grid Block | SGL | SWU | SGL + SWU |
+----------------+---------------+---------------+---------------+
| (121, 323, 42) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 |
+----------------+---------------+---------------+---------------+
)");
}
BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points)
{
auto checker = Opm::SatfuncConsistencyChecks<float>{"Grid Block", 5};
checker.resetCheckSet();
checker.addCheck(std::make_unique<MonotoneGas>());
checker.addCheck(std::make_unique<NonNegOSAT_GO>());
checker.addCheck(std::make_unique<MonotoneWater>());
checker.addCheck(std::make_unique<NonNegOSAT_OW>());
checker.finaliseCheckSet();
checker.checkEndpoints( 1234, makePoints());
checker.checkEndpoints( 1729, makePoints());
checker.checkEndpoints( 1618, makePoints());
checker.checkEndpoints(31415, makePoints());
BOOST_CHECK_MESSAGE(checker.anyFailedChecks(),
"There must be at least one failed check");
auto rpt = std::string{};
checker.reportFailures(Opm::SatfuncConsistencyChecks<float>::ViolationLevel::Standard,
[&rpt](std::string_view record)
{
rpt += fmt::format("{}\n", record);
});
// Note that grid blocks are reported in sorted order rather in the
// order of insertion.
BOOST_CHECK_EQUAL(rpt, R"(Consistency Problem:
Gas Phase End-Point Monotonicity
SGL <= SGCR < SGU
Total Violations: 4
List of Violations
+------------+---------------+---------------+---------------+
| Grid Block | SGL | SGCR | SGU |
+------------+---------------+---------------+---------------+
| 1234 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 |
| 1618 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 |
| 1729 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 |
| 31415 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 |
+------------+---------------+---------------+---------------+
Consistency Problem:
Oil Phase Non-Negative Saturation (G/O)
SWL + SGU <= 1
Total Violations: 4
List of Violations
+------------+---------------+---------------+---------------+
| Grid Block | SWL | SGU | SWL + SGU |
+------------+---------------+---------------+---------------+
| 1234 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 |
| 1618 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 |
| 1729 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 |
| 31415 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 |
+------------+---------------+---------------+---------------+
Consistency Problem:
Water Phase End-Point Monotonicity
SWL <= SWCR < SWU
Total Violations: 4
List of Violations
+------------+---------------+---------------+---------------+
| Grid Block | SWL | SWCR | SWU |
+------------+---------------+---------------+---------------+
| 1234 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 |
| 1618 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 |
| 1729 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 |
| 31415 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 |
+------------+---------------+---------------+---------------+
Consistency Problem:
Oil Phase Non-Negative Saturation (O/W)
SGL + SWU <= 1
Total Violations: 4
List of Violations
+------------+---------------+---------------+---------------+
| Grid Block | SGL | SWU | SGL + SWU |
+------------+---------------+---------------+---------------+
| 1234 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 |
| 1618 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 |
| 1729 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 |
| 31415 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 |
+------------+---------------+---------------+---------------+
)");
}
BOOST_AUTO_TEST_SUITE_END() // Multiple_Failing_Tests