diff --git a/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp b/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp index e3b6fbf48..06a68f89a 100644 --- a/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp +++ b/opm/simulators/flow/SimulatorFullyImplicitBlackoil.hpp @@ -317,11 +317,14 @@ public: // \Note: The report steps are met in any case // \Note: The sub stepping will require a copy of the state variables if (adaptiveTimeStepping_) { - auto tuningUpdater = [enableTUNING, this, reportStep = timer.currentStepNum()]() + auto tuningUpdater = [enableTUNING, this, + reportStep = timer.currentStepNum()](const double curr_time, + double dt, const int timeStep) { auto& schedule = this->simulator_.vanguard().schedule(); auto& events = this->schedule()[reportStep].events(); + bool result = false; if (events.hasEvent(ScheduleEvents::TUNING_CHANGE)) { // Unset the event to not trigger it again on the next sub step schedule.clear_event(ScheduleEvents::TUNING_CHANGE, reportStep); @@ -335,14 +338,46 @@ public: // \Note: Need to update both solver (model) and simulator since solver is re-created each report step. solver_->model().updateTUNING(tuning); this->updateTUNING(tuning); + dt = this->adaptiveTimeStepping_->suggestedNextStep(); } else { + dt = max_next_tstep; this->adaptiveTimeStepping_->updateNEXTSTEP(max_next_tstep); } - return max_next_tstep >0; + result = max_next_tstep > 0; } - return false; + + const auto& wcycle = schedule[reportStep].wcycle.get(); + if (wcycle.empty()) { + return result; + } + + const auto& wmatcher = schedule.wellMatcher(reportStep); + double wcycle_time_step = + wcycle.nextTimeStep(curr_time, + dt, + wmatcher, + this->wellModel_().wellOpenTimes(), + this->wellModel_().wellCloseTimes(), + [this, reportStep, schedule, timeStep](const std::string& name) + { + if (timeStep != 0) { + return false; + } + const auto& wg_events = schedule[reportStep].wellgroup_events(); + return wg_events.hasEvent(name, ScheduleEvents::REQUEST_OPEN_WELL); + }); + + if (dt != wcycle_time_step) { + this->adaptiveTimeStepping_->updateNEXTSTEP(wcycle_time_step); + return true; + } + + return result; }; - tuningUpdater(); + + tuningUpdater(timer.simulationTimeElapsed(), + this->adaptiveTimeStepping_->suggestedNextStep(), 0); + const auto& events = schedule()[timer.currentStepNum()].events(); bool event = events.hasEvent(ScheduleEvents::NEW_WELL) || events.hasEvent(ScheduleEvents::INJECTION_TYPE_CHANGED) || diff --git a/opm/simulators/timestepping/AdaptiveTimeStepping.hpp b/opm/simulators/timestepping/AdaptiveTimeStepping.hpp index 33771bd6a..8f75687cf 100644 --- a/opm/simulators/timestepping/AdaptiveTimeStepping.hpp +++ b/opm/simulators/timestepping/AdaptiveTimeStepping.hpp @@ -180,10 +180,10 @@ void registerAdaptiveParameters(); SimulatorReport step(const SimulatorTimer& simulatorTimer, Solver& solver, const bool isEvent, - const std::function tuningUpdater) + const std::function tuningUpdater) { // Maybe update tuning - tuningUpdater(); + tuningUpdater(simulatorTimer.simulationTimeElapsed(), suggestedNextTimestep_, 0); SimulatorReport report; const double timestep = simulatorTimer.currentStepLength(); @@ -215,7 +215,9 @@ void registerAdaptiveParameters(); // Maybe update tuning // get current delta t auto oldValue = suggestedNextTimestep_; - if (tuningUpdater()) { + if (tuningUpdater(substepTimer.simulationTimeElapsed(), + substepTimer.currentStepLength(), + substepTimer.currentStepNum())) { // Use provideTimeStepEstimate to make we sure don't simulate longer than the report step is. substepTimer.provideTimeStepEstimate(suggestedNextTimestep_); suggestedNextTimestep_ = oldValue; diff --git a/opm/simulators/utils/UnsupportedFlowKeywords.cpp b/opm/simulators/utils/UnsupportedFlowKeywords.cpp index f5aa82596..d8afce549 100644 --- a/opm/simulators/utils/UnsupportedFlowKeywords.cpp +++ b/opm/simulators/utils/UnsupportedFlowKeywords.cpp @@ -672,7 +672,6 @@ const KeywordValidation::UnsupportedKeywords& unsupportedKeywords() {"WCONINJP", {true, std::nullopt}}, {"WCUTBACK", {true, std::nullopt}}, {"WCUTBACT", {true, std::nullopt}}, - {"WCYCLE", {true, std::nullopt}}, {"WDRILTIM", {true, std::nullopt}}, {"WDRILPRI", {true, std::nullopt}}, {"WDRILRES", {true, std::nullopt}}, diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index a047d28f2..d2b864b90 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -450,7 +450,7 @@ template class WellContributions; // Keep track of the domain of each well, if using subdomains. std::map well_domain_; - // Store the local index of the wells perforated cells in the domain, if using sumdomains + // Store the local index of the wells perforated cells in the domain, if using subdomains SparseTable well_local_cells_; const Grid& grid() const diff --git a/opm/simulators/wells/BlackoilWellModelGeneric.hpp b/opm/simulators/wells/BlackoilWellModelGeneric.hpp index 10b66ab83..b0107bc01 100644 --- a/opm/simulators/wells/BlackoilWellModelGeneric.hpp +++ b/opm/simulators/wells/BlackoilWellModelGeneric.hpp @@ -205,6 +205,9 @@ public: const GuideRate& guideRate() const { return guideRate_; } + const std::map& wellOpenTimes() const { return well_open_times_; } + const std::map& wellCloseTimes() const { return well_close_times_; } + bool reportStepStarts() const { return report_step_starts_; } bool shouldBalanceNetwork(const int reportStepIndex, @@ -468,6 +471,12 @@ protected: std::vector wells_ecl_; std::vector>> well_perf_data_; + // Times at which wells were opened (for WCYCLE) + std::map well_open_times_; + + // Times at which wells were shut (for WCYCLE) + std::map well_close_times_; + /// Connection index mappings class ConnectionIndexMap { diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index f85b2596d..db8b06a48 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -177,6 +178,11 @@ namespace Opm { const auto& events = this->schedule()[reportStepIdx].wellgroup_events(); for (auto& wellPtr : this->well_container_) { const bool well_opened_this_step = this->report_step_starts_ && events.hasEvent(wellPtr->name(), effective_events_mask); + if (well_opened_this_step && this->wellState().well(wellPtr->name()).status == Well::Status::OPEN) { + this->well_open_times_.insert_or_assign(wellPtr->name(), this->simulator_.time()); + this->well_close_times_.erase(wellPtr->name()); + } + wellPtr->init(&this->phase_usage_, this->depth_, this->gravity_, this->B_avg_, well_opened_this_step); } } @@ -672,8 +678,8 @@ namespace Opm { GLiftEclWells ecl_well_map; initGliftEclWellMap(ecl_well_map); well->wellTesting(simulator_, simulationTime, this->wellState(), - this->groupState(), this->wellTestState(), this->phase_usage_, - ecl_well_map, deferred_logger); + this->groupState(), this->wellTestState(), this->phase_usage_, + ecl_well_map, this->well_open_times_, deferred_logger); } catch (const std::exception& e) { const std::string msg = fmt::format("Exception during testing of well: {}. The well will not open.\n Exception message: {}", wellEcl.name(), e.what()); deferred_logger.warning("WELL_TESTING_FAILED", msg); @@ -919,6 +925,13 @@ namespace Opm { if (nw > 0) { well_container_.reserve(nw); + const auto& wmatcher = this->schedule().wellMatcher(report_step); + const auto& wcycle = this->schedule()[report_step].wcycle.get(); + const auto cycle_states = wcycle.wellStatus(this->simulator_.time(), + wmatcher, + this->well_open_times_, + this->well_close_times_); + for (int w = 0; w < nw; ++w) { const Well& well_ecl = this->wells_ecl_[w]; @@ -940,6 +953,8 @@ namespace Opm { this->wellState().shutWell(w); } + this->well_open_times_.erase(well_name); + this->well_close_times_.erase(well_name); continue; } @@ -957,6 +972,9 @@ namespace Opm { if (!closed_this_step) { this->wellTestState().open_well(well_name); this->wellTestState().open_completions(well_name); + this->well_open_times_.insert_or_assign(well_name, + this->simulator_.time()); + this->well_close_times_.erase(well_name); } events.clearEvent(ScheduleEvents::REQUEST_OPEN_WELL); } @@ -970,12 +988,16 @@ namespace Opm { if (well_ecl.getAutomaticShutIn()) { // shut wells are not added to the well container this->wellState().shutWell(w); + this->well_close_times_.erase(well_name); + this->well_open_times_.erase(well_name); continue; } else { if (!well_ecl.getAllowCrossFlow()) { // stopped wells where cross flow is not allowed // are not added to the well container this->wellState().shutWell(w); + this->well_close_times_.erase(well_name); + this->well_open_times_.erase(well_name); continue; } // stopped wells are added to the container but marked as stopped @@ -993,19 +1015,55 @@ namespace Opm { // Treat as shut, do not add to container. local_deferredLogger.debug(fmt::format(" Well {} gets shut due to having zero rate constraint and disallowing crossflow ", well_ecl.name()) ); this->wellState().shutWell(w); + this->well_close_times_.erase(well_name); + this->well_open_times_.erase(well_name); continue; } } if (well_status == Well::Status::STOP) { this->wellState().stopWell(w); + this->well_close_times_.erase(well_name); + this->well_open_times_.erase(well_name); wellIsStopped = true; } + if (!wcycle.empty()) { + const auto it = cycle_states.find(well_name); + if (it != cycle_states.end()) { + if (!it->second) { + this->wellState().shutWell(w); + continue; + } else { + this->wellState().openWell(w); + } + } + } + well_container_.emplace_back(this->createWellPointer(w, report_step)); - if (wellIsStopped) + if (wellIsStopped) { well_container_.back()->stopWell(); + this->well_close_times_.erase(well_name); + this->well_open_times_.erase(well_name); + } + } + + if (!wcycle.empty()) { + auto schedule_open = [this, report_step](const std::string& name) + { + const auto& wg_events = this->schedule()[report_step].wellgroup_events(); + return wg_events.hasEvent(name, ScheduleEvents::REQUEST_OPEN_WELL); + }; + for (const auto& [wname, wscale] : wcycle.efficiencyScale(this->simulator_.time(), + this->simulator_.timeStepSize(), + wmatcher, + this->well_open_times_, + schedule_open)) + { + this->wellState()[wname].efficiency_scaling_factor = wscale; + this->schedule_.add_event(ScheduleEvents::WELLGROUP_EFFICIENCY_UPDATE, report_step); + } } } diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 51f25bb32..9ed3c7a1d 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -301,6 +301,7 @@ public: WellTestState& welltest_state, const PhaseUsage& phase_usage, GLiftEclWells& ecl_well_map, + std::map& open_times, DeferredLogger& deferred_logger); void checkWellOperability(const Simulator& simulator, diff --git a/opm/simulators/wells/WellInterface_impl.hpp b/opm/simulators/wells/WellInterface_impl.hpp index be1bdaa8f..a35f46c77 100644 --- a/opm/simulators/wells/WellInterface_impl.hpp +++ b/opm/simulators/wells/WellInterface_impl.hpp @@ -384,6 +384,7 @@ namespace Opm WellTestState& well_test_state, const PhaseUsage& phase_usage, GLiftEclWells& ecl_well_map, + std::map& open_times, DeferredLogger& deferred_logger) { deferred_logger.info(" well " + this->name() + " is being tested"); @@ -480,6 +481,7 @@ namespace Opm // set the status of the well_state to open ws.open(); well_state = well_state_copy; + open_times.try_emplace(this->name(), well_test_state.lastTestTime(this->name())); } } diff --git a/regressionTests.cmake b/regressionTests.cmake index eeb5b38f5..524c9e58b 100644 --- a/regressionTests.cmake +++ b/regressionTests.cmake @@ -583,6 +583,17 @@ foreach(templ_case RANGE 1 6) ) endforeach() +foreach(wcycle_case RANGE 0 7) + add_test_compareECLFiles(CASENAME WCYCLE-${wcycle_case} + FILENAME WCYCLE-${wcycle_case} + SIMULATOR flow + ABS_TOL ${abs_tol} + REL_TOL ${rel_tol} + DIR wcycle + TEST_ARGS --enable-tuning=true + ) +endforeach() + add_test_compareECLFiles(CASENAME udq_uadd FILENAME UDQ_M1 SIMULATOR flow