/* 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 . */ #ifndef OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP #define OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP #include #include #include #include #include #include #include #include namespace Opm { template 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 class SatfuncConsistencyChecks { public: /// Call-back interface for an individual check. /// /// Specific checks are expected to inherit from this base class. class Check { public: /// Virtual destructor since class has virtual functions virtual ~Check() = default; /// 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& 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 Scalar 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; /// Call-back function type for formatting a numeric end-point ID. using PointIDFormatCallback = std::function; /// 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); /// 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& 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 anyFailedStandardChecks() 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. /// /// 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 /// 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 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 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 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 (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::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> 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 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. /// /// \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. /// /// \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" /// (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); /// 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 /// 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::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. /// /// \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::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::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 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 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; /// 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. /// /// \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(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 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 void checkLoop(Body&& body) const; }; } // namespace Opm #endif // OPM_SATFUNC_CONSISTENCY_CHECK_MODULE_HPP