diff --git a/opm/simulators/utils/PartiallySupportedFlowKeywords.cpp b/opm/simulators/utils/PartiallySupportedFlowKeywords.cpp index 7193cfc9d11..d3b9f755460 100644 --- a/opm/simulators/utils/PartiallySupportedFlowKeywords.cpp +++ b/opm/simulators/utils/PartiallySupportedFlowKeywords.cpp @@ -673,9 +673,7 @@ partiallySupported() { {11,{true, allow_values {}, "WECON(WCUT2): Feature not supported and should be defaulted"}}, // WELOPEN {13,{true, allow_values {}, "WECON(GLR): Feature not supported and should be defaulted"}}, // GLR - {14,{true, allow_values {}, "WECON(LRAT): Feature not supported and should be defaulted"}}, // LRAT {15,{true, allow_values {}, "WECON(TEMP): Feature not supported and should be defaulted"}}, // TEMP - {16,{true, allow_values {}, "WECON(RESV): Feature not supported and should be defaulted"}}, // RESV }, }, { diff --git a/opm/simulators/wells/EconomicLimitsMessage.hpp b/opm/simulators/wells/EconomicLimitsMessage.hpp index be4854d3649..71a2217078e 100644 --- a/opm/simulators/wells/EconomicLimitsMessage.hpp +++ b/opm/simulators/wells/EconomicLimitsMessage.hpp @@ -22,6 +22,8 @@ #include +#include + #include #include @@ -42,6 +44,18 @@ inline std::string economicLimitDateString(const std::time_t start_time, const d return fmt::format("{:%d-%b-%Y}", fmt::gmtime(cur_time)); } +//! \brief The "at time ... (date = ...)" clause shared by the well (WECON) and +//! connection (CECON) economic-limit messages. +inline std::string economicLimitWhenString(const UnitSystem& unit_system, + const std::time_t start_time, + const double sim_time) +{ + return fmt::format("at time {:.2f} {} (date = {})", + unit_system.from_si(UnitSystem::measure::time, sim_time), + unit_system.name(UnitSystem::measure::time), + economicLimitDateString(start_time, sim_time)); +} + //! \brief Separator line used to frame economic-limit workover messages. //! Built once and returned by reference (all callers use the same line). inline const std::string& economicLimitMessageSeparator() diff --git a/opm/simulators/wells/WellInterfaceFluidSystem.cpp b/opm/simulators/wells/WellInterfaceFluidSystem.cpp index 5eef2885654..939f56b1f91 100644 --- a/opm/simulators/wells/WellInterfaceFluidSystem.cpp +++ b/opm/simulators/wells/WellInterfaceFluidSystem.cpp @@ -40,6 +40,8 @@ #include #include +#include + namespace Opm { @@ -151,6 +153,21 @@ calculateReservoirRates(const bool use_well_bhp_temperature, SingleWellState +typename FluidSystem::Scalar +WellInterfaceFluidSystem:: +totalReservoirVoidageRate(const std::vector& surface_rates) const +{ + std::vector voidage_rates(surface_rates.size(), 0.0); + this->rateConverter_ + .calcReservoirVoidageRates(/*fipreg*/ 0, + this->pvtRegionIdx_, + surface_rates, + voidage_rates); + + return std::accumulate(voidage_rates.begin(), voidage_rates.end(), Scalar{0.0}); +} + template bool WellInterfaceFluidSystem:: diff --git a/opm/simulators/wells/WellInterfaceFluidSystem.hpp b/opm/simulators/wells/WellInterfaceFluidSystem.hpp index 68d4313a32d..8eb25501ad2 100644 --- a/opm/simulators/wells/WellInterfaceFluidSystem.hpp +++ b/opm/simulators/wells/WellInterfaceFluidSystem.hpp @@ -70,6 +70,8 @@ class WellInterfaceFluidSystem : public WellInterfaceGeneric& surface_rates) const override; + protected: WellInterfaceFluidSystem(const Well& well, const ParallelWellInfo& parallel_well_info, diff --git a/opm/simulators/wells/WellInterfaceGeneric.cpp b/opm/simulators/wells/WellInterfaceGeneric.cpp index 66c537b45e8..60a2a17a034 100644 --- a/opm/simulators/wells/WellInterfaceGeneric.cpp +++ b/opm/simulators/wells/WellInterfaceGeneric.cpp @@ -346,14 +346,16 @@ updateWellTestState(const SingleWellState& ws, WellTestState& wellTestState, const UnitSystem& unit_system, const std::time_t start_time, - DeferredLogger& deferred_logger) const + DeferredLogger& deferred_logger, + std::string* closure_reason) const { const WellTest well_test(*this); // updating well test state based on Economic limits for operable wells if (this->isOperableAndSolvable()) { well_test.updateWellTestStateEconomic(ws, simulationTime, writeMessageToOPMLog, during_well_test, wellTestState, - zero_group_target, unit_system, start_time, deferred_logger); + zero_group_target, unit_system, start_time, + deferred_logger, closure_reason); well_test.updateWellTestStateCECON(ws, simulationTime, writeMessageToOPMLog, wellTestState, unit_system, start_time, deferred_logger); } else { diff --git a/opm/simulators/wells/WellInterfaceGeneric.hpp b/opm/simulators/wells/WellInterfaceGeneric.hpp index 9e4c7e20c32..a8123741db1 100644 --- a/opm/simulators/wells/WellInterfaceGeneric.hpp +++ b/opm/simulators/wells/WellInterfaceGeneric.hpp @@ -197,6 +197,9 @@ class WellInterfaceGeneric { //! \param unit_system Unit system to use //! \param start_time Starting time //! \param deferred_logger Deferred logging helper + //! \param closure_reason when non-null, receives the reason a limit closed + //! the well instead of it being logged here. Used by the WTEST re-open + //! testing, where no shut-in actually happens. void updateWellTestState(const SingleWellState& ws, const double& simulationTime, const bool& writeMessageToOPMLog, @@ -205,7 +208,8 @@ class WellInterfaceGeneric { WellTestState& wellTestState, const UnitSystem& unit_system, const std::time_t start_time, - DeferredLogger& deferred_logger) const; + DeferredLogger& deferred_logger, + std::string* closure_reason = nullptr) const; bool isPressureControlled(const WellStateType& well_state) const; @@ -232,6 +236,11 @@ class WellInterfaceGeneric { virtual Scalar connectionDensity(const int globalConnIdx, const int openConnIdx) const = 0; + //! \brief The total reservoir voidage rate for the given surface phase rates + //! (or potentials), using the region average PVT properties that also + //! produce the voidage rates in the well state. + virtual Scalar totalReservoirVoidageRate(const std::vector& surface_rates) const = 0; + void addPerforations(const std::vector& perfs); protected: diff --git a/opm/simulators/wells/WellInterface_impl.hpp b/opm/simulators/wells/WellInterface_impl.hpp index 1f9e7ac1d98..112696ea296 100644 --- a/opm/simulators/wells/WellInterface_impl.hpp +++ b/opm/simulators/wells/WellInterface_impl.hpp @@ -426,7 +426,7 @@ namespace Opm unit_system.name(UnitSystem::measure::time), economicLimitDateString(start_time, simulation_time)); - deferred_logger.info(fmt::format(" well {} is being tested {}", this->name(), when)); + deferred_logger.info(fmt::format("Well {} is being tested {}.", this->name(), when)); GroupStateHelperType groupStateHelper_copy = groupStateHelper; WellStateType well_state_copy = well_state; @@ -464,6 +464,21 @@ namespace Opm } WellTestState welltest_state_temp; + // Why a limit closed the well again. Empty when there is no reason to give. + std::string closure_reason; + + // The test can fail to re-open the well at several points below, and they + // all report it through here. The outcome goes to the PRT; the reason only + // to the debug log, because a shut well is re-tested at every interval and + // this message recurs for as long as it stays shut. + auto notReopened = [this, &when, &deferred_logger](const std::string_view reason) + { + deferred_logger.info( + fmt::format("Well {} is not re-opened {}.", this->name(), when)); + if (!reason.empty()) { + deferred_logger.debug(fmt::format("Because {}.", reason)); + } + }; bool testWell = true; // if a well is closed because all completions are closed, we need to check each completion @@ -473,26 +488,21 @@ namespace Opm const std::size_t original_number_closed_completions = welltest_state_temp.num_closed_completions(); bool converged = solveWellForTesting(simulator, groupStateHelper_copy, well_state_copy); if (!converged) { - const auto msg = fmt::format("WTEST: Well {} is not solvable (physical)", this->name()); - deferred_logger.debug(msg); + notReopened("the well equations could not be solved"); return; } updateWellOperability(simulator, well_state_copy, groupStateHelper_copy); if ( !this->isOperableAndSolvable() ) { - const auto msg = fmt::format("WTEST: Well {} is not operable (physical)", this->name()); - deferred_logger.debug(msg); + notReopened("the well is not operable"); return; } std::vector potentials; try { computeWellPotentials(simulator, well_state_copy, groupStateHelper_copy, potentials); } catch (const std::exception& e) { - const std::string msg = fmt::format("well {}: computeWellPotentials() " - "failed during testing for re-opening: ", - this->name(), e.what()); - deferred_logger.info(msg); + notReopened(fmt::format("computing the well potentials failed: {}", e.what())); return; } const int np = well_state_copy.numPhases(); @@ -508,7 +518,8 @@ namespace Opm welltest_state_temp, simulator.vanguard().eclState().getUnits(), simulator.vanguard().schedule().getStartTime(), - deferred_logger); + deferred_logger, + &closure_reason); this->closeCompletions(welltest_state_temp); // Stop testing if the well is closed or shut due to all completions shut @@ -525,8 +536,10 @@ namespace Opm if (!welltest_state_temp.well_is_closed(this->name())) { well_test_state.open_well(this->name()); + const std::string& sep = economicLimitMessageSeparator(); deferred_logger.info( - fmt::format("well {} is re-opened {}", this->name(), when)); + fmt::format("{}\nWell {} is re-opened {}.\n{}", + sep, this->name(), when, sep)); // also reopen completions for (const auto& completion : this->well_ecl_.getCompletions()) { @@ -536,6 +549,10 @@ namespace Opm well_state = well_state_copy; open_times.try_emplace(this->name(), well_test_state.lastTestTime(this->name())); } + else { + // A limit closed the well again: discard the tested state, stay shut. + notReopened(closure_reason); + } } diff --git a/opm/simulators/wells/WellTest.cpp b/opm/simulators/wells/WellTest.cpp index a66f3ce0cca..09f3a13aebf 100644 --- a/opm/simulators/wells/WellTest.cpp +++ b/opm/simulators/wells/WellTest.cpp @@ -50,6 +50,19 @@ namespace Opm { +namespace { + +//! \brief The unit of \p measure as a message suffix, i.e. preceded by a space, +//! or an empty string for the dimensionless quantities (e.g. water cut). +std::string unitSuffix(const UnitSystem& unit_system, + const UnitSystem::measure measure) +{ + const std::string_view unit = unit_system.name(measure); + return unit.empty() ? std::string{} : fmt::format(" {}", unit); +} + +} // Anonymous namespace + template template bool WellTest:: @@ -196,41 +209,76 @@ template bool WellTest:: checkRateEconLimits(const WellEconProductionLimits& econ_production_limits, const std::vector& rates_or_potentials, - DeferredLogger& deferred_logger) const + RateLimitCheckReport& report) const { const auto& pu = well_.phaseUsage(); - if (econ_production_limits.onMinOilRate()) { + + // A limit on a phase the run does not have is ignored: there is nothing to + // compare it against, and asking for the index of an inactive phase throws. + const bool oil_active = pu.phaseIsActive(IndexTraits::oilPhaseIdx); + const bool gas_active = pu.phaseIsActive(IndexTraits::gasPhaseIdx); + const bool water_active = pu.phaseIsActive(IndexTraits::waterPhaseIdx); + + // Record the violated limit for the closing message. The values are handed + // in as magnitudes, since production is stored negative. + auto violates = [&report](const std::string_view quantity_name, + const UnitSystem::measure rate_measure, + const Scalar rate_value, + const Scalar rate_limit) + { + report.quantity_name = quantity_name; + report.rate_measure = rate_measure; + report.rate_value = rate_value; + report.rate_limit = rate_limit; + return true; + }; + + if (econ_production_limits.onMinOilRate() && oil_active) { const int oil_pos = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx); - const Scalar oil_rate = rates_or_potentials[oil_pos]; + const Scalar oil_rate = std::abs(rates_or_potentials[oil_pos]); const Scalar min_oil_rate = econ_production_limits.minOilRate(); - if (std::abs(oil_rate) < min_oil_rate) { - return true; + if (oil_rate < min_oil_rate) { + return violates("oil", UnitSystem::measure::liquid_surface_rate, + oil_rate, min_oil_rate); } } - if (econ_production_limits.onMinGasRate() ) { + if (econ_production_limits.onMinGasRate() && gas_active) { const int gas_pos = pu.canonicalToActivePhaseIdx(IndexTraits::gasPhaseIdx); - const Scalar gas_rate = rates_or_potentials[gas_pos]; + const Scalar gas_rate = std::abs(rates_or_potentials[gas_pos]); const Scalar min_gas_rate = econ_production_limits.minGasRate(); - if (std::abs(gas_rate) < min_gas_rate) { - return true; + if (gas_rate < min_gas_rate) { + return violates("gas", UnitSystem::measure::gas_surface_rate, + gas_rate, min_gas_rate); } } - if (econ_production_limits.onMinLiquidRate() ) { - const int oil_pos = pu.canonicalToActivePhaseIdx(IndexTraits::oilPhaseIdx); - const int water_pos = pu.canonicalToActivePhaseIdx(IndexTraits::waterPhaseIdx); - const Scalar oil_rate = rates_or_potentials[oil_pos]; - const Scalar water_rate = rates_or_potentials[water_pos]; - const Scalar liquid_rate = oil_rate + water_rate; + if (econ_production_limits.onMinLiquidRate() && (oil_active || water_active)) { + // Oil plus water, either of which may be absent (e.g. an oil-gas case). + Scalar liquid_rate = 0.0; + for (const auto phase : {IndexTraits::oilPhaseIdx, IndexTraits::waterPhaseIdx}) { + if (pu.phaseIsActive(phase)) { + liquid_rate += rates_or_potentials[pu.canonicalToActivePhaseIdx(phase)]; + } + } + liquid_rate = std::abs(liquid_rate); const Scalar min_liquid_rate = econ_production_limits.minLiquidRate(); - if (std::abs(liquid_rate) < min_liquid_rate) { - return true; + if (liquid_rate < min_liquid_rate) { + return violates("liquid", UnitSystem::measure::liquid_surface_rate, + liquid_rate, min_liquid_rate); } } if (econ_production_limits.onMinReservoirFluidRate()) { - deferred_logger.warning("NOT_SUPPORTING_MIN_RESERVOIR_FLUID_RATE", "Minimum reservoir fluid production rate limit is not supported yet"); + // Net voidage rate, converted with the same region average PVT + // properties as the voidage rates in the well state. + const Scalar voidage_rate = + std::abs(well_.totalReservoirVoidageRate(rates_or_potentials)); + const Scalar min_voidage_rate = econ_production_limits.minReservoirFluidRate(); + if (voidage_rate < min_voidage_rate) { + return violates("reservoir fluid", UnitSystem::measure::rate, + voidage_rate, min_voidage_rate); + } } return false; @@ -362,7 +410,8 @@ updateWellTestStateEconomic(const SingleWellState& ws, const bool zero_group_target, const UnitSystem& unit_system, const std::time_t start_time, - DeferredLogger& deferred_logger) const + DeferredLogger& deferred_logger, + std::string* closure_reason) const { if (well_.wellIsStopped()) return; @@ -379,28 +428,37 @@ updateWellTestStateEconomic(const SingleWellState& ws, return; } - // flag to check if the mim oil/gas rate limit is violated + // flag to check if the min oil/gas/liquid/reservoir-fluid rate limit is violated bool rate_limit_violated = false; + // The violated limit, for the closing message below. Only meaningful when + // rate_limit_violated is true. + RateLimitCheckReport rate_report; - const auto& quantity_limit = econ_production_limits.quantityLimit(); + const bool limits_on_potentials = + (econ_production_limits.quantityLimit() == WellEconProductionLimits::QuantityLimit::POTN); if (econ_production_limits.onAnyRateLimit()) { - if (quantity_limit == WellEconProductionLimits::QuantityLimit::POTN) { + if (limits_on_potentials) { rate_limit_violated = this->checkRateEconLimits(econ_production_limits, ws.well_potentials, - deferred_logger); + rate_report); // Due to instability of the bhpFromThpLimit code the potentials are sometimes wrong // this can lead to premature shutting of wells due to rate limits of the potentials. // Since rates are supposed to be less or equal to the potentials, we double-check // that also the rate limit is violated before shutting the well. - if (rate_limit_violated) + // The message reports the potentials, so this second check fills a + // report of its own, which is then discarded. + if (rate_limit_violated) { + RateLimitCheckReport rate_check; rate_limit_violated = this->checkRateEconLimits(econ_production_limits, ws.surface_rates, - deferred_logger); + rate_check); + } } else { if (!zero_group_target) { rate_limit_violated - = this->checkRateEconLimits(econ_production_limits, ws.surface_rates, deferred_logger); + = this->checkRateEconLimits(econ_production_limits, ws.surface_rates, + rate_report); } } } @@ -415,19 +473,12 @@ updateWellTestStateEconomic(const SingleWellState& ws, } well_test_state.close_well(well_.name(), WellTestConfig::Reason::ECONOMIC, simulation_time); - if (write_message_to_opmlog) { - // State when the well stops flowing, as the ratio-limit and CECON - // messages below already do. That instant is the end of the time - // step the limit was detected on, not its start. - const std::string_view action = - well_.wellEcl().getAutomaticShutIn() ? "shut" : "stopped"; - deferred_logger.info( - fmt::format("well {} will be {} due to rate economic limit " - "at time {:.2f} {} (date = {})", - well_.name(), action, - unit_system.from_si(UnitSystem::measure::time, simulation_time), - unit_system.name(UnitSystem::measure::time), - economicLimitDateString(start_time, simulation_time))); + if (write_message_to_opmlog || closure_reason != nullptr) { + // Same layout as the ratio-limit workover messages below. + this->reportEconomicLimitClosure( + economicLimitWhenString(unit_system, start_time, simulation_time), + rateViolationReason(unit_system, rate_report, limits_on_potentials), + write_message_to_opmlog, closure_reason, deferred_logger); } // the well is closed, not need to check other limits return; @@ -446,7 +497,8 @@ updateWellTestStateEconomic(const SingleWellState& ws, const auto workover = econ_production_limits.workover(); // Shared "at time ..." and ratio-violation clauses for the messages - // below; only built when a message will actually be logged. + // below; only built when they will actually be used. + const bool build_message = write_message_to_opmlog || (closure_reason != nullptr); std::string when; std::string reason; auto make_reason = [&unit_system](const RatioLimitCheckReport& report) @@ -454,13 +506,8 @@ updateWellTestStateEconomic(const SingleWellState& ws, return ratioViolationReason(unit_system, report.ratio_name, report.ratio_measure, report.ratio_value, report.ratio_limit); }; - if (write_message_to_opmlog) { - when = fmt::format( - "at time {:.2f} {} (date = {})", - unit_system.from_si(UnitSystem::measure::time, simulation_time), - unit_system.name(UnitSystem::measure::time), - economicLimitDateString(start_time, simulation_time)); - + if (build_message) { + when = economicLimitWhenString(unit_system, start_time, simulation_time); reason = make_reason(ratio_report); } @@ -520,7 +567,7 @@ updateWellTestStateEconomic(const SingleWellState& ws, } ratio_report = this->checkRatioEconLimits(econ_production_limits, ws, closed_this_event, deferred_logger); - if (write_message_to_opmlog && ratio_report.ratio_limit_violated) { + if (build_message && ratio_report.ratio_limit_violated) { reason = make_reason(ratio_report); } } @@ -529,14 +576,8 @@ updateWellTestStateEconomic(const SingleWellState& ws, case WellEconProductionLimits::EconWorkover::WELL: { well_test_state.close_well(well_.name(), WellTestConfig::Reason::ECONOMIC, simulation_time); - if (write_message_to_opmlog) { - const std::string action = - well_.wellEcl().getAutomaticShutIn() ? "shut" : "stopped"; - const std::string& sep = economicLimitMessageSeparator(); - deferred_logger.info( - fmt::format("{}\nWell {} will be {} {},\nBecause {}.\n{}", - sep, well_.name(), action, when, reason, sep)); - } + this->reportEconomicLimitClosure(when, reason, write_message_to_opmlog, + closure_reason, deferred_logger); break; } case WellEconProductionLimits::EconWorkover::NONE: @@ -692,11 +733,8 @@ updateWellTestStateCECON(const SingleWellState& ws, // Build the "at time ... (date = ...)" and ratio-violation clauses that // are shared by all CECON workover messages below. - const std::string when = fmt::format( - "at time {:.2f} {} (date = {})", - unit_system.from_si(UnitSystem::measure::time, simulation_time), - unit_system.name(UnitSystem::measure::time), - economicLimitDateString(start_time, simulation_time)); + const std::string when = + economicLimitWhenString(unit_system, start_time, simulation_time); const std::string reason = ratioViolationReason(unit_system, ratio_name, ratio_measure, ratio_value, ratio_limit); @@ -837,6 +875,42 @@ closeOffendingCompletion(const int offending_completion, return allCompletionsClosed; } +template +void WellTest:: +reportEconomicLimitClosure(const std::string& when, + const std::string& reason, + const bool write_message_to_opmlog, + std::string* closure_reason, + DeferredLogger& deferred_logger) const +{ + if (write_message_to_opmlog) { + const std::string_view action = + well_.wellEcl().getAutomaticShutIn() ? "shut" : "stopped"; + const std::string& sep = economicLimitMessageSeparator(); + deferred_logger.info( + fmt::format("{}\nWell {} will be {} {},\nBecause {}.\n{}", + sep, well_.name(), action, when, reason, sep)); + } + else if (closure_reason != nullptr) { + *closure_reason = reason; + } +} + +template +std::string WellTest:: +rateViolationReason(const UnitSystem& unit_system, + const RateLimitCheckReport& report, + const bool on_potentials) +{ + const std::string unit_suffix = unitSuffix(unit_system, report.rate_measure); + return fmt::format( + "the {} production {} {:.4e}{} is below the limit {:.4e}{}", + report.quantity_name, + on_potentials ? "potential" : "rate", + unit_system.from_si(report.rate_measure, report.rate_value), unit_suffix, + unit_system.from_si(report.rate_measure, report.rate_limit), unit_suffix); +} + template std::string WellTest:: ratioViolationReason(const UnitSystem& unit_system, @@ -845,9 +919,7 @@ ratioViolationReason(const UnitSystem& unit_system, const Scalar ratio_value, const Scalar ratio_limit) { - const std::string ratio_unit = unit_system.name(ratio_measure); - const std::string unit_suffix = ratio_unit.empty() ? std::string{} - : " " + ratio_unit; + const std::string unit_suffix = unitSuffix(unit_system, ratio_measure); if (std::isinf(ratio_value)) { return fmt::format( "{} is infinite and exceeds the limit {:.4e}{}", diff --git a/opm/simulators/wells/WellTest.hpp b/opm/simulators/wells/WellTest.hpp index 382566a2240..795f6676c16 100644 --- a/opm/simulators/wells/WellTest.hpp +++ b/opm/simulators/wells/WellTest.hpp @@ -74,7 +74,8 @@ class WellTest { bool zero_group_target, const UnitSystem& unit_system, const std::time_t start_time, - DeferredLogger& deferred_logger) const; + DeferredLogger& deferred_logger, + std::string* closure_reason = nullptr) const; void updateWellTestStateCECON(const SingleWellState& ws, const double simulation_time, @@ -108,6 +109,36 @@ class WellTest { Scalar ratio_limit = 0.0; }; + //! \brief The violated minimum rate limit, for the closing message. + struct RateLimitCheckReport { + //! \brief "oil", "gas", "liquid" or "reservoir fluid". Always a string + //! literal, so the view outlives the report. + std::string_view quantity_name{}; + UnitSystem::measure rate_measure = UnitSystem::measure::identity; + //! \brief Magnitude compared against \c rate_limit, from the potentials + //! rather than the rates when WECON item 10 is POTN. + Scalar rate_value = 0.0; + Scalar rate_limit = 0.0; + }; + + //! \brief Report that an economic limit closed the well: to the PRT during the + //! regular update, or into \p closure_reason when a well test is only + //! trying the well out and no shut-in actually happens. + void reportEconomicLimitClosure(const std::string& when, + const std::string& reason, + const bool write_message_to_opmlog, + std::string* closure_reason, + DeferredLogger& deferred_logger) const; + + //! \brief Format the " production rate ... is below the limit ..." + //! clause of the WECON rate-limit closing message. + //! + //! \param on_potentials say "production potential" instead of "production + //! rate" (WECON item 10 is POTN). + static std::string rateViolationReason(const UnitSystem& unit_system, + const RateLimitCheckReport& report, + const bool on_potentials); + //! \brief Format the " ... exceeds the limit ..." clause shared by the //! WECON and CECON workover messages. //! @@ -168,9 +199,16 @@ class WellTest { const UnitSystem::measure ratio_measure, RatioLimitCheckReport& report) const; + //! \brief Check the minimum production rate limits (WECON items 2, 3, 14 and + //! 16) against \p rates_or_potentials, the well's surface rates or, + //! when WECON item 10 is POTN, its potentials. Item 16 applies to the + //! reservoir voidage rate those rates correspond to. Limits on a phase + //! the run does not have are ignored. + //! + //! \param report the first violated limit; untouched when none is violated. bool checkRateEconLimits(const WellEconProductionLimits& econ_production_limits, const std::vector& rates_or_potentials, - DeferredLogger& deferred_logger) const; + RateLimitCheckReport& report) const; //! \brief Check all active ratio limits, ignoring \p excluded_completions //! (completions already closed by the ongoing workover event). diff --git a/regressionTests.cmake b/regressionTests.cmake index aea8bd8d653..02da783b5a9 100644 --- a/regressionTests.cmake +++ b/regressionTests.cmake @@ -1056,6 +1056,25 @@ add_test_compareECLFiles( --enable-tuning=true ) +add_test_compareECLFiles( + CASENAME + wecon_item_14 + FILENAME + WECON-02 + SIMULATOR + flow + DEV_SIMULATOR + flow_blackoil + ABS_TOL + ${abs_tol} + REL_TOL + ${rel_tol} + DIR + wecon_wtest + TEST_ARGS + --enable-tuning=true +) + add_test_compareECLFiles( CASENAME gconinje_resv_gas_01