diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8c0ed51..7950f5084 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -542,6 +542,45 @@ opm_add_test(test_parallel_region_phase_pvaverage_np4 4 ) +opm_add_test(test_parallel_satfunc_consistency_checks_np2 + EXE_NAME + test_SatfuncConsistencyChecks_parallel + CONDITION + MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND + DRIVER_ARGS + -n 2 + -b ${PROJECT_BINARY_DIR} + NO_COMPILE + PROCESSORS + 2 +) + +opm_add_test(test_parallel_satfunc_consistency_checks_np3 + EXE_NAME + test_SatfuncConsistencyChecks_parallel + CONDITION + MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND + DRIVER_ARGS + -n 3 + -b ${PROJECT_BINARY_DIR} + NO_COMPILE + PROCESSORS + 3 +) + +opm_add_test(test_parallel_satfunc_consistency_checks_np4 + EXE_NAME + test_SatfuncConsistencyChecks_parallel + CONDITION + MPI_FOUND AND Boost_UNIT_TEST_FRAMEWORK_FOUND + DRIVER_ARGS + -n 4 + -b ${PROJECT_BINARY_DIR} + NO_COMPILE + PROCESSORS + 4 +) + opm_add_test(test_broadcast DEPENDS "opmsimulators" LIBRARIES opmsimulators ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index ef349a98d..d50c32860 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -340,6 +340,7 @@ if (HAVE_ECL_INPUT) list(APPEND TEST_SOURCE_FILES tests/test_nonnc.cpp tests/test_SatfuncConsistencyChecks.cpp + tests/test_SatfuncConsistencyChecks_parallel.cpp ) endif() diff --git a/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp b/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp index 08c9d180d..86b19ea37 100644 --- a/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp +++ b/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.cpp @@ -21,6 +21,10 @@ #include +#include + +#include + #include #include @@ -141,6 +145,22 @@ checkEndpoints(const std::size_t pointID, }); } +template +void Opm::SatfuncConsistencyChecks:: +collectFailures(const int root, + const Parallel::Communication& comm) +{ + if (comm.size() == 1) { + // Not a parallel run. Violation structure complete without + // exchanging additional information, so nothing to do. + return; + } + + for (auto& violation : this->violations_) { + this->collectFailures(root, comm, violation); + } +} + template bool Opm::SatfuncConsistencyChecks::anyFailedChecks() const { @@ -194,9 +214,77 @@ void Opm::SatfuncConsistencyChecks::ViolationSample::clear() // --------------------------------------------------------------------------- +namespace { + bool anyFailedChecks(const std::vector& count) + { + return std::any_of(count.begin(), count.end(), + [](const std::size_t n) { return n > 0; }); + } +} + template -void -Opm::SatfuncConsistencyChecks:: +void Opm::SatfuncConsistencyChecks:: +collectFailures(const int root, + const Parallel::Communication& comm, + ViolationSample& violation) +{ + // Count total number of violations of each check across all ranks. + // This should be the final number emitted in reportFailures() on the + // root process. + auto totalCount = violation.count; + comm.sum(totalCount.data(), violation.count.size()); + + if (! ::anyFailedChecks(totalCount)) { + // No failed checks on any rank for this severity level. + // + // No additional work needed, since every rank will have zero + // failure counts for all checks. + return; + } + + // CSR-like structures for the failure counts, sampled point IDs, and + // sampled check values from all ranks. One set of all-to-one messages + // for each quantity. If this stage becomes a bottleneck we must devise + // a better communication structure that reduces the number of messages. + const auto& [rankCount, startRankCount] = + gatherv(violation.count, comm, root); + + const auto& [rankPointID, startRankPointID] = + gatherv(violation.pointID, comm, root); + + const auto& [rankCheckValues, startRankCheckValues] = + gatherv(violation.checkValues, comm, root); + + if (comm.rank() == root) { + // Re-initialise this violation sample to prepare for incorporating + // contributions from all MPI ranks--including the current rank. + violation.clear(); + this->buildStructure(violation); + + const auto numRanks = comm.size(); + for (auto rank = 0*numRanks; rank < numRanks; ++rank) { + this->incorporateRankViolations + (rankCount.data() + startRankCount[rank], + rankPointID.data() + startRankPointID[rank], + rankCheckValues.data() + startRankCheckValues[rank], + violation); + } + } + + // The final violation counts for reporting purposes should be the sum + // of the per-rank counts. This ensures that all ranks give the same + // answer to the anyFailedChecks() predicate, although the particular + // sample points will differ across the ranks. + violation.count.swap(totalCount); + + // Ensure that all ranks are synchronised here before proceeding. We + // don't want to end up in a situation where the ranks have a different + // notion of what to send/receive. + comm.barrier(); +} + +template +void Opm::SatfuncConsistencyChecks:: buildStructure(ViolationSample& violation) { violation.count.assign(this->battery_.size(), 0); @@ -212,13 +300,13 @@ buildStructure(ViolationSample& violation) } template +template void Opm::SatfuncConsistencyChecks:: -processViolation(const ViolationLevel level, - const std::size_t checkIx, - const std::size_t pointID) +processViolation(ViolationSample& violation, + const std::size_t checkIx, + const std::size_t pointID, + PopulateCheckValues&& populateCheckValues) { - auto& violation = this->violations_[this->index(level)]; - const auto nViol = ++violation.count[checkIx]; // Special case handling for number of violations not exceeding number @@ -240,12 +328,59 @@ processViolation(const ViolationLevel level, // reported violations. Record the pointID and the corresponding check // values in their appropriate locations. - violation.pointID[checkIx*this->numSamplePoints_ + sampleIx] = pointID; + violation.pointID[this->violationPointIDStart(checkIx) + sampleIx] = pointID; - auto* exportedCheckValues = violation.checkValues.data() + auto* const checkValues = violation.checkValues.data() + this->violationValueStart(checkIx, sampleIx); - this->battery_[checkIx]->exportCheckValues(exportedCheckValues); + populateCheckValues(checkValues); +} + +template +void Opm::SatfuncConsistencyChecks:: +processViolation(const ViolationLevel level, + const std::size_t checkIx, + const std::size_t pointID) +{ + this->processViolation(this->violations_[this->index(level)], checkIx, pointID, + [this, checkIx](Scalar* const exportedCheckValues) + { + this->battery_[checkIx]->exportCheckValues(exportedCheckValues); + }); +} + +template +void Opm::SatfuncConsistencyChecks:: +incorporateRankViolations(const std::size_t* const count, + const std::size_t* const pointID, + const Scalar* const checkValues, + ViolationSample& violation) +{ + this->checkLoop([this, count, pointID, checkValues, &violation] + (const Check* currentCheck, + const std::size_t checkIx) + { + if (count[checkIx] == 0) { + // No violations of this check on this rank. Nothing to do. + return; + } + + const auto* const srcPointID = pointID + + this->violationPointIDStart(checkIx); + + const auto numCheckValues = currentCheck->numExportedCheckValues(); + const auto numSrcSamples = this->numPoints(count[checkIx]); + + for (auto srcSampleIx = 0*numSrcSamples; srcSampleIx < numSrcSamples; ++srcSampleIx) { + this->processViolation(violation, checkIx, srcPointID[srcSampleIx], + [numCheckValues, + srcCheckValues = checkValues + this->violationValueStart(checkIx, srcSampleIx)] + (Scalar* const destCheckValues) + { + std::copy_n(srcCheckValues, numCheckValues, destCheckValues); + }); + } + }); } namespace { @@ -471,8 +606,15 @@ Opm::SatfuncConsistencyChecks:: numPoints(const ViolationSample& violation, const std::size_t checkIx) const { - return std::min(this->numSamplePoints_, - violation.count[checkIx]); + return this->numPoints(violation.count[checkIx]); +} + +template +std::size_t +Opm::SatfuncConsistencyChecks:: +numPoints(const std::size_t violationCount) const +{ + return std::min(this->numSamplePoints_, violationCount); } template @@ -507,6 +649,14 @@ void Opm::SatfuncConsistencyChecks::ensureRandomBitGeneratorIsInitialise this->urbg_ = std::make_unique(seeds); } +template +std::vector::size_type +Opm::SatfuncConsistencyChecks:: +violationPointIDStart(const std::size_t checkIx) const +{ + return checkIx * this->numSamplePoints_; +} + template typename std::vector::size_type Opm::SatfuncConsistencyChecks:: @@ -522,10 +672,7 @@ bool Opm::SatfuncConsistencyChecks:: 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; }); + return ::anyFailedChecks(this->violations_[this->index(level)].count); } template diff --git a/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp b/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp index f009978a8..11303b292 100644 --- a/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp +++ b/opm/simulators/utils/satfunc/SatfuncConsistencyChecks.hpp @@ -20,6 +20,8 @@ #ifndef OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP #define OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP +#include + #include #include #include @@ -198,6 +200,19 @@ namespace Opm { void checkEndpoints(const std::size_t pointID, const EclEpsScalingPointsInfo& endPoints); + /// Collect consistency violations from all ranks in MPI communicator. + /// + /// Incorporates violation counts and sampled failure points into + /// the internal structures on each rank. Aggregate results useful + /// for subsequent call to reportFailures() on root process. + /// + /// \param[in] root MPI root process. This is the process onto + /// which the counts and samples will be collected. Typically + /// the index of the IO rank. + /// + /// \param[in] comm MPI communication object. + void collectFailures(int root, const Parallel::Communication& comm); + /// Whether or not any checks failed at the \c Standard level. bool anyFailedChecks() const; @@ -210,6 +225,10 @@ namespace Opm { /// Reports only those conditions/checks for which there is at least /// one violation. /// + /// In a parallel run it is only safe to call this function on the + /// MPI process to which the consistency check violations were + /// collected in a previous call to collectFailures(). + /// /// \param[in] level Report's severity level. /// /// \param[in] emitReportRecord Call-back function for outputting a @@ -299,6 +318,26 @@ namespace Opm { /// is a common case in production runs. std::unique_ptr urbg_{}; + /// Collect violations of single severity level from all ranks in + /// MPI communicator. + /// + /// Incorporates violation counts and sampled failure points into + /// the internal structures on each rank. Aggregate results useful + /// for subsequent call to reportFailures(). + /// + /// \param[in] root MPI root process. This is the process/rank onto + /// which the counts and samples will be collected. Typically + /// the index of the IO rank. + /// + /// \param[in] comm MPI communication object. + /// + /// \param[in, out] violation Current rank's violation structure for + /// a single severity level. Holds aggregate values across all + /// ranks, including updated sample points, on return. + void collectFailures(int root, + const Parallel::Communication& comm, + ViolationSample& violation); + /// Allocate and initialise backing storage for a single set of /// sampled consistency check violations. /// @@ -306,6 +345,44 @@ namespace Opm { /// 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. + /// + /// \tparam PopulateCheckValues Call-back function type + /// encapsulating block of code populate sequence of check values + /// for a single, failed consistency check. Expected to be a + /// callable type with a function call operator of the form + /// \code + /// void operator()(Scalar* checkValues) const + /// \endcode + /// in which the \c checkValues points the start of a sequence of + /// values associated to particular check. The call-back function + /// is expected to know how many values are in a valid sequence and + /// to fill in exactly this many values. + /// + /// \param[in, out] violation Current rank's violation sample at + /// particular 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. + /// + /// \param[in] populateCheckValues Call-back function to populate a + /// sequence of values pertaining to specified check. Typically + /// \code Check::exportCheckValues() \endcode or a copy routine + /// to incorporate samples from multiple MPI ranks. + template + void processViolation(ViolationSample& violation, + const std::size_t checkIx, + const std::size_t pointID, + PopulateCheckValues&& populateCheckValues); + /// Internalise a single violation into internal data structures. /// /// Counts the violation and uses "reservoir sampling" @@ -324,6 +401,24 @@ namespace Opm { const std::size_t checkIx, const std::size_t pointID); + /// Incorporate single severity level's set of violations from + /// single MPI rank into current rank's internal data structures. + /// + /// \param[in] count Start of sequence of failure counts for all + /// checks from single MPI rank. + /// + /// \param[in] pointID Start of sequence of sampled point IDs for + /// all checks from a single MPI rank. + /// + /// \param[in] checkValues Start of sequence of sampled check values + /// for all checks from a single MPI rank. + /// + /// \param[in, out] violation + void incorporateRankViolations(const std::size_t* count, + const std::size_t* pointID, + const Scalar* checkValues, + ViolationSample& violation); + /// Generate random index in the sample size. /// /// \param[in] sampleSize Total number of violations of a particular @@ -337,6 +432,16 @@ namespace Opm { /// initialised. void ensureRandomBitGeneratorIsInitialised(); + /// Compute start offset into ViolationSample::pointID for + /// particular check. + /// + /// \param[in] checkIx Numerical check index in the range + /// [0..battery_.size()). + /// + /// \return Start offset into ViolationSample::pointID. + std::vector::size_type + violationPointIDStart(const std::size_t checkIx) const; + /// Compute start offset into ViolationSample::checkValues for /// particular check and sample index. /// @@ -442,6 +547,17 @@ namespace Opm { std::size_t numPoints(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] violationCount Total number of check violations. + /// + /// \return Number of active sample points. + std::size_t numPoints(const std::size_t violationCount) const; + /// Whether or not any checks failed at specified severity level. /// /// \param[in] level Violation severity level. diff --git a/tests/test_SatfuncConsistencyChecks.cpp b/tests/test_SatfuncConsistencyChecks.cpp index ac50317ec..269ad1587 100644 --- a/tests/test_SatfuncConsistencyChecks.cpp +++ b/tests/test_SatfuncConsistencyChecks.cpp @@ -259,6 +259,9 @@ BOOST_AUTO_TEST_CASE(Critical_Violation) checker.checkEndpoints(42, makePoints()); + BOOST_CHECK_MESSAGE(! checker.anyFailedChecks(), + "There must be no failed standard level checks"); + BOOST_CHECK_MESSAGE(checker.anyFailedCriticalChecks(), "There must be at least one failed Critical check"); diff --git a/tests/test_SatfuncConsistencyChecks_parallel.cpp b/tests/test_SatfuncConsistencyChecks_parallel.cpp new file mode 100644 index 000000000..23e8a03ef --- /dev/null +++ b/tests/test_SatfuncConsistencyChecks_parallel.cpp @@ -0,0 +1,2127 @@ +/* + 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 . +*/ + +#include + +#define BOOST_TEST_MODULE TestSatfuncConsistencyChecks_Parallel + +#define BOOST_TEST_NO_MAIN + +#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 + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +#if HAVE_MPI + struct MPIError + { + MPIError(std::string_view errstr, const int ec) + : errorstring { errstr } + , errorcode { ec } + {} + + std::string errorstring; + int errorcode; + }; + + void MPI_err_handler(MPI_Comm*, int* err_code, ...) + { + std::array err_string_vec{'\0'}; + auto err_length = 0; + + MPI_Error_string(*err_code, err_string_vec.data(), &err_length); + + auto err_string = std::string_view { + err_string_vec.data(), + static_cast(err_length) + }; + + std::cerr << "An MPI Error ocurred:\n -> " << err_string << '\n'; + + throw MPIError { err_string, *err_code }; + } + + // Register a throwing error handler to allow for debugging with + // + // catch throw + // + // in GDB. + void register_error_handler() + { + MPI_Errhandler handler{}; + + MPI_Comm_create_errhandler(MPI_err_handler, &handler); + MPI_Comm_set_errhandler(MPI_COMM_WORLD, handler); + } + +#else // !HAVE_MPI + + void register_error_handler() + {} + +#endif // HAVE_MPI + + class NProc_Is + { + public: + explicit NProc_Is(const int expectNP) + : expectNP_ { expectNP } + {} + + boost::test_tools::assertion_result + operator()(boost::unit_test::test_unit_id) const + { + auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + if (comm.size() == this->expectNP_) { + return true; + } + + boost::test_tools::assertion_result response(false); + response.message() << "Number of MPI processes (" + << comm.size() + << ") differs from expected " + << this->expectNP_; + + return response; + } + + private: + int expectNP_{}; + }; + + bool init_unit_test_func() + { + return true; + } + +} // Anonymous namespace + +BOOST_AUTO_TEST_SUITE(NoFailures) + +namespace { + class Standard : public Opm::SatfuncConsistencyChecks::Check + { + public: + void test(const Opm::EclEpsScalingPointsInfo&) 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 makePoints() { return {}; } +} // Anonymous namespace + +BOOST_AUTO_TEST_CASE(All_Good) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Cell", 1}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank(), makePoints()); + + checker.collectFailures(0, comm); + + 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{}; + if (comm.rank() == 0) { + checker.reportFailures(Opm::SatfuncConsistencyChecks::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::Check + { + public: + void test(const Opm::EclEpsScalingPointsInfo&) 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::Check + { + public: + void test(const Opm::EclEpsScalingPointsInfo&) 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 makePoints() { return {}; } +} // Anonymous namespace + +BOOST_AUTO_TEST_SUITE(NProc_2, * boost::unit_test::precondition(NProc_Is{2})) + +BOOST_AUTO_TEST_CASE(Standard_Violation) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Cell", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 2 + +List of Violations ++------+---------------+ +| Cell | SWL | ++------+---------------+ +| 1 | 1.729000e+01 | +| 2 | 1.729000e+01 | ++------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Critical_Violation) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"PVTNUM", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(42 * (comm.rank() + 1), makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(! checker.anyFailedChecks(), + "There must be no failed standard level checks"); + + BOOST_CHECK_MESSAGE(checker.anyFailedCriticalChecks(), + "There must be at least one failed Critical check"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 2 + +List of Violations ++--------+---------------+ +| PVTNUM | PRESS | ++--------+---------------+ +| 42 | 3.141593e+02 | +| 84 | 3.141593e+02 | ++--------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_2 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_3, * boost::unit_test::precondition(NProc_Is{3})) + +BOOST_AUTO_TEST_CASE(Standard_Violation) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Cell", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 3 + +List of Violations ++------+---------------+ +| Cell | SWL | ++------+---------------+ +| 1 | 1.729000e+01 | +| 2 | 1.729000e+01 | +| 3 | 1.729000e+01 | ++------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Critical_Violation) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"PVTNUM", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(42 * (comm.rank() + 1), makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(! checker.anyFailedChecks(), + "There must be no failed standard level checks"); + + BOOST_CHECK_MESSAGE(checker.anyFailedCriticalChecks(), + "There must be at least one failed Critical check"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 3 + +List of Violations ++--------+---------------+ +| PVTNUM | PRESS | ++--------+---------------+ +| 42 | 3.141593e+02 | +| 84 | 3.141593e+02 | +| 126 | 3.141593e+02 | ++--------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_3 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_4, * boost::unit_test::precondition(NProc_Is{4})) + +BOOST_AUTO_TEST_CASE(Standard_Violation) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Cell", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 4 + +List of Violations ++------+---------------+ +| Cell | SWL | ++------+---------------+ +| 1 | 1.729000e+01 | +| 2 | 1.729000e+01 | +| 3 | 1.729000e+01 | +| 4 | 1.729000e+01 | ++------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Critical_Violation) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"PVTNUM", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(42 * (comm.rank() + 1), makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(! checker.anyFailedChecks(), + "There must be no failed standard level checks"); + + BOOST_CHECK_MESSAGE(checker.anyFailedCriticalChecks(), + "There must be at least one failed Critical check"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 4 + +List of Violations ++--------+---------------+ +| PVTNUM | PRESS | ++--------+---------------+ +| 42 | 3.141593e+02 | +| 84 | 3.141593e+02 | +| 126 | 3.141593e+02 | +| 168 | 3.141593e+02 | ++--------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_4 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE_END() // Single_Exported_Value + +// =========================================================================== + +BOOST_AUTO_TEST_SUITE(Two_Exported_Values) + +namespace { + class Violation : public Opm::SatfuncConsistencyChecks::Check + { + void test(const Opm::EclEpsScalingPointsInfo&) 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 makePoints() { return {}; } +} // Anonymous namespace + +BOOST_AUTO_TEST_SUITE(NProc_2, * boost::unit_test::precondition(NProc_Is{2})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Bucket", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 2 + +List of Violations ++--------+---------------+---------------+ +| Bucket | a | a + 1/2 | ++--------+---------------+---------------+ +| 1 | 1.600000e+00 | 2.100000e+00 | +| 2 | 1.600000e+00 | 2.100000e+00 | ++--------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_2 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_3, * boost::unit_test::precondition(NProc_Is{3})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Bucket", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 3 + +List of Violations ++--------+---------------+---------------+ +| Bucket | a | a + 1/2 | ++--------+---------------+---------------+ +| 1 | 1.600000e+00 | 2.100000e+00 | +| 2 | 1.600000e+00 | 2.100000e+00 | +| 3 | 1.600000e+00 | 2.100000e+00 | ++--------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_3 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_4, * boost::unit_test::precondition(NProc_Is{4})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Bucket", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 4 + +List of Violations ++--------+---------------+---------------+ +| Bucket | a | a + 1/2 | ++--------+---------------+---------------+ +| 1 | 1.600000e+00 | 2.100000e+00 | +| 2 | 1.600000e+00 | 2.100000e+00 | +| 3 | 1.600000e+00 | 2.100000e+00 | +| 4 | 1.600000e+00 | 2.100000e+00 | ++--------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_3 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE_END() // Two_Exported_Values + +// =========================================================================== + +BOOST_AUTO_TEST_SUITE(Five_Exported_Values) + +namespace { + class Violation : public Opm::SatfuncConsistencyChecks::Check + { + void test(const Opm::EclEpsScalingPointsInfo&) 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 makePoints() { return {}; } +} // Anonymous namespace + +BOOST_AUTO_TEST_SUITE(NProc_2, * boost::unit_test::precondition(NProc_Is{2})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 2 + +List of Violations ++------------+---------------+---------------+---------------+---------------+---------------+ +| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU | ++------------+---------------+---------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| 2 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | ++------------+---------------+---------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 16}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + const auto rankMultiplier = 100'000; + + checker.setPointIDFormatCallback([rankMultiplier](const std::size_t pointID) + { + const auto rank = pointID / rankMultiplier; + const auto pt = pointID % rankMultiplier; + + return fmt::format("({}, {})", rank, pt); + }); + + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1234, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1729, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1618, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 31415, makePoints()); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + checker.collectFailures(0, comm); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 8 + +List of Violations ++------------+---------------+---------------+---------------+---------------+---------------+ +| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU | ++------------+---------------+---------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | ++------------+---------------+---------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_2 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_3, * boost::unit_test::precondition(NProc_Is{3})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 3 + +List of Violations ++------------+---------------+---------------+---------------+---------------+---------------+ +| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU | ++------------+---------------+---------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| 2 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| 3 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | ++------------+---------------+---------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 16}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + const auto rankMultiplier = 100'000; + + checker.setPointIDFormatCallback([rankMultiplier](const std::size_t pointID) + { + const auto rank = pointID / rankMultiplier; + const auto pt = pointID % rankMultiplier; + + return fmt::format("({}, {})", rank, pt); + }); + + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1234, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1729, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1618, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 31415, makePoints()); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + checker.collectFailures(0, comm); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 12 + +List of Violations ++------------+---------------+---------------+---------------+---------------+---------------+ +| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU | ++------------+---------------+---------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | ++------------+---------------+---------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_3 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_4, * boost::unit_test::precondition(NProc_Is{4})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 4 + +List of Violations ++------------+---------------+---------------+---------------+---------------+---------------+ +| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU | ++------------+---------------+---------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| 2 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| 3 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| 4 | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | ++------------+---------------+---------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 16}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + const auto rankMultiplier = 100'000; + + checker.setPointIDFormatCallback([rankMultiplier](const std::size_t pointID) + { + const auto rank = pointID / rankMultiplier; + const auto pt = pointID % rankMultiplier; + + return fmt::format("({}, {})", rank, pt); + }); + + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1234, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1729, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1618, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 31415, makePoints()); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + checker.collectFailures(0, comm); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 16 + +List of Violations ++------------+---------------+---------------+---------------+---------------+---------------+ +| Grid Block | SGL | SOWCR | SWCR | 1-SOWCR-SGL | SWU | ++------------+---------------+---------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (1, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (2, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (3, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (4, 1234) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (4, 1618) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (4, 1729) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | +| (4, 31415) | 1.000000e-01 | 7.000000e-01 | 3.000000e-01 | 2.000000e-01 | 8.800000e-01 | ++------------+---------------+---------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_3 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE_END() // Five_Exported_Values + +// =========================================================================== + +BOOST_AUTO_TEST_SUITE(Multiple_Failing_Tests) + +namespace { + class MonotoneWater : public Opm::SatfuncConsistencyChecks::Check + { + void test(const Opm::EclEpsScalingPointsInfo&) 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::Check + { + void test(const Opm::EclEpsScalingPointsInfo&) 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::Check + { + void test(const Opm::EclEpsScalingPointsInfo&) 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::Check + { + void test(const Opm::EclEpsScalingPointsInfo&) 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 makePoints() { return {}; } +} // Anonymous namespace + +BOOST_AUTO_TEST_SUITE(NProc_2, * boost::unit_test::precondition(NProc_Is{2})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 2 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SGCR | SGU | ++------------+---------------+---------------+---------------+ +| 1 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| 2 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (G/O) + SWL + SGU <= 1 + Total Violations: 2 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SGU | SWL + SGU | ++------------+---------------+---------------+---------------+ +| 1 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| 2 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Water Phase End-Point Monotonicity + SWL <= SWCR < SWU + Total Violations: 2 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SWCR | SWU | ++------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| 2 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (O/W) + SGL + SWU <= 1 + Total Violations: 2 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SWU | SGL + SWU | ++------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| 2 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | ++------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 16}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + const auto rankMultiplier = 100'000; + + checker.setPointIDFormatCallback([rankMultiplier](const std::size_t pointID) + { + const auto rank = pointID / rankMultiplier; + const auto pt = pointID % rankMultiplier; + + return fmt::format("({}, {})", rank, pt); + }); + + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1234, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1729, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1618, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 31415, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 8 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SGCR | SGU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (G/O) + SWL + SGU <= 1 + Total Violations: 8 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SGU | SWL + SGU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Water Phase End-Point Monotonicity + SWL <= SWCR < SWU + Total Violations: 8 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SWCR | SWU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (O/W) + SGL + SWU <= 1 + Total Violations: 8 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SWU | SGL + SWU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | ++------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_2 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_3, * boost::unit_test::precondition(NProc_Is{3})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 3 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SGCR | SGU | ++------------+---------------+---------------+---------------+ +| 1 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| 2 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| 3 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (G/O) + SWL + SGU <= 1 + Total Violations: 3 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SGU | SWL + SGU | ++------------+---------------+---------------+---------------+ +| 1 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| 2 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| 3 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Water Phase End-Point Monotonicity + SWL <= SWCR < SWU + Total Violations: 3 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SWCR | SWU | ++------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| 2 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| 3 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (O/W) + SGL + SWU <= 1 + Total Violations: 3 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SWU | SGL + SWU | ++------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| 2 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| 3 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | ++------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 16}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + const auto rankMultiplier = 100'000; + + checker.setPointIDFormatCallback([rankMultiplier](const std::size_t pointID) + { + const auto rank = pointID / rankMultiplier; + const auto pt = pointID % rankMultiplier; + + return fmt::format("({}, {})", rank, pt); + }); + + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1234, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1729, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1618, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 31415, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 12 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SGCR | SGU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (G/O) + SWL + SGU <= 1 + Total Violations: 12 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SGU | SWL + SGU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Water Phase End-Point Monotonicity + SWL <= SWCR < SWU + Total Violations: 12 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SWCR | SWU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (O/W) + SGL + SWU <= 1 + Total Violations: 12 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SWU | SGL + SWU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | ++------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_3 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(NProc_4, * boost::unit_test::precondition(NProc_Is{4})) + +BOOST_AUTO_TEST_CASE(Standard) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 4}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + checker.checkEndpoints(comm.rank() + 1, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 4 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SGCR | SGU | ++------------+---------------+---------------+---------------+ +| 1 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| 2 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| 3 | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| 4 | 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 | ++------------+---------------+---------------+---------------+ +| 1 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| 2 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| 3 | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| 4 | 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 | ++------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| 2 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| 3 | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| 4 | 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 | ++------------+---------------+---------------+---------------+ +| 1 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| 2 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| 3 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| 4 | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | ++------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_CASE(Standard_Multiple_Failing_Points) +{ + const auto comm = Opm::Parallel::Communication { + Dune::MPIHelper::getCommunicator() + }; + + auto checker = Opm::SatfuncConsistencyChecks{"Grid Block", 16}; + + checker.resetCheckSet(); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.addCheck(std::make_unique()); + checker.finaliseCheckSet(); + + const auto rankMultiplier = 100'000; + + checker.setPointIDFormatCallback([rankMultiplier](const std::size_t pointID) + { + const auto rank = pointID / rankMultiplier; + const auto pt = pointID % rankMultiplier; + + return fmt::format("({}, {})", rank, pt); + }); + + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1234, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1729, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 1618, makePoints()); + checker.checkEndpoints(rankMultiplier*(comm.rank() + 1) + 31415, makePoints()); + + checker.collectFailures(0, comm); + + BOOST_CHECK_MESSAGE(checker.anyFailedChecks(), + "There must be at least one failed check"); + + BOOST_CHECK_MESSAGE(! checker.anyFailedCriticalChecks(), + "There must be no failed critical checks"); + + if (comm.rank() == 0) { + auto rpt = std::string{}; + checker.reportFailures(Opm::SatfuncConsistencyChecks::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: 16 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SGCR | SGU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (1, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (2, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (3, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (4, 1234) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (4, 1618) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (4, 1729) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | +| (4, 31415) | 0.000000e+00 | -1.000000e-01 | 8.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (G/O) + SWL + SGU <= 1 + Total Violations: 16 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SGU | SWL + SGU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (1, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (2, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (3, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (4, 1234) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (4, 1618) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (4, 1729) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | +| (4, 31415) | 2.500000e-01 | 8.000000e-01 | 1.050000e+00 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Water Phase End-Point Monotonicity + SWL <= SWCR < SWU + Total Violations: 16 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SWL | SWCR | SWU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (1, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (2, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (3, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (4, 1234) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (4, 1618) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (4, 1729) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | +| (4, 31415) | 1.000000e-01 | 3.000000e-01 | 3.000000e-01 | ++------------+---------------+---------------+---------------+ + + +Consistency Problem: + Oil Phase Non-Negative Saturation (O/W) + SGL + SWU <= 1 + Total Violations: 16 + +List of Violations ++------------+---------------+---------------+---------------+ +| Grid Block | SGL | SWU | SGL + SWU | ++------------+---------------+---------------+---------------+ +| (1, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (1, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (2, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (3, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (4, 1234) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (4, 1618) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (4, 1729) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | +| (4, 31415) | 1.000000e-01 | 1.000000e+00 | 1.100000e+00 | ++------------+---------------+---------------+---------------+ + + +)"); + } +} + +BOOST_AUTO_TEST_SUITE_END() // NProc_4 + +// --------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE_END() // Multiple_Failing_Tests + +// =========================================================================== + +int main(int argc, char** argv) +{ + Dune::MPIHelper::instance(argc, argv); + + register_error_handler(); + + return boost::unit_test::unit_test_main(&init_unit_test_func, argc, argv); +}