mirror of
https://github.com/OPM/opm-simulators.git
synced 2025-02-25 18:55:30 -06:00
Add New Platform for Saturation Function Consistency Checks
The intention is that this will ultimately replace the existing RelpermDiagnostics component which does not really work in parallel and which does not report enough context to help diagnose underlying issues. For now, though, we just add the shell of a new set of checks and hook that up to the build. Class SatfuncConsistencyChecks<Scalar> manages a configurable set of consistency checks, the implementations of which must publicly derive from SatfuncConsistencyChecks<Scalar>::Check. Client code will configure a set of checks by first calling SatfuncConsistencyChecks<Scalar>::resetCheckSet() then register individual checks by calling SatfuncConsistencyChecks<Scalar>::addCheck() and finally build requisite internal structures by calling SatfuncConsistencyChecks<Scalar>::finaliseCheckSet() Client code will then run the checks by calling SatfuncConsistencyChecks<Scalar>::checkEndpoints() typically in a loop. Class SatfuncConsistencyChecks<Scalar> will count consistency check failures and attribute these to each individual check as needed. We also maintain separate counts for "Standard" and "Critical" failures. The former will typically generate warnings while the latter will typically cause the simulation run to stop. Individual checks get to decide which check is "Critical", and client code gets to decide how to respond to "Critical" failures. Member function SatfuncConsistencyChecks<Scalar>::reportFailures() will generate a textual report of the known set of consistency check failures at a give severity level. As an internal implementation detail, SatfuncConsistencyChecks uses "reservoir sampling" (https://en.wikipedia.org/wiki/Reservoir_sampling) to track details about individual failed checks. We maintain at most a fixed number of individual points (constructor argument).
This commit is contained in:
parent
ac42250b25
commit
c3939c5444
@ -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
|
||||
|
560
opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp
Normal file
560
opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp
Normal 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>;
|
505
opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp
Normal file
505
opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp
Normal 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
|
811
tests/test_SatfuncConsistencyChecks.cpp
Normal file
811
tests/test_SatfuncConsistencyChecks.cpp
Normal 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
|
Loading…
Reference in New Issue
Block a user