/* Copyright 2018 SINTEF Digital, Mathematics and Cybernetics. Copyright 2018, 2024 Equinor. 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_CONVERGENCEREPORT_HEADER_INCLUDED #define OPM_CONVERGENCEREPORT_HEADER_INCLUDED #include #include #include #include #include #include namespace Opm { /// Represents the convergence status of the whole simulator, to /// make it possible to query and store the reasons for /// convergence failures. class ConvergenceReport { public: // ----------- Types ----------- enum Status { AllGood = 0, ReservoirFailed = 1 << 0, WellFailed = 1 << 1, }; enum struct Severity { None = 0, Normal = 1, TooLarge = 2, NotANumber = 3, }; struct PenaltyCard { int nonConverged{0}; int distanceDecay{0}; int largeWellResiduals{0}; int total() const { return nonConverged + distanceDecay + largeWellResiduals; } PenaltyCard& operator+=(const PenaltyCard& other) { nonConverged += other.nonConverged; distanceDecay += other.distanceDecay; largeWellResiduals += other.largeWellResiduals; return *this; } template void serializeOp(Serializer& serializer) { serializer(nonConverged); serializer(distanceDecay); serializer(largeWellResiduals); } }; using CnvPvSplit = std::pair< std::vector, std::vector>; class ReservoirFailure { public: enum struct Type { Invalid, MassBalance, Cnv }; // Default constructor needed for object serialisation. Don't // use this for anything else. ReservoirFailure() = default; ReservoirFailure(Type t, Severity s, int phase) : type_(t), severity_(s), phase_(phase) {} Type type() const { return type_; } Severity severity() const { return severity_; } int phase() const { return phase_; } template void serializeOp(Serializer& serializer) { serializer(this->type_); serializer(this->severity_); serializer(this->phase_); } private: // Note to maintainers: If you change this list of data members, // then please update serializeOp() accordingly. Type type_ { Type::Invalid }; Severity severity_ { Severity::None }; int phase_ { -1 }; }; class ReservoirConvergenceMetric { public: // Default constructor needed for object serialisation. Don't // use this for anything else. ReservoirConvergenceMetric() = default; ReservoirConvergenceMetric(ReservoirFailure::Type t, int phase, double value) : type_(t), phase_(phase), value_(value) {} ReservoirFailure::Type type() const { return type_; } int phase() const { return phase_; } double value() const { return value_; } template void serializeOp(Serializer& serializer) { serializer(this->type_); serializer(this->phase_); serializer(this->value_); } private: // Note to maintainers: If you change this list of data members, // then please update serializeOp() accordingly. ReservoirFailure::Type type_ { ReservoirFailure::Type::Invalid }; int phase_ { -1 }; double value_ { 0.0 }; }; class WellFailure { public: enum struct Type { Invalid, MassBalance, Pressure, ControlBHP, ControlTHP, ControlRate, Unsolvable, WrongFlowDirection, }; // Default constructor needed for object serialisation. Don't // use this for anything else. WellFailure() = default; WellFailure(Type t, Severity s, int phase, const std::string& well_name) : type_(t), severity_(s), phase_(phase), well_name_(well_name) {} Type type() const { return type_; } Severity severity() const { return severity_; } int phase() const { return phase_; } const std::string& wellName() const { return well_name_; } template void serializeOp(Serializer& serializer) { serializer(this->type_); serializer(this->severity_); serializer(this->phase_); serializer(this->well_name_); } private: // Note to maintainers: If you change this list of data members, // then please update serializeOp() accordingly. Type type_ { Type::Invalid }; Severity severity_ { Severity::None }; int phase_ { -1 }; std::string well_name_ {}; }; // ----------- Mutating member functions ----------- ConvergenceReport() : ConvergenceReport{0.0} {} explicit ConvergenceReport(const double reportTime) : reportTime_{reportTime} , status_{AllGood} , res_failures_{} , well_failures_{} , wellGroupTargetsViolated_(false) {} void clear() { status_ = AllGood; res_failures_.clear(); well_failures_.clear(); wellGroupTargetsViolated_ = false; } void setReservoirFailed(const ReservoirFailure& rf) { status_ = static_cast(status_ | ReservoirFailed); res_failures_.push_back(rf); } void setWellFailed(const WellFailure& wf) { status_ = static_cast(status_ | WellFailed); well_failures_.push_back(wf); } template void setReservoirConvergenceMetric(Args&&... args) { this->res_convergence_.emplace_back(std::forward(args)...); } void setWellGroupTargetsViolated(const bool wellGroupTargetsViolated) { wellGroupTargetsViolated_ = wellGroupTargetsViolated; } void setCnvPoreVolSplit(const CnvPvSplit& cnvPvSplit, const double eligiblePoreVolume) { this->cnvPvSplit_ = cnvPvSplit; this->eligiblePoreVolume_ = eligiblePoreVolume; } ConvergenceReport& operator+=(const ConvergenceReport& other) { reportTime_ = std::max(reportTime_, other.reportTime_); status_ = static_cast(status_ | other.status_); res_failures_.insert(res_failures_.end(), other.res_failures_.begin(), other.res_failures_.end()); well_failures_.insert(well_failures_.end(), other.well_failures_.begin(), other.well_failures_.end()); res_convergence_.insert(res_convergence_.end(), other.res_convergence_.begin(), other.res_convergence_.end()); assert(reservoirFailed() != res_failures_.empty()); assert(wellFailed() != well_failures_.empty()); wellGroupTargetsViolated_ = (wellGroupTargetsViolated_ || other.wellGroupTargetsViolated_); // Note regarding the CNV pore-volume split: We depend on the // fact that the quantities have already been aggregated across // all MPI ranks--see the implementation of member function // BlackoilModel::getReservoirConvergence() for details--and are // therefore equal on all ranks. Consequently, we simply assign // 'other's values here, if it is non-empty. Empty splits // typically come from well contributions. if (! other.cnvPvSplit_.first.empty()) { this->cnvPvSplit_ = other.cnvPvSplit_; this->eligiblePoreVolume_ = other.eligiblePoreVolume_; } return *this; } // ----------- Const member functions (queries) ----------- double reportTime() const { return reportTime_; } double eligiblePoreVolume() const { return this->eligiblePoreVolume_; } const CnvPvSplit& cnvPvSplit() const { return this->cnvPvSplit_; } bool converged() const { return (status_ == AllGood) && !wellGroupTargetsViolated_; } bool reservoirFailed() const { return status_ & ReservoirFailed; } bool wellFailed() const { return status_ & WellFailed; } const std::vector& reservoirFailures() const { return res_failures_; } const std::vector& reservoirConvergence() const { return res_convergence_; } const std::vector& wellFailures() const { return well_failures_; } const PenaltyCard& getPenaltyCard() const { return penaltyCard_; } void addNonConvergedPenalty() { penaltyCard_.nonConverged++; } void addDistanceDecayPenalty() { penaltyCard_.distanceDecay++; } void addLargeWellResidualsPenalty() { penaltyCard_.largeWellResiduals++; } Severity severityOfWorstFailure() const { // A function to get the worst of two severities. auto smax = [](Severity s1, Severity s2) { return s1 < s2 ? s2 : s1; }; auto s = Severity::None; for (const auto& f : res_failures_) { s = smax(s, f.severity()); } for (const auto& f : well_failures_) { s = smax(s, f.severity()); } return s; } template void serializeOp(Serializer& serializer) { serializer(this->reportTime_); serializer(this->status_); serializer(this->res_failures_); serializer(this->well_failures_); serializer(this->res_convergence_); serializer(this->wellGroupTargetsViolated_); serializer(this->cnvPvSplit_); serializer(this->eligiblePoreVolume_); serializer(this->penaltyCard_); } private: // ----------- Member variables ----------- // Note to maintainers: If you change this list of data members, // then please update serializeOp() accordingly. double reportTime_; Status status_; std::vector res_failures_; std::vector well_failures_; std::vector res_convergence_; bool wellGroupTargetsViolated_; CnvPvSplit cnvPvSplit_{}; double eligiblePoreVolume_{}; PenaltyCard penaltyCard_; }; struct StepReport { int report_step; int current_step; std::vector report; }; std::string to_string(const ConvergenceReport::ReservoirFailure::Type t); std::string to_string(const ConvergenceReport::Severity s); std::string to_string(const ConvergenceReport::WellFailure::Type t); std::string to_string(const ConvergenceReport::WellFailure& wf); std::string to_string(const ConvergenceReport::PenaltyCard& pc); } // namespace Opm #endif // OPM_CONVERGENCEREPORT_HEADER_INCLUDED